diff --git a/.gitignore b/.gitignore index 8e96571a225e..4b79d87c01e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ # Typically *NIX text editors, by default, append '~' to files on saving to make backups *~ -# Gradle work directory +# Gradle work directory and caches .gradle +.gradletasknamecache # Build output directies /target @@ -47,3 +48,8 @@ ObjectStore # Additional databases used in local envs databases/mysql/ databases/postgis/ + +# Vim +*.swp +*.swo + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..6fc0e918dc3c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +dist: trusty +language: java + +jobs: + include: + - stage: Oracle JDK 8 + jdk: oraclejdk8 + - stage: AdoptOpenJDK 11.0.3 + install: + - curl -L -o install-jdk.sh https://github.com/sormuras/bach/raw/master/install-jdk.sh + - source ./install-jdk.sh --target ./openjdk11 --url https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.3%2B7/OpenJDK11U-jdk_x64_linux_hotspot_11.0.3_7.tar.gz + +before_script: + - java -version + - ./gradlew assemble +script: + - ./gradlew check -Plog-test-progress=true +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + - rm -f $HOME/.gradle/caches/*/fileHashes/fileHashes.bin + - rm -f $HOME/.gradle/caches/*/fileHashes/fileHashes.lock +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd5025e999cd..70e915f20662 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,29 +1,69 @@ -# Guidelines for Contributing +# Contributing Contributions from the community are essential in keeping Hibernate (any Open Source -project really) strong and successful. While we try to keep requirements for -contributing to a minimum, there are a few guidelines we ask that you mind. +project really) strong and successful. + +# Legal + +All original contributions to Hibernate are licensed under the +[GNU Lesser General Public License (LGPL)](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt), +version 2.1 or later, or, if another license is specified as governing the file or directory being +modified, such other license. The LGPL text is included verbatim in the [lgpl.txt](lgpl.txt) file +in the root directory of the ORM repository. + +All contributions are subject to the [Developer Certificate of Origin (DCO)](https://developercertificate.org/). +The DCO text is also included verbatim in the [dco.txt](dco.txt) file in the root directory of the ORM repository. + + +## Guidelines + +While we try to keep requirements for contributing to a minimum, there are a few guidelines +we ask that you mind. + +For code contributions, these guidelines include: +* respect the project code style - find templates for [Eclipse](https://community.jboss.org/docs/DOC-16649) + and [IntelliJ IDEA](https://community.jboss.org/docs/DOC-15468) +* have a corresponding JIRA issue and the key for this JIRA issue should be used in the commit message +* have a set of appropriate tests. For bug reports, the tests reproduce the initial reported bug + and illustrates that the solution actually fixes the bug. For features/enhancements, the + tests illustrate the feature working as intended. In both cases the tests are incorporated into + the project to protect against regressions. +* if applicable, documentation is updated to reflect the introduced changes +* the code compiles and the tests pass (`./gradlew clean build`) + +For documentation contributions, mainly just respect the project code style, especially in regards +to use of tabs - as mentioned above, code style templates are available for both Eclipse and IntelliJ +IDEA IDEs. Ideally these contributions would also have a corresponding JIRA issue, although this +is less necessary for documentation contributions. + ## Getting Started If you are just getting started with Git, GitHub and/or contributing to Hibernate via -GitHub there are a few pre-requisite steps. +GitHub there are a few pre-requisite steps to follow: -* Make sure you have signed a [Contributor License Agreement](https://cla.jboss.org) (CLA) for the Hibernate project * Make sure you have a [Hibernate JIRA account](https://hibernate.atlassian.net) * Make sure you have a [GitHub account](https://github.com/signup/free) * [Fork](https://help.github.com/articles/fork-a-repo) the Hibernate repository. As discussed in the linked page, this also includes: * [Set](https://help.github.com/articles/set-up-git) up your local git install * Clone your fork -* See the wiki pages for setting up your IDE, whether you use [IntelliJ IDEA](https://community.jboss.org/wiki/ContributingToHibernateUsingIntelliJ) -or [Eclipse](https://community.jboss.org/wiki/ContributingToHibernateUsingEclipse). +* See the wiki pages for setting up your IDE, whether you use +[IntelliJ IDEA](https://community.jboss.org/wiki/ContributingToHibernateUsingIntelliJ) +or [Eclipse](https://community.jboss.org/wiki/ContributingToHibernateUsingEclipse)(1). + ## Create the working (topic) branch -Create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) on which you -will work. The convention is to name the branch using the JIRA issue key. If there is not already a JIRA issue -covering the work you want to do, create one. Assuming you will be working from the master branch and working +Create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) +on which you will work. The convention is to incorporate the JIRA issue key in the name of this branch, +although this is more of a mnemonic strategy than a hard-and-fast rule - but doing so helps: +* remember what each branch is for +* isolate the work from other contributions you may be working on. + +_If there is not already a JIRA issue covering the work you want to do, create one._ + +Assuming you will be working from the master branch and working on the JIRA HHH-123 : `git checkout -b HHH-123 master` @@ -31,6 +71,7 @@ on the JIRA HHH-123 : `git checkout -b HHH-123 master` Do yo thing! + ## Commit * Make commits of logical units. @@ -46,7 +87,20 @@ appreciated btw), please use rebasing rather than merging. Merging creates ## Submit -* If you have not already, sign the [Contributor License Agreement](https://cla.jboss.org). * Push your changes to the topic branch in your fork of the repository. * Initiate a [pull request](http://help.github.com/articles/creating-a-pull-request) * Update the JIRA issue, adding a comment including a link to the created pull request + _if the JIRA key was not used in the commit message_. + + +It is important that this topic branch on your fork: + +* be isolated to just the work on this one JIRA issue, or multiple issues if they are + related and also fixed/implemented by this work. The main point is to not push + commits for more than one PR to a single branch - GitHub PRs are linked to + a branch rather than specific commits. +* remain until the PR is closed. Once the underlying branch is deleted the corresponding + PR will be closed, if not already, and the changes will be lost. + +# Notes +(1) Gradle `eclipse` plugin is no longer supported, so the recommended way to import the project in your IDE is with the proper IDE tools/plugins. Don't try to run `./gradlew clean eclipse --refresh-dependencies` from the command line as you'll get an error because `eclipse` no longer exists diff --git a/README.md b/README.md index 2648fb730762..fe84e5d16c8c 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,35 @@ -Hibernate ORM is a component/library providing Object/Relational Mapping (ORM) support -to applications and other components/libraries. It is also provides an implementation of the -JPA specification, which is the standardized Java specification for ORM. See -[Hibernate.org](http://hibernate.org/orm/) for additional information. +Hibernate ORM is a library providing Object/Relational Mapping (ORM) support +to applications, libraries and frameworks. + +It also provides an implementation of the JPA specification, which is the standard Java specification for ORM. + +This is the repository of its source code: see [Hibernate.org](http://hibernate.org/orm/) for additional information. [![Build Status](http://ci.hibernate.org/job/hibernate-orm-master-h2-main/badge/icon)](http://ci.hibernate.org/job/hibernate-orm-master-h2-main/) -Quickstart -========== +Building from sources +========= - git clone git://github.com/hibernate/hibernate-orm.git - cd hibernate-orm - ./gradlew clean build +The build requires a Java 8 JDK as JAVA_HOME. -The build requires a Java 8 JDK as JAVA_HOME, but will ensure Java 6 compatibility. - +You will need [Git](https://git-scm.com/) to obtain the [source](https://github.com/hibernate/hibernate-orm/). -Resources -========= - -Hibernate uses [Gradle](http://gradle.org) as its build tool. See the _Gradle Primer_ section below if you are new to +Hibernate uses [Gradle](https://gradle.org) as its build tool. See the _Gradle Primer_ section below if you are new to Gradle. -Contributors should read the [Contributing Guide](CONTRIBUTING.md) +Contributors should read the [Contributing Guide](CONTRIBUTING.md). See the guides for setting up [IntelliJ](https://developer.jboss.org/wiki/ContributingToHibernateUsingIntelliJ) or -[Eclipse](https://developer.jboss.org/wiki/ContributingToHibernateUsingEclipse) as your development environment. [Building Hibernate ORM](https://community.jboss.org/wiki/BuildingHibernateORM4x) -is somewhat outdated, but still has +[Eclipse](https://developer.jboss.org/wiki/ContributingToHibernateUsingEclipse) as your development environment. +Check out the _Getting Started_ section in CONTRIBUTING.md for getting started working on Hibernate source. -CI Builds + +Continuous Integration ========= Hibernate makes use of [Jenkins](http://jenkins-ci.org) for its CI needs. The project is built continuous on each @@ -40,9 +37,8 @@ push to the upstream repository. Overall there are a few different jobs, all o [http://ci.hibernate.org/view/ORM/](http://ci.hibernate.org/view/ORM/) - Gradle primer -============= +========= This section describes some of the basics developers and contributors new to Gradle might need to know to get productive quickly. The Gradle documentation is very well done; 2 in @@ -61,12 +57,18 @@ For contributors who do not otherwise use Gradle and do not want to install it, features called the wrapper. It lets you run Gradle builds without a previously installed Gradle distro in a zero-conf manner. Hibernate configures the Gradle wrapper for you. If you would rather use the wrapper and not install Gradle (or to make sure you use the version of Gradle intended for older builds) you would just use -the command `gradlew` (or `gradlew.bat`) rather than `gradle` (or `gradle.bat`) in the following discussions. -Note that `gradlew` is only available in the project's root dir, so depending on your `pwd` you may need to adjust -the path to `gradlew` as well. +the command `gradlew` (or `gradlew.bat`) rather than `gradle` (or `gradle.bat`) in the following discussions. +Note that `gradlew` is only available in the project's root dir, so depending on your working directory you may +need to adjust the path to `gradlew` as well. + +Examples use the `gradle` syntax, but just swap `gradlew` (properly relative) for `gradle` if you wish to use +the wrapper. + +Another reason to use `gradlew` is that it uses the exact version of Gradle that the build is defined to work with. + Executing Tasks ---------------- +------------------------ Gradle uses the concept of build tasks (equivalent to Ant targets or Maven phases/goals). You can get a list of available tasks via @@ -81,7 +83,7 @@ either: 2. name the "task path". For example, in order to run the tests for the _hibernate-core_ module from the root directory you could say `gradle hibernate-core:test` Common Java related tasks -------------------------- +------------------------ * _build_ - Assembles (jars) and tests this project * _buildDependents_ - Assembles and tests this project and all projects that depend on it. So think of running this in hibernate-core, Gradle would assemble and test hibernate-core as well as hibernate-envers (because envers depends on core) @@ -97,3 +99,50 @@ never uses this, but it can be useful for testing your build with other local Ma * _idea_ - Generates an IntelliJ/IDEA project (although the preferred approach is to use IntelliJ's Gradle import). * _clean_ - Cleans the build directory + +Testing and databases +===================== + +Testing against a specific database can be achieved in 2 different ways: + + +Using the "Matrix Testing Plugin" for Gradle. +--------------------------------------------- + +Coming soon... + + +Using "profiles" +------------------------ + +The Hibernate build defines a number of database testing "profiles" in `databases.gradle`. These +profiles can be activated by name using the `db` build property which can be passed either as +a JVM system prop (`-D`) or as a Gradle project property (`-P`). Examples below use the Gradle +project property approach. + + gradle clean build -Pdb=pgsql + +To run a test from your IDE, you need to ensure the property expansions happen. +Use the following command: + + gradle clean compile -Pdb=pgsql + +_*NOTE : If you are running tests against a JDBC driver that is not available via Maven central (generally due to license nonsense - Oracle, DB2, etc) be sure to add these drivers to your local Maven repo cache (~/.m2/repository) or (better) add it to a personal Maven repo server*_ + +Running database-specific tests from the IDE using "profiles" +------------------------------------------------------------- + +You can run any test on any particular database that is configured in a `databases.gradle` profile. + +All you have to do is run the following command: + + gradlew setDataBase -Pdb=pgsql + +or you can use the shortcut version: + + gradlew sDB -Pdb=pgsql + +You can do this from the module which you are interested in testing or from the `hibernate-orm` root folder. + +Afterward, just pick any test from the IDE and run it as usual. Hibernate will pick the database configuration from the `hibernate.properties` +file that was set up by the `setDataBase` Gradle task. diff --git a/build.gradle b/build.gradle index c2c0b8165ce7..f5e36dc2f903 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -import org.apache.tools.ant.filters.ReplaceTokens - /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -9,456 +7,113 @@ import org.apache.tools.ant.filters.ReplaceTokens buildscript { repositories { - mavenCentral() - mavenLocal() jcenter() - - maven { - name 'jboss-nexus' - url "http://repository.jboss.org/nexus/content/groups/public/" - } - maven { - name "jboss-snapshots" - url "http://snapshots.jboss.org/maven2/" - } + mavenCentral() } dependencies { classpath 'org.hibernate.build.gradle:gradle-maven-publish-auth:2.0.1' - classpath 'org.hibernate.build.gradle:hibernate-matrix-testing:2.0.0-SNAPSHOT' + classpath 'org.hibernate.build.gradle:hibernate-matrix-testing:2.0.0.Final' classpath 'org.hibernate.build.gradle:version-injection-plugin:1.0.0' classpath 'org.hibernate.build.gradle:gradle-xjc-plugin:1.0.2.Final' - classpath 'com.github.lburgazzoli:lb-karaf-features-gen:1.0.0-SNAPSHOT' + classpath 'gradle.plugin.com.github.lburgazzoli:gradle-karaf-plugin:0.1.1' + classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.7' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' + classpath 'de.thetaphi:forbiddenapis:2.5' } } plugins { - id 'com.gradle.build-scan' version '1.3' + id 'com.gradle.build-scan' version '1.9' id 'me.champeau.buildscan-recipes' version '0.1.7' } -apply plugin: 'eclipse' -apply plugin: 'idea' -apply from: "./libraries.gradle" -apply from: "./databases.gradle" - allprojects { - repositories { - mavenCentral() - mavenLocal() - - maven { - name 'jboss-nexus' - url "http://repository.jboss.org/nexus/content/groups/public/" - } - maven { - name "jboss-snapshots" - url "http://snapshots.jboss.org/maven2/" - } - } -} - -ext { - hibernateTargetVersion = '5.2.8.Final' - expectedGradleVersion = '3.2.1' - baselineJavaVersion = '1.8' - - osgiExportVersion = hibernateTargetVersion.replaceAll( '-SNAPSHOT', '.SNAPSHOT' ) - - final String[] versionComponents = hibernateTargetVersion.split( '\\.' ); - hibernateFullVersion = hibernateTargetVersion - hibernateMajorMinorVersion = versionComponents[0] + '.' + versionComponents[1] - hibernateMajorVersion = versionComponents[0] -} - -idea { - project { - jdkName = baselineJavaVersion - languageLevel = baselineJavaVersion - - vcs = 'Git' - } - module { - name = "hibernate-orm" - } -} - -// Used in MANIFEST.MF for OSGi Bundles -def osgiDescription() { - return "A module of the Hibernate O/RM project" -} - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -subprojects { subProject -> - apply plugin: 'idea' - apply plugin: 'eclipse' - - defaultTasks 'build' - - group = 'org.hibernate' - version = rootProject.hibernateTargetVersion - - ext.exportPackageVersion = rootProject.osgiExportVersion - - // minimize changes, at least for now (gradle uses 'build' by default).. - buildDir = "target" - - if ( subProject.name.startsWith( 'release' ) || subProject.name.startsWith( 'documentation' ) ) { - return; - } - - if ( subProject.name.startsWith( 'hibernate-orm-modules' ) ) { - return; - } - - // everything below here in the closure applies to java projects - apply plugin: 'java' - apply plugin: 'maven-publish' - apply plugin: 'maven-publish-auth' - apply plugin: 'osgi' - - apply plugin: 'findbugs' - apply plugin: 'checkstyle' - apply plugin: 'build-dashboard' - apply plugin: 'project-report' - - apply plugin: org.hibernate.build.HibernateBuildPlugin - - sourceCompatibility = rootProject.baselineJavaVersion - targetCompatibility = rootProject.baselineJavaVersion - - configurations { - provided { - // todo : need to make sure these are non-exported - description = 'Non-exported compile-time dependencies.' - } - jbossLoggingTool { - description = 'Dependencies for running the jboss-logging tooling.' - } - configurations { - all*.exclude group: 'xml-apis', module: 'xml-apis' - } - } - - // appropriately inject the common dependencies into each sub-project - dependencies { - compile libraries.logging - - provided libraries.logging_annotations - - jbossLoggingTool( libraries.logging_processor ) - - testCompile( libraries.junit ) - testCompile( libraries.byteman ) - testCompile( libraries.byteman_install ) - testCompile( libraries.byteman_bmunit ) - - testRuntime( libraries.log4j ) - testRuntime( libraries.javassist ) - testRuntime( libraries.byteBuddy ) - testRuntime( libraries.woodstox ) - - //Databases - testRuntime( libraries.h2 ) - testRuntime( libraries.hsqldb ) - testRuntime( libraries.postgresql ) - testRuntime( libraries.mysql ) - testRuntime( libraries.mariadb ) - testRuntime( libraries.mssql ) - testRuntime( libraries.informix ) - - if (db.equalsIgnoreCase("oracle")) { - dependencies { - testRuntime( libraries.oracle ) + repositories { + mavenCentral() + //Allow loading additional dependencies from a local path; + //useful to load JDBC drivers which can not be distributed in public. + if (System.env['ADDITIONAL_REPO'] != null) { + flatDir { + dirs "${System.env.ADDITIONAL_REPO}" } } - // 6.6 gave me some NPE problems from within checkstyle... - checkstyle 'com.puppycrawl.tools:checkstyle:6.5' - } - - // mac-specific stuff ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // should really use Jvm.current().toolsJar - ext.toolsJar = file("${System.getProperty('java.home')}/../lib/tools.jar") - if ( ext.toolsJar.exists() ) { - dependencies{ - testCompile files( toolsJar ) - } } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // compilation - compileJava.options.encoding = 'UTF-8' - - tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - } - - task compile - compile.dependsOn compileJava, processResources, compileTestJava, processTestResources - - sourceSets.main { - compileClasspath += configurations.provided - compileClasspath += configurations.jbossLoggingTool - } - - subProject.getConvention().findPlugin( JavaPluginConvention.class ).sourceSets.each { sourceSet -> - JavaCompile javaCompileTask = project.tasks.findByName( sourceSet.compileJavaTaskName ) as JavaCompile + apply plugin: 'idea' - // NOTE : this aptDir stuff is needed until we can have IntelliJ run annotation processors for us - // which cannot happen until we can fold hibernate-testing back into hibernate-core/src/test - // which cannot happen until... ugh - File aptDir = subProject.file( "${subProject.buildDir}/generated-src/apt/${sourceSet.name}" ) - sourceSet.allJava.srcDir( aptDir ) + // minimize changes, at least for now (gradle uses 'build' by default).. + buildDir = "target" - javaCompileTask.options.compilerArgs += [ - "-nowarn", - "-encoding", "UTF-8", - "-s", "${aptDir.absolutePath}" - ] - javaCompileTask.doFirst { - aptDir.mkdirs() - } - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // testing - subProject.tasks.withType( Test.class ).all { task -> - task.jvmArgs += [ - '-XX:+HeapDumpOnOutOfMemoryError', - "-XX:HeapDumpPath=${project.file( "${project.buildDir}/OOM-dump.hprof" ).absolutePath}", - '-XX:MetaspaceSize=512M' - ] - - task.maxHeapSize = '2G' - - task.systemProperties['hibernate.test.validatefailureexpected'] = true - task.systemProperties += System.properties.findAll { it.key.startsWith( "hibernate.") } - -// uncomment to help identify pauses in test executions : where they occur -// task.beforeTest { descriptor -> -// println "Starting test: " + descriptor -// } -// task.afterTest { descriptor -> -// println "Completed test: " + descriptor -// } - } - - processTestResources.doLast( { - copy { - from( sourceSets.test.java.srcDirs ) { - include '**/*.properties' - include '**/*.xml' - } - into sourceSets.test.output.classesDir - } - copy { - from file('src/test/resources') - into file( "${buildDir}/resources/test" ) - exclude 'src/test/resources/arquillian.xml' - exclude 'src/test/resources/hibernate.properties' - } - copy { - from file('src/test/resources/hibernate.properties') - into file( "${buildDir}/resources/test" ) - filter( ReplaceTokens, tokens: dbBundle[db] ) - } - } ) - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + apply from: rootProject.file( 'gradle/base-information.gradle' ) +} - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // artifact - jar { - manifest = osgiManifest { - // GRADLE-1411: Even if we override Imports and Exports - // auto-generation with instructions, classesDir and classpath - // need to be here (temporarily). - classesDir = sourceSets.main.output.classesDir - classpath = configurations.runtime +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Release Task - instruction 'Import-Package', - // Temporarily support JTA 1.1 -- Karaf and other frameworks still - // use it. Without this, the plugin generates [1.2,2). - 'javax.transaction;version="[1.1,2)"', - // Tell Gradle OSGi to still dynamically import the other packages. - // IMPORTANT: Do not include the * in the modules' .gradle files. - // If it exists more than once, the manifest will physically contain a *. - '*' +task release { + description = "The task performed when we are performing a release build. Relies on " + + "the fact that subprojects will appropriately define a release task " + + "themselves if they have any release-related activities to perform" - instruction 'Bundle-Vendor', 'Hibernate.org' - instruction 'Bundle-Description', subProject.osgiDescription() - instruction 'Implementation-Url', 'http://hibernate.org' - instruction 'Implementation-Version', version - instruction 'Implementation-Vendor', 'Hibernate.org' - instruction 'Implementation-Vendor-Id', 'org.hibernate' - instruction 'Implementation-Title', name - instruction 'Specification-Title', name - instruction 'Specification-Version', version - instruction 'Specification-Vendor', 'Hibernate.org' + // Force to release with JDK 8. Releasing with JDK 11 is not supported yet: + // - the hibernate-orm-modules tests do not run due to an issue with the ASM version currently used by Gradle + doFirst { + if ( !JavaVersion.current().isJava8() ) { + throw new IllegalStateException( "Please use JDK 8 to perform the release." ) } } +} - task sourcesJar(type: Jar, dependsOn: compileJava) { - from sourceSets.main.allSource - classifier = 'sources' - } - - sourcesJar { - manifest = jar.manifest - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // IDE options - idea { - module { - jdkName = subProject.sourceCompatibility - - excludeDirs = [file( ".gradle" )] - excludeDirs += file( "$buildDir/classes" ) - excludeDirs += file( "$buildDir/bundles" ) - excludeDirs += file( "$buildDir/packages" ) - excludeDirs += file( "$buildDir/dependency-cache" ) - excludeDirs += file( "$buildDir/libs" ) - excludeDirs += file( "$buildDir/reports" ) - excludeDirs += file( "$buildDir/test-results" ) - excludeDirs += file( "$buildDir/tmp" ) - excludeDirs += file( "$buildDir/matrix" ) - excludeDirs += file( "$buildDir/resources" ) +task publish { + description = "The task performed when we want to just publish maven artifacts. Relies on " + + "the fact that subprojects will have a task named pubappropriately define a release task " + + "themselves if they have any release-related activities to perform" +} - downloadSources = true - scopes.PROVIDED.plus += [configurations.provided] - } - } - eclipse { - jdt { - sourceCompatibility = subProject.sourceCompatibility - targetCompatibility = subProject.targetCompatibility - } - classpath { - plusConfigurations.add( configurations.provided ) - } - } - // eclipseClasspath will not add sources to classpath unless the dirs actually exist. - // TODO: Eclipse's annotation processor handling is also fairly stupid (and completely lacks in the - // Gradle plugin). For now, just compile first in order to get the logging classes. - eclipseClasspath.dependsOn compile - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CI Build Task +task ciBuild { + description = "The task performed when one of the 'main' jobs are triggered on the " + + "CI server. Just as above, relies on the fact that subprojects will " + + "appropriately define a release task themselves if they have any tasks " + + "which should be performed from these CI jobs" +} - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Report configs - checkstyle { - sourceSets = [ subProject.sourceSets.main ] - configFile = rootProject.file( 'shared/config/checkstyle/checkstyle.xml' ) - showViolations = false - } - // exclude generated java sources - by explicitly setting the base source dir - checkstyleMain.source = 'src/main/java' - - // define a second checkstyle task for checking non-fatal violations - task nonFatalCheckstyle(type:Checkstyle) { - source = subProject.sourceSets.main.java - classpath = subProject.configurations.checkstyle - showViolations = false - configFile = rootProject.file( 'shared/config/checkstyle/checkstyle-non-fatal.xml' ) - } - findbugs { - sourceSets = [ subProject.sourceSets.main, subProject.sourceSets.test ] - ignoreFailures = true - toolVersion = '3.0.1' - // for now we need to set this to low so that FindBugs will actually report the DM_CONVERT_CASE warning we care about - reportLevel = 'low' - // remove all low level bug warnings except DM_CONVERT_CASE - excludeFilterConfig=resources.text.fromString(excludeAllLowLevelBugsExcept('DM_CONVERT_CASE')) - } +wrapper { + gradleVersion = '4.10.3' + distributionType = Wrapper.DistributionType.ALL +} - // exclude generated java sources and cfg package is a mess mainly from annotation stuff - findbugsMain.doFirst { - classes = classes.filter { - !it.path.contains( 'org/hibernate/hql/internal/antlr' ) && - !it.path.contains( 'org/hibernate/boot/jaxb/cfg/spi' ) && - !it.path.contains( 'org/hibernate/sql/ordering/antlr/Generated' ) && - !it.path.contains( 'org/hibernate/sql/ordering/antlr/OrderByTemplateTokenTypes' ) && - !it.path.contains( 'org/hibernate/boot/jaxb/hbm/spi/Jaxb' ) && - !it.path.contains( 'org/hibernate/boot/jaxb/hbm/spi/Adapter' ) && - !it.path.contains( 'org/hibernate/boot/jaxb/hbm/spi/ObjectFactory' ) && - !it.path.contains( 'org/hibernate/cfg' ) && - !it.path.contains( '_\$logger' ) - } - } - // because cfg package is a mess mainly from annotation stuff - checkstyleMain.exclude '**/org/hibernate/cfg/**' - checkstyleMain.exclude '**/org/hibernate/cfg/*' - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +buildScan { + licenseAgreementUrl = 'https://gradle.com/terms-of-service' + licenseAgree = 'yes' + recipe 'git-commit', baseUrl: 'https://github.com/hibernate/hibernate-orm/tree' +} - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Publishing - publishing { - publications { - mavenJava(MavenPublication) { - from components.java - artifact( sourcesJar ) { - classifier 'sources' - } - } - // http://issues.gradle.org/browse/GRADLE-2966 - // Once ^^ is resolved: - // 1) Move hibernate-testing module into hibernate-core tests - // 2) Define a second publication on hibernate-core for publishing the testing jar - // We could kind of do this now, but it would just be the jar. Every module would still need - // to duplicate the testing dependencies. Well, on second thought, we could centralize the - // testing dependencies here within the subprojects block - } - } - model { - tasks.generatePomFileForMavenJavaPublication { - destination = file( "$subProject.buildDir/generated-pom.xml" ) - } - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -} -task release(type: Task, dependsOn: 'release:release') -task wrapper(type: Wrapper) { - gradleVersion = expectedGradleVersion -} -buildScan { - licenseAgreementUrl = 'https://gradle.com/terms-of-service' - licenseAgree = 'yes' - recipe 'git-commit', baseUrl: 'https://github.com/hibernate/hibernate-orm/tree' -} +//idea { +// project { +// jdkName = baselineJavaVersion +// languageLevel = baselineJavaVersion +// +// vcs = 'Git' +// } +// module { +// name = "hibernate-orm" +// } +//} -def excludeAllLowLevelBugsExcept(String[] bugTypes){ - def writer = new StringWriter() - def xml = new groovy.xml.MarkupBuilder(writer); - xml.FindBugsFilter { - Match { - Confidence( value: '3' ) - bugTypes.each { bug -> - Not { - Bug( pattern: "${bug}" ) - } - } - } - } - return writer.toString( ) -} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 7353f8c1c2c0..284b69b6c168 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -7,23 +7,16 @@ repositories { mavenCentral() jcenter() - - maven { - name 'jboss-nexus' - url "http://repository.jboss.org/nexus/content/groups/public/" - } - maven { - name "jboss-snapshots" - url "http://snapshots.jboss.org/maven2/" - } } apply plugin: "groovy" +buildDir = "target" + dependencies { compile gradleApi() compile localGroovy() compile 'org.hibernate.build.gradle:gradle-animalSniffer-plugin:1.0.1.Final' - compile 'org.hibernate.build.gradle:hibernate-matrix-testing:2.0.0-SNAPSHOT' -} \ No newline at end of file + compile 'org.hibernate.build.gradle:hibernate-matrix-testing:2.0.0.Final' +} diff --git a/changelog.txt b/changelog.txt index 0072c1b802f5..91d05c802fd1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,1244 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.20.Final (November 16th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31894/tab/release-report-all-issues + +** Bug + * [HHH-14257] - An Entity A with a map collection having as index an Embeddable with a an association to the Entity A fails with a NPE + +** Task + * [HHH-14225] - CVE-2020-25638 Potential for SQL injection on use_sql_comments logging enabled + * [HHH-14324] - Add .gradletasknamecache to .gitignore + +** Improvement + * [HHH-14325] - Add Query hint for specifying "query spaces" for native queries + + +Changes in 5.3.19.Final (November 10th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31874/tab/release-report-all-issues + +** Bug + * [HHH-13310] - getParameterValue() not working for collections + * [HHH-14275] - Broken link to Infinispan User Guide in Hibernate 5.3 User Guide + +** Task + * [HHH-14309] - Improve `BulkOperationCleanupAction#affectedEntity` + +** Sub-task + * [HHH-14196] - Add parsing of persistence.xml/orm.xml documents in the EE 9 namespace + + +Changes in 5.3.18.Final (August 5th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31849/tab/release-report-all-issues + +** Bug + * [HHH-12268] - LazyInitializationException thrown from lazy collection when batch fetching enabled and owning entity refreshed with lock + * [HHH-13110] - @PreUpdate method on a Embeddable null on the parent caused NullPointerException + * [HHH-13936] - No auto transaction joining from SessionImpl.doFlush + * [HHH-14077] - CVE-2019-14900 SQL injection issue using JPA Criteria API + +** Task + * [HHH-14013] - Upgrade to Hibernate Validator 6.0.20.Final + * [HHH-14096] - Removal of unused code: XMLHelper and its SAXReader factory helper + * [HHH-14103] - Add test cases showing that an entity's transient attribute can be overridden to be persistent in entity subclasses + +Changes in 5.3.17.Final (April 30th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31835/tab/release-report-all-issues + +** Bug + * [HHH-13695] - DDL export forgets to close a Statement + +** Task + * [HHH-13953] - Upgrade dom4j to 2.1.3 + +** Improvement + * [HHH-13960] - Add SAXReader sec features to match the defaults + +Changes in 5.3.16.Final (March 27th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31822/tab/release-report-all-issues + +** Bug + * [HHH-13184] - Oracle dialect detection does not return latest dialect in the default case + * [HHH-13891] - ProxyFactory should not be built if any ID or property getter/setter methods are final + * [HHH-13910] - MySQL57Dialect selected by automatic dialect resolution when using MySQL 8.0 database + +** Task + * [HHH-13822] - OSGi integration tests need to be able to download dependencies from Maven Central using HTTPS + +** Improvement + * [HHH-12977] - Update latest dialect for MySQL + * [HHH-13851] - Rework initialization of ProxyFactoryFactory to move responsibility out of PojoEntityTuplizer + +Changes in 5.3.15.Final (January 7th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31809/tab/release-report-all-issues + +** Bug + * [HHH-13433] - EntityManager.find() should only check for roll-back-only condition if there is an active JTA transaction, otherwise ORM should throw convert( e, lockOptions ) + * [HHH-13651] - NPE on flushing when ElementCollection field contains null element + * [HHH-13675] - Optimize PersistentBag.groupByEqualityHash() + * [HHH-13737] - Add debug logging and a test case for HHH-13433 + +** Improvement + * [HHH-12858] - integration overrides during JPA bootstrap ought to override all logically related settings + * [HHH-13432] - Have EntityManagerFactory expose persistence.xml `jta-data-source` element as a `javax.persistence.nonJtaDataSource` property + +Changes in 5.3.14.Final (November 7th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31801/tab/release-report-all-issues + +** Bug + * [HHH-13307] - On release of batch it still contained JDBC statements using JTA + * [HHH-13633] - Bugs join-fetching a collection when scrolling with a stateless session using enhancement as proxy + * [HHH-13634] - PersistenceContext can get cleared before load completes using StatelessSessionImpl + * [HHH-13640] - Uninitialized HibernateProxy mapped as NO_PROXY gets initialized when reloaded with enhancement-as-proxy enabled + * [HHH-13653] - Uninitialized entity does not get initialized when a setter is called with enhancement-as-proxy enabled + * [HHH-13698] - Hibernate does not recognize MySQL 8 error code 3572 as PessimisticLockException + +Changes in 5.3.13.Final (October 8th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31792/tab/release-report-all-issues + +** Bug + * [HHH-13586] - ClassCastException when using a single region name for both entity and query results + * [HHH-13645] - StatsNamedContainer#getOrCompute throws NullPointerException when computed value is null + +** Improvement + * [HHH-13130] - Provide Gradle-based bytecode enhancement as a task separate from the compileJava task + +Changes in 5.3.12.Final (September 11th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31784/tab/release-report-all-issues + +** Bug + * [HHH-12968] - Flush is not flushing inserts for inherited tables before a select within a transaction + * [HHH-12990] - JPA Model generator does not work in Java 9+ + * [HHH-13128] - Missing jaxb-runtime dependency for hibernate-jpamodelgen + * [HHH-13580] - LocalTimeTest#writeThenNativeRead* and OffsetTimeTest#writeThenNativeRead* failing on MySQL + * [HHH-13581] - LocalTimeTest#writeThenRead* and OffsetTimeTest#writeThenRead* failing on MariaDB + * [HHH-13582] - LocalDateTest failures on MySQL + * [HHH-13590] - TransientObjectException merging a non-proxy association to a HibernateProxy + * [HHH-13592] - AutoFlushEvent#isFlushRequired is always false + * [HHH-13607] - Exception thrown while flushing uninitialized enhanced proxy with immutable natural ID + * [HHH-13611] - Restore EntityMetamodel constructor to take SessionFactoryImplementor argument instead of PersisterCreationContext. + * [HHH-13616] - Enable the hibernate-orm-modules test for JDK 11 + +** Task + * [HHH-13007] - No longer use net.bytebuddy.experimental=true when testing on JDK11 + * [HHH-13043] - Upgrade to JAXB 2.3 + * [HHH-13271] - Javadoc build failures on JDK 12 + * [HHH-13275] - Re-introduce usage of net.bytebuddy.experimental=true when testing on JDK > 11 + * [HHH-13415] - Improve build compatibility with JDK11.0.3 + * [HHH-13419] - Support building javadoc with JDK 11.0.3 + * [HHH-13421] - Disable OSGi testing for JDK 11+ + * [HHH-13504] - Upgrade ByteBuddy to 1.9.11 + * [HHH-13605] - InstantTest, OffsetDateTimeTest, ZonedDateTimeTest fail for MariaDB on CI + +** Improvement + * [HHH-12946] - Include JAXB as a dependency as it's not provided by JDK 11 + * [HHH-13022] - Make OSGi integration work on JDK11 + * [HHH-13069] - Update the links to JBoss Nexus to use the direct repository over https + * [HHH-13127] - Document JAXB dependencies should be added for using hibernate-jpamodelgen in Eclipse IDE + * [HHH-13428] - Minor cleanup of build scripts + + +Changes in 5.3.11.Final (August 15th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31770/tab/release-report-all-issues + +** Bug + * [HHH-13357] - OffsetTimeTest fails using TimeAsTimestampRemappingH2Dialect in non-GMT European time zones + * [HHH-13379] - Regression of Instant serialization + * [HHH-13424] - Table nullability should not depend on JpaCompliance.isJpaCacheComplianceEnabled() + * [HHH-13455] - Enabling Enhancement as a Proxy causes IllegalStateException when using Javassist + * [HHH-13459] - Unit test lock up when they run on PostgreSQL + * [HHH-13460] - FetchGraphTest is failing on MariaDB + * [HHH-13466] - ClassCastException when changing a Collection association to a Set if @PreUpdate listener exists + * [HHH-13492] - OptimisticLockException after locking, refreshing, and updating an entity + * [HHH-13505] - NullPointerException thrown by StatisticsImpl#getCacheRegionStatistics + * [HHH-13514] - Calling the wrong method inside SessionDelegatorBaseImpl#createStoredProcedureQuery + * [HHH-13544] - Restore logged warning on jdbc code mapping issue in NationalizedTypeMappings + * [HHH-13550] - Fix Oracle failure for test added by HHH-13424 + * [HHH-13554] - QueryAndSQLTest.testNativeQueryWithFormulaAttributeWithoutAlias() fails on Oracle, MSSQL, Sybase, DB2, MariaDB + * [HHH-13555] - FetchGraphTest, MergeProxyTest and ProxyDeletionTest fail due to ConstraintViolationException + * [HHH-13556] - Tests doing dynamic fetch scrolling a collection fail on DB2 + * [HHH-13557] - LocalTimeTest#writeThenNativeRead and OffsetTimeTest#writeThenNativeRead tests are failing on SQL Server + * [HHH-13558] - InstantTest, LocalDateTimeTest, OffsetDateTimeTest, ZonedDateTimeTest failing on Sybase for year 1600 + * [HHH-13569] - org.hibernate.test.annotations.embedded.EmbeddedTest failures on Sybase + * [HHH-13570] - Test failures due to Sybase not supporting UPDATE statement with WITH(NOWAIT) + * [HHH-13571] - Test failures due to cross joined table out of scope of a subsequent JOIN on Sybase + * [HHH-13573] - Test failure due to Sybase not supporting cascade delete on foreign key definitions + * [HHH-13574] - SybaseASE does not support PARTITION BY + * [HHH-13577] - LockTest.testContendedPessimisticLock and StatementIsClosedAfterALockExceptionTest.testStatementIsClosed tests fail on Sybase + +** New Feature + * [HHH-11147] - Allow enhanced entities to be returned in a completely uninitialized state + +** Task + * [HHH-13026] - Documentation: fixing link to Infinispan documentation section regarding Hibernate 2LC + * [HHH-13416] - Unguarded debug message being rendered in org.hibernate.engine.internal.Collections.processReachableCollection + * [HHH-13513] - Partial revert of string interning introduced by HHH-3924 + * [HHH-13520] - Deprecate mutators on SqlStatementLogger + * [HHH-13525] - Make test SessionDelegatorBaseImplTest more resilient to previously existing alias definition + * [HHH-13526] - Optimise ResourceRegistryStandardImpl#release + * [HHH-13527] - Performance regression in org.hibernate.stat.internal.StatisticsImpl + * [HHH-13528] - Invoke afterStatements only at the end of releasing all statements for a batch + * [HHH-13529] - Performance regression in org.hibernate.engine.spi.SessionFactoryImplementor#getDialect + * [HHH-13531] - Some more opportunities to reuse the constants pool in AliasConstantsHelper + * [HHH-13534] - AbstractLoadPlanBasedLoader never needs a List of AfterLoadAction + +** Improvement + * [HHH-11032] - Improve performance of PersistentBag.equalsSnapshot + * [HHH-13442] - CollectionType#getCollection() method improvements + * [HHH-13444] - Remove ignored EntityMode field from CollectionKey + * [HHH-13447] - Minimize number of EventListenerRegistry lookups within a Session use + * [HHH-13448] - Avoid retrieving PRE_LOAD and POST_LOAD Event listeners within the inner loops of TwoPhaseLoad + * [HHH-13450] - Do not compute the full role name of a collection unless necessary + * [HHH-13451] - Logging typo in CascadingActions causing significant allocations + * [HHH-13452] - Missing log level guard on formatting in DefaultPersistEventListener#entityIsDeleted + * [HHH-13453] - Optimise CascadingActions for the most likely case + * [HHH-13458] - Update Hibernate's custom IdentityMap to better match its use + * [HHH-13462] - Introduce a fastpath for SessionImpl#fireLoad to be used by internal loops + * [HHH-13467] - Make average BatchFetchQueue consume less memory + * [HHH-13471] - Avoid invoking delayedAfterCompletion() multiple times from the same SessionImpl method + * [HHH-13475] - SessionImpl#applyQuerySettingsAndHints should not rely on defensive copies to just read properties + * [HHH-13476] - Micro-optimisations of TwoPhaseLoad#getOverridingEager + * [HHH-13477] - Make heavily invoked method final: EventListenerGroupImpl#listeners() + * [HHH-13478] - Various low hanging fruits identified by CPU flame graphs + * [HHH-13494] - LobTypeMappings should not use a Bounded ConcurrentHashmap + * [HHH-13495] - NationalizedTypeMappings should not use a Bounded ConcurrentHashmap + * [HHH-13508] - Reuse alias names generated by BasicLoader#generateSuffixes + * [HHH-13511] - Remove old org.hibernate.loader.DefaultEntityAliases#intern + * [HHH-13512] - Avoid allocating an array in org.hibernate.internal.util.StringHelper#unquote(String[], Dialect) if there are no changes to be applied + * [HHH-13521] - Avoid excessive validation of enabled filters + * [HHH-13522] - Optimise LoadQueryInfluencers by making maps lazily initialized + * [HHH-13523] - StatementPreparerImpl should not need to retrieve the JDBCService as often + * [HHH-13524] - Remove unused fields xref,unassociatedResultSets from JdbcCoordinatorImpl + + + +Changes in 5.3.10.final (April 19th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31759/tab/release-report-done + +** Bug + * [HHH-12939] - Database name not quoted at schema update + * [HHH-13138] - Work around class loading issues so that bytecode enhanced tests can run as expected + * [HHH-13241] - Constraint violation when deleting entites in bi-directional, lazy OneToMany association with bytecode enhancement + * [HHH-13266] - LocalDateTime values are wrong around 1900 (caused by JDK-8061577) + * [HHH-13277] - HibernateMethodLookupDispatcher - Issue with Security Manager + * [HHH-13300] - query.getSingleResult() throws org.hibernate.NonUniqueResultException instead of javax.persistence.NonUniqueResultException + * [HHH-13326] - Transaction passed to Hibernate Interceptor methods is null when JTA is used + * [HHH-13343] - Bytecode enhancement using ByteBuddy fails when the class is not available from the provided ClassLoader + * [HHH-13364] - Query.getSingleResult and getResultList() throw PessimisticLockException when pessimistic lock fails with timeout + +** Task + * [HHH-13376] - Upgrade Javassist dependency to 3.23.2-GA + + + +Changes in 5.3.9.final (February 25th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31757/tab/release-report-done + +** Bug + * [HHH-13107] - JtaWithStatementsBatchTest fails on Oracle + * [HHH-13112] - Proxies on entity types in the default package lead to MappingException with JDK9+ + * [HHH-13262] - javax.persistence.TransactionRequiredException: Executing an update/delete query + * [HHH-13269] - Embeddable collection regression due to HHH-11544 + * [HHH-13281] - java.lang.ClassCastException: org.hibernate.internal.SessionImpl cannot be cast to org.hibernate.ejb.HibernateEntityManager + * [HHH-13285] - ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory after dom4j update + + + +Changes in 5.3.8.final (February 19th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31715/tab/release-report-done + +** Bug + * [HHH-10891] - Exception at bootstrap when @Any is inside an @Embeddable object + * [HHH-11209] - NullPointerException in EntityType.replace() with a PersistentBag + * [HHH-12555] - Merging a blob on an entity results in a class cast exception + * [HHH-13050] - On release of batch it still contained JDBC statements logged; unable to release batch statement + * [HHH-13059] - OneToMany with referencedColumnName returns too many entities + * [HHH-13064] - Documentation of Lock and LockModeType is on two columns instead of 3 + * [HHH-13076] - Hibernate “Transaction already active” behaviour with custom transaction manager + * [HHH-13084] - Querying entity with non-ID property named 'id' fails if entity has an IdClass composite key + * [HHH-13097] - Hibernate enhancer is superslow after upgrade to latest 5.3 or 5.4-SNAPSHOT + * [HHH-13114] - Query "select count(h) from Human h" fails if a subclass has a non-Id property named "id" + * [HHH-13129] - Cascaded merge fails for detached bytecode-enhanced entity with uninitialized ToOne + * [HHH-13164] - Detecting transient state of mandatory toOne relations is broken + * [HHH-13169] - Table alias used instead of exact table name in multitable update query + * [HHH-13172] - Log a warning instead of throwing an Exception when @AttributeOverride is used in conjunction with inheritance + * [HHH-13194] - Some methods returning org.hibernate.query.Query are not defined for StatelessSession + * [HHH-13244] - setting hibernate.jpa.compliance.proxy=true and org.hibernate debug level to DEBUG breaks hibernate + +** Task + * [HHH-13099] - Update to Byte Buddy 1.9.4 + * [HHH-13100] - All custom implementation of Byte Buddy "Implementation" s should have a proper equals and hashcode + +** Improvement + * [HHH-12917] - Interning of strings for Filter definitions + * [HHH-12918] - Interning of strings for Formula and Column exctraction templates + * [HHH-12919] - Interning of strings for EntityReferenceAliases + * [HHH-13005] - Upgrade ByteBuddy to 1.9.0 + * [HHH-13057] - Prevent Byte Buddy's Advice helper to need reloading many resources from the ClassLoader + * [HHH-13220] - In the ByteBuddy enhancer, avoid creating a PersistentAttributeTransformer if the class is not enhanced + + + +Changes in 5.3.7.final (October 16th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31714/tab/release-report-done + +** Bug + * [HHH-12784] - Javassist support broken by HHH-12760 + * [HHH-12920] - AbstractCachedDomainDataAccess.clearCache() throws MissingFormatArgumentException at DEBUG level + * [HHH-12934] - Exception handling documentation does not apply only to "Session-per-application anti-pattern" + * [HHH-12935] - Constraint and AuxiliaryDatabaseObject export identifiers are not qualified by schema or catalog + * [HHH-12937] - Where clause for collections of basic, embeddable and "any" elements is ignored when mapped using hbm.xml + * [HHH-12964] - Upgrade to dom4j 2.1.1 + * [HHH-13027] - org.hibernate.ejb.HibernatePersistence can no longer be used as a persistence provider name + +** Improvement + * [HHH-12961] - The links in the Javadoc of the SAP HANA dialects don't work + * [HHH-13011] - Add option enabling/disabling use of an entity's mapped where-clause when loading collections of that entity + + + +Changes in 5.3.6.final (August 28th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31704/tab/release-report-done + +** Bug + * [HHH-12931] - Revert HHH-12542 as it introduces some issues with the security manager + * [HHH-12932] - Add privileged blocks in ByteBuddyState initialization + + + +Changes in 5.3.5.final (August 14th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31695/tab/release-report-done + +** Bug + * [HHH-12871] - Metamodel contains managed types related to dynamic-map entities that have been excluded. + * [HHH-12875] - Class level where="..." clause in hbm.xml mappings is not enforced on collections of that class + * [HHH-12882] - Where clauses mapped on collections and entities need parentheses when used in conjunction + * [HHH-12890] - Fix link to JPA Metamodel generator documentation + * [HHH-12903] - CommitFlushCollectionTest fails when running on Oracle. + * [HHH-12905] - Passing null as parameter is not allowed even when enablePassingNulls() has been called + * [HHH-12906] - Statistics.getCollectionRoleNames() reports incorrect value + +** Task + * [HHH-10782] - Add a comment about what you can expect from a query plan cache cleanup + * [HHH-12898] - Enable integration tests for Oracle Standard Edition Two 12.1.0.2.v12 on the AWS build slaves + * [HHH-12899] - Enable integration tests for MS SQL Server on the AWS build slaves + * [HHH-12901] - Enable loading of additional JDBC drivers from a local path + * [HHH-12909] - Upgrade ByteBuddy to 1.8.17 + +** Improvement + * [HHH-12196] - Sybase Dialect not supporting max result - paging + * [HHH-12361] - In the User Guide, omit constructors and equals/hashCode for brevity + * [HHH-12608] - Add the ST_DWithin() function in DB2 Spatial Dialect + * [HHH-12892] - Fix spelling issues in the User Guide + * [HHH-12907] - Avoid garbage collection pressure when creating proxies with ByteBuddy + + + +Changes in 5.3.4.final (August 2nd, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31688/tab/release-report-done + +** Bug + * [HHH-10603] - ORA-00932: inconsistent datatypes: expected - got BLOB after HHH-10345 with Oracle12cDialect + * [HHH-12492] - JPA delete query generated has missing table alias and thus incorrect semantics + * [HHH-12834] - org.hibernate.envers.test.integration.collection.StringMapNationalizedLobTest fails with Sybase + * [HHH-12835] - Wrong assertion in BatchFetchQueueHelper + * [HHH-12846] - Merge cascade of collection fails when orphan removal enabled with flush mode commit + * [HHH-12847] - NullPointerException in FetchStyleLoadPlanBuildingAssociationVisitationStrategy::adjustJoinFetchIfNeeded + * [HHH-12848] - UpgradeSkipLockedTest, PessimisticReadSkipLockedTest and OracleFollowOnLockingTest fail with Oracle12c + * [HHH-12849] - QuotedIdentifierTest fails with ORA-04043 on Oracle12c + * [HHH-12851] - ConverterTest fails with SQL Server depending on collation + * [HHH-12861] - SchemaUpdate doesn't work with Sybase + * [HHH-12863] - SchemaUpdateTest should be skipped with Sybase + * [HHH-12868] - Using CacheConcurrencyStrategy.NONE leads to a NPE when trying to load an entity + * [HHH-12869] - SingletonEhcacheRegionFactory initialization fails + * [HHH-12880] - LockModeTest hangs indefinitely with Sybase due to HHH-12847 + +** New Feature + * [HHH-12857] - Support the security manager with ByteBuddy as bytecode provider + +** Task + * [HHH-12730] - User types built using 5.1 are not binary compatible with 5.3 + * [HHH-12792] - Document binary incompatibility of persisters and tuplizers + * [HHH-12877] - Upgrade ByteBuddy to 1.8.15 + + + +Changes in 5.3.3.final (July 23, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31687/tab/release-report-done + +** Bug + * [HHH-7686] - org.hibernate.proxy.map.MapProxy loses all important state on serialization + * [HHH-8805] - [SchemaUpdate] javax.persistence.ForeignKey doesn't respect ConstraintMode.NO_CONSTRAINT + * [HHH-12200] - Docs mention outdated APIs + * [HHH-12542] - WildFly integration test, HibernateNativeAPINaturalIdTestCase, fails when security manager is enabled + * [HHH-12666] - Add an option for restoring 5.1 native exception handling + * [HHH-12695] - Incompatibility in return value for org.hibernate.procedure.ParameterRegistration.getType() 5.1 vs 5.3 + * [HHH-12718] - Entity changes in @PreUpdate callback are not persisted when lazy loading is active for more than one field + * [HHH-12720] - LazyInitializationException with hibernate.enable_lazy_load_no_trans + * [HHH-12740] - Subselect fetching doesn't work when multiLoad was used + * [HHH-12753] - org.hibernate.envers.test.integration.collection.StringMapNationalizedLobTest fails with DB2 + * [HHH-12768] - TimeAndTimestampTest fails with SQL Server and MYSQL + * [HHH-12771] - Caused by: java.lang.UnsupportedOperationException: Cache provider [org.hibernate.cache.ehcache.internal.EhcacheRegionFactory@3271ec2a] does not support `transactional` access + * [HHH-12776] - NullPointerException when executing native query on an Audited Entity + * [HHH-12779] - Revert HHH-12670 - Allows native SQL queries that take a given resultClass to map the result set to the required type + * [HHH-12781] - Update Javassist dependency to 3.23.1 + * [HHH-12786] - Deleting an entity leads to NullPointerException in ByteBuddy proxy + * [HHH-12787] - SessionJdbcBatchTest hangs with DB2 + * [HHH-12791] - ComponentTuplizer generates a LOT of proxy classes when using Bytebuddy as bytecode provider + * [HHH-12795] - Setting FlushMode to manual for a @NamedQuery is ignored + * [HHH-12797] - Fix cache modes relationships table layout in the documentation + * [HHH-12798] - Nested spatial functions are not rendered correctly on SAP HANA + * [HHH-12800] - TuplizerInstantiatesByteBuddySubclassTest uses ByteBuddy operation unsafe with JDK 11 + * [HHH-12802] - Hibernate does not throw an exception when more than one entity is loaded with the same ID + * [HHH-12815] - LocalDateCustomSessionLevelTimeZoneTest fails with mysql 5.5 and 5.7 + * [HHH-12822] - Skip "case when" tests requiring casts for DB2 + * [HHH-12823] - CompositeIdTest.testDistinctCountOfEntityWithCompositeId fails on databases that don't support tuple distinct counts because it expects wrong exception + * [HHH-12824] - ASTParserLoadingTest.testComponentNullnessChecks fail with DB2 because it uses legacy-style query parameter + * [HHH-12825] - CriteriaHQLAlignmentTest.testCountReturnValues fails on databases that don't support tuple distinct counts because it expects wrong exception + * [HHH-12826] - Persist cascade of collection fails when orphan removal enabled with flush mode commit. + * [HHH-12827] - NUMERIC column type is not handled correctly on DB2 + * [HHH-12829] - Invalid references to outdated EhCache classes + * [HHH-12832] - SchemaUpdateHaltOnErrorTest and SchemaMigratorHaltOnErrorTest fail with DB2 + * [HHH-12833] - UniqueConstraintDropTest fails with DB2 + * [HHH-12838] - AndNationalizedTests fails with DB2 + * [HHH-12839] - EntityProxySerializationTest fails with oracle + * [HHH-12843] - CreateDeleteTest and FlushIdGenTest fail with ORA-00936 on oracle + * [HHH-12844] - HbmWithIdentityTest fails with ORA-00936 on oracle + +** Task + * [HHH-12742] - Document the removal of JPAIntegrator SPI + * [HHH-12773] - Document org.hibernate.Query.getHibernateFirstResult(), setHibernateFirstResult(), getHibernateMaxResults(), setHibernateMaxResults() in migration guide + * [HHH-12774] - JARs missing from the distribution ZIP + * [HHH-12785] - Test Javassist support + * [HHH-12788] - Enable mockito-inline for the Agroal integration module + * [HHH-12789] - Upgrade to Mockito 2.19.0 + * [HHH-12793] - Upgrade Karaf, pax-exam and reenable the OSGi tests + * [HHH-12799] - Enforce version alignment of Mockito and ByteBuddy dependencies + * [HHH-12801] - Error message in SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest differs with JDK 11 + * [HHH-12803] - Upgrade ByteBuddy to 1.8.13 + * [HHH-12805] - Upgrade Mockito to 2.19.1 + * [HHH-12807] - Disable the hibernate-orm-modules tests for JDK 11 + * [HHH-12808] - Upgrade Gradle to 4.8.1 + * [HHH-12809] - Use an HTTP link for the Javadoc link to our Bean Validation documentation + * [HHH-12813] - Disable Asciidoclet in Javadoc generation + * [HHH-12816] - Enable the experimental features of ByteBuddy when building with JDK 11 + * [HHH-12820] - Merge the migration guides in the code base + * [HHH-12828] - ScannerTests#testGetBytesFromInputStream() is not stable enough + +** Improvement + * [HHH-12349] - User Guide documentation for @Filter is too verbose + * [HHH-12778] - BasicProxyFactoryImpl.getProxy() swallows exception + * [HHH-12804] - No need to mock Map in CollectionBinderTest + * [HHH-12811] - @UpdateTimestamp and @CreationTimestamp missing @Target annotation and breaking in Kotlin + * [HHH-12830] - Improve error output with transaction issues + + + +Changes in 5.3.2.final (July 5, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31676/tab/release-report-done + +** Sub-task + * [HHH-12683] - Update documentation about support for JPA CriteriaUpdate and CriteriaDelete + +** Bug + * [HHH-9403] - AssertionFailure: Fail to process type argument in a generic declaration + * [HHH-11979] - Invalid SQL when force increment version of inherited entity + * [HHH-12124] - The JPA Metamodel does not allow to retrieve the actual EmbeddableType since all instances are registered by the associated Java type + * [HHH-12247] - Bootstrap error information is only logged at DEBUG level + * [HHH-12353] - Document that Session.getReference not always returns a T + * [HHH-12396] - Problem with mapping of the annotation @Enumerated(EnumType.STRING) + * [HHH-12476] - NativeQuery with EntityGraphs assumes a HQL query + * [HHH-12531] - JCache existing cache not detected. + * [HHH-12553] - ConcurrentModificationException in OsgiClassLoader + * [HHH-12561] - bulk_id_strategy does not work with globally_quoted_identifiers + * [HHH-12594] - Using property "hibernate.default_batch_fetch_size" crashes bootstrapping + * [HHH-12603] - Contributing using Eclipse Documentation out of Date + * [HHH-12607] - Wrong audit data of ElementCollection attribute of map + * [HHH-12633] - ClassCastException when updating lazy loaded bytecode enhanced byte[] + * [HHH-12639] - No user friendly error on incompatible WildFly Transactions Client + * [HHH-12640] - Update to JBossStandAloneJtaPlatform should be backward compatible attempting old names as well + * [HHH-12649] - Auto-register entity and collection caches based on the Hibernate @Cache annotation (XML mapping) settings + * [HHH-12651] - org.hibernate.Session.*Query(Ljava/lang/String) methods return different types in 5.1 and 5.3 + * [HHH-12657] - ClassCastException: org.hibernate.mapping.SingleTableSubclass cannot be cast to org.hibernate.mapping.RootClass + * [HHH-12660] - Missing verb in reference documentation + * [HHH-12661] - Hibernate types (e.g. NumericBooleanType, YesNoType and any implementations of UserType) cannot bind value in StoredProcedureQuery + * [HHH-12668] - support for persistence_2_2.xsd and orm_2_2.xsd + * [HHH-12671] - INSERT time in-db generated properties not persisted with IDENTITY insert + * [HHH-12684] - Hibernate fails when mapping one-to-many collections by non-primary key + * [HHH-12685] - java.lang.ClassCastException when checking if parameter isBound in criteria query + * [HHH-12687] - ManyToOne associations in embeddable collection elements and composite IDs are always eagerly loaded + * [HHH-12688] - Duplicated Error Information Displayed in the Log + * [HHH-12691] - Code block is broken in documentation about AUTO flushing + * [HHH-12697] - Headings problem in Hibernate Documentation 5.3.1 - Proxool configuration + * [HHH-12698] - Headings problem in Hibernate Documentation 5.3.1 - Transation Patterns + * [HHH-12700] - Missing property in sample code in documentation of bulk-id strategies + * [HHH-12715] - Error in documentation sample code about JPQL and HQL + * [HHH-12724] - Add javax.activation as a dependency + * [HHH-12729] - Binary and behavioral incompatibilities of org.hibernate.Query.getFirstResult(), setFirstResult(), getMaxResults(), setMaxResults() + * [HHH-12731] - TOC hidden in the generated Asciidoctor docs + * [HHH-12732] - Don't generate auxiliary HTML files in the documentation + * [HHH-12738] - Session/EntityManager is closed in ForeignGenerator (JTA setup) + * [HHH-12739] - CLONE - AssertionFailure: Fail to process type argument in a generic declaration + * [HHH-12754] - *EqualsHashCodeTest, UnspecifiedEnumTypeTest fail with DB2, SQL Server, Sybase and Oracle + * [HHH-12755] - RevisionConstraintQuery.testRevisionsLtQuery fails with PostgreSQL 10.1 and EnterpriseDB 10.1 + * [HHH-12757] - EntityMapCompositeElementTest fail with oracle + * [HHH-12764] - IdClassReferenceIdentifierTest fail with Oracle + * [HHH-12765] - LazyInitializationWithoutInlineDirtyTrackingTest fails with SQL Server + * [HHH-12767] - Fix tests failing on Oracle + +** New Feature + * [HHH-12662] - JPQL queries fail when using the Java attribute type which has an associated AttributeConverter (only the DB column type works) + +** Task + * [HHH-12348] - Hibernate ORM Document user guide, architecture of Hibernate class is out of date + * [HHH-12637] - Add more tests and improve fix for HHH-12592 + * [HHH-12658] - Upgrade to Gradle 4.7 + * [HHH-12659] - Configure ForbiddenAPIs for JDK10 as intended target compatibility + * [HHH-12663] - Avoid depending on any SNAPSHOT dependency + * [HHH-12674] - Upgrade to Gradle 4.8 + * [HHH-12677] - Update javadoc API links from EE7 to EE8 + * [HHH-12689] - Upgrade to Gradle WildFly build tools 0.0.9 + * [HHH-12692] - Modify SessionImpl.toString to be quiet by default and verbose when trace is enabled + * [HHH-12694] - Upgrade to Hibernate Commons Annotations 5.0.4.Final + * [HHH-12701] - Upgrade to Jandex 2.0.5.Final + * [HHH-12743] - Cleaning ProviderChecker from some deprecated and dead code + * [HHH-12744] - Remove no longer necessary jboss-deployment-structure.xml from integration tests + * [HHH-12758] - Arquillian JVM configuration for integration tests is ignoring system properties + * [HHH-12759] - Upgrade integration tests to WildFly 13.0.0.Final + * [HHH-12760] - Remove no longer needed EqualsHelper and cleanup some equality checks + * [HHH-12766] - Upgrading to Byteman 4.0.3 + +** Improvement + * [HHH-11495] - Cache MetaModel#getImplementors() results + * [HHH-12341] - Documentation refers to nonexistent "image clob" + * [HHH-12350] - User Guide documentation for @Any is too verbose + * [HHH-12351] - User Guide: document why Subselect always requires Synchronize + * [HHH-12604] - Replace EqualsHelper.equals with Objects.equals + * [HHH-12630] - Add error logging to org.hibernate.cache.spi.AbstractRegionFactory.start(SessionFactoryOptions, Map) + * [HHH-12654] - Throw MappingException if both @Inheritance and @AttributeOverride are used + * [HHH-12656] - Document how contributors can run tests on different databases + * [HHH-12670] - Allows native SQL queries that take a given resultClass to map the result set to the required type + * [HHH-12686] - Replace EmptyIterator with Collections.emptyIterator() + * [HHH-12699] - Performance issue in ResultSetWrapperProxy.locateCorrespondingColumnIndexMethod() line 137 due to repeated use of Class.getMethod(String, Class[]) reflection call + * [HHH-12702] - Make JCacheRegionFactory easier to subclass + * [HHH-12710] - BaseCoreFunctionalTestCase opens an InputStream for mapping files but never closes it + * [HHH-12716] - Sample code is required for documentation of disabling polymorphism in queries + * [HHH-12723] - Revert the changes applied by HHH-12585 to the DefaultFlushEntityEventListener#invokeInterceptor() method + * [HHH-12725] - Upgrade the Asciidoctor plugin to 1.5.7 for JDK 9 compatibility + * [HHH-12727] - Performance issue in ResourceRegistryStandardImpl.register lines 67, 70 + * [HHH-12734] - StrategySelectorImpl - Add original exception when selected strategy could not be loaded + * [HHH-12741] - Register new reserved words added in MySQL 8.0 + * [HHH-12749] - Avoid setting the isolation level to the same value in C3P0ConnectionProvider + * [HHH-12769] - Rework LockTest#testContendedPessimisticLock so that it can work on Oracle without throwing exceptions + + + +Changes in 5.3.1.final (May 25, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31673/tab/release-report-done + +** Bug + * [HHH-12569] - Referential integrity violation on insert when using @OneToOne with @JoinColumn with hibernate.order_inserts=true + * [HHH-12577] - Warning log appears on an AttributeConverter + * [HHH-12579] - Bytecode enhancement with Generics on @MappedSuperclass crashes bootstrapping while using Bytebuddy + * [HHH-12581] - NPE for Criteria query containing fetch join as a regression of HHH-12338 + * [HHH-12584] - Bytebuddy ReflectionOptimizer does not work with abstract class + * [HHH-12586] - Strange date type confusion in JdbcDateTypeDescriptor + * [HHH-12587] - Flushing enhanced entity with @Cache(usage = CacheConcurrencyStrategy.NONE) fails + * [HHH-12592] - Merge of detached, enhanced entity with orphanRemoval = true collection fails since 5.2.13 + * [HHH-12599] - Add Javadoc indicating that region names do not include a prefix + * [HHH-12602] - Bytecode Enhancement documentation refers to removed property hibernate.ejb.use_class_enhancer + * [HHH-12612] - TYPE_USE annotated collections and elements fail metamodel generation. + * [HHH-12614] - Protection domain ignored when enhancing+loading classes with ByteBuddy + * [HHH-12617] - Caching log message prints null rather than class name. + * [HHH-12620] - Update JBossStandAloneJtaPlatform to use org.wildfly.transaction.client.* TM/UT + * [HHH-12621] - Thread-unsafe behavior of Query Spaces in Named Queries + * [HHH-12622] - JdbcResourceLocalTransactionCoordinatorImpl#markRollbackOnly should be ignored if there is no TX + * [HHH-12627] - Caching debug log error: java.util.MissingFormatArgumentException: Format specifier '%s' + * [HHH-12631] - Fix invalid tracev calls in DefaultResolveNaturalIdEventListener + * [HHH-12634] - Make EntityPrinter more permissive regarding the parameters passed + +** Task + * [HHH-12575] - Upgrade to Classmate 1.3.4 + * [HHH-12576] - Upgrade to jboss-transaction-api 1.1.1.Final + * [HHH-12580] - The WildFly module of ByteBuddy should be marked as private API + * [HHH-12583] - Deprecate hibernate.proc.param_null_passing setting + * [HHH-12610] - Upgrade to Byte Buddy 1.8.11 to improve JDK compatibility + +** Improvement + * [HHH-12559] - Add support for MySQL 8 SKIP LOCKED and NOWAIT + * [HHH-12572] - Exclude LockMode.WRITE from loader creation loop + * [HHH-12585] - Improve DefaultFlushEntityEventListener#invokeInterceptor method execution + * [HHH-12589] - Add support for registering custom SQL functions when bootstrapping via JPA + * [HHH-12591] - Remove second call to Scope#setSessionFactory(SessionFactoryImplementor) from TypeConfiguration#scope(SessionFactoryImplementor,BootstrapContext) + * [HHH-12605] - Boxed variables never null + * [HHH-12606] - Container contents are never accessed + * [HHH-12615] - Make AbstractEntityPersister#getLoaderByLockMode() and a few others protected final + * [HHH-12616] - Clarify ambiguity in License name + * [HHH-12618] - ByteBuddy enhancement - Use MethodHandle lookup if available + * [HHH-12626] - Avoid high CPU contention by not allocating Session UUIDs eagerly + * [HHH-12629] - Make some methods protected in DefaultLoadListener + * [HHH-12636] - Upgrade to ByteBuddy 1.8.12 to fix issue with entities having no package + + + +Changes in 5.3.0.Final (May 14, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31661/tab/release-report-done + +** Bug + * [HHH-8786] - find, refresh, lock should throw LockTimeoutException or PessimisticLockException when lock problems occur + * [HHH-10435] - ClassLoaderServiceImpl is getting a Permission check failed "createClassLoader" when run under Java security manager + * [HHH-11859] - Class annotated with @Audited (withModifiedFlag = true) is giving error when executing update. + * [HHH-12011] - Field annotated with target TYPE_USE break metamodel generation + * [HHH-12090] - PropertyAccessBuildingException: Could not locate setter for property named XXX for Java 8 default methods + * [HHH-12199] - Static fields should be ignored when resolving property type via reflection + * [HHH-12362] - Allow both SQL query hints and comments + * [HHH-12470] - Batching statements fails for delete + * [HHH-12517] - Incorrect method references in @deprecated elements of the Query javadoc + * [HHH-12529] - Some StatisticsImpl methods throw an exception instead of returning null + * [HHH-12534] - SAP HANA dialects use unqualified dummy table in queries + * [HHH-12535] - SAP HANA dialect doesn't support circular cascade delete constraints + * [HHH-12539] - NPE in AbstractPropertyMapping.getCommonPersistentClass when creating UnionSubclassEntityPersister for dynamic-map + * [HHH-12540] - Reusing same EntityTransaction with JTA enabled + * [HHH-12546] - locate function doesn't work on SAP HANA + * [HHH-12565] - Can't use TYPE function on leaf subtype of a table per class inheritance hierarchy + +** New Feature + * [HHH-12505] - Option to disable scanning of entity mapping metadata + +** Task + * [HHH-12316] - Document usage of the new Feature Packs + * [HHH-12503] - Finalize 5.3 Migration Guide + * [HHH-12519] - Use Forbidden APIs library (Gradle plugin) to check our use of APIs + * [HHH-12527] - Verify that all binary compatibility breaks between 5.1 and 5.3 are accounted for + * [HHH-12530] - Add processing of unknown hints + * [HHH-12545] - ByteBuddy based enhancer not accepting special character in description names + * [HHH-12554] - Make ByteBuddy EnhancerImpl more closely match the semantics described in Enhancer javadoc + * [HHH-12562] - Remove DefaultSchemaNameResolver#delegate since the value should not be cached + +** Improvement + * [HHH-12463] - Delegate CustomType#equals/hashCode to wrapped UserType + * [HHH-12472] - WildFly (IronJacamar) - EntityManager#find with roll-back-only leads to exception rather than return null + * [HHH-12537] - Query hint test for SAP HANA + * [HHH-12541] - Test for SAP HANA calculation views + * [HHH-12544] - Add jipijapa hook to plug in specialized caching and transaction services + * [HHH-12556] - Share data structures between similar LoadPlan based EntityLoaders + * [HHH-12558] - Lazy load EntityLoaders to improve memory usage + * [HHH-12560] - Make sure only one Service registration (initiator/provided) exists per role + * [HHH-12570] - MariaDB 10.3 adds support for lock timeouts via WAIT plus NOWAIT + + + +Changes in 5.3.0.CR2 (April 27, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31640/tab/release-report-done + +** Bug + * [HHH-3813] - Automatic flush to the join table before a criteria query + * [HHH-3930] - one-to-one causes redundant select query + * [HHH-7119] - Hibernate filter's parameters are not populated when an entity's Collection is populated using a fetch mode of subselect. + * [HHH-7809] - ehcache region factory should set a name for the CacheManager + * [HHH-8382] - Oracle ORA-24816 - Issue HH4635 solved for annotation, not for hbm version + * [HHH-8944] - ColumnTransformer handling is too aggressive in qualifying "column names" + * [HHH-9186] - ORM wrongly assumes that an element of a set has a primary key + * [HHH-9460] - Removing non-optional bidirectional @OneToOne association with cascade + * [HHH-10333] - Schema validation: incorrect use of schema and catalog filters + * [HHH-10667] - Envers cannot support @IdClass referencing foreign entity identifier + * [HHH-11286] - Schema-validation fails with Oracle + * [HHH-11440] - In hibernate 5.2.4 hbm2ddl.auto=validate does not work with oracle + * [HHH-11544] - Joins over type variable defined relations is non-deterministic + * [HHH-11766] - Accessing lazy basic property on entity loaded from 2nd level cache throws exception + * [HHH-11867] - @UpdateTimestamp not working with @Inheritance( strategy = JOINED ) + * [HHH-11901] - Map with null values cannot be audited + * [HHH-11924] - ElementCollection ignore converter for XML mapping + * [HHH-11981] - Association query causes QueryException: Named parameter [revision] not set. + * [HHH-12059] - hibernate.hbm2ddl.auto=update stopped working in Oracle since 5.1.4 + * [HHH-12092] - Bad PrimitiveCharacterArrayNClobType INSTANCE typo + * [HHH-12166] - AbstractCompositionAttribute#getAttributes throws NPE for nested CompositeCustomType + * [HHH-12221] - Incorrect formatting of SQL statement on logging when entities/tables or fields/columns named like keywords (e.g. "group" or "order"). + * [HHH-12225] - NullPointerException When Using type() in HQL + * [HHH-12226] - ObjectNotFoundException thrown when @NotFound(action = NotFoundAction.IGNORE) used with enhancement + * [HHH-12230] - SelectCase does not work when simultaneously exists in select and group by sections + * [HHH-12257] - Refreshing an entity clears the lock mode returned from EntityManager.getLockMode + * [HHH-12260] - Detach of entity with lazy-loaded collection and orphan removal leads to exception during flush of session + * [HHH-12273] - Load Proxy by its identifier should consider the Session UUID + * [HHH-12285] - DB connection exception on rollback causes connection leak + * [HHH-12290] - Failure with JPQL positional queries with collection parameter (IN statement for example) + * [HHH-12292] - QueryParameterBindingValidator does not allow null values in Object arrays + * [HHH-12297] - Relations are not loaded when using Fetch Profiles + * [HHH-12304] - MappingException occurs when a custom enum type is applied to an audited property. + * [HHH-12306] - Fix org.hibernate.envers.test.integration.manytomany.MappedByEmbeddableAttributeTest on HANA + * [HHH-12313] - org.hibernate.jpa.test.transaction.TransactionCommitFailureTest fails on HANA + * [HHH-12314] - CriteriaAPI - Cannot use a not clause on a join, with explicit "on" argument + * [HHH-12332] - 5.2.14 regression: NullPointerException in AbstractPropertyMapping.getSuperCollection + * [HHH-12352] - The new ByteBuddy module for WildFly 12 is not working on JDK9 + * [HHH-12355] - Insert fails on one-to-one mapping when an intermediate embeddable type is between mappings when using ordered inserts. + * [HHH-12357] - NamingHelper uses system default encoding + * [HHH-12369] - Integer overflow in limit handlers when firstResult used with maxResults=Integer.MAX_VALUE on DB2 + * [HHH-12370] - Lazily-initialized byte[] LOB gets turned into a String, resulting in poor performance + * [HHH-12374] - Order inserts sorting code gives up too soon + * [HHH-12375] - 5.2.15 regression: 'could not resolve property: attributes of: org.hibernate.test.inheritance.discriminator.JoinedInheritanceTest$BaseEntity' with FetchType.EAGER + * [HHH-12379] - Add support for persistence_2_2.xsd and orm_2_2.xsd + * [HHH-12380] - Stackoverflow when order_inserts=true + * [HHH-12383] - JoinedSubclassEntityPersister throws ClassCastException for AnyType + * [HHH-12387] - Immutable entities can be updated via bulk update queries + * [HHH-12388] - User Guide and Javadoc typo fixes + * [HHH-12389] - Remove usage of javax.script.ScriptEngine from org.hibernate.test.bytecode.enhancement.access.MixedAccessTest + * [HHH-12391] - calls to EntityTransaction.rollback() should be ignored if the LogicalConnection.physicalConnection is null or the LogicalConnection.physicalConnection.getAutoCommit() returns true + * [HHH-12392] - Caching SchemaResolver delegate with multiple data sources + * [HHH-12397] - org.hibernate.jpa.test.query.QueryTest fails with TImeoutException + * [HHH-12410] - Cannot use AttributeConverter with spatial types + * [HHH-12412] - QueryException Thrown on Abstract Property of Abstract Class + * [HHH-12423] - SecondaryTable is not taking into account the schema while mapping the entity + * [HHH-12427] - Prevent classloader leak in ByteBuddy based BasicProxyFactoryImpl + * [HHH-12439] - Merging of new entities can fail depending on cascade order + * [HHH-12448] - Possible memory leak in Envers due to Narayana Transaction Reaper + * [HHH-12451] - Hibernate CurrencyType info is not correct in the table + * [HHH-12464] - NPE upon insert & delete with identity generated id + * [HHH-12473] - EntityManager.close() should throw an ISE if called on already closed EntityManager + * [HHH-12479] - Document the converted:: prefix for HBM type mappings + * [HHH-12487] - Calling getTransaction() on a closed Session should not throw ISE + * [HHH-12498] - Audit entity with composite-key association to non-audit entity leads to NullPointerException + * [HHH-12507] - InsertOrderingWithCompositeTypeAssociation test fails on Oracle due to reserved word + * [HHH-12508] - SessionFactoryOptions#isSecondLevelCacheEnabled returns true by default with NoCachingRegionFactory + +** New Feature + * [HHH-8058] - Querying property-level revisions + * [HHH-11769] - New MariaDB Dialect for MariaDB >= v10.1 + * [HHH-11790] - Support for DB2 spatial extender + * [HHH-12315] - Publish WildFly Feature Packs rather than a zip file of modules + * [HHH-12417] - default strategy based on registrations with StrategySelector + * [HHH-12424] - Fix unintended binary compatibility breaks between 5.1 and 5.3 + +** Task + * [HHH-12317] - Move module path of the new Feature packs to org.hibernate.orm + * [HHH-12321] - Separate the Wildfly module for ByteByddy to make it private API + * [HHH-12327] - Fix MapProxyTool not to depend directly on Javassist, allowing for BytecodeProvider agonostic support + * [HHH-12328] - ByteBuddyInterceptor#intercept should not wrap Exception types + * [HHH-12334] - ASTUtil improvements in Map usage + * [HHH-12335] - StrategySelectorImpl can avoid some unnecessary String formatting during bootstrap + * [HHH-12336] - Avoid unnecessary invocations of fillInStackTrace() in the tests + * [HHH-12339] - Optimise TypeNames for memory consumption: avoid autoboxing + * [HHH-12340] - BasicTypeRegistry would benefit from string interning + * [HHH-12342] - Upgrade to Byteman 4.0.1 + * [HHH-12343] - Upgrade to WildFly 12.0.0.Final for integration tests + * [HHH-12344] - Upgrade to JBoss Logging 3.3.2.Final + * [HHH-12358] - Upgrade Agroal dependency to 0.4 + * [HHH-12366] - Enable EE8 preview mode on WildFly 12 for integration tests + * [HHH-12367] - Create a separate JBoss Module for Hibernate Envers + * [HHH-12382] - TypeTest is creating a Proxy which is not necessary + * [HHH-12406] - Add a test for HHH-11440 + * [HHH-12455] - WildFly provisioning build helpers should not implicitly change repository configurations + * [HHH-12474] - Make sure the JPA version is defined by a single property across build files + * [HHH-12475] - Remove unnecessary dependencies from gradle build. + * [HHH-12477] - Javassist no longer needed in the JBoss Module for Hibernate Envers + * [HHH-12478] - Upgrade to Mockito 2.18.0 to improve memory utilization of tests + * [HHH-12501] - Fallback implementation for BytecodeProvider should match the default + * [HHH-12509] - Reduce memory usage of PreparedStatementSpyConnectionProvider + * [HHH-12510] - Upgrade PostgreSQL driver + +** Improvement + * [HHH-7555] - Ability to query only for @Revision object without instantion of entities. + * [HHH-11253] - Make Byte Buddy BytecodeProvider impl the default + * [HHH-11356] - Adjust the 2nd-Cache SPIs to better reflect supported uses + * [HHH-11528] - Rename hibernate-modules to better represent their nature of feature pack + * [HHH-11953] - Disallow dynamic creation JCache Cache instances + * [HHH-12296] - Upgrade to Byte Buddy 1.7.10 to support JDK10 + * [HHH-12302] - Schema creation uses non-unicode string types on SAP HANA + * [HHH-12323] - Update Statistics API and SPI based on changes to 2nd level caching changes + * [HHH-12331] - Avoid swallowed instances of PropertyNotFoundException + * [HHH-12346] - Replace StringHelper#join by Java's String#join + * [HHH-12364] - ElementCollectionMapTest contains unnecessary MapKeyJoinColumn + * [HHH-12365] - User Guide: call_key should change to call_timestamp_epoch + * [HHH-12373] - Better document AuditReader#getEntityName() as throwing an exception rather than it returning null. + * [HHH-12376] - Apply some ThreadLocal optimisations made possible by new Java 8 API + * [HHH-12378] - JDK 9 support: Remove javax.annotation.Generated import + * [HHH-12384] - Have proxies generated by ByteBuddy to conform to legacy naming strategies + * [HHH-12398] - Upgrade to Byte Buddy 1.8.0 to support JDK10 and preliminary support for JDK11 + * [HHH-12399] - Re-introduce Environment#jvmHasTimestampBug as deprecated method + * [HHH-12415] - Update Gradle wrapper to Gradle 4.6 + * [HHH-12419] - Incorrect batch inserts example + * [HHH-12426] - SAP HANA spatial dialect should support all SAP HANA spatial functions + * [HHH-12432] - Upgrade to Hibernate Commons Annotations 5.0.3.Final + * [HHH-12440] - Manage the SessionFactory's UUID on SessionFactoryOptions - wider availability + * [HHH-12443] - Introduce TypeConfiguration + * [HHH-12444] - Introduce BootstrapContext + * [HHH-12454] - Offer flag to consider id generator with local scope (legacy non JPA behavior) + * [HHH-12467] - ByteBuddy TypeCache stale entries should be cleared to avoid (weak) references to application classloader + * [HHH-12471] - Avoid using a TypeCache in the ByteBuddy BytecodeProvider + * [HHH-12481] - Reduce the visibility of internal implementations of Callback + * [HHH-12482] - Avoid logging overhead within CallbackBuilderLegacyImpl loops + * [HHH-12484] - Improved error output for LazyInitializationException to include entity-related info + * [HHH-12485] - MetamodelImpl throws exceptions unnecessarily since it could cache failed imports + * [HHH-12486] - SessionFactoryHelper#findEntityPersisterByName unnecessarily tries to find entity persisters via a method that causes MappingExceptions + * [HHH-12491] - Document the usage of the maven-compiler-plugin for hibernate-jpamodelgen + * [HHH-12493] - Further reduce allocations of ByteBuddy engines + * [HHH-12511] - Make ASTPrinter threadsafe and have code reuse their instances + * [HHH-12514] - Avoid repeated creations of costly Xsd definitions and definition lookups + * [HHH-12515] - LocalXsdResolver should attempt local resource loading before attempting it via URL + * [HHH-12521] - Take advantage of Java 8 improvements to optimise Statistics + * [HHH-12523] - Invoke CacheTransactionSynchronization.transactionCompleting + * [HHH-12524] - Rename JBoss modules from hibernate-core-jbossmodules to hibernate-orm-jbossmodules + * [HHH-12525] - Allow JBoss module definition to eventually import an Infinispan 2LC module + * [HHH-12526] - WildFly integration tests no longer need to override the Javassist module + +** Deprecation + * [HHH-12441] - Port hibernate-ehcache to the new caching SPI, but deprecate + + + +Changes in 5.3.0.CR1 (February 15, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31633/tab/release-report-done + +** Bug + * [HHH-8916] - SQLServer2005LimitHandler bind top parameter to wrong position + * [HHH-10961] - Update address of Free Software Foundation + * [HHH-12114] - Union-select aliases not injected before "clazz_" conditions in HQL query + * [HHH-12141] - SQL insert in stateless session causes javax.persistence.TransactionRequiredException + * [HHH-12271] - SchemaDropperImpl does not drop constraints with IF EXISTS + * [HHH-12286] - Update Vibur dependency from 21.3 to 22.0 + * [HHH-12289] - One call of the SessionImpl#listeners( ) method from SessionImpl#autoFlushIfRequired() is useless. + * [HHH-12294] - Regression after fixing HHH-12064 + +** Task + * [HHH-12293] - Upgrade to Hibernate Commons Annotations 5.0.2.Final + +** Improvement + * [HHH-12236] - Document 5.3 changes + * [HHH-12280] - Resolve {alias} in @Formula like Restrictions.sqlRestriction() + * [HHH-12282] - Allow disabling of invalidation of second-level cache entries for multi-table entities + + + + +Changes in 5.3.0.Beta2 (February 1, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31621/tab/release-report-done + +** Bug + * [HHH-1268] - Unidirection OneToMany causes duplicate key entry violation when removing from list + * [HHH-11587] - Reordering items in List throws a constraint violation + * [HHH-11845] - Warn user when multiple persistence-units use the same name + * [HHH-12107] - ClassCastException when using L2Cache with "structured_cache"=true + * [HHH-12227] - {h-schema} tag is not replaced in @Formula + * [HHH-12238] - aliasToBean throws confusing ClassCastException if class lacks setters + * [HHH-12240] - mapped attribute link to the attribute in the embeddable attribute in the referenced entity is not supported + * [HHH-12241] - BinderHelper:644 logdebugf FAILS + * [HHH-12244] - The validation-api in libraries.gradle should be 2.0.1.Final, not 1.1.0.Final + * [HHH-12245] - The metamodel generator does not handle primitive types very well + * [HHH-12246] - Gradle build fails with Java 9 + * [HHH-12249] - Wrong format in debug message of CollectionBinder + * [HHH-12256] - org.hibernate.test.lazyload.JtaLazyLoadingTest is not using JTA + * [HHH-12266] - The release task does not upload the documentation + +** New Feature + * [HHH-12252] - New module for Agroal connection pool integration + * [HHH-12264] - Updated Vibur dependency from 21.2 to 21.3 + +** Task + * [HHH-12172] - Move to BinTray for publishing artifacts + * [HHH-12231] - Use the org.wildfly.build.provision Gradle plugin to fetch WildFly instances for integration testing + * [HHH-12250] - Avoid WildFly thin servers as they require Maven settings + * [HHH-12267] - Update migration guide to cover Generators name scope changes + +** Improvement + * [HHH-11571] - Please update Hibernate with awareness of WebSphere Liberty transaction manager + * [HHH-12034] - According to JPA, a Proxy should be loaded even when accessing the identifier + * [HHH-12258] - Make credentials masking consistent + + + + + +Changes in 5.3.0.Beta1 (January 17, 2018) +------------------------------------------------------------------------------------------------------------------------ +https://hibernate.atlassian.net/projects/HHH/versions/31612/tab/release-report-all-issues + +** Sub-task + * [HHH-12133] - Create ManagedBeanRegistry and ManagedBean + * [HHH-12134] - Convert entity listener CDI support to use ManagedBean/MenagedBeanRepository + * [HHH-12135] - Support for AttributeConverters as CDI beans + +** Bug + * [HHH-1830] - Error during parse query on MS SQL + * [HHH-9965] - Pagination ignored on collection fetch join: Add the ability to throw an exception instead of loggin a warn + * [HHH-10575] - MapKeyColumn on Map<> association causes Insert constraint violation + * [HHH-11366] - Problem with Pax exam and injection + * [HHH-11913] - Schema generation ignores index DESC/ASC order + * [HHH-12075] - SQLQuery.executeUpdate() ignores SQLQuery.setTimeout() + * [HHH-12096] - Problem finding correlated getter-method for field access + * [HHH-12097] - EntityManagerFactory open/closed checks per JPA spec + * [HHH-12099] - Query#getLockMode ought to throw exception for non-SELECT + * [HHH-12106] - Database name not quoted at schema update + * [HHH-12116] - Positional parameters report position as name + * [HHH-12122] - Checking @OrderBy for special cases should perform case-insensitive checking + * [HHH-12125] - Support @GeneratedValue without explicit generator definition + * [HHH-12129] - Fix expected exceptions on various Query methods + * [HHH-12136] - Various improvements for ProcedureCall/StoredProcedureQuery + * [HHH-12150] - @MapKeyColumn referring to otherwise non-mapped column + * [HHH-12157] - TableGenerator defined on one class is not visible on another + * [HHH-12171] - Fix tests for hibernate-orm-modules + * [HHH-12173] - The new org.hibernate.resource.beans.spi.ExtendedBeanManager breaks compatibility with implementations of org.hibernate.jpa.event.spi.jpa.ExtendedBeanManager + * [HHH-12175] - ParameterParser doesn't handle JPA positional parameters correctly + * [HHH-12203] - NUMERIC column type is not handled correctly on HANA + * [HHH-12212] - Derived Identifiers component column size not applied + +** New Feature + * [HHH-10294] - EntityGraph improvement: For each jpa attribute, generate also a String constant holding the attribute field name + * [HHH-10541] - Create Vibur DBCP connection pool module + * [HHH-12147] - Add support for JPA2.2 @TableGenerators and @SequenceGenerators + * [HHH-12148] - Add setting indicating if the value stored in the table used by the @TableGenerator is the last value generated or the next value to be used. + +** Task + * [HHH-12098] - Prep for 5.3 + * [HHH-12117] - Make sure Hibernate returns null on failed attempt to create EMF + * [HHH-12155] - Update documentation regarding limitation of defining caching on just root entity + * [HHH-12167] - Add matrix testing configuration for HANA database + * [HHH-12176] - Relax the checkstyle requirement for file headers. + * [HHH-12177] - Drop hibernate-infinispan module - relocated + * [HHH-12183] - Upgrade Gradle to work with JDK 9.0.1. + * [HHH-12211] - Test failure on MariaDB when the database charset is configured to UTF8 + +** Improvement + * [HHH-9641] - Resume uploading Javadoc JARs to Maven + * [HHH-11019] - Extend DelayedPostInsertIdentifier support to include checks for FlushMode (EXTENDED PC) + * [HHH-11798] - Provide method for overriding delete statement in GlobalTemporaryTableBulkIdStrategy + * [HHH-12095] - MavenEnhancePlugin causes compile phase run twice + * [HHH-12131] - Small memory improvement when parsing / transforming UUID + * [HHH-12139] - Allow Hibernate's Transaction act like JPA's EntityTransaction + * [HHH-12146] - Support enabling caching at any level within a mapped hierarchy + * [HHH-12164] - Upgrade the version of Hibernate Validator used for testing + * [HHH-12185] - Simplify SessionFactoryBuilder / SessionFactoryOptions handling + * [HHH-12187] - Drop custom javadoc css + * [HHH-12188] - Add Java 9 automatic module name hinting + * [HHH-12189] - Only call setAccessible() when member is not accessible + * [HHH-12190] - General tidying of Gradle scripts + * [HHH-12191] - Add Travis CI support + * [HHH-12216] - Improve logging for when Hibernate throws the "illegally attempted to associate a proxy with two open Sessions" Exception + +** Deprecation + * [HHH-12194] - Deprecate Environment-scoped settings + +** Remove Feature + * [HHH-12101] - Remove support for legacy HQL-style positional parameters + * [HHH-12110] - IllegalStateException should be thrown for some methods when called on a closed EntityManager + * [HHH-12118] - Removing handling of old JVM_HAS_TIMESTAMP_BUG + + + +Changes in 5.2.12.Final (October 19, 2017) +------------------------------------------------------------------------------------------------------------------------ +https://hibernate.atlassian.net/projects/HHH/versions/31000 + +** Bug + * [HHH-3870] - Hibernate proxies Groovy's getMetaClass method breaking proxies when used with Groovy + * [HHH-7842] - Hibernate Criteria does not respect fetch mode, when alias is used + * [HHH-11615] - Envers integration tests fail when WildFly security manager is enabled + * [HHH-11640] - NamedQuery doesn't log comment when UPDATE/DELETE + * [HHH-11651] - unwrapping errors in multiple classes + * [HHH-11656] - Optimistic Locking with HANA Dialect results in invalid SQL syntax. + * [HHH-11732] - HHH000352: in StatelessSession on rollback with JDBC batch + * [HHH-11816] - JoinProcessor considers table names with colons dynamic filter parameters + * [HHH-11838] - Id retrieving from proxy with FK leads to query execution + * [HHH-11863] - Implement REF_CURSOR support for StoredProcedureQuery.getOutputParameterValue(4); + * [HHH-11965] - Using unproxy in getter does not work properly + * [HHH-11969] - hibernate-spatial requires old 9.4-1200-jdbc41 dependency + * [HHH-11970] - Use of @NotFound(IGNORE) and @BatchSize when there are unresolved foreign key values results in extra queries + * [HHH-11971] - QueryParameterBindingValidator does not handle primitive types + * [HHH-11980] - MultiTenantConnectionProvider is required for MultiTenancyStrategy.DISCRIMINATOR + * [HHH-11988] - Envers creates unnecessary audit records for unchanged BigDecimal values + * [HHH-11996] - order_inserts causing constraint violation + * [HHH-11997] - EntityManager.createNamedQuery throwing illegalstateexception + * [HHH-12018] - NonUniqueObjectException when trying to update audited ElementCollection + * [HHH-12022] - hibernate-spatial adds org.slf4j:slf4j-simple:jar:1.7.7:runtime dependency + +** New Feature + * [HHH-6382] - Support OnDelete=OnDeleteAction.CASCADE for unidirectional OneToMany ( JPA ) + * [HHH-11984] - Add support for navigating between different doc versions + * [HHH-12006] - Make User Guide sections bookmark-able + * [HHH-12020] - Add SAP HANA to the list of provided dialects + * [HHH-12021] - Fix tests failing on SAP HANA + * [HHH-12033] - README.md links should use Markdown notation instead of AsciiDoc + +** Task + * [HHH-11507] - Upgrade to Gradle 4.2 + * [HHH-12001] - Allow ORM to be built with Java 9 + * [HHH-12010] - Improve documentation for computeAggregationInInstanceContext + +** Improvement + * [HHH-2897] - Adding support for use of sequence objects in DB2 V8 OS390 + * [HHH-9576] - Use JDBC bind variables for handling JPA Criteria query numeric literals + * [HHH-11999] - Envers documentation issues + * [HHH-12026] - Make sure that search icon is rendered correctly in TOC + * [HHH-12037] - Remove unused code in ArrayHelper + * [HHH-12042] - Update to latest geolatte-geom version + +** Deprecation + * [HHH-11989] - Deprecate LogicalConnectionImplementor#makeShareableCopy + +** Remove Feature + * [HHH-11906] - Add support for MySQL query optimizer hints + + +Changes in 5.2.11.Final (September 13, 2017) +------------------------------------------------------------------------------------------------------------------------ +https://hibernate.atlassian.net/projects/HHH/versions/28600/ + +** Bug + * [HHH-5933] - NoopOptimizer ignores negative allocationSize; uses default of 1 instead + * [HHH-10747] - Enhanced entity classes initialize lazy collections when loaded. + * [HHH-11283] - hibernate spatial - getSRID returning 0 + * [HHH-11374] - ConcurrentStatisticsImpl#getSecondLevelCacheStatistics() throws NPE if second level cache is not activated + * [HHH-11463] - HBM mappings generate a foreign-key even when foreign-key="none" is specified. + * [HHH-11465] - [Patch] @AttributeOverride does not work for CompositeUserType inside of @Embeddable + * [HHH-11600] - Sap HANA PreparedStatement implements CallableStatement and is treated as such by Hibernate + * [HHH-11614] - Wrong result for @Lob column with postgresql DB since 5.2.9 + * [HHH-11624] - LazyInitializationException on enhanced class for Map + * [HHH-11634] - ActionQueue#InsertActionSorter fails to generate right order + * [HHH-11635] - MySQLSkipAutoCommitTest fails when run on MariaDB + * [HHH-11642] - SQLServerException: The index 2 is out of range when executiong Spring Data findAll(Pageable) + * [HHH-11645] - HikariCP shutdown() method is deprecated, close() should be called instead + * [HHH-11646] - Incorrect search-and-replace has changed "before" to "beforeQuery" and "after" with "afterQuery" during HHH-10664 + * [HHH-11650] - Parenthesis are interpreted in WHERE conditions when using paging and SQL Server + * [HHH-11655] - SessionImpl does not load EntityPersister by entity name + * [HHH-11703] - Entity with Natural ID not being cached in the persistenceContext, causing extra queries + * [HHH-11707] - README.md claims Java 6 compatibility + * [HHH-11709] - NoopOptimizer skips negative values and 0 when it has a positive incrementSize + * [HHH-11712] - PostgreSQL does not support positive/negative initial sequence values for descending/ascending sequences unless MAXVALUE/MINVALUE is defined as well + * [HHH-11714] - Entities with InheritanceType.SINGLE_TABLE and SecondaryTable are not being saved correctly + * [HHH-11716] - @Transient annotation not respected when class defines 'get' and 'is' accessor variants + * [HHH-11718] - Fix various alerts reported by lgtm.com + * [HHH-11725] - Javadoc typo + * [HHH-11726] - PASS_DISTINCT_THROUGH hint is ignored when used in conjunction with maxResults/firstResult limiters + * [HHH-11728] - Typo in PooledConnections + * [HHH-11729] - Add clarifications in the User Guide related to how Hibernate FetchMode types translate to JPA + * [HHH-11730] - Unable to audit entity with originalId property + * [HHH-11739] - globally_quoted_identifiers_skip_column_definitions property does the opposite of what the doc describes + * [HHH-11740] - Default MultiTableBulkIdStrategy for DB2 does not work with connection pools + * [HHH-11743] - Query.stream() does not map Tuple + * [HHH-11747] - Pagination with DB2390Dialect: TypedQuery.getResultList() always returns the first "maxResults" rows of the table for each call + * [HHH-11748] - RelatedId queries against Id annotated properties result in AuditException + * [HHH-11762] - PersistenceUnitUtilImpl#getIdentifier throws MappingException for non-entity + * [HHH-11764] - JTS geometry being bound to byte array instead of PGgeometry + * [HHH-11768] - foreign key violation with order_inserts=true and cascading persist + * [HHH-11770] - Audit queries for a OneToMany that is mapped to an EmbeddedId fails. + * [HHH-11783] - Wrong comment in UpdateTimestampsCache + * [HHH-11788] - Project gitignore ignores test classes + * [HHH-11795] - Support Ant Task for Bytecode Enhancement + * [HHH-11804] - Embeddable class' name and the reference to it are different + * [HHH-11815] - @org.hibernate.annotations.Index and NullPointerException in IndexOrUniqueKeySecondPass + * [HHH-11818] - ClassCastException when binding a MaterializedNClobType value as NClob + * [HHH-11826] - ImplicitNamingStrategyComponentPathImpl generates invalid SQL for Entity with Embedded ElementCollection + * [HHH-11827] - JPA entity native query not eagerly fetching associations as suggested by documentation. + * [HHH-11830] - Shared Session memory leak via TransactionObserver reference + * [HHH-11832] - ManyToManyWithDynamicFilterTest fails on Sybase due to reserved word + * [HHH-11837] - MapsId and PrimaryKeyJoinColumn examples in the documentation should use OneToOne rather than ManyToOne + * [HHH-11841] - QueryException on map associaton when using Envers + * [HHH-11851] - BaseEnversFunctionalTestCase tests do not test against all parameterized audit strategies. + * [HHH-11864] - AutoCommit mode not reset after use by SchemaValidator + * [HHH-11868] - Documentation : Bad Hibernate type mapped to java.time.ZonedDateTime (Java 8) + * [HHH-11881] - Null Set collection elements are inserted into collection table + * [HHH-11884] - wrong class in returnedClass() in UserType example + * [HHH-11892] - Audit data not loaded for @ElementCollection + * [HHH-11897] - Fix support for Tuple results for native queries + * [HHH-11904] - EnumExplicitTypeTest test assert fails on mariadb clusters + * [HHH-11905] - AbstractLobTest Fails on Sybase + * [HHH-11910] - SchemaUpdateTest fails on databases using case-insensitive identifiers + * [HHH-11914] - SchemaUpdate.setHaltOnError(true) does nothing + * [HHH-11915] - DatabaseMetaData#getIndexInfo can return column names enclosed in quotes on PostgresPlus + * [HHH-11916] - Unnecessary initialization of lazy collection on PERSIST cascade + * [HHH-11922] - Entity with null many-to-one cannot be loaded when associated entity has composite ID with hibernate.create_empty_composites.enabled=true + * [HHH-11927] - CascadeMergeToChildBeforeParentTest should not assume the persisted entity has the id with a value of 1 + * [HHH-11928] - Empty left join fetched collection is uninitialized when collection key is composite with hibernate.create_empty_composites.enabled=true + * [HHH-11935] - Log a warning and update documentation that enabling "empty" composites is an experimental feature + * [HHH-11944] - Fix the Session related delegating classes + * [HHH-11957] - DB2 substring method needs to be exposed in DB297Dialect + * [HHH-11982] - AbstractSharedSessionContract#getInterceptor should not call checkTransactionSynchStatus() + +** New Feature + * [HHH-11907] - Add the getResultStream() default method in org.hibernate.query.Query + * [HHH-11942] - ANTLR parser should fail when providing an extra parenthesis + +** Task + * [HHH-11698] - Upgrade to Byte Buddy 1.6.14 for improved JDK9 compatibility + * [HHH-11752] - Remove reference to old types. + * [HHH-11756] - Typo in public API method name: requiresPostCommitHanding on PostInsertEventListener + * [HHH-11808] - Update migration guide and documentation + * [HHH-11878] - Minor typo in CascadeStyles.java + * [HHH-11950] - Target WildFly 11 for produced hibernate-orm-modules + +** Improvement + * [HHH-8955] - Add HSQLDialect support for trunc + * [HHH-10934] - Preventing duplicate ForeignKey generation + * [HHH-11176] - Add support for Tuple results for native queries + * [HHH-11186] - Add examples for all Hibernate annotations + * [HHH-11290] - Migrate all documentation snippets that derive the source code from extras instead of actual Unit Tests + * [HHH-11411] - Two column navigation and search box for documentation + * [HHH-11500] - Provide the cause of the error when validating @Loader named queries + * [HHH-11526] - Documentation for custom collection types + * [HHH-11546] - Add support for SAP NetWeaver Application Server as JTA Platform + * [HHH-11647] - Use ALTER TABLE IF EXISTS on Postgres + * [HHH-11750] - Fix typos in Hibernate 5.2 user guide + * [HHH-11759] - Improve deterministic nature of generated SQL of audited properties. + * [HHH-11793] - Change docs to point out that EAGER associations cannot be turn to LAZY with entity graphs + * [HHH-11820] - Do not inject CollectionTracker into entity without collection + * [HHH-11824] - Remove reflection for accessing Types.REF_CURSOR + * [HHH-11886] - Elaborate Envers documentation and switch to actual source code examples + * [HHH-11929] - Improve Performance of SQLServer2012LimitHandler.hasOrderBy() + * [HHH-11934] - Add a protected getter for the delegate in SessionFactoryDelegatingImpl + * [HHH-11945] - Make ExceptionConverterImpl use SharedSessionContractImplementor instead of AbstractSharedSessionContract + * [HHH-11946] - Configure the Configurable services in SessionFactoryServiceRegistryImpl + * [HHH-11951] - Improve TransactionStatus javadoc + * [HHH-11956] - Add createCustomLoader() to the NativeQueryInterpreter contract + * [HHH-11962] - Unmark deprecated the NativeQuery methods and add missing covariant overrides + +** Patch + * [HHH-3924] - Use intern() to reuse strings and reduce memory usage + +** Deprecation + * [HHH-11660] - Deprecate org.hibernate.mapping.RelationalModel + * [HHH-11700] - Deprecate three org.hibernate.engine.spi.SessionFactoryImplementor methods + * [HHH-11737] - Remove dependency on legacy criteria package. + +** Remove Feature + * [HHH-11890] - Remove old docbook folder from the documentation module + * [HHH-11891] - Clarify documentation about Hibernate support for basic array types + + +Changes in 5.2.10.Final (April 14, 2017) +------------------------------------------------------------------------------------------------------------------------ +https://hibernate.atlassian.net/projects/HHH/versions/28100 + +** Bug + * [HHH-3628] - Hilo optimizer problem in case of multiple threads accessing the sequence table + * [HHH-8001] - Apply query timeouts to Oracle follow-on locking + * [HHH-9663] - Orphan removal does not work for OneToOne relations + * [HHH-10062] - ScrollableResults with join fetch reuses proxy rather than loading actual object. + * [HHH-10728] - NullPointerException when using CriteriaBuilder.selectCase with CriteriaBuilder.equal + * [HHH-11459] - Bytecode-enhanced Entity cannot be merged or refreshed + * [HHH-11557] - DB2 gets confused with numerical parameters in nullif function DB2Dialect + * [HHH-11575] - Multiple revisions are created during a single transaction with FlushMode COMMIT + * [HHH-11576] - Unloaded collections get deleted when entity is bytecode enhanced + * [HHH-11579] - Disable Query parameter validation when a Session is unwrapped from an EntityManager + * [HHH-11580] - EnversPreCollectionRemoveEventListener fails because EntityManager is closed when using JPA + JTA + Envers + * [HHH-11582] - Hibernate-Envers has incoherent behavior for modified flag when create new Entity + * [HHH-11591] - Nullable check should not be skipped for OneToOne annotated with @NotFound(action = NotFoundAction.IGNORE) + * [HHH-11592] - The Field org.hibernate.jpa.AvailableSettings.JDBC_PASSWORD is initialized with org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER + * [HHH-11596] - @OneToOne association with @JoinTable ignores optional attribute + * [HHH-11601] - Fix tests failing on Oracle + * [HHH-11602] - Session close counter statistic not updated when using Hibernate in JPA mode + * [HHH-11609] - Cascade @OneToOne persist with enabled order_inserts generates statements in a wrong order + * [HHH-11612] - SINGLE_TABLE associated entity query missing restriction of DiscriminatorColumn - reverting HHH-11375 + * [HHH-11616] - Refactor org.hibernate.jpa.test.lock.LockTest + * [HHH-11617] - Statement leak in case of "SQLGrammarException: could not extract ResultSet" + * [HHH-11625] - Namespace uses physicalNamingStrategy.toPhysicalCatalogName() for schema name. + +** New Feature + * [HHH-10654] - LockOptions.SKIP_LOCKED semantics implementation on MSSQL + * [HHH-10850] - SQLServerDialect doesRepeatableReadCauseReadersToBlockWriters impelmentation + +** Improvement + * [HHH-10831] - Hibernate method to un-proxy a javassist proxy + * [HHH-11409] - Bind registered collection types using their type handler + * [HHH-11499] - Add a new DB2 dialect that uses "cross join" for cross joins instead of "," + * [HHH-11542] - Allow the auto-commit resolution to be configurable for RESOURCE_LOCAL transactions + * [HHH-11569] - Return only distinct elements when query is hinted with EntityGraph + * [HHH-11584] - Made parameter names of Restrictions#between more readable + * [HHH-11585] - Batch ordering fails for bidirectional one-to-one associations + * [HHH-11593] - Fix test issues in SQL Server + * [HHH-11598] - Use the default catalog when scanning the tables for hbm2ddl schema migration + + +Changes in 5.2.9.Final (March 16, 2017) +------------------------------------------------------------------------------------------------------------------------ +https://hibernate.atlassian.net/projects/HHH/versions/27600 + +** Bug + * [HHH-9114] - @IdClass with @MappedSuperclass results in "property not found" + * [HHH-11372] - Do not send RemoveExpiredCommands in repl/dist caches + * [HHH-11373] - Silence lock acquisition failures on remote nodes + * [HHH-11381] - In nonstrict mode, putFromLoad after evict can behave incorrectly + * [HHH-11397] - Query parameter binding validation issue + * [HHH-11470] - Schema update should not try to query sequences for Dialects not supporting them (DB2400Dialect, DerbyDialect, DB2390Dialect) + * [HHH-11477] - HQL query against field marked with @Lob fails on PostgreSQL + * [HHH-11502] - XML Mapped Entity with a ManyToOne association to a Annotation Mapped Entity -> NullPointerException + * [HHH-11503] - LimitHandler parameter binding fails on SQL Server 2012 + * [HHH-11506] - Lazy properties are not updated on bytecode-enhanced entity if not all lazy properties (e.g. collections) are initialized + * [HHH-11510] - NativeQuery#iterate throws QuerySyntaxException instead of UnsupportedOperationException + * [HHH-11511] - QuerySyntaxException when sorting by a column using a JPQL reserved keyword + * [HHH-11516] - Level two cache may not be enabled when using @Cacheable without/instead of @Cache + * [HHH-11529] - Getting NullPointerException from ScanningCoordinator debug log + * [HHH-11536] - Fix unit tests failing on Oracle + * [HHH-11538] - Redundant left outer joins in generated SQL + * [HHH-11540] - Metamodel does not include embeddable type used in type variables + * [HHH-11545] - ForeignKey definition of @CollectionTable isn't properly used + * [HHH-11547] - Misspelling in documentation + * [HHH-11549] - Unable to locate MappedSuperclass version attribute when mixing annotations with hibernate mapping files + * [HHH-11554] - Inherited interfaces are not considered when creating EntityMetamodel + * [HHH-11555] - AbstractSharedSessionContract doesn't restore ExceptionConverter after de-serialization + * [HHH-11559] - Fix tests catching exceptions without re-throwing them + * [HHH-11560] - Envers throws a MappingException for Lob + ElementCollection for non-audited properties. + * [HHH-11570] - Hibernate Envers listeners fail because EntityManager is closed when using JPA/JTA/Hibernate 5.2.8/Envers + +** Improvement + * [HHH-11089] - Naming Strategy Does not affect the user-specified index/foreign-key names + * [HHH-11143] - Log a warning if @Cache / @Cacheable specified on non-root entities + * [HHH-11473] - Refactor MySQL Dialects + * [HHH-11509] - Infomix limit handler support for offset + * [HHH-11518] - Log4DelegatingLogger slows down testsuite execution by formatting messages too eagerly + * [HHH-11530] - IdentityGeneratorExtendsTest.testIdentifierGeneratorExtendsIdentityGenerator failing on Oracle DBs + * [HHH-11551] - Forward IOException in ClassFileArchiveEntryHandler::toClassFile + * [HHH-11558] - Envers Query API throws NullPointerException when providing a non-audited entity class. + * [HHH-11563] - Avoid calling multiple times org.hibernate.mapping.Component#getComponentClass() during the PojoComponentTuplizer creation + * [HHH-11564] - ThreadLocal access in ManagedSessionContext does not need synchronization + * [HHH-11568] - Throw QueryException rather than antlr-specific exceptions when query parsing fails. + Changes in 5.2.8.Final (February 17, 2017) ------------------------------------------------------------------------------------------------------------------------ diff --git a/databases.gradle b/databases.gradle deleted file mode 100644 index 21b161865f4d..000000000000 --- a/databases.gradle +++ /dev/null @@ -1,83 +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 . - */ - -// build a map of the database settings to use. -ext { - db = 'h2' - dbBundle = [ - h2 : [ - 'db.dialect' : 'org.hibernate.dialect.H2Dialect', - 'jdbc.driver': 'org.h2.Driver', - 'jdbc.user' : 'sa', - 'jdbc.pass' : '', - 'jdbc.url' : 'jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=10000', - ], - hsqldb : [ - 'db.dialect' : 'org.hibernate.dialect.HSQLDialect', - 'jdbc.driver': 'org.hsqldb.jdbc.JDBCDriver', - 'jdbc.user' : 'sa', - 'jdbc.pass' : '', - 'jdbc.url' : 'jdbc:hsqldb:mem:test' - ], - 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' - ], - pgsql : [ - 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', - 'jdbc.driver': 'org.postgresql.Driver', - 'jdbc.user' : 'hibernate_orm_test', - 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:postgresql:hibernate_orm_test' - ], - mysql : [ - 'db.dialect' : 'org.hibernate.dialect.MySQL57InnoDBDialect', - 'jdbc.driver': 'com.mysql.jdbc.Driver', - 'jdbc.user' : 'hibernateormtest', - 'jdbc.pass' : 'hibernateormtest', - 'jdbc.url' : 'jdbc:mysql://localhost/hibernate_orm_test' - ], - mariadb : [ - 'db.dialect' : 'org.hibernate.dialect.MariaDB53Dialect', - 'jdbc.driver': 'org.mariadb.jdbc.Driver', - 'jdbc.user' : 'hibernate_orm_test', - 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mariadb://127.0.0.1/hibernate_orm_test' - ], - postgis : [ - 'db.dialect' : 'org.hibernate.spatial.dialect.postgis.PostgisDialect', - 'jdbc.driver': 'org.postgresql.Driver', - 'jdbc.user' : 'hibernate_orm_test', - 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:postgresql:hibernate_orm_test' - ], - oracle : [ - 'db.dialect' : 'org.hibernate.dialect.Oracle10gDialect', - 'jdbc.driver': 'oracle.jdbc.driver.OracleDriver', - 'jdbc.user' : 'hibernate_orm_test', - 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:oracle:thin:@localhost:1521/xe' - ], - 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://localhost;instance=SQLEXPRESS;databaseName=hibernate_orm_test' - ], - informix : [ - 'db.dialect' : 'org.hibernate.dialect.InformixDialect', - 'jdbc.driver': 'com.informix.jdbc.IfxDriver', - 'jdbc.user' : 'informix', - 'jdbc.pass' : 'in4mix', - 'jdbc.url' : 'jdbc:informix-sqli://192.168.99.100:9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix' - ] - ] -} diff --git a/databases/hana/matrix.gradle b/databases/hana/matrix.gradle new file mode 100644 index 000000000000..8782b540d708 --- /dev/null +++ b/databases/hana/matrix.gradle @@ -0,0 +1,8 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +jdbcDependency 'com.sap.cloud.db.jdbc:ngdbc:2.2.16' \ No newline at end of file diff --git a/databases/hana/resources/hibernate.properties b/databases/hana/resources/hibernate.properties new file mode 100644 index 000000000000..f53e3204b525 --- /dev/null +++ b/databases/hana/resources/hibernate.properties @@ -0,0 +1,26 @@ +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# + +hibernate.dialect org.hibernate.dialect.HANAColumnStoreDialect +hibernate.connection.driver_class com.sap.db.jdbc.Driver +hibernate.connection.url jdbc:sap://localhost:39015/ +hibernate.connection.username HIBERNATE_TEST +hibernate.connection.password H1bernate_test + +hibernate.connection.pool_size 5 + +hibernate.show_sql false +hibernate.format_sql true + +hibernate.max_fetch_depth 5 + +hibernate.cache.region_prefix hibernate.test +hibernate.cache.region.factory_class org.hibernate.testing.cache.CachingRegionFactory + +javax.persistence.validation.mode=NONE +hibernate.service.allow_crawling=false +hibernate.session.events.log=true \ No newline at end of file diff --git a/databases/mariadb/matrix.gradle b/databases/mariadb/matrix.gradle index 7ebdee14585b..39b03e208f25 100644 --- a/databases/mariadb/matrix.gradle +++ b/databases/mariadb/matrix.gradle @@ -4,4 +4,4 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:1.5.7' \ No newline at end of file +jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:2.2.4' \ No newline at end of file diff --git a/databases/mssqlserver/matrix.gradle b/databases/mssqlserver/matrix.gradle new file mode 100644 index 000000000000..9e763e239a6b --- /dev/null +++ b/databases/mssqlserver/matrix.gradle @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +jdbcDependency 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8' \ No newline at end of file diff --git a/databases/mssqlserver/resources/hibernate.properties b/databases/mssqlserver/resources/hibernate.properties new file mode 100644 index 000000000000..7f582bf0a02b --- /dev/null +++ b/databases/mssqlserver/resources/hibernate.properties @@ -0,0 +1,33 @@ +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# + +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# + +hibernate.dialect org.hibernate.dialect.SQLServer2012Dialect +hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver +hibernate.connection.url jdbc:sqlserver://hibernate-testing-mssql-express.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com +hibernate.connection.username hibernate_orm_test +hibernate.connection.password hibernate_orm_test + +hibernate.connection.pool_size 5 + +hibernate.show_sql false +hibernate.format_sql true + +hibernate.max_fetch_depth 5 + +hibernate.cache.region_prefix hibernate.test +hibernate.cache.region.factory_class org.hibernate.testing.cache.CachingRegionFactory + +javax.persistence.validation.mode=NONE +hibernate.service.allow_crawling=false +hibernate.session.events.log=true \ No newline at end of file diff --git a/databases/oracle/matrix.gradle b/databases/oracle/matrix.gradle index b4a64b9e80a1..2355701ef41c 100644 --- a/databases/oracle/matrix.gradle +++ b/databases/oracle/matrix.gradle @@ -4,4 +4,6 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'com.oracle.ojdbc:ojdbc7:12.1.0.2.0' +// Expected to match the jar name: drop a copy of ojdbc8.jar in a local directory, +// then point the environment variable ADDITIONAL_REPO to that directory. +jdbcDependency name : 'ojdbc8' diff --git a/databases/oracle/resources/hibernate.properties b/databases/oracle/resources/hibernate.properties index eeb6db8d9ddd..05f554eec394 100644 --- a/databases/oracle/resources/hibernate.properties +++ b/databases/oracle/resources/hibernate.properties @@ -7,8 +7,9 @@ hibernate.dialect org.hibernate.dialect.Oracle12cDialect hibernate.connection.driver_class oracle.jdbc.driver.OracleDriver -hibernate.connection.url jdbc:oracle:thin:@orm-testing.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL -hibernate.connection.username ormmasteruser +hibernate.connection.url jdbc:oracle:thin:@hibernate-testing-oracle-se.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL +hibernate.connection.username hibernate_orm_test +hibernate.connection.password hibernate_orm_test hibernate.connection.pool_size 5 diff --git a/databases/pgsql/matrix.gradle b/databases/pgsql/matrix.gradle index 4be31dd8e7e2..9536407774ea 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:9.4-1203-jdbc41' +jdbcDependency 'org.postgresql:postgresql:42.2.2' diff --git a/dco.txt b/dco.txt new file mode 100644 index 000000000000..0cdce0c397f0 --- /dev/null +++ b/dco.txt @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. \ No newline at end of file diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index b85ad857a4aa..543478981efd 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -1,123 +1,81 @@ import org.apache.tools.ant.filters.ReplaceTokens import org.asciidoctor.gradle.AsciidoctorTask + /* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -buildscript { - repositories { - mavenCentral() - mavenLocal() - - maven { - name 'jboss-nexus' - url "http://repository.jboss.org/nexus/content/groups/public/" - } - jcenter() - } - dependencies { - classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2' - } + +ext { + projectsToSkipWhenAggregatingJavadocs = [ + 'documentation', + 'hibernate-entitymanager', + 'hibernate-infinispan', + 'hibernate-ehcache', + 'hibernate-java8', + 'hibernate-orm-modules', + 'release' + ] } -apply plugin: "java" +rootProject.subprojects { subproject -> + if ( !this.projectsToSkipWhenAggregatingJavadocs.contains( subproject.name ) ) { + this.evaluationDependsOn( subproject.path ) + } +} + +apply from: rootProject.file( 'gradle/java-module.gradle' ) + apply plugin: 'org.asciidoctor.convert' apply plugin: 'hibernate-matrix-testing' -apply from: "${rootProject.projectDir}/utilities.gradle" - defaultTasks 'buildDocs' -configurations { - asciidoclet { - description = 'Dependencies for Asciidoclet (the javadoc doclet tool for using Asciidoc)' - } - //asciidoctor -} - -if ( JavaVersion.current().isJava8Compatible() ) { - tasks.withType( Javadoc ) { - options.addStringOption( 'Xdoclint:none', '-quiet' ) - } -} dependencies { ext.pressgangVersion = '3.0.0' - // asciidoctor 'org.asciidoctor:asciidoctorj:1.5.2' - asciidoclet 'org.asciidoctor:asciidoclet:0.+' - compile( libraries.jpa ) + compile( project( ':hibernate-core' ) ) compile( project( ':hibernate-jpamodelgen' ) ) testCompile( 'org.apache.commons:commons-lang3:3.4' ) - testCompile( project(':hibernate-core') ) - testCompile( project(':hibernate-ehcache') ) + testCompile( project(':hibernate-envers') ) testCompile( project(':hibernate-spatial') ) testCompile( project(path: ':hibernate-core', configuration: 'tests') ) testCompile( project(':hibernate-testing') ) + testCompile "org.osgi:org.osgi.core:4.3.1" + testCompile( libraries.mockito ) testCompile( libraries.mockito_inline ) + + testRuntime( libraries.wildfly_transaction_client ) testRuntime( libraries.h2 ) testRuntime( libraries.hsqldb ) testRuntime( libraries.postgresql ) testRuntime( libraries.mysql ) testRuntime( libraries.mariadb ) testRuntime( libraries.mssql ) + testRuntime( libraries.hana ) - if (db.equalsIgnoreCase("oracle")) { - dependencies { - testRuntime( libraries.oracle ) - } - } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Java 9 ftw! - if ( JavaVersion.current().isJava9Compatible() ) { - // The JDK used to run Gradle is Java 9+, and we assume that that is the same - // JDK for executing tasks - compile( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - compile( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - compile( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - compile( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - compile( 'javax:javaee-api:7.0' ) - - testCompile( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testCompile( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - testCompile( 'javax:javaee-api:7.0' ) - - testRuntime( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testRuntime( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - testRuntime( 'javax:javaee-api:7.0' ) - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + testCompile( project( ':hibernate-jcache' ) ) + testRuntime( libraries.ehcache3 ) } -processTestResources.doLast( { - copy { - from( sourceSets.test.java.srcDirs ) { - include '**/*.properties' - include '**/*.xml' - } - into sourceSets.test.output.classesDir - } - copy { - ext.targetDir = file( "${buildDir}/resources/test" ) - from file('src/test/resources') - into targetDir - filter( ReplaceTokens, tokens: dbBundle[db] ); - } -} ) + +if ( project.ormVersion.isSnapshot ) { + // only run the ci build tasks for SNAPSHOT versions + task ciBuild( dependsOn: [clean, test] ) +} +else { + task release( dependsOn: [clean, test] ) +} // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // grouping tasks - declaration, see below for task dependency definitions @@ -146,79 +104,58 @@ final File javadocDir = mkdir( new File( (File) project.buildDir, 'javadocs' ) ) task aggregateJavadocs(type: Javadoc) { description = 'Builds the aggregated (unified) JavaDocs across all sub-projects' - final int copyrightYear = new GregorianCalendar().get( Calendar.YEAR ); + final int currentYear = new GregorianCalendar().get( Calendar.YEAR ) + + // exclude any generated sources and internal packages + exclude( '**/generated-src/**' ) + exclude( '**/internal/**' ) + + + // apply standard config + maxMemory = '512m' + destinationDir = javadocDir + configure( options ) { + overview = project.file( 'src/main/javadoc/overview.html' ) + windowTitle = 'Hibernate JavaDocs' + docTitle = "Hibernate JavaDoc ($project.version)" + bottom = "Copyright © 2001-$currentYear Red Hat, Inc. All Rights Reserved." + use = true + options.encoding = 'UTF-8' + + links = [ + 'https://docs.oracle.com/javase/8/docs/api/', + 'http://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', + 'http://docs.jboss.org/cdi/api/2.0/', + 'https://javaee.github.io/javaee-spec/javadocs/' + ] + + if ( JavaVersion.current().isJava11Compatible() ) { + //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 + // but after excluding the first two builds; see also specific comments on + // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 + // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people + // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. + System.out.println("Forcing Javadoc in Java 8 compatible mode"); + options.source = project.baselineJavaVersion + } - // exclude any generated sources (this is not working: http://forums.gradle.org/gradle/topics/excluding_generated_source_from_javadoc) - exclude "**/generated-src/**" + if ( JavaVersion.current().isJava8Compatible() ) { + options.addStringOption( 'Xdoclint:none', '-quiet' ) + } + } // process each project, building up: // 1) appropriate sources // 2) classpath - // 3) the package list for groups - Set apiPackages = new HashSet() - Set spiPackages = new HashSet() - Set internalPackages = new HashSet() - parent.subprojects.each{ Project subProject-> + parent.subprojects.each { Project subProject-> // skip certain sub-projects - if ( ['release','documentation', 'hibernate-orm-modules'].contains( subProject.name ) ) { - return; - } - - // we only care about the main SourceSet... - source subProject.sourceSets.main.java - - if( classpath ) { - classpath += subProject.sourceSets.main.output + subProject.sourceSets.main.compileClasspath - } - else { - classpath = subProject.sourceSets.main.output + subProject.sourceSets.main.compileClasspath - } + if ( ! projectsToSkipWhenAggregatingJavadocs.contains( subProject.name ) ) { + // we only care about the main SourceSet... + source subProject.sourceSets.main.java - subProject.sourceSets.main.java.each { javaFile -> - final String packageName = determinePackageName( subProject.sourceSets.main.java, javaFile ); - if ( packageName.endsWith( ".internal" ) || packageName.contains( ".internal." ) ) { - internalPackages.add( packageName ); - } - else if ( packageName.endsWith( ".spi" ) || packageName.contains( ".spi." ) ) { - spiPackages.add( packageName ); - } - else if ( packageName.startsWith( "org.hibernate.testing" ) ) { - // do nothing as testing support is already handled... - } - else { - apiPackages.add( packageName ); - } + classpath += subProject.sourceSets.main.output + subProject.sourceSets.main.compileClasspath + subProject.configurations.provided } } - - // apply standard config - maxMemory = '512m' - destinationDir = javadocDir - configure( options ) { - overview = rootProject.file( 'shared/javadoc/overview.html' ) - stylesheetFile = rootProject.file( 'shared/javadoc/stylesheet.css' ) - windowTitle = 'Hibernate JavaDocs' - docTitle = "Hibernate JavaDoc ($project.version)" - bottom = "Copyright © 2001-$copyrightYear Red Hat, Inc. All Rights Reserved." - use = true - options.encoding = 'UTF-8' - links = [ 'http://download.oracle.com/javase/6/docs/api/', 'http://download.oracle.com/javaee/6/api/' ] - group( 'API', apiPackages.asList() ) - group( 'SPI', spiPackages.asList() ) - group( 'Internal', internalPackages.asList() ) - group ( 'Testing Support', ['org.hibernate.testing*'] ) -// ugh, http://issues.gradle.org/browse/GRADLE-1563 -// tags ["todo:X"] -// work around: - addStringOption( "tag", "todo:X" ) - } - - doLast { - copy { - from rootProject.file( 'shared/javadoc/images' ) - into new File( javadocDir, "/images" ) - } - } } asciidoctor { @@ -235,7 +172,16 @@ task renderTopicalGuides(type: AsciidoctorTask, group: 'Documentation') { backends "html5" separateOutputDirs false options logDocuments: true - attributes icons: 'font', experimental: true, 'source-highlighter': 'prettify', majorMinorVersion: rootProject.hibernateMajorMinorVersion, fullVersion: rootProject.hibernateFullVersion + attributes icons: 'font', + experimental: true, + 'source-highlighter': 'prettify', + majorMinorVersion: rootProject.ormVersion.family, + fullVersion: rootProject.ormVersion.fullName + resources { + from('src/main/asciidoc/topical/') { + include '**/images/**' + } + } } @@ -244,6 +190,9 @@ task renderTopicalGuides(type: AsciidoctorTask, group: 'Documentation') { task renderGettingStartedGuides(type: AsciidoctorTask, group: 'Documentation') { description = 'Renders the Getting Started Guides (quick starts) in HTML format using Asciidoctor.' sourceDir = file( 'src/main/asciidoc/quickstart/guides' ) + sources { + include 'index.adoc' + } outputDir = new File("$buildDir/asciidoc/quickstart/html_single") backends "html5" separateOutputDirs false @@ -259,45 +208,34 @@ task buildTutorialZip(type: Zip) { expand( version: project.version, slf4j: "1.7.5", - junit: parent.junitVersion, - h2: parent.h2Version + junit: project.junitVersion, + h2: project.h2Version ) } renderGettingStartedGuides.dependsOn buildTutorialZip - - -// Mapping Guides ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -task renderMappingGuide(type: AsciidoctorTask, group: 'Documentation') { - description = 'Renders the Mapping Guides in HTML format using Asciidoctor.' - sourceDir = file( 'src/main/asciidoc/mapping' ) - outputDir = new File("$buildDir/asciidoc/mapping/html") - backends "html5" - separateOutputDirs false - options logDocuments: true - //attributes icons: 'font', experimental: true, 'source-highlighter': 'prettify', linkcss: true, stylesheet: "css/hibernate.css" - attributes icons: 'font', experimental: true, 'source-highlighter': 'prettify', linkcss: true - resources { - from('src/main/asciidoc/') { - include 'images/**' - include 'css/**' - } - } -} - // User Guide ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ task renderUserGuide(type: AsciidoctorTask, group: 'Documentation') { description = 'Renders the User Guides in HTML format using Asciidoctor.' sourceDir = file( 'src/main/asciidoc/userguide' ) + sources { + 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', linkcss: true, stylesheet: "css/hibernate.css", majorMinorVersion: rootProject.hibernateMajorMinorVersion, fullVersion: rootProject.hibernateFullVersion - resources { + attributes icons: 'font', experimental: true, + 'source-highlighter': 'prettify', + linkcss: true, + stylesheet: "css/hibernate.css", + majorMinorVersion: rootProject.ormVersion.family, + fullVersion: rootProject.ormVersion.fullName, + docinfo: 'private' + + resources { from('src/main/asciidoc/userguide/') { include 'images/**' } @@ -307,6 +245,9 @@ task renderUserGuide(type: AsciidoctorTask, group: 'Documentation') { from('src/main/style/asciidoctor') { include 'css/**' } + from('src/main/style/asciidoctor') { + include 'js/**' + } } } @@ -315,11 +256,19 @@ task renderUserGuide(type: AsciidoctorTask, group: 'Documentation') { task renderIntegrationGuide(type: AsciidoctorTask, group: 'Documentation') { description = 'Renders the User Guides in HTML format using Asciidoctor.' sourceDir = file( 'src/main/asciidoc/integrationguide' ) + sources { + 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, 'source-highlighter': 'prettify', linkcss: true, stylesheet: "css/hibernate.css", majorMinorVersion: rootProject.hibernateMajorMinorVersion + attributes icons: 'font', + experimental: true, + 'source-highlighter': 'prettify', + linkcss: true, + stylesheet: "css/hibernate.css", + majorMinorVersion: rootProject.ormVersion.family resources { from('src/main/asciidoc/integrationguide/') { include 'images/**' @@ -333,6 +282,14 @@ task renderIntegrationGuide(type: AsciidoctorTask, group: 'Documentation') { } } +// Testing + +// resources inherently exclude sources +sourceSets.test.resources { + setSrcDirs( ['src/test/java','src/test/resources'] ) +} + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // grouping tasks // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -348,3 +305,6 @@ buildDocsForPublishing.dependsOn renderTopicalGuides buildDocsForPublishing.dependsOn renderGettingStartedGuides buildDocsForPublishing.dependsOn renderUserGuide buildDocsForPublishing.dependsOn renderIntegrationGuide + +checkstyleMain.exclude '**/org/hibernate/userguide/model/*' + diff --git a/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc b/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc index 9ba671f81ea5..a23200c17388 100644 --- a/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc +++ b/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc @@ -11,7 +11,7 @@ It will also delve into the ways third-party integrators and applications can le === What is a Service? A services provides a certain types of functionality, in a pluggable manner. -Specifically they are interfaces defining certain functionality and then implementations of those `Service` contract interfaces. +Specifically, they are interfaces defining certain functionality and then implementations of those `Service` contract interfaces. The interface is known as the `Service` role; the implementation class is known as the `Service` implementation. The pluggability comes from the fact that the `Service` implementation adheres to contract defined by the interface of the `Service` role and that consumers of the `Service` program to the `Service` role, not the implementation. diff --git a/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc b/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc index fa1b5ff3a287..605bc13cddd5 100644 --- a/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc +++ b/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc @@ -9,14 +9,14 @@ hibernate-core:: The main (core) Hibernate module. Defines its ORM features and hibernate-envers:: Hibernate's historical entity versioning feature hibernate-spatial:: Hibernate's Spatial/GIS data-type support hibernate-osgi:: Hibernate support for running in OSGi containers. -hibernate-c3p0:: Integrates the link:$$http://www.mchange.com/projects/c3p0/$$[C3P0] connection pooling library into Hibernate -hibernate-hikaricp:: Integrates the link:$$http://brettwooldridge.github.io/HikariCP/$$[HikariCP] connection pooling library into Hibernate -hibernate-proxool:: Integrates the link:$$http://proxool.sourceforge.net/$$[Proxool] connection pooling library into Hibernate -hibernate-jcache:: Integrates the link:$$https://jcp.org/en/jsr/detail?id=107$$[JCache] caching specification into Hibernate, +hibernate-agroal:: Integrates the http://agroal.github.io/[Agroal] connection pooling library into Hibernate +hibernate-c3p0:: Integrates the http://www.mchange.com/projects/c3p0/[C3P0] connection pooling library into Hibernate +hibernate-hikaricp:: Integrates the http://brettwooldridge.github.io/HikariCP/[HikariCP] connection pooling library into Hibernate +hibernate-vibur:: Integrates the http://www.vibur.org/[Vibur DBCP] connection pooling library into Hibernate +hibernate-proxool:: Integrates the http://proxool.sourceforge.net/[Proxool] connection pooling library into Hibernate +hibernate-jcache:: Integrates the https://jcp.org/en/jsr/detail?id=107$$[JCache] caching specification into Hibernate, enabling any compliant implementation to become a second-level cache provider. -hibernate-ehcache:: Integrates the link:$$http://ehcache.org/$$[Ehcache] caching library into Hibernate as a second-level cache provider. -hibernate-infinispan:: Integrates the link:$$http://infinispan.org/$$[Infinispan] caching library into Hibernate as a second-level cache provider. - +hibernate-ehcache:: Integrates the http://ehcache.org/[Ehcache] caching library into Hibernate as a second-level cache provider. === Release Bundle Downloads diff --git a/documentation/src/main/asciidoc/quickstart/guides/preface.adoc b/documentation/src/main/asciidoc/quickstart/guides/preface.adoc index 77c2417a8a77..ba4111af91ba 100644 --- a/documentation/src/main/asciidoc/quickstart/guides/preface.adoc +++ b/documentation/src/main/asciidoc/quickstart/guides/preface.adoc @@ -3,7 +3,7 @@ [preface] == Preface -Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. +Working with both Object-Oriented software and Relational Databases can be cumbersome and time-consuming. Development costs are significantly higher due to a paradigm mismatch between how data is represented in objects versus relational databases. Hibernate is an Object/Relational Mapping (ORM) solution for Java environments. The term Object/Relational Mapping refers to the technique of mapping data between an object model representation to diff --git a/documentation/src/main/asciidoc/quickstart/guides/tutorial_annotations.adoc b/documentation/src/main/asciidoc/quickstart/guides/tutorial_annotations.adoc index 9478f8114b13..3bf9f8e97395 100644 --- a/documentation/src/main/asciidoc/quickstart/guides/tutorial_annotations.adoc +++ b/documentation/src/main/asciidoc/quickstart/guides/tutorial_annotations.adoc @@ -97,7 +97,8 @@ any mapping information associated with `title`. === Take it further! .Practice Exercises -- [ ] Add an association to the `Event` entity to model a message thread. Use the _Developer Guide_ -as a guide. -- [ ] Add a callback to receive notifications when an `Event` is created, updated or deleted. Try the same with -an event listener. Use the _Developer Guide_ as a guide. \ No newline at end of file +- [ ] Add an association to the `Event` entity to model a message thread. Use the +http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html[_User Guide_] for more details. +- [ ] Add a callback to receive notifications when an `Event` is created, updated or deleted. +Try the same with an event listener. Use the +http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html[_User Guide_] for more details. diff --git a/documentation/src/main/asciidoc/topical/bootstrap/JpaBootstrapping.adoc b/documentation/src/main/asciidoc/topical/bootstrap/JpaBootstrapping.adoc index 488683b0693f..7ac9b4d814a0 100644 --- a/documentation/src/main/asciidoc/topical/bootstrap/JpaBootstrapping.adoc +++ b/documentation/src/main/asciidoc/topical/bootstrap/JpaBootstrapping.adoc @@ -3,7 +3,7 @@ Bootstrapping Hibernate as a JPA provider can be done in a JPA-spec compliant manner or using a proprietary bootstrapping approach. The standardized approach has some limitations in certain environments. But aside from -those limitations, it is *highly* recommended that you use JPA-standardized boostrapping. +those limitations, it is *highly* recommended that you use JPA-standardized bootstrapping. NOTE: Under the covers, all of Hibernate's JPA bootstrapping makes use of its core bootstrapping. Be sure to see the _Native Bootstrapping_ guide as well. diff --git a/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc b/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc index 2f9641b4efea..6f82eda7c655 100644 --- a/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc +++ b/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc @@ -58,7 +58,7 @@ There are other ways to specify configuration properties, including: * Place a file named hibernate.properties in a root directory of the classpath. * Place a file named hibernate.properties in a root directory of the classpath. * Pass an instance of java.util.Properties to `Configuration#setProperties`. -* Set System properties using java `-Dproperty=value`. +* Set System properties using Java `-Dproperty=value`. * Include `` elements in `hibernate.cfg.xml` diff --git a/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc b/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc index 0429beb725a3..e596c9ef3dd0 100644 --- a/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc +++ b/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc @@ -4,7 +4,7 @@ This guide discusses the process of bootstrapping a Hibernate `org.hibernate.SessionFactory`. It also discusses the ways in which applications and integrators can hook-in to and affect that process. This bootstrapping process is defined in 2 distinct steps. The first step is the building of a ServiceRegistry -holding the services Hibernate will need at bootstrap- and run-time. The second step is the building of +holding the services Hibernate will need at bootstrap- and runtime. The second step is the building of a Metadata object representing the mapping information for the application's model and its mapping to the database. @@ -215,7 +215,7 @@ over the `SessionFactory` building process. ---- SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder(); - // Supply an SessionFactory-level Interceptor + // Supply a SessionFactory-level Interceptor sessionFactoryBuilder.applyInterceptor( new MySessionFactoryInterceptor() ); // Add a custom observer diff --git a/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc index 7ea2b0fcf203..3e5922680f19 100644 --- a/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc +++ b/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc @@ -4,7 +4,6 @@ This guide covers Hibernate's ability to enhance an applications domain model, the ways to perform that enhancement and the capabilities introduced into the domain model by the enhancement. - == The capabilities Hibernate will enhance the classes in an application's domain model in order to add one or more of the @@ -24,12 +23,20 @@ Ultimately all enhancement is handled by the `org.hibernate.bytecode.enhance.spi enhancement can certainly be crafted on top of Enhancer, but that is beyond the scope of this guide. Here we will focus on the means Hibernate already exposes for performing these enhancements. -=== Run-time enhancement +=== Runtime enhancement + +Currently runtime enhancement of the domain model is only supported in managed JPA environments following the JPA defined SPI for performing class transformations. + +Even then, this support is disabled by default. To enable runtime enhancement, specify one of the following configuration properties: + +`*hibernate.enhancer.enableDirtyTracking*` (e.g. `true` or `false` (default value)):: +Enable dirty tracking feature in runtime bytecode enhancement. + +`*hibernate.enhancer.enableLazyInitialization*` (e.g. `true` or `false` (default value)):: +Enable lazy loading feature in runtime bytecode enhancement. This way, even basic types (e.g. `@Basic(fetch = FetchType.LAZY`)) can be fetched lazily. -Currently run-time enhancement of the domain model is only supported in managed JPA environments following the -JPA defined SPI for performing class transformations. Even then, this support is disabled by default. In this -scenario, run-time enhancement can be enabled by specifying `hibernate.ejb.use_class_enhancer=true` as a -persistent unit property. +`*hibernate.enhancer.enableAssociationManagement*` (e.g. `true` or `false` (default value)):: +Enable association management feature in runtime bytecode enhancement which automatically synchronizes a bidirectional association when only one side is changed. === Build-time enhancement diff --git a/documentation/src/main/asciidoc/topical/generated/GeneratedValues.adoc b/documentation/src/main/asciidoc/topical/generated/GeneratedValues.adoc index 7bfa630b59a1..d1b2f4c66172 100644 --- a/documentation/src/main/asciidoc/topical/generated/GeneratedValues.adoc +++ b/documentation/src/main/asciidoc/topical/generated/GeneratedValues.adoc @@ -14,7 +14,7 @@ statement. == Legacy support (in-database generation) Historically applications would have to manually refresh the state of any entities that included such generated values -to account for these generated values. Starting in version 3.2, however, Hibernate began allowing the aplication to +to account for these generated values. Starting in version 3.2, however, Hibernate began allowing the application to mark attributes as generated, which allows the application to delegate this responsibility to Hibernate. When Hibernate issues an SQL INSERT or UPDATE for an entity that has defined in-database value generation, it immediately issues a select afterwards to retrieve the generated values. diff --git a/documentation/src/main/asciidoc/topical/logging/Logging.adoc b/documentation/src/main/asciidoc/topical/logging/Logging.adoc index cf491b455b0a..f85e399fce3b 100644 --- a/documentation/src/main/asciidoc/topical/logging/Logging.adoc +++ b/documentation/src/main/asciidoc/topical/logging/Logging.adoc @@ -58,7 +58,7 @@ JDK Logging would be used if none of the other framework jars are available. == Log Categories of Interest It is recommended that you familiarize yourself with the log messages sent from Hibernate. A lot of work has been put -into making the Hibernate loggiong as detailed as possible, without making it unreadable. It is an essential +into making the Hibernate logging as detailed as possible, without making it unreadable. It is an essential troubleshooting device. Some log categories of particular interest include: .Hibernate Log Categories of Interest diff --git a/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc b/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc index 7e2f7510bd33..65dac38984e9 100644 --- a/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc +++ b/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc @@ -1,12 +1,12 @@ = JPA Static Metamodel Generator -:imagesdir: . -:version: CURRENT-VERSION +:imagesdir: images +:version: {fullVersion} :toc: - + [[whatisit]] == What is it about? -JPA 2 defines a typesafe Criteria API which allows +Criteria+ queries +JPA 2 defines a typesafe Criteria API which allows `Criteria` queries to be constructed in a strongly-typed manner, utilizing so called static metamodel classes. For developers it is important that the task of the metamodel generation @@ -14,11 +14,11 @@ can be automated. Hibernate Static Metamodel Generator is an annotation processor based on http://jcp.org/en/jsr/detail?id=269[JSR_269] with the task of creating JPA 2 static metamodel classes. -The following example shows two JPA 2 entities +Order+ and +Item+, together -with the metamodel class +Order_+ and a typesafe query. +The following example shows two JPA 2 entities `Order` and `Item`, together +with the metamodel class `Order_` and a typesafe query. [[jpa2-entity-example]] -.JPA 2 annotated entities +Order+ and +Item+ +.JPA 2 annotated entities `Order` and `Item` ==== [source, JAVA] @@ -83,13 +83,13 @@ public class Order_ { ==== [source, JAVA] ---- - CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Order.class); -SetJoin itemNode = cq.from(Order.class).join(Order_.items); -cq.where( cb.equal(itemNode.get(Item_.id), 5 ) ).distinct(true); +SetJoin itemNode = cq.from(Order.class).join(Order_.items); +cq.where( cb.equal(itemNode.get(Item_.id), 5 ) ).distinct(true); ---- ==== @@ -97,8 +97,8 @@ cq.where( cb.equal(itemNode.get(Item_.id), 5 ) ).distinct(true); [TIP] ==== Hibernate Static Metamodel Generator also takes into consideration xml -configuration specified in +orm.xml+ or mapping files specified in -+persistence.xml+. However, if XML is your only configuration source, +configuration specified in `orm.xml` or mapping files specified in +`persistence.xml`. However, if XML is your only configuration source, you need to add in at least on of the mapping file the following persistence unit metadata: ---- @@ -126,7 +126,7 @@ package p is created. managed class by appending "_" to the name of the managed class. * The metamodel class X_ must be annotated with the -+javax.persistence.StaticMetamodel+ annotation. +`javax.persistence.StaticMetamodel` annotation. * If class X extends another class S, where S is the most derived managed class (i.e., entity or mapped superclass) extended by X, then @@ -160,14 +160,14 @@ a declaration as follows: + where K is the type of the key of the map in class X -Import statements must be included for the needed +javax.persistence.metamodel+ types as +Import statements must be included for the needed `javax.persistence.metamodel` types as appropriate and all classes X, Y, Z, and K. [[chapter-usage]] == Usage The jar file for the annotation processor can be found in the -http://repository.jboss.com/[JBoss Maven repository] under: +https://search.maven.org/[Maven Central repository] under: ==== [source, XML] @@ -179,19 +179,19 @@ http://repository.jboss.com/[JBoss Maven repository] under: {version} ---- - ==== + Alternatively, it can be found in the ORM distribution bundle on -http://sourceforge.net/projects/hibernate/files/hibernate4[SourceForge]. +https://sourceforge.net/projects/hibernate/files/hibernate-orm/[SourceForge]. In most cases the annotation processor will automatically run provided -the processor jar is added to the build classpath and a JDK >6 is used. +the processor jar is added to the build classpath. This happens due to Java's Service Provider contract and the fact -the the Hibernate Static Metamodel Generator jar files contains the +the Hibernate Static Metamodel Generator jar files contains the file _javax.annotation.processing.Processor_ in the _META-INF/services_ directory. The fully qualified name of the processor itself is: -+org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor+. +`org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor`. === Usage from the command line @@ -225,10 +225,9 @@ Ant can be configured to just run annotation processing. The option _-proc:only_ instructs the compiler to just run the annotation processing. You can also completely disable processing by specifying _-proc:none_. - [TIP] ==== -Run +'javac -help'+ to see which other annotation processor relevant +Run `'javac -help'` to see which other annotation processor relevant options can be specified. ==== @@ -247,8 +246,8 @@ pass the processor option to the compiler plugin: maven-compiler-plugin - 1.6 - 1.6 + 1.8 + 1.8 org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor @@ -274,20 +273,21 @@ plugin as seen in below. maven-compiler-plugin - 1.6 - 1.6 + 1.8 + 1.8 -proc:none ---- ==== -Once disabled, the http://code.google.com/p/maven-annotation-plugin[maven-processor-plugin] +Once disabled, the https://bsorrentino.github.io/maven-annotation-plugin/[maven-processor-plugin] for annotation processing can be used: [[maven-processor-plugin]] .Configuration with maven-processor-plugin ==== [source, XML] +[subs="verbatim,attributes"] ---- org.bsc.maven @@ -311,13 +311,38 @@ for annotation processing can be used: org.hibernate hibernate-jpamodelgen - WORKING + {version} ---- ==== +Another possibility is to supply the dependency as an annotation processor path to the maven-compiler-plugin: + +[[maven-compiler-plugin]] +.Configuration with maven-compiler-plugin +==== +[source, XML] +[subs="verbatim,attributes"] +---- + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + + + org.hibernate + hibernate-jpamodelgen + {fullVersion} + + + + +---- +==== + === Usage within the IDE Of course you also want to have annotation processing available in your favorite IDE. The @@ -330,11 +355,11 @@ Intellij Idea contains from version 9.x onwards a specific configuration section annotation processing under the project settings window. The screenshots show you how to configure the Hibernate Static Metamodel Generator. -image::idea-annotation-processor-config.png[] +image:idea-annotation-processor-config.png[] In the annotation processor configuration, enable annotation processing and select obtain from project classpath. -Add the annotation processor name +org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor+ +Add the annotation processor name `org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor` (and optionally the annotation processor options). Select the module(s) containing your entities. If you have configured Maven as recommended, it is best to select the same output directory @@ -350,12 +375,15 @@ Just check the "Enable annotation processing" option, configure the directory fo generated sources and finally add the Hibernate Static Metamodel Generator and JPA 2 jar files to the factory path. -image::eclipse-annotation-processor-config.png[] +If you use JDK 11+, you also need to add the `javax.xml.bind:jaxb-api` and +`org.glassfish.jaxb:jaxb-runtime` jars as JAXB is not included in the JDK anymore. + +image:eclipse-annotation-processor-config.png[] === Processor specific options The Hibernate Static Metamodel Generator accepts a series of custom -options which can be passed to the processor in the format: +-A[property]=[value]+ +options which can be passed to the processor in the format: `-A[property]=[value]` The supported properties can be found in the table below: @@ -363,7 +391,7 @@ The supported properties can be found in the table below: |=============== |*Option name* | *Option value and usage* -|debug | If set to +true+ additional trace +|debug | If set to `true` additional trace information will be outputted by the processor |persistenceXml | Per default the processor looks in @@ -378,8 +406,8 @@ The supported properties can be found in the table below: Even when this option is specified _/META-INF/orm.xml_ is implicit. -|lazyXmlParsing | Possible values are +true+ or +false+. If set to - +true+ the annotation processor tries to +|lazyXmlParsing | Possible values are `true` or `false`. If set to + `true` the annotation processor tries to determine whether any of the xml files has changed between invocations and if unchanged skips the xml parsing. @@ -387,27 +415,27 @@ The supported properties can be found in the table below: of wrong results in some cases of mixed mode configurations. To determine wether a file has been modified a temporary file - +Hibernate-Static-Metamodel-Generator.tmp+ + `Hibernate-Static-Metamodel-Generator.tmp` is used. This file gets created in the - +java.io.tmpdir+ directory. + `java.io.tmpdir` directory. -|fullyAnnotationConfigured | If set to +true+ the processor will - ignore +orm.xml+ and +persistence.xml+. +|fullyAnnotationConfigured | If set to `true` the processor will + ignore `orm.xml` and `persistence.xml`. -|addGeneratedAnnotation | If set to +true+ the processor will +|addGeneratedAnnotation | If set to `true` the processor will add the @Generated to the generated Java source file. Adding this annotation using JDK 5 will cause a compilation error. In this - case set the flag to false. The default for this option is +true+ + case set the flag to false. The default for this option is `true` |addGenerationDate | If set to true the generation date of the metamodel class will be inserted in the date parameter of the @Generated annotation. - The default is +false+. This parameter is + The default is `false`. This parameter is ignored if _addGeneratedAnnotation_ is set to _false_. -|addSuppressWarningsAnnotation| If set to +true+ the processor will - add @SuppressWarnings("all")+ to the +|addSuppressWarningsAnnotation| If set to `true` the processor will + add `@SuppressWarnings("all")` to the generated Java source file. Per default this annotation is not generated. See also https://hibernate.onjira.com/browse/METAGEN-50[METAGEN-50]. diff --git a/documentation/src/main/asciidoc/topical/metamodelgen/eclipse-annotation-processor-config.png b/documentation/src/main/asciidoc/topical/metamodelgen/images/eclipse-annotation-processor-config.png similarity index 100% rename from documentation/src/main/asciidoc/topical/metamodelgen/eclipse-annotation-processor-config.png rename to documentation/src/main/asciidoc/topical/metamodelgen/images/eclipse-annotation-processor-config.png diff --git a/documentation/src/main/asciidoc/topical/metamodelgen/idea-annotation-processor-config.png b/documentation/src/main/asciidoc/topical/metamodelgen/images/idea-annotation-processor-config.png similarity index 100% rename from documentation/src/main/asciidoc/topical/metamodelgen/idea-annotation-processor-config.png rename to documentation/src/main/asciidoc/topical/metamodelgen/images/idea-annotation-processor-config.png diff --git a/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc b/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc index 40b4d76e320a..856dba0fc0d9 100644 --- a/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc +++ b/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc @@ -1,5 +1,5 @@ = Services and Registries -:imagesdir: . +:imagesdir: images :toc: Services and Registries are new *as a formalized concept* starting in 4.0. But the functionality provided by @@ -12,7 +12,7 @@ applications can leverage and customize Services and Registries. == What is a Service? -Services provide various types of functionality, in a pluggable manner. Specifically they are interfaces defining +Services provide various types of functionality, in a pluggable manner. Specifically, they are interfaces defining certain functionality and then implementations of those service contract interfaces. The interface is known as the service role; the implementation class is known as the service implementation. The pluggability comes from the fact that the service implementation adheres to contract defined by the interface of the service role and that consumers diff --git a/documentation/src/main/asciidoc/topical/registries/registry_hierarchy.jpg b/documentation/src/main/asciidoc/topical/registries/images/registry_hierarchy.jpg similarity index 100% rename from documentation/src/main/asciidoc/topical/registries/registry_hierarchy.jpg rename to documentation/src/main/asciidoc/topical/registries/images/registry_hierarchy.jpg diff --git a/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc b/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc index d350e011dc69..af6ca0fddbf1 100644 --- a/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc +++ b/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc @@ -7,84 +7,254 @@ The http://wildfly.org/[WildFly application server] includes Hibernate ORM as th This means that you don't need to package Hibernate ORM with the applications you deploy on WildFly, instead the application server will automatically enable Hibernate support if it detects that your application is using JPA. -You can also benefit from these modules when not using JPA or JavaEE, to avoid including Hibernate ORM and all its +You can also benefit from these modules when not using JPA, to avoid including Hibernate ORM and all its dependencies into your deployment. This will require activating the module explicitly using a `jboss-deployment-structure.xml` file or a Manifest entry: -see https://docs.jboss.org/author/display/WFLY10/Class+Loading+in+WildFly[Class Loading in WildFly] for some examples. +see https://docs.jboss.org/author/display/WFLY/Class+Loading+in+WildFly[Class Loading in WildFly] for some examples. -There may be times though where a newer version of Hibernate ORM is available than the one coming with a given WildFly release. -For that case the Hibernate ORM project provides a ZIP file containing the required modules, so that each new version -can also be included in WildFly. Such a module will not replace the existing Hibernate ORM module, but it will become an -alternative option that your application can choose to use instead of the default version it includes. +Often a newer version of Hibernate ORM is available than the one coming with a given WildFly release; to make sure +you can enjoy the latest version of Hibernate on any reasonably recent WildFly edition we publish _WildFly feature packs_, these can be used with various WildFly provisioning tools to create a custom server with a different +version of Hibernate ORM. -Our goal is to provide a module ZIP file targeted at the WildFly version current at the time of the Hibernate release (e.g. WildFly 10 for Hibernate 5.1.x and 5.2.x). +== What is a WildFly feature pack -== Where to download the modules from +WildFly is a runtime built on https://jboss-modules.github.io/jboss-modules/manual/[JBoss Modules]; this is a light weight and efficient modular classloader which allows the different components of a modern server to be defined as independent modules. -The module ZIP files can be downloaded from Maven Central, to facilitate automatic unpacking during your build. +Hibernate ORM and its components are defined as one such module; this implies you can even have multiple different versions of an Hibernate ORM module in the same runtime while having their classpaths isolated from each other: you can add the very latest Hibernate ORM releases to WildFly without having to remove the existing copy. -.Maven identifier for the WildFly modules zip file +This gives you the flexibility to use the latest version for one of your application with the peace of mind that you won't break other applications requiring a different version of Hibernate. We don't generally recommend to abuse this system but it's often useful to be able for example to upgrade and test one application at a time, rather than having to mandate a new version for multiple services and have to update them all in one shot. + +A feature pack is a zip file containing some XML files which define the structure of the JBoss Module(s) and list the Java "jar" files which will be needed by identifying them via Maven coordinates. + +This has some further benefits: + +- A feature pack is very small as it's just a zipped file with some lines of XML. +- In terms of disk space you can build a "thin" server which doesn't actually include a copy of your Maven artifacts but just loads the classes on demand from your local Maven cache. +- You still have the option to build a "full" server so that it can be re-distributed without needing to copy a local Maven repository. +- When using the provisioning tool you benefit from a composable approach, so N different packs can be combined to form a custom server. +- Since the feature pack XML merely lists which artifacts are recommended (rather than including a binary copy) it is easy to override the exact versions; this is ideal to apply micro, urgent fixes. +- A feature pack can declare transitive dependencies on other feature packs, so you will automatically be provided all non-optional dependencies of Hibernate ORM. + +It is also interesting to highlight what it is not: differently than most build systems, the focus of JBoss Modules is not on how a project is built but how it should be run. + +An important aspect is that runtime dependencies of a JBoss Module are *not transitive*: so for example if the latest Hibernate ORM requires Byte Buddy version 5 (as an example) while any other module that your application needs depends on Byte Buddy version 6 this will not be a problem. + +Upgrading your applications could not be easier, as you won't have to ensure that all your dependencies are aligned to use the same versions. + + +== How to get the latest Hibernate ORM feature pack for WildFly + +The feature pack can be downloaded from Maven Central, to facilitate automatic unpacking during your build. +Such a feature pack is released whenever any new version of Hibernate ORM is released. + +.Maven identifier for the WildFly feature pack ==== [source, XML] [subs="verbatim,attributes"] ---- org.hibernate - hibernate-orm-modules + hibernate-orm-jbossmodules {fullVersion} - wildfly-10-dist - zip ---- ==== -Once downloaded, extract the contents of the ZIP file into the _modules_ directory of your WildFly installation. +Typically you won't download this file directly but you will use either a Maven plugin or a Gradle plugin to build the custom WildFly server. -.Example Maven build step to prepare WildFly with custom Hibernate ORM modules for integration tests +== Create a Provisioning Configuration File +You will need a small XML file to define which feature packs you want to assemble. + +The following example will create a full WildFly server but also include a copy of the latest Hibernate ORM modules: + + +.Example Provisioning Configuration File ==== [source, XML] [subs="verbatim,attributes"] ---- + + + + + + +---- +==== + +Of course should you wish your custom server to have more features you can list additional feature packs. + +It is also possible to build a "thin" server by not setting the _copy-module-artifacts_ flag, or you can further customize and filter out things you want removed. + +See https://github.com/wildfly/wildfly-build-tools[the README of the WildFly Build Tools project] on Github for more details. + +Next you can use either the https://github.com/wildfly/wildfly-build-tools[Maven plugin] or the https://plugins.gradle.org/plugin/org.wildfly.build.featurepack[Gradle plugin] to actually create a fresh copy of your custom server. + +== Maven users: invoke the WildFly Provisioning Plugin + +Assuming the previous Provisioning Configuration File is saved as `server-provisioning.xml`, you will just have to refer the plugin to it, pick an output directory name and bing the plugin to the build lifecycle. + +.Example Maven Provisioning +==== +[source, XML] +---- + + + + org.wildfly.build + wildfly-server-provisioning-maven-plugin + + + server-provisioning + + build + + compile + + server-provisioning.xml + wildfly-custom + + +---- +==== + +==== JPA version override + +With WildFly 12 being built with JavaEE7 in mind, it ships the JPA 2.1 API. + +Hibernate ORM 5.3 requires JPA 2.2, and it is not possible at this time to replace the JPA API using the Maven provisioning plugin so you'll have to apply a "WildFly patch" as well. + +A WildFly patch can be applied from the WildFly CLI; here we show how to automate it all with Maven plugins. + +.Example Maven script to patch the JPA version in WildFly: +==== +[source, XML] +---- + + maven-dependency-plugin + + + fetch-jpa-patch + process-test-resources + + copy + + + + + org.hibernate.javax.persistence + hibernate-jpa-api-2.2-wildflymodules + wildfly-12.0.0.Final-patch + 1.0.0.Beta2 + zip + ${project.build.directory} + true + hibernate-jpa-api-2.2-wildflymodules-patch.zip + + + + + + -maven-dependency-plugin - - - unpack - pre-integration-test - - unpack - - - - - org.wildfly - wildfly-dist - ${wildflyVersion} - zip - true - - ${project.build.directory}/wildfly-node1 - - - - org.hibernate - hibernate-orm-modules - ${hibernateVersion} - wildfly-10-dist - zip - true - - ${project.build.directory}/wildfly-node1/wildfly-${wildflyVersion}/modules - - - - - - + org.wildfly.plugins + wildfly-maven-plugin + + + apply-wildfly-jpa22-patch-file + pre-integration-test + + execute-commands + + + true + ${jbossHome.provisionedPath} + + false + + patch apply --override-all ${project.build.directory}/hibernate-jpa-api-2.2-wildflymodules-patch.zip + + + + ---- ==== +== Gradle users: invoke the WildFly Provisioning plugin + +A Gradle plugin is also available, and in this case it will take just a couple of lines. + +Remember when creating a "thin server": the WildFly classloader will not be able to load jars from the local Gradle cache: this might trigger a second download as it looks into local Maven repositories exclusively. +Especially if you are developing additional feature packs using Gradle, make sure to publish them into a Maven repository so that WildFly can load them. + +Follows a full Gradle build script; in contrast to the previous Maven example which is incomplete to keep it short, is a fully working build script. Also it won't require to apply additional patches to replace the JPA version. + +.Example Gradle Provisioning +==== +[source, Groovy] +---- +plugins { + id "org.wildfly.build.provision" version '0.0.6' +} + +repositories { + mavenLocal() + mavenCentral() + maven { + name 'jboss-public' + url 'https://repository.jboss.org/nexus/content/groups/public/' + } +} + +provision { + //Optional destination directory: + destinationDir = file("wildfly-custom") + + //Update the JPA API: + override( 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api' ) { + groupId = 'javax.persistence' + artifactId = 'javax.persistence-api' + version = '2.2' + } + configuration = file( 'wildfly-server-provisioning.xml' ) + //Define variables which need replacing in the provisioning configuration! + variables['wildfly.version'] = '12.0.0.Final' + variables['hibernate-orm.version'] = '5.3.0.Final' +} +---- +==== + +you could paste this into a new file named `build.gradle` in an empty directory, then invoke: + + gradle provision + +and you'll have a full WildFly 12.0.0.Final server generated in the `wildfly-custom` subdirectory, including a copy of Hibernate ORM version 5.3.0.Final (in addition to the any other version that WildFly normally includes). + + +==== A note on repositories: + + mavenLocal():: + strictly not necessary but will make your builds much faster if you run it more than once. + jboss-nexus:: + This additional repository is required. Most components of WildFly are available in Maven Central but there are some occasional exceptions. + +==== The JPA version override + +The JPA API is a fundamental component of the application server as it is used to integrate with various other standards; at this stage while the feature packs offer some degree of composability it is not yet possible +to have additional, independent copies of the JPA API: it needs to be replaced. + +Hibernate ORM 5.3.0 requires JPA 2.2, yet WildFly 12 ships with JPA version 2.1. Luckily this provisioning tool is also able to override any artifact resolution. + +Of course when future versions of WildFly will be based on JPA 2.2, this step might soon no longer be necessary. + + == WildFly module identifiers: slots and conventions Note that the Hibernate ORM modules coming with WildFly will remain untouched: you can switch between the original version and the new version from the ZIP file as needed as a matter of configuration. Different applications can use different versions. @@ -96,9 +266,9 @@ By convention all modules included with WildFly use the "main" slot, while the m will use a slot name which matches the version, and also provide an alias to match its "major.minor" version. Our suggestion is to depend on the module using the "major.minor" representation, as this simplifies rolling out bugfix -releases (micro version updates) of Hibernate ORM without changing application configuration (micro versions are always expected to be backwards compatible and released as bugfix only). +releases (micro version updates) of Hibernate ORM without changing application configuration (micro versions are always expected to be backward compatible and released as bugfix only). -For example if your application wants to use the latest version of Hibernate ORM version {majorMinorVersion}.x it should declare to use the module _org.hibernate:{majorMinorVersion}_. You can of course decide to use the full version instead for more precise control, in case an application requires a very specific version. +For example, if your application wants to use the latest version of Hibernate ORM version {majorMinorVersion}.x it should declare to use the module _org.hibernate:{majorMinorVersion}_. You can of course decide to use the full version instead for more precise control, in case an application requires a very specific version. == Switch to a different Hibernate ORM slot @@ -134,53 +304,21 @@ In order to use a different module other than the default _org.hibernate:main_ s Needless to say, this will affect the classpath of your application: if your single application declares multiple persistence units, they should all make a consistent choice! -This property is documented in the https://docs.jboss.org/author/display/WFLY10/JPA+Reference+Guide[WildFly JPA Reference Guide]; +This property is documented in the https://docs.jboss.org/author/display/WFLY/JPA+Reference+Guide[WildFly JPA Reference Guide]; you might want to check it out as it lists several other useful properties. -== Avoiding outdated Javassist versions from WildFly - -Since Hibernate ORM version 5.2, it requires a more recent version of Javassist than the one provided by WildFly 10. -Unfortunately the JPA subsystem of WildFly will expose its Javassist version to any JPA application even if you override -the module using the above mentioned `jboss.as.jpa.providerModule` property. - -To avoid this problem use a `jboss-deployment-structure.xml` to explicitly demand to not get the WildFly copy of -javassist. This will allow Hibernate ORM to use the Javassist version provided by its own module, which will contain -the recommended versions. - -.WildFly configuration file to avoid the wrong Javassist version - -==== -[source, XML] -[subs="verbatim,attributes"] ----- - - - - - - - - - ----- -==== - -This file needs to be included in your deployment, in the top level archive. -The exact position depends on the deployment kind: for example when deploying a `WAR` file, include it in `WEB-INF`; -other common deployment archives will expect this resource to be found in `META-INF`. - -See https://docs.jboss.org/author/display/WFLY10/Class+Loading+in+WildFly[Class Loading in WildFly] for more details -about using a custom `jboss-deployment-structure.xml`. - == Limitations of using the custom WildFly modules -When using these modules you're going to give up on some of the integration which the application server -normally automates. +When using the custom modules provided by the feature packs you're going to give up on some of the integration which the application server normally automates. -For example enabling an Infinispan 2nd level cache is straight forward when using the default Hibernate ORM +For example, enabling an Infinispan 2nd level cache is straight forward when using the default Hibernate ORM module, as WildFly will automatically setup the dependency to the Infinispan and clustering components. When using these custom modules such integration will no longer work automatically: you can still -enable all normally available features but these will require manual configuration, as if you were +enable all normally available features but these will require explicit configuration, as if you were running Hibernate in a different container, or in no container. +You might be able to get a matching feature pack from the Infinispan or Ehcache projects, you can create a module yourself (after all it's just a simple XML file), or you can just add such additional dependencies in your application as in the old days: modules and feature packs give you some advantages but good old-style jars are also still a viable option. + +Needless to say, those users not interested in having the very latest versions can just use the versions integrated in WildFly and benefit from the library combinations carefully tested by the WildFly team. + diff --git a/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide-docinfo.html b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide-docinfo.html new file mode 100644 index 000000000000..0fd842c784cc --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide-docinfo.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc index 86d6ad089571..994be7f23d37 100644 --- a/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc +++ b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc @@ -1,7 +1,8 @@ = Hibernate ORM {fullVersion} User Guide Vlad Mihalcea, Steve Ebersole, Andrea Boriero, Gunnar Morling, Gail Badner, Chris Cranford, Emmanuel Bernard, Sanne Grinovero, Brett Meyer, Hardy Ferentschik, Gavin King, Christian Bauer, Max Rydahl Andersen, Karel Maesen, Radim Vansa, Louis Jacomet -:toc: +:toc2: :toclevels: 3 +:sectanchors: include::Preface.adoc[] diff --git a/documentation/src/main/asciidoc/userguide/Preface.adoc b/documentation/src/main/asciidoc/userguide/Preface.adoc index ec196cd5a514..d21ebf8f564f 100644 --- a/documentation/src/main/asciidoc/userguide/Preface.adoc +++ b/documentation/src/main/asciidoc/userguide/Preface.adoc @@ -1,10 +1,10 @@ [[preface]] == Preface -Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. +Working with both Object-Oriented software and Relational Databases can be cumbersome and time-consuming. Development costs are significantly higher due to a paradigm mismatch between how data is represented in objects versus relational databases. Hibernate is an Object/Relational Mapping solution for Java environments. -The term http://en.wikipedia.org/wiki/Object-relational_mapping[Object/Relational Mapping] refers to the technique of mapping data from an object model representation to a relational data model representation (and visa versa). +The term http://en.wikipedia.org/wiki/Object-relational_mapping[Object/Relational Mapping] refers to the technique of mapping data from an object model representation to a relational data model representation (and vice versa). Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to SQL data types), but also provides data query and retrieval facilities. It can significantly reduce development time otherwise spent with manual data handling in SQL and JDBC. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 3f0336dc7759..214362104ecf 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -16,6 +16,8 @@ See the <> section for more info The http://docs.oracle.com/javaee/7/api/javax/persistence/AssociationOverride.html[`@AssociationOverride`] annotation is used to override an association mapping (e.g. `@ManyToOne`, `@OneToOne`, `@OneToMany`, `@ManyToMany`) inherited from a mapped superclass or an embeddable. +See the <> section for more info. + [[annotations-jpa-associationoverrides]] ==== `@AssociationOverrides` @@ -26,6 +28,8 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/AssociationOverrides.h The http://docs.oracle.com/javaee/7/api/javax/persistence/AttributeOverride.html[`@AttributeOverride`] annotation is used to override an attribute mapping inherited from a mapped superclass or an embeddable. +See the <> section for more info. + [[annotations-jpa-attributeoverrides]] ==== `@AttributeOverrides` @@ -75,10 +79,12 @@ See the <> annotations to map columns of a given SELECT query to a certain object constructor. +See the <> section for more info. + [[annotations-jpa-convert]] ==== `@Convert` -The http://docs.oracle.com/javaee/7/api/javax/persistence/Convert.html[`@Convert`] annotation is used to specify the http://docs.oracle.com/javaee/7/api/javax/persistence/AttributeConverter.html[`AttributeConverter`] implementation used to convert the current annotated basic attribute. +The http://docs.oracle.com/javaee/7/api/javax/persistence/Convert.html[`@Convert`] annotation is used to specify the http://docs.oracle.com/javaee/7/api/javax/persistence/AttributeConverter.html[`AttributeConverter`] implementation used to convert the currently annotated basic attribute. If the `AttributeConverter` uses http://docs.oracle.com/javaee/7/api/javax/persistence/Converter.html#autoApply--[`autoApply`], then all entity attributes with the same target type are going to be converted automatically. @@ -110,7 +116,7 @@ See the <> section for more info. @@ -153,7 +159,7 @@ See the <> section for more info. [[annotations-jpa-entitylisteners]] ==== `@EntityListeners` -The http://docs.oracle.com/javaee/7/api/javax/persistence/EntityListeners.html[`@EntityListeners`] annotation is used to specify an array of callback listener classes that are used by the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/EntityListeners.html[`@EntityListeners`] annotation is used to specify an array of callback listener classes that are used by the currently annotated entity. See the <> section for more info. @@ -174,12 +180,16 @@ See the <> section for more info. [[annotations-jpa-excludesuperclasslisteners]] ==== `@ExcludeSuperclassListeners` -The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the current annotated entity skips the invocation of listeners declared by its superclass. +The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the currently annotated entity skips the invocation of listeners declared by its superclass. + +See the <> section for more info. [[annotations-jpa-fieldresult]] ==== `@FieldResult` @@ -225,6 +235,8 @@ See the <> chapter for more info. + [[annotations-jpa-inheritance]] ==== `@Inheritance` @@ -254,7 +266,7 @@ See the <> section for more info. @@ -284,11 +296,15 @@ See the <> section for more info. + [[annotations-jpa-mapkeycolumn]] ==== `@MapKeyColumn` The http://docs.oracle.com/javaee/7/api/javax/persistence/MapKeyColumn.html[`@MapKeyColumn`] annotation is used to specify the database column which stores the key of a `java.util.Map` association for which the map key is a basic type. +See the <> for an example of `@MapKeyColumn` annotation usage. + [[annotations-jpa-mapkeyenumerated]] ==== `@MapKeyEnumerated` @@ -319,14 +335,14 @@ See the <> section for more info. [[annotations-jpa-mapsid]] ==== `@MapsId` -The http://docs.oracle.com/javaee/7/api/javax/persistence/MapsId.html[`@MapsId`] annotation is used to specify that the entity identifier is mapped by the current annotated `@ManyToOne` or `@OneToOne` association. +The http://docs.oracle.com/javaee/7/api/javax/persistence/MapsId.html[`@MapsId`] annotation is used to specify that the entity identifier is mapped by the currently annotated `@ManyToOne` or `@OneToOne` association. See the <> section for more info. @@ -373,6 +389,8 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/NamedQueries.html[`@Na The http://docs.oracle.com/javaee/7/api/javax/persistence/NamedQuery.html[`@NamedQuery`] annotation is used to specify a JPQL query that can be retrieved later by its name. +See the <> section for more info. + [[annotations-jpa-namedstoredprocedurequeries]] ==== `@NamedStoredProcedureQueries` @@ -383,11 +401,15 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/NamedStoredProcedureQu The http://docs.oracle.com/javaee/7/api/javax/persistence/NamedStoredProcedureQuery.html[`@NamedStoredProcedureQuery`] annotation is used to specify a stored procedure query that can be retrieved later by its name. +See the <> section for more info. + [[annotations-jpa-namedsubgraph]] ==== `@NamedSubgraph` The http://docs.oracle.com/javaee/7/api/javax/persistence/NamedSubgraph.html[`@NamedSubgraph`] annotation used to specify a subgraph in an Entity Graph. +See the <> section for more info. + [[annotations-jpa-onetomany]] ==== `@OneToMany` @@ -405,7 +427,7 @@ See the <> section for more info. @@ -421,6 +443,8 @@ See the <> section for more info. + [[annotations-jpa-persistencecontexts]] ==== `@PersistenceContexts` @@ -431,11 +455,15 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceContexts.ht The http://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceProperty.html[`@PersistenceProperty`] annotation is used by the <> annotation to declare JPA provider properties that are passed to the underlying container when the `EntityManager` instance is created. +See the <> section for more info. + [[annotations-jpa-persistenceunit]] ==== `@PersistenceUnit` The http://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceUnit.html[`@PersistenceUnit`] annotation is used to specify the `EntityManagerFactory` that needs to be injected as a dependency. +See the <> section for more info. + [[annotations-jpa-persistenceunits]] ==== `@PersistenceUnits` @@ -493,7 +521,7 @@ See the <> section for more info. @@ -508,10 +536,12 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/PrimaryKeyJoinColumns. The http://docs.oracle.com/javaee/7/api/javax/persistence/QueryHint.html[`@QueryHint`] annotation is used to specify a JPA provider hint used by a `@NamedQuery` or a `@NamedNativeQuery` annotation. +See the <> section for more info. + [[annotations-jpa-secondarytable]] ==== `@SecondaryTable` -The http://docs.oracle.com/javaee/7/api/javax/persistence/SecondaryTable.html[`@SecondaryTable`] annotation is used to specify a secondary table for the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/SecondaryTable.html[`@SecondaryTable`] annotation is used to specify a secondary table for the currently annotated entity. See the <> section for more info. @@ -523,7 +553,9 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/SecondaryTables.html[` [[annotations-jpa-sequencegenerator]] ==== `@SequenceGenerator` -The http://docs.oracle.com/javaee/7/api/javax/persistence/SequenceGenerator.html[`@SequenceGenerator`] annotation is used to specify the database sequence used by the identifier generator of the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/SequenceGenerator.html[`@SequenceGenerator`] annotation is used to specify the database sequence used by the identifier generator of the currently annotated entity. + +See the <> section for more info. [[annotations-jpa-sqlresultsetmapping]] ==== `@SqlResultSetMapping` @@ -542,22 +574,26 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/SqlResultSetMappings.h The http://docs.oracle.com/javaee/7/api/javax/persistence/StoredProcedureParameter.html[`@StoredProcedureParameter`] annotation is used to specify a parameter of a <>. +See the <> section for more info. + [[annotations-jpa-table]] ==== `@Table` -The http://docs.oracle.com/javaee/7/api/javax/persistence/Table.html[`@Table`] annotation is used to specify the primary table of the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/Table.html[`@Table`] annotation is used to specify the primary table of the currently annotated entity. See the <> section for more info. [[annotations-jpa-tablegenerator]] ==== `@TableGenerator` -The http://docs.oracle.com/javaee/7/api/javax/persistence/TableGenerator.html[`@TableGenerator`] annotation is used to specify the database table used by the identity generator of the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/TableGenerator.html[`@TableGenerator`] annotation is used to specify the database table used by the identity generator of the currently annotated entity. + +See the <> section for more info. [[annotations-jpa-temporal]] ==== `@Temporal` -The http://docs.oracle.com/javaee/7/api/javax/persistence/Temporal.html[`@Temporal`] annotation is used to specify the `TemporalType` of the current annotated `java.util.Date` or `java.util.Calendar` entity attribute. +The http://docs.oracle.com/javaee/7/api/javax/persistence/Temporal.html[`@Temporal`] annotation is used to specify the `TemporalType` of the currently annotated `java.util.Date` or `java.util.Calendar` entity attribute. See the <> chapter for more info. @@ -571,14 +607,16 @@ See the <> chapter for more info. [[annotations-jpa-version]] ==== `@Version` The http://docs.oracle.com/javaee/7/api/javax/persistence/Version.html[`@Version`] annotation is used to specify the version attribute used for optimistic locking. -See the <> section for more info. +See the <> section for more info. [[annotations-hibernate]] === Hibernate annotations @@ -674,7 +712,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern The same behavior can be achieved using the `definition` attribute of the JPA <> annotation. -See the <> chapter for more info. +See the <> chapter for more info. [[annotations-hibernate-columns]] ==== `@Columns` @@ -698,7 +736,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-creationtimestamp]] ==== `@CreationTimestamp` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CreationTimestamp.html[`@CreationTimestamp`] annotation is used to specify that the current annotated temporal type must be initialized with the current JVM timestamp value. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CreationTimestamp.html[`@CreationTimestamp`] annotation is used to specify that the currently annotated temporal type must be initialized with the current JVM timestamp value. See the <> section for more info. @@ -734,7 +772,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern By default, Hibernate uses a cached `UPDATE` statement that sets all table columns. When the entity is annotated with the `@DynamicUpdate` annotation, the `PreparedStatement` is going to include only the columns whose values have been changed. -See the <> section for more info on how `@DynamicUpdate` works. +See the <> section for more info. [NOTE] ==== @@ -749,7 +787,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-fetch]] ==== `@Fetch` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Fetch.html[`@Fetch`] annotation is used to specify the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/FetchMode.html[`FetchMode`] (e.g. `JOIN`, `SELECT`, `SUBSELECT`) used for the current annotated association: +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Fetch.html[`@Fetch`] annotation is used to specify the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/FetchMode.html[`FetchMode`] (e.g. `JOIN`, `SELECT`, `SUBSELECT`) used for the currently annotated association: See the <> section for more info. @@ -823,7 +861,7 @@ See the <> section for more info. @@ -831,7 +869,7 @@ See the <> section for more info. @@ -922,6 +960,8 @@ FALSE:: Eagerly load the association. This one is not needed since the JPA `Fetc NO_PROXY:: This option will fetch the association lazily while returning real entity object. PROXY:: This option will fetch the association lazily while returning a proxy instead. +See the <> section for more info. + [[annotations-hibernate-listindexbase]] ==== `@ListIndexBase` @@ -929,6 +969,8 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern By default, `List` indexes are stored starting at zero. Generally used in conjunction with <>. +See the <> section for more info. + [[annotations-hibernate-loader]] ==== `@Loader` @@ -948,6 +990,8 @@ See the <> section for more info. + [[annotations-hibernate-metavalue]] ==== `@MetaValue` @@ -963,7 +1007,18 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-namednativequery]] ==== `@NamedNativeQuery` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NamedNativeQuery.html[`@NamedNativeQuery`] annotation extends the JPA <> with Hibernate specific features. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NamedNativeQuery.html[`@NamedNativeQuery`] annotation extends the JPA <> with Hibernate specific features, like: + +- flush mode for this particular query +- if the query should be cached, and which cache region should be used +- the selected entity `CacheModeType` strategy +- the JDBC `Statement` fetch size +- the JDBC `Statement` execution timeout +- if the query is a `CallableStatement`, targeting a stored procedure or a database function +- what SQL-level comment should be sent to the database +- if the query is read-only, hence it does not store the resulted entities into the currently running Persistence Context + +See the <> section for more info. [[annotations-hibernate-namedqueries]] ==== `@NamedQueries` @@ -973,19 +1028,30 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-namedquery]] ==== `@NamedQuery` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NamedQuery.html[`@NamedQuery`] annotation extends the JPA <> with Hibernate specific features. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NamedQuery.html[`@NamedQuery`] annotation extends the JPA <> with Hibernate specific features, like: + +- flush mode for this particular query +- if the query should be cached, and which cache region should be used +- the selected entity `CacheModeType` strategy +- the JDBC `Statement` fetch size +- the JDBC `Statement` execution timeout +- if the query is a `CallableStatement`, targeting a stored procedure or a database function +- what SQL-level comment should be sent to the database +- if the query is read-only, hence it does not store the resulted entities into the currently running Persistence Context + +See the <> section for more info. [[annotations-hibernate-nationalized]] ==== `@Nationalized` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Nationalized.html[`@Nationalized`] annotation is used to specify that the current annotated attribute is a character type (e.g. `String`, `Character`, `Clob`) that is stored in a nationalized column type (`NVARCHAR`, `NCHAR`, `NCLOB`). +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Nationalized.html[`@Nationalized`] annotation is used to specify that the currently annotated attribute is a character type (e.g. `String`, `Character`, `Clob`) that is stored in a nationalized column type (`NVARCHAR`, `NCHAR`, `NCLOB`). See the <> section for more info. [[annotations-hibernate-naturalid]] ==== `@NaturalId` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NaturalId.html[`@NaturalId`] annotation is used to specify that the current annotated attribute is part of the natural id of the entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NaturalId.html[`@NaturalId`] annotation is used to specify that the currently annotated attribute is part of the natural id of the entity. See the <> section for more info. @@ -1006,10 +1072,12 @@ The `NotFoundAction` defines with two possibilities: `EXCEPTION`:: An exception is thrown when an element is not found (default and recommended). `IGNORE`:: Ignore the element when not found in the database. +See the <> section for more info. + [[annotations-hibernate-ondelete]] ==== `@OnDelete` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OnDelete.html[`@OnDelete`] annotation is used to specify the delete strategy employed by the current annotated collection, array or joined subclasses. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OnDelete.html[`@OnDelete`] annotation is used to specify the delete strategy employed by the currently annotated collection, array or joined subclasses. This annotation is used by the automated schema generation tool to generated the appropriate FOREIGN KEY DDL cascade directive. The two possible strategies are defined by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OnDeleteAction.html[`OnDeleteAction`] enumeration: @@ -1017,15 +1085,19 @@ The two possible strategies are defined by the https://docs.jboss.org/hibernate/ CASCADE:: Use the database FOREIGN KEY cascade capabilities. NO_ACTION:: Take no action. +See the <> chapter for more info. + [[annotations-hibernate-optimisticlock]] ==== `@OptimisticLock` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLock.html[`@OptimisticLock`] annotation is used to specify if the current annotated attribute will trigger an entity version increment upon being modified. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLock.html[`@OptimisticLock`] annotation is used to specify if the currently annotated attribute will trigger an entity version increment upon being modified. + +See the <> section for more info. [[annotations-hibernate-optimisticlocking]] ==== `@OptimisticLocking` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation is used to specify the current annotated an entity optimistic locking strategy. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation is used to specify the currently annotated an entity optimistic locking strategy. The four possible strategies are defined by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLockType.html[`OptimisticLockType`] enumeration: @@ -1034,15 +1106,17 @@ VERSION:: The implicit optimistic locking mechanism is using a dedicated version ALL:: The implicit optimistic locking mechanism is using *all* attributes as part of an expanded WHERE clause restriction for the `UPDATE` and `DELETE` SQL statements. DIRTY:: The implicit optimistic locking mechanism is using the *dirty* attributes (the attributes that were modified) as part of an expanded WHERE clause restriction for the `UPDATE` and `DELETE` SQL statements. -See the <> section for more info. +See the <> section for more info. [[annotations-hibernate-orderby]] ==== `@OrderBy` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OrderBy.html[`@OrderBy`] annotation is used to specify a *SQL* ordering directive for sorting the current annotated collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OrderBy.html[`@OrderBy`] annotation is used to specify a *SQL* ordering directive for sorting the currently annotated collection. It differs from the JPA <> annotation because the JPA annotation expects a JPQL order-by fragment, not an SQL directive. +See the <> section for more info. + [[annotations-hibernate-paramdef]] ==== `@ParamDef` @@ -1053,13 +1127,15 @@ See the <>, <>, and <>, <>. [[annotations-hibernate-parent]] ==== `@Parent` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Parent.html[`@Parent`] annotation is used to specify that the current annotated embeddable attribute references back the owning entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Parent.html[`@Parent`] annotation is used to specify that the currently annotated embeddable attribute references back the owning entity. + +See the <> section for more info. [[annotations-hibernate-persister]] ==== `@Persister` @@ -1070,6 +1146,8 @@ For entities, the custom persister must implement the https://docs.jboss.org/hib For collections, the custom persister must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/collection/CollectionPersister.html[`CollectionPersister`] interface. +See the <> section for more info. + [[annotations-hibernate-polymorphism]] ==== `@Polymorphism` @@ -1077,13 +1155,17 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern There are two possible `PolymorphismType` options: -EXPLICIT:: The current annotated entity is retrieved only if explicitly asked. -IMPLICIT:: The current annotated entity is retrieved if any of its super entity are retrieved. This is the default option. +EXPLICIT:: The currently annotated entity is retrieved only if explicitly asked. +IMPLICIT:: The currently annotated entity is retrieved if any of its super entity are retrieved. This is the default option. + +See the <> section for more info. [[annotations-hibernate-proxy]] ==== `@Proxy` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`] annotation is used to specify a custom Proxy implementation for the current annotated entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`] annotation is used to specify a custom proxy implementation for the currently annotated entity. + +See the <> section for more info. [[annotations-hibernate-rowid]] ==== `@RowId` @@ -1093,10 +1175,12 @@ For instance, Oracle defines the https://docs.oracle.com/cd/B19306_01/server.102 According to Oracle documentation, `ROWID` is the fastest way to access a single row from a table. +See the <> section for more info. + [[annotations-hibernate-selectbeforeupdate]] ==== `@SelectBeforeUpdate` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the current annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the currently annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached. See the <> section for more info on how `@SelectBeforeUpdate` works. @@ -1130,17 +1214,19 @@ The `SourceType` offers two options: DB:: Get the timestamp from the database. VM:: Get the timestamp from the current JVM. +See the <> section for more info. + [[annotations-hibernate-sqldelete]] ==== `@SQLDelete` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDelete.html[`@SQLDelete`] annotation is used to specify a custom SQL `DELETE` statement for the current annotated entity or collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDelete.html[`@SQLDelete`] annotation is used to specify a custom SQL `DELETE` statement for the currently annotated entity or collection. See the <> section for more info. [[annotations-hibernate-sqldeleteall]] ==== `@SQLDeleteAll` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDeleteAll.html[`@SQLDeleteAll`] annotation is used to specify a custom SQL `DELETE` statement when removing all elements of the current annotated collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDeleteAll.html[`@SQLDeleteAll`] annotation is used to specify a custom SQL `DELETE` statement when removing all elements of the currently annotated collection. See the <> section for more info. @@ -1151,17 +1237,19 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern The alias (e.g. `myAlias`) can then be used in the `@Filter` `condition` clause using the `{alias}` (e.g. `{myAlias}`) placeholder. +See the <> section for more info. + [[annotations-hibernate-sqlinsert]] ==== `@SQLInsert` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLInsert.html[`@SQLInsert`] annotation is used to specify a custom SQL `INSERT` statement for the current annotated entity or collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLInsert.html[`@SQLInsert`] annotation is used to specify a custom SQL `INSERT` statement for the currently annotated entity or collection. See the <> section for more info. [[annotations-hibernate-sqlupdate]] ==== `@SQLUpdate` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLUpdate.html[`@SQLUpdate`] annotation is used to specify a custom SQL `UPDATE` statement for the current annotated entity or collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLUpdate.html[`@SQLUpdate`] annotation is used to specify a custom SQL `UPDATE` statement for the currently annotated entity or collection. See the <> section for more info. @@ -1170,6 +1258,8 @@ See the <> section for more info. + [[annotations-hibernate-synchronize]] ==== `@Synchronize` @@ -1179,11 +1269,15 @@ With this information in place, Hibernate will properly trigger an entity flush Therefore, the `@Synchronize` annotation prevents the derived entity from returning stale data when executing entity queries against the `@Subselect` entity. +See the <> section for more info. + [[annotations-hibernate-table]] ==== `@Table` The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Table.html[`@Table`] annotation is used to specify additional information to a JPA <> annotation, like custom `INSERT`, `UPDATE` or `DELETE` statements or a specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/FetchMode.html[`FetchMode`]. +See the <> section for more info about Hibernate-specific `@Table` mapping. + [[annotations-hibernate-tables]] ==== `@Tables` @@ -1191,18 +1285,21 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-target]] ==== `@Target` +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the currently annotated association is using an interface type. -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the current annotated association is using an interface type. +See the <> section for more info. [[annotations-hibernate-tuplizer]] ==== `@Tuplizer` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation is used to specify a custom tuplizer for the current annotated entity or embeddable. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation is used to specify a custom tuplizer for the currently annotated entity or embeddable. For entities, the tupelizer must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tuple/entity/EntityTuplizer.html[`EntityTuplizer`] interface. For embeddables, the tupelizer must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tuple/component/ComponentTuplizer.html[`ComponentTuplizer`] interface. +See the <> section for more info. + [[annotations-hibernate-tuplizers]] ==== `@Tuplizers` @@ -1211,15 +1308,17 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-type]] ==== `@Type` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Type.html[`@Type`] annotation is used to specify the Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/Type.html[`@Type`] used by the current annotated basic attribute. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Type.html[`@Type`] annotation is used to specify the Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/Type.html[`@Type`] used by the currently annotated basic attribute. -See the <> section for more info. +See the <> section for more info. [[annotations-hibernate-typedef]] ==== `@TypeDef` The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/TypeDef.html[`@TypeDef`] annotation is used to specify a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/Type.html[`@Type`] definition which can later be reused for multiple basic attribute mappings. +See the <> section for more info. + [[annotations-hibernate-typedefs]] ==== `@TypeDefs` @@ -1228,7 +1327,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-updatetimestamp]] ==== `@UpdateTimestamp` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/UpdateTimestamp.html[`@UpdateTimestamp`] annotation is used to specify that the current annotated timestamp attribute should be updated with the current JVM timestamp whenever the owning entity gets modified. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/UpdateTimestamp.html[`@UpdateTimestamp`] annotation is used to specify that the currently annotated timestamp attribute should be updated with the current JVM timestamp whenever the owning entity gets modified. - `java.util.Date` - `java.util.Calendar` @@ -1236,6 +1335,8 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern - `java.sql.Time` - `java.sql.Timestamp` +See the <> section for more info. + [[annotations-hibernate-valuegenerationtype]] ==== `@ValueGenerationType` @@ -1254,3 +1355,5 @@ See the <> section for more info. diff --git a/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc b/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc index 21e577da2d22..b59113f9a232 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc @@ -40,7 +40,7 @@ log4j.logger.org.hibernate.type.descriptor.sql=trace ---- However, there are some other alternatives like using datasource-proxy or p6spy. -The advantage of using a JDBC `Driver` or `DataSource` Proxy is that you can go beyond simple SQL logging: +The advantage of using a JDBC `Driver` or `DataSource` proxy is that you can go beyond simple SQL logging: - statement execution time - JDBC batching logging @@ -164,8 +164,8 @@ JPA offers `SINGLE_TABLE`, `JOINED`, and `TABLE_PER_CLASS` to deal with inherita - `SINGLE_TABLE` performs the best in terms of executed SQL statements. However, you cannot use `NOT NULL` constraints on the column-level. You can still use triggers and rules to enforce such constraints, but it's not as straightforward. - `JOINED` addresses the data integrity concerns because every subclass is associated with a different table. - Polymorphic queries or ``@OneToMany` base class associations don't perform very well with this strategy. - However, polymorphic @ManyToOne` associations are fine, and they can provide a lot of value. + Polymorphic queries or `@OneToMany` base class associations don't perform very well with this strategy. + However, polymorphic `@ManyToOne` associations are fine, and they can provide a lot of value. - `TABLE_PER_CLASS` should be avoided since it does not render efficient SQL statements. [[best-practices-fetching]] @@ -215,12 +215,12 @@ If you need to fetch multiple collections, to avoid a Cartesian Product, you sho Hibernate has two caching layers: -- the first-level cache (Persistence Context) which is a application-level repeatable reads. +- the first-level cache (Persistence Context) which provides application-level repeatable reads. - the second-level cache which, unlike application-level caches, it doesn't store entity aggregates but normalized dehydrated entity entries. The first-level cache is not a caching solution "per se", being more useful for ensuring `READ COMMITTED` isolation level. -While the first-level cache is short lived, being cleared when the underlying `EntityManager` is closed, the second-level cache is tied to an `EntityManagerFactory`. +While the first-level cache is short-lived, being cleared when the underlying `EntityManager` is closed, the second-level cache is tied to an `EntityManagerFactory`. Some second-level caching providers offer support for clusters. Therefore, a node needs only to store a subset of the whole cached data. Although the second-level cache can reduce transaction response time since entities are retrieved from the cache rather than from the database, @@ -233,8 +233,8 @@ and you should consider these alternatives prior to jumping to a second-level ca After properly tuning the database, to further reduce the average response time and increase the system throughput, application-level caching becomes inevitable. -Topically, a key-value application-level cache like https://memcached.org/[Memcached] or http://redis.io/[Redis] is a common choice to store data aggregates. -If you can duplicate all data in the key-value store, you have the option of taking down the database system for maintenance without completely loosing availability since read-only traffic can still be served from the cache. +Typically, a key-value application-level cache like https://memcached.org/[Memcached] or http://redis.io/[Redis] is a common choice to store data aggregates. +If you can duplicate all data in the key-value store, you have the option of taking down the database system for maintenance without completely losing availability since read-only traffic can still be served from the cache. One of the main challenges of using an application-level cache is ensuring data consistency across entity aggregates. That's where the second-level cache comes to the rescue. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 99a3e67f9b79..e3f131213265 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -5,8 +5,8 @@ === Strategy configurations Many configuration settings define pluggable strategies that Hibernate uses for various purposes. -The configuration of many of these strategy type settings accept definition in various forms. -The documentation of such configuration settings refer here. +The configurations of many of these strategy type settings accept definition in various forms. +The documentation of such configuration settings refers here. The types of forms available in such cases include: short name (if defined):: @@ -19,897 +19,1139 @@ strategy Class name:: The class name (`java.lang.String`) of the strategy implementation to use [[configurations-general]] -=== General Configuration - -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.dialect` | `org.hibernate.dialect. -PostgreSQL94Dialect` | -The classname of a Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] from which Hibernate can generate SQL optimized for a particular relational database. - -In most cases Hibernate can choose the correct https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] implementation based on the JDBC metadata returned by the JDBC driver. - -|`hibernate.current_session_context_class` |`jta`, `thread`, `managed`, or a custom class implementing `org.hibernate.context.spi. -CurrentSessionContext` | - +=== General configuration + +`*hibernate.dialect*` (e.g. `org.hibernate.dialect.PostgreSQL94Dialect`):: +The class name of a Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] from which Hibernate can generate SQL optimized for a particular relational database. ++ +In most cases, Hibernate can choose the correct https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] implementation based on the JDBC metadata returned by the JDBC driver. ++ +`*hibernate.current_session_context_class*` (e.g. `jta`, `thread`, `managed`, or a custom class implementing `org.hibernate.context.spi.CurrentSessionContext`):: ++ Supply a custom strategy for the scoping of the _current_ `Session`. - ++ The definition of what exactly _current_ means is controlled by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] implementation in use. - -Note that for backwards compatibility, if a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] is not configured but JTA is configured this will default to the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[`JTASessionContext`]. - -|=================================================================================================================================================================================================================================================================== ++ +Note that for backward compatibility, if a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] is not configured but JTA is configured this will default to the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[`JTASessionContext`]. + +[[configurations-jpa-compliance]] +=== JPA compliance + +`*hibernate.jpa.compliance.transaction*` (e.g. `true` or `false` (default value)):: +This setting controls if Hibernate `Transaction` should behave as defined by the spec for JPA's `javax.persistence.EntityTransaction` +since it extends the JPA one. + +`*hibernate.jpa.compliance.query*` (e.g. `true` or `false` (default value)):: +Controls whether Hibernate's handling of `javax.persistence.Query` (JPQL, Criteria and native-query) should strictly follow the JPA spec. ++ +This includes both in terms of parsing or translating a query as well as calls to the `javax.persistence.Query` methods throwing spec +defined exceptions whereas Hibernate might not. + +`*hibernate.jpa.compliance.list*` (e.g. `true` or `false` (default value)):: +Controls whether Hibernate should recognize what it considers a "bag" (`org.hibernate.collection.internal.PersistentBag`) +as a List (`org.hibernate.collection.internal.PersistentList`) or as a bag. ++ +If enabled, we will recognize it as a List where `javax.persistence.OrderColumn` +is just missing (and its defaults will apply). + +`*hibernate.jpa.compliance.closed*` (e.g. `true` or `false` (default value)):: +JPA defines specific exceptions upon calling specific methods on `javax.persistence.EntityManager` and `javax.persistence.EntityManagerFactory` +objects which have been closed previously. ++ +This setting controls whether the JPA spec-defined behavior or the Hibernate behavior will be used. ++ +If enabled, Hibernate will operate in the JPA specified way, throwing exceptions when the spec says it should. + +`*hibernate.jpa.compliance.proxy*` (e.g. `true` or `false` (default value)):: +The JPA spec says that a `javax.persistence.EntityNotFoundException` should be thrown when accessing an entity Proxy +which does not have an associated table row in the database. ++ +Traditionally, Hibernate does not initialize an entity proxy when accessing its identifier since we already know the identifier value, +hence we can save a database roundtrip. ++ +If enabled Hibernate will initialize the entity proxy even when accessing its identifier. + +`*hibernate.jpa.compliance.global_id_generators*` (e.g. `true` or `false` (default value) ):: +The JPA spec says that the scope of TableGenerator and SequenceGenerator names is global to the persistence unit (across all generator types). ++ +Traditionally, Hibernate has considered the names locally scoped. ++ +If enabled, the names used by `@TableGenerator` and `@SequenceGenerator` will be considered global so configuring two different generators +with the same name will cause a `java.lang.IllegalArgumentException' to be thrown at boot time. [[configurations-database-connection]] === Database connection properties -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.connection.driver_class` or `javax.persistence.jdbc.driver` | `org.postgresql.Driver` | Names the JDBC `Driver` class name. -|`hibernate.connection.url` or `javax.persistence.jdbc.url` | `jdbc:postgresql:hibernate_orm_test` | Names the JDBC connection URL. -|`hibernate.connection.username` or `javax.persistence.jdbc.user` | | Names the JDBC connection user name. -|`hibernate.connection.password` or `javax.persistence.jdbc.password` | | Names the JDBC connection password. -|`hibernate.connection.isolation` | `REPEATABLE_READ` or -`Connection.TRANSACTION_REPEATABLE_READ` | Names the JDBC connection transaction isolation level. -|`hibernate.connection.autocommit` | `true` or `false` (default value) | Names the JDBC connection autocommit mode. -|`hibernate.connection.datasource` | | - +`*hibernate.connection.driver_class*` or `*javax.persistence.jdbc.driver*` (e.g. `org.postgresql.Driver`):: +Names the JDBC `Driver` class name. + +`*hibernate.connection.url*` or `*javax.persistence.jdbc.url*` (e.g. `jdbc:postgresql:hibernate_orm_test`):: +Names the JDBC connection URL. + +`*hibernate.connection.username*` or `*javax.persistence.jdbc.user*`:: +Names the JDBC connection user name. + +`*hibernate.connection.password*` or `*javax.persistence.jdbc.password*`:: +Names the JDBC connection password. + +`*hibernate.connection.isolation*` (e.g. `REPEATABLE_READ` or `Connection.TRANSACTION_REPEATABLE_READ`):: +Names the JDBC connection transaction isolation level. + +`*hibernate.connection.autocommit*` (e.g. `true` or `false` (default value)):: +Names the initial autocommit mode for JDBC Connections returned from a connection pool created in certain ConnectionProvider impl. ++ +See discussion of `hibernate.connection.provider_disables_autocommit` as well. + +`*hibernate.connection.provider_disables_autocommit*` (e.g. `true` or `false` (default value)):: +Indicates a promise by the user that Connections that Hibernate obtains from the configured ConnectionProvider +have auto-commit disabled when they are obtained from that provider, whether that provider is backed by +a DataSource or some other Connection pooling mechanism. Generally, this occurs when: +* Hibernate is configured to get Connections from an underlying DataSource, and that DataSource is already configured to disable auto-commit on its managed Connections +* Hibernate is configured to get Connections from a non-DataSource connection pool and that connection pool is already configured to disable auto-commit. +For the Hibernate provided implementation this will depend on the value of `hibernate.connection.autocommit` setting. ++ +Hibernate uses this assurance as an opportunity to opt-out of certain operations that may have a performance +impact (although this impact is generally negligible). Specifically, when a transaction is started via the +Hibernate or JPA transaction APIs Hibernate will generally immediately acquire a Connection from the +provider and: +* check whether the Connection is initially in auto-commit mode via a call to `Connection#getAutocommit` to know how to clean up the Connection when released. +* start a JDBC transaction by calling `Connection#setAutocommit(false)` ++ +We can skip both of those steps if we know that the ConnectionProvider will always return Connections with auto-commit disabled. +That is the purpose of this setting. By setting it to `true`, the `Connection` acquisition can be delayed until the first +SQL statement is needed to be executed. The connection acquisition delay allows you to reduce the database connection lease +time, therefore allowing you to increase the transaction throughput. ++ +==== +It is *inappropriate* to set this value to `true` when the Connections Hibernate gets +from the provider do not, in fact, have auto-commit disabled. + +Doing so will lead to Hibernate executing SQL operations outside of any JDBC/SQL transaction. +==== + +`*hibernate.connection.datasource*`:: Either a `javax.sql.DataSource` instance or a JNDI name under which to locate the `DataSource`. - ++ For JNDI names, ses also `hibernate.jndi.class`, `hibernate.jndi.url`, `hibernate.jndi`. -|`hibernate.connection` | | Names a prefix used to define arbitrary JDBC connection properties. These properties are passed along to the JDBC provider when creating a connection. -|`hibernate.connection.provider_class` | `org.hibernate.hikaricp.internal. -HikariCPConnectionProvider` a| - +`*hibernate.connection*`:: + Names a prefix used to define arbitrary JDBC connection properties. These properties are passed along to the JDBC provider when creating a connection. +`*hibernate.connection.provider_class*` (e.g. `org.hibernate.hikaricp.internal. HikariCPConnectionProvider`):: Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.html[`ConnectionProvider`] to use for obtaining JDBC connections. - ++ Can reference: ++ +** an instance of `ConnectionProvider` +** a `Class` usage[, region] where usage is the cache strategy used and region the cache region name. -|`hibernate.ejb.collectioncache`| `hibernate.ejb.collectioncache -.org.hibernate.ejb.test.Item.distributors` = `read-write, RegionName`/> | Sets the associated collection cache concurrency strategy for the designated region. Caching configuration should follow the following pattern `hibernate.ejb.collectioncache..` usage[, region] where usage is the cache strategy used and region the cache region name -|================================================================================================================================================================================================================================================================================================================== +`*hibernate.cache.region.factory_class*` (e.g. `jcache`):: +Either a shortcut name (e.g. `jcache`, `ehcache`) or the fully-qualified name of the `RegionFactory` implementation class. + +`*hibernate.cache.default_cache_concurrency_strategy*`:: +Setting used to give the name of the default https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CacheConcurrencyStrategy.html[`CacheConcurrencyStrategy`] to use +when either `@javax.persistence.Cacheable` or `@org.hibernate.annotations.Cache`. `@org.hibernate.annotations.Cache` is used to override the global setting. + +`*hibernate.cache.use_minimal_puts*` (e.g. `true` (default value) or `false`):: +Optimizes second-level cache operation to minimize writes, at the cost of more frequent reads. This is most useful for clustered caches and is enabled by default for clustered cache implementations. + +`*hibernate.cache.use_query_cache*` (e.g. `true` or `false` (default value)):: +Enables the query cache. You still need to set individual queries to be cachable. + +`*hibernate.cache.use_second_level_cache*` (e.g. `true` (default value) or `false`):: +Enable/disable the second level cache, which is enabled by default, although the default `RegionFactor` is `NoCachingRegionFactory` (meaning there is no actual caching implementation). + +`*hibernate.cache.query_cache_factory*` (e.g. Fully-qualified class name):: +A custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/cache/spi/QueryCacheFactory.html[`QueryCacheFactory`] interface. The default is the built-in `StandardQueryCacheFactory`. + +`*hibernate.cache.region_prefix*` (e.g. A string):: +A prefix for second-level cache region names. + +`*hibernate.cache.use_structured_entries*` (e.g. `true` or `false` (default value)):: +Forces Hibernate to store data in the second-level cache in a more human-readable format. + +`*hibernate.cache.auto_evict_collection_cache*` (e.g. `true` or `false` (default: false)):: +Enables the automatic eviction of a bi-directional association's collection cache when an element in the `ManyToOne` collection is added/updated/removed without properly managing the change on the `OneToMany` side. + +`*hibernate.cache.use_reference_entries*` (e.g. `true` or `false`):: +Optimizes second-level cache operation to store immutable entities (aka "reference") which do not have associations into cache directly, this case, disassembling and deep copy operations can be avoided. The default value of this property is `false`. + +`*hibernate.ejb.classcache*` (e.g. `hibernate.ejb.classcache.org.hibernate.ejb.test.Item` = `read-write`):: +Sets the associated entity class cache concurrency strategy for the designated region. Caching configuration should follow the following pattern `hibernate.ejb.classcache.` usage[, region] where usage is the cache strategy used and region the cache region name. + +`*hibernate.ejb.collectioncache*` (e.g. `hibernate.ejb.collectioncache.org.hibernate.ejb.test.Item.distributors` = `read-write, RegionName`):: +Sets the associated collection cache concurrency strategy for the designated region. Caching configuration should follow the following pattern `hibernate.ejb.collectioncache..` usage[, region] where usage is the cache strategy used and region the cache region name [[configurations-infinispan]] === Infinispan properties -[width="100%",cols="20%,20%,60%",] -|===================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.cache.infinispan.cfg` | `org/hibernate/cache/infinispan/ -builder/infinispan-configs.xml` | Classpath or filesystem resource containing the Infinispan configuration settings. -|`hibernate.cache.infinispan.statistics` | | Property name that controls whether Infinispan statistics are enabled. The property value is expected to be a boolean true or false, and it overrides statistic configuration in base Infinispan configuration, if provided. -|`hibernate.cache.infinispan.use_synchronization` | | Deprecated setting because Infinispan is designed to always register a `Synchronization` for `TRANSACTIONAL` caches. -|`hibernate.cache.infinispan.cachemanager` | There is no default value, the user must specify the property. | Specifies the JNDI name under which the `EmbeddedCacheManager` is bound. -|===================================================================================================================================================================================================================================================== +For more details about how to customize the Infinispan second-level cache provider, check out the +https://infinispan.org/docs/stable/titles/integrating/integrating.html#configuration_properties[Infinispan User Guide]. [[configurations-transactions]] === Transactions properties -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.transaction.jta.platform` |`JBossAS`, `BitronixJtaPlatform` | - +`*hibernate.transaction.jta.platform*` (e.g. `JBossAS`, `BitronixJtaPlatform`):: Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatform.html[`JtaPlatform`] implementation to use for integrating with JTA systems. Can reference either a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatform.html[`JtaPlatform`] instance or the name of the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatform.html[`JtaPlatform`] implementation class -|`hibernate.jta.prefer_user_transaction` |`true` or `false` (default value) | - +`*hibernate.jta.prefer_user_transaction*` (e.g. `true` or `false` (default value)):: Should we prefer using the `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform#retrieveUserTransaction` over using `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform#retrieveTransactionManager` -|`hibernate.transaction.jta.platform_resolver` | | Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformResolver.html[`JtaPlatformResolver`] implementation to use. -|`hibernate.jta.cacheTransactionManager` | `true` (default value) or `false` | A configuration value key used to indicate that it is safe to cache. -|`hibernate.jta.cacheUserTransaction` | `true` or `false` (default value) | A configuration value key used to indicate that it is safe to cache. -|`hibernate.transaction.flush_before_completion` |`true` or `false` (default value) | Causes the session be flushed during the before completion phase of the transaction. If possible, use built-in and automatic session context management instead. -|`hibernate.transaction.auto_close_session` |`true` or `false` (default value) |Causes the session to be closed during the after completion phase of the transaction. If possible, use built-in and automatic session context management instead. -|`hibernate.transaction.coordinator_class` | a| +`*hibernate.transaction.jta.platform_resolver*`:: +Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformResolver.html[`JtaPlatformResolver`] implementation to use. -Names the implementation of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.html[`TransactionCoordinatorBuilder`] to use for creating https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/resource/transaction/spi/TransactionCoordinator.html[`TransactionCoordinator`] instances. +`*hibernate.jta.cacheTransactionManager*` (e.g. `true` (default value) or `false`):: +A configuration value key used to indicate that it is safe to cache. -Can be +`*hibernate.jta.cacheUserTransaction*` (e.g. `true` or `false` (default value)):: +A configuration value key used to indicate that it is safe to cache. -* `TransactionCoordinatorBuilder` instance -* `TransactionCoordinatorBuilder` implementation `Class` reference -* `TransactionCoordinatorBuilder` implementation class name (fully-qualified name) or short name +`*hibernate.transaction.flush_before_completion*` (e.g. `true` or `false` (default value)):: +Causes the session be flushed during the before completion phase of the transaction. If possible, use built-in and automatic session context management instead. -The following short names are defined for this setting: +`*hibernate.transaction.auto_close_session*` (e.g. `true` or `false` (default value)):: +Causes the session to be closed during the after completion phase of the transaction. If possible, use built-in and automatic session context management instead. -`jdbc`:: Manages transactions via calls to `java.sql.Connection` (default for non-JPA applications) -`jta`:: Manages transactions via JTA. See <> +`*hibernate.transaction.coordinator_class*`:: +Names the implementation of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.html[`TransactionCoordinatorBuilder`] to use for creating https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/resource/transaction/spi/TransactionCoordinator.html[`TransactionCoordinator`] instances. ++ +Can be a`TransactionCoordinatorBuilder` instance, `TransactionCoordinatorBuilder` implementation `Class` reference, a `TransactionCoordinatorBuilder` implementation class name (fully-qualified name) or a short name. ++ +The following short names are defined for this setting: ++ +`jdbc`::: Manages transactions via calls to `java.sql.Connection` (default for non-JPA applications) +`jta`::: Manages transactions via JTA. See <> ++ If a JPA application does not provide a setting for `hibernate.transaction.coordinator_class`, Hibernate will automatically build the proper transaction coordinator based on the transaction type for the persistence unit. - ++ If a non-JPA application does not provide a setting for `hibernate.transaction.coordinator_class`, Hibernate will use `jdbc` as the default. This default will cause problems if the application actually uses JTA-based transactions. A non-JPA application that uses JTA-based transactions should explicitly set `hibernate.transaction.coordinator_class=jta` or provide a custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/resource/transaction/TransactionCoordinatorBuilder.html[`TransactionCoordinatorBuilder`] that builds a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/resource/transaction/TransactionCoordinator.html[`TransactionCoordinator`] that properly coordinates with JTA-based transactions. -|`hibernate.jta.track_by_thread` | `true` (default value) or `false` | - +`*hibernate.jta.track_by_thread*` (e.g. `true` (default value) or `false`):: A transaction can be rolled back by another thread ("tracking by thread") and not the original application. Examples of this include a JTA transaction timeout handled by a background reaper thread. - ++ The ability to handle this situation requires checking the Thread ID every time Session is called, so enabling this can certainly have a performance impact. -|`hibernate.transaction.factory_class` | | This is a legacy setting that's been deprecated and you should use the `hibernate.transaction.jta.platform` instead. +[line-through]#`*hibernate.transaction.factory_class*`#:: ++ +WARNING: This is a legacy setting that's been deprecated and you should use the `hibernate.transaction.jta.platform` instead. -|=================================================================================================================================================================================================================================== +`*hibernate.jta.allowTransactionAccess*`(e.g. `true` (default value) or `false`):: +It allows access to the underlying `org.hibernate.Transaction` even when using JTA +since the JPA specification prohibits this behavior. ++ +If this configuration property is set to `true`, access is granted to the underlying `org.hibernate.Transaction`. +If it's set to `false`, you won't be able to access the `org.hibernate.Transaction`. ++ +The default behavior is to allow access unless the `Session` is bootstrapped via JPA. [[configurations-multi-tenancy]] === Multi-tenancy settings -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.multiTenancy` | `NONE` (default value), `SCHEMA`, `DATABASE`, and `DISCRIMINATOR` (not implemented yet) | The multi-tenancy strategy in use. -|`hibernate.multi_tenant_connection_provider` | `true` or `false` (default value) | Names a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/connections/spi/MultiTenantConnectionProvider.html[`MultiTenantConnectionProvider`] implementation to use. As `MultiTenantConnectionProvider` is also a service, can be configured directly through the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/StandardServiceRegistryBuilder.html[`StandardServiceRegistryBuilder`]. -|`hibernate.tenant_identifier_resolver` | a| +`*hibernate.multiTenancy*` (e.g. `NONE` (default value), `SCHEMA`, `DATABASE`, and `DISCRIMINATOR` (not implemented yet)):: +The multi-tenancy strategy in use. -Names a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentTenantIdentifierResolver.html[`CurrentTenantIdentifierResolver`] implementation to resolve the resolve the current tenant identifier so that calling `SessionFactory#openSession()` would get a `Session` that's connected to the right tenant. +`*hibernate.multi_tenant_connection_provider*` (e.g. `true` or `false` (default value)):: +Names a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/connections/spi/MultiTenantConnectionProvider.html[`MultiTenantConnectionProvider`] implementation to use. As `MultiTenantConnectionProvider` is also a service, can be configured directly through the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/StandardServiceRegistryBuilder.html[`StandardServiceRegistryBuilder`]. -Can be: - -* `CurrentTenantIdentifierResolver` instance -* `CurrentTenantIdentifierResolver` implementation `Class` object reference -* `CurrentTenantIdentifierResolver` implementation class name - -|`hibernate.multi_tenant.datasource.identifier_for_any` | `true` or `false` (default value) | When the `hibernate.connection.datasource` property value is resolved to a `javax.naming.Context` object, this configuration property defines the JNDI name used to locate the `DataSource` used for fetching the initial `Connection` which is used to access to the database metadata of the underlying database(s) (in situations where we do not have a tenant id, like startup processing). +`*hibernate.tenant_identifier_resolver*`:: +Names a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentTenantIdentifierResolver.html[`CurrentTenantIdentifierResolver`] implementation to resolve the resolve the current tenant identifier so that calling `SessionFactory#openSession()` would get a `Session` that's connected to the right tenant. ++ +Can be a `CurrentTenantIdentifierResolver` instance, `CurrentTenantIdentifierResolver` implementation `Class` object reference or a `CurrentTenantIdentifierResolver` implementation class name. -|=================================================================================================================================================================================================================================== +`*hibernate.multi_tenant.datasource.identifier_for_any*` (e.g. `true` or `false` (default value)):: +When the `hibernate.connection.datasource` property value is resolved to a `javax.naming.Context` object, this configuration property defines the JNDI name used to locate the `DataSource` used for fetching the initial `Connection` which is used to access to the database metadata of the underlying database(s) (in situations where we do not have a tenant id, like startup processing). [[configurations-hbmddl]] === Automatic schema generation -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.hbm2ddl.auto` |`none` (default value), `create-only`, `drop`, `create`, `create-drop`, `validate`, and `update` a| - +`*hibernate.hbm2ddl.auto*` (e.g. `none` (default value), `create-only`, `drop`, `create`, `create-drop`, `validate`, and `update`):: Setting to perform `SchemaManagementTool` actions automatically as part of the `SessionFactory` lifecycle. Valid options are defined by the `externalHbm2ddlName` value of the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/Action.html[`Action`] enum: - -`none`:: No action will be performed. -`create-only`:: Database creation will be generated. -`drop`:: Database dropping will be generated. -`create`:: Database dropping will be generated followed by database creation. -`create-drop`:: Drop the schema and recreate it on SessionFactory startup. Additionally, drop the schema on SessionFactory shutdown. -`validate`:: Validate the database schema -`update`:: Update the database schema - -|`javax.persistence.schema-generation.database.action` |`none` (default value), `create-only`, `drop`, `create`, `create-drop`, `validate`, and `update` a| - ++ +`none`::: No action will be performed. +`create-only`::: Database creation will be generated. +`drop`::: Database dropping will be generated. +`create`::: Database dropping will be generated followed by database creation. +`create-drop`::: Drop the schema and recreate it on SessionFactory startup. Additionally, drop the schema on SessionFactory shutdown. +`validate`::: Validate the database schema +`update`::: Update the database schema + +`*javax.persistence.schema-generation.database.action*` (e.g. `none` (default value), `create-only`, `drop`, `create`, `create-drop`, `validate`, and `update`):: Setting to perform `SchemaManagementTool` actions automatically as part of the `SessionFactory` lifecycle. Valid options are defined by the `externalJpaName` value of the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/Action.html[`Action`] enum: ++ +`none`::: No action will be performed. +`create`::: Database creation will be generated. +`drop`::: Database dropping will be generated. +`drop-and-create`::: Database dropping will be generated followed by database creation. -`none`:: No action will be performed. -`create`:: Database creation will be generated. -`drop`:: Database dropping will be generated. -`drop-and-create`:: Database dropping will be generated followed by database creation. - -|`javax.persistence.schema-generation.scripts.action` |`none` (default value), `create-only`, `drop`, `create`, `create-drop`, `validate`, and `update` a| - +`*javax.persistence.schema-generation.scripts.action*` (e.g. `none` (default value), `create-only`, `drop`, `create`, `create-drop`, `validate`, and `update`):: Setting to perform `SchemaManagementTool` actions writing the commands into a DDL script file. Valid options are defined by the `externalJpaName` value of the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/Action.html[`Action`] enum: ++ +`none`::: No action will be performed. +`create`::: Database creation will be generated. +`drop`::: Database dropping will be generated. +`drop-and-create`::: Database dropping will be generated followed by database creation. -`none`:: No action will be performed. -`create`:: Database creation will be generated. -`drop`:: Database dropping will be generated. -`drop-and-create`:: Database dropping will be generated followed by database creation. - -|`javax.persistence.schema-generation-connection` | |Allows passing a specific `java.sql.Connection` instance to be used by `SchemaManagementTool` -|`javax.persistence.database-product-name` | | +`*javax.persistence.schema-generation-connection*`:: +Allows passing a specific `java.sql.Connection` instance to be used by `SchemaManagementTool` +`*javax.persistence.database-product-name*`:: Specifies the name of the database provider in cases where a Connection to the underlying database is not available (aka, mainly in generating scripts). In such cases, a value for this setting _must_ be specified. - ++ The value of this setting is expected to match the value returned by `java.sql.DatabaseMetaData#getDatabaseProductName()` for the target database. - ++ Additionally, specifying `javax.persistence.database-major-version` and/or `javax.persistence.database-minor-version` may be required to understand exactly how to generate the required schema commands. -|`javax.persistence.database-major-version` | | - +`*javax.persistence.database-major-version*`:: Specifies the major version of the underlying database, as would be returned by `java.sql.DatabaseMetaData#getDatabaseMajorVersion` for the target database. - ++ This value is used to help more precisely determine how to perform schema generation tasks for the underlying database in cases where `javax.persistence.database-product-name` does not provide enough distinction. -|`javax.persistence.database-minor-version` | | - +`*javax.persistence.database-minor-version*`:: Specifies the minor version of the underlying database, as would be returned by `java.sql.DatabaseMetaData#getDatabaseMinorVersion` for the target database. - ++ This value is used to help more precisely determine how to perform schema generation tasks for the underlying database in cases where `javax.persistence.database-product-name` and `javax.persistence.database-major-version` does not provide enough distinction. -|`javax.persistence.schema-generation.create-source` | a| - -Specifies whether schema generation commands for schema creation are to be determine based on object/relational mapping metadata, DDL scripts, or a combination of the two. +`*javax.persistence.schema-generation.create-source*`:: +Specifies whether schema generation commands for schema creation are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/SourceType.html[`SourceType`] for valid set of values. - ++ If no value is specified, a default is assumed as follows: - -* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `scripts` is assumed ++ +* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `script` is assumed * otherwise, `metadata` is assumed -|`javax.persistence.schema-generation.drop-source` | a| - -Specifies whether schema generation commands for schema dropping are to be determine based on object/relational mapping metadata, DDL scripts, or a combination of the two. +`*javax.persistence.schema-generation.drop-source*`:: +Specifies whether schema generation commands for schema dropping are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/SourceType.html[`SourceType`] for valid set of values. - ++ If no value is specified, a default is assumed as follows: - -* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `scripts` is assumed ++ +* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then the `script` option is assumed * otherwise, `metadata` is assumed -|`javax.persistence.schema-generation.create-script-source` | | - +`*javax.persistence.schema-generation.create-script-source*`:: Specifies the `create` script file as either a `java.io.Reader` configured for reading of the DDL script file or a string designating a file `java.net.URL` for the DDL script. - ++ Hibernate historically also accepted `hibernate.hbm2ddl.import_files` for a similar purpose, but `javax.persistence.schema-generation.create-script-source` should be preferred over `hibernate.hbm2ddl.import_files`. -|`javax.persistence.schema-generation.drop-script-source` | | Specifies the `drop` script file as either a `java.io.Reader` configured for reading of the DDL script file or a string designating a file `java.net.URL` for the DDL script. -|`javax.persistence.schema-generation.scripts.create-target` | |For cases where the `javax.persistence.schema-generation.scripts.action` value indicates that schema creation commands should be written to DDL script file, `javax.persistence.schema-generation.scripts.create-target` specifies either a `java.io.Writer` configured for output of the DDL script or a string specifying the file URL for the DDL script. -|`javax.persistence.schema-generation.scripts.drop-target` | |For cases where the `javax.persistence.schema-generation.scripts.action` value indicates that schema dropping commands should be written to DDL script file, `javax.persistence.schema-generation.scripts.drop-target` specifies either a `java.io.Writer` configured for output of the DDL script or a string specifying the file URL for the DDL script. -|`javax.persistence.hibernate.hbm2ddl.import_files` | `import.sql` (default value) a| +`*javax.persistence.schema-generation.drop-script-source*`:: + Specifies the `drop` script file as either a `java.io.Reader` configured for reading of the DDL script file or a string designating a file `java.net.URL` for the DDL script. -Comma-separated names of the optional files containing SQL DML statements executed during the `SessionFactory` creation. -File order matters, the statements of a give file are executed before the statements of the following one. +`*javax.persistence.schema-generation.scripts.create-target*`:: +For cases where the `javax.persistence.schema-generation.scripts.action` value indicates that schema creation commands should be written to DDL script file, `javax.persistence.schema-generation.scripts.create-target` specifies either a `java.io.Writer` configured for output of the DDL script or a string specifying the file URL for the DDL script. + +`*javax.persistence.schema-generation.scripts.drop-target*`:: +For cases where the `javax.persistence.schema-generation.scripts.action` value indicates that schema dropping commands should be written to DDL script file, `javax.persistence.schema-generation.scripts.drop-target` specifies either a `java.io.Writer` configured for output of the DDL script or a string specifying the file URL for the DDL script. +`*javax.persistence.hibernate.hbm2ddl.import_files*` (e.g. `import.sql` (default value)):: +Comma-separated names of the optional files containing SQL DML statements executed during the `SessionFactory` creation. +File order matters, the statements of a given file are executed before the statements of the following one. ++ These statements are only executed if the schema is created, meaning that `hibernate.hbm2ddl.auto` is set to `create`, `create-drop`, or `update`. `javax.persistence.schema-generation.create-script-source` / `javax.persistence.schema-generation.drop-script-source` should be preferred. -|`javax.persistence.sql-load-script-source` | | - +`*javax.persistence.sql-load-script-source*`:: JPA variant of `hibernate.hbm2ddl.import_files`. Specifies a `java.io.Reader` configured for reading of the SQL load script or a string designating the file `java.net.URL` for the SQL load script. A "SQL load script" is a script that performs some database initialization (INSERT, etc). -|`hibernate.hbm2ddl.import_files_sql_extractor` | | - +`*hibernate.hbm2ddl.import_files_sql_extractor*`:: Reference to the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/hbm2ddl/ImportSqlCommandExtractor.html[`ImportSqlCommandExtractor`] implementation class to use for parsing source/import files as defined by `javax.persistence.schema-generation.create-script-source`, `javax.persistence.schema-generation.drop-script-source` or `hibernate.hbm2ddl.import_files`. - ++ Reference may refer to an instance, a Class implementing `ImportSqlCommandExtractor` of the fully-qualified name of the `ImportSqlCommandExtractor` implementation. If the fully-qualified name is given, the implementation must provide a no-arg constructor. - ++ The default value is https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/hbm2ddl/SingleLineSqlCommandExtractor.html[`SingleLineSqlCommandExtractor`]. -|`hibernate.hbm2dll.create_namespaces` | `true` or `false` (default value) |Specifies whether to automatically create also the database schema/catalog. -|`javax.persistence.create-database-schemas` | `true` or `false` (default value) | +`*hibernate.hbm2dll.create_namespaces*` (e.g. `true` or `false` (default value)):: +Specifies whether to automatically create also the database schema/catalog. +`*javax.persistence.create-database-schemas*` (e.g. `true` or `false` (default value)):: The JPA variant of `hibernate.hbm2dll.create_namespaces`. Specifies whether the persistence provider is to create the database schema(s) in addition to creating database objects (tables, sequences, constraints, etc). The value of this boolean property should be set to `true` if the persistence provider is to create schemas in the database or to generate DDL that contains "CREATE SCHEMA" commands. ++ If this property is not supplied (or is explicitly `false`), the provider should not attempt to create database schemas. -|`hibernate.hbm2ddl.schema_filter_provider` | | - +`*hibernate.hbm2ddl.schema_filter_provider*`:: Used to specify the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaFilterProvider.html[`SchemaFilterProvider`] to be used by `create`, `drop`, `migrate`, and `validate` operations on the database schema. `SchemaFilterProvider` provides filters that can be used to limit the scope of these operations to specific namespaces, tables and sequences. All objects are included by default. -|`hibernate.hbm2ddl.jdbc_metadata_extraction_strategy` |`grouped` (default value) or `individually` a| - +`*hibernate.hbm2ddl.jdbc_metadata_extraction_strategy*` (e.g. `grouped` (default value) or `individually`):: Setting to choose the strategy used to access the JDBC Metadata. Valid options are defined by the `strategy` value of the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/JdbcMetadaAccessStrategy.html[`JdbcMetadaAccessStrategy`] enum: ++ +`grouped`::: https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaMigrator.html[`SchemaMigrator`] and https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaValidator.html[`SchemaValidator`] execute a single `java.sql.DatabaseMetaData#getTables(String, String, String, String[])` call to retrieve all the database table in order to determine if all the `javax.persistence.Entity` have a corresponding mapped database tables.This strategy may require `hibernate.default_schema` and/or `hibernate.default_catalog` to be provided. +`individually`::: https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaMigrator.html[`SchemaMigrator`] and https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaValidator.html[`SchemaValidator`] execute one `java.sql.DatabaseMetaData#getTables(String, String, String, String[])` call for each `javax.persistence.Entity` in order to determine if a corresponding database table exists. + +`*hibernate.hbm2ddl.delimiter*` (e.g. `;`):: +Identifies the delimiter to use to separate schema management statements in script outputs. -`grouped`:: https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaMigrator.html[`SchemaMigrator`] and https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaValidator.html[`SchemaValidator`] execute a single `java.sql.DatabaseMetaData#getTables(String, String, String, String[])` call to retrieve all the database table in order to determine if all the `javax.persistence.Entity` have a corresponding mapped database tables. -`individually`:: https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaMigrator.html[`SchemaMigrator`] and https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaValidator.html[`SchemaValidator`] execute one `java.sql.DatabaseMetaData#getTables(String, String, String, String[])` call for each `javax.persistence.Entity` in order to determine if a corresponding database table exists. +`*hibernate.schema_management_tool*` (e.g. A schema name):: +Used to specify the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaManagementTool.html[`SchemaManagementTool`] to use for performing schema management. The default is to use https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.html[`HibernateSchemaManagementTool`] -|`hibernate.hbm2ddl.delimiter` | `;` |Identifies the delimiter to use to separate schema management statements in script outputs. +`*hibernate.synonyms*` (e.g. `true` or `false` (default value)):: +If enabled, allows schema update and validation to support synonyms. Due to the possibility that this would return duplicate tables (especially in Oracle), this is disabled by default. -|`hibernate.schema_management_tool` |A schema name |Used to specify the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/spi/SchemaManagementTool.html[`SchemaManagementTool`] to use for performing schema management. The default is to use https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.html[`HibernateSchemaManagementTool`] -|`hibernate.synonyms` |`true` or `false` (default value) |If enabled, allows schema update and validation to support synonyms. Due to the possibility that this would return duplicate tables (especially in Oracle), this is disabled by default. -|`hibernate.hbm2dll.extra_physical_table_types` |`BASE TABLE` |Identifies a comma-separated list of values to specify extra table types, other than the default `TABLE` value, to recognize as defining a physical table by schema update, creation and validation. -|`hibernate.schema_update.unique_constraint_strategy` |`DROP_RECREATE_QUIETLY`, `RECREATE_QUIETLY`, `SKIP` a| +`*hibernate.hbm2dll.extra_physical_table_types*` (e.g. `BASE TABLE`):: +Identifies a comma-separated list of values to specify extra table types, other than the default `TABLE` value, to recognize as defining a physical table by schema update, creation and validation. +`*hibernate.schema_update.unique_constraint_strategy*` (e.g. `DROP_RECREATE_QUIETLY`, `RECREATE_QUIETLY`, `SKIP`):: Unique columns and unique keys both use unique constraints in most dialects. `SchemaUpdate` needs to create these constraints, but DBs support for finding existing constraints is extremely inconsistent. Further, non-explicitly-named unique constraints use randomly generated characters. - ++ Therefore, the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/hbm2ddl/UniqueConstraintSchemaUpdateStrategy.html[`UniqueConstraintSchemaUpdateStrategy`] offers the following options: ++ +`DROP_RECREATE_QUIETLY`::: Default option. +Attempt to drop, then (re-)create each unique constraint. Ignore any exceptions being thrown. +`RECREATE_QUIETLY`::: +Attempts to (re-)create unique constraints, ignoring exceptions thrown if the constraint already existed +`SKIP`::: +Does not attempt to create unique constraints on a schema update. -`DROP_RECREATE_QUIETLY`:: Default option. - Attempt to drop, then (re-)create each unique constraint. Ignore any exceptions being thrown. -`RECREATE_QUIETLY`:: - Attempts to (re-)create unique constraints, ignoring exceptions thrown if the constraint already existed -`SKIP`:: - Does not attempt to create unique constraints on a schema update. -|`hibernate.hbm2ddl.charset_name` |`Charset.defaultCharset()` |Defines the charset (encoding) used for all input/output schema generation resources. By default, Hibernate uses the default charset given by `Charset.defaultCharset()`. This configuration property allows you to override the default JVM setting so that you can specify which encoding is used when reading and writing schema generation resources (e.g. File, URL). -|`hibernate.hbm2ddl.halt_on_error` |`true` or `false` (default value) |Whether the schema migration tool should halt on error, therefore terminating the bootstrap process. By default, the `EntityManagerFactory` or `SessionFactory` are created even if the schema migration throws exceptions. To prevent this default behavior, set this property value to `true`. +`*hibernate.hbm2ddl.charset_name*` (e.g. `Charset.defaultCharset()`):: +Defines the charset (encoding) used for all input/output schema generation resources. By default, Hibernate uses the default charset given by `Charset.defaultCharset()`. This configuration property allows you to override the default JVM setting so that you can specify which encoding is used when reading and writing schema generation resources (e.g. File, URL). -|=================================================================================================================================================================================================================================== +`*hibernate.hbm2ddl.halt_on_error*` (e.g. `true` or `false` (default value)):: +Whether the schema migration tool should halt on error, therefore terminating the bootstrap process. By default, the `EntityManagerFactory` or `SessionFactory` are created even if the schema migration throws exceptions. To prevent this default behavior, set this property value to `true`. [[configurations-exception-handling]] === Exception handling -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.jdbc.sql_exception_converter` | Fully-qualified name of class implementing `SQLExceptionConverter` |The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/exception/spi/SQLExceptionConverter.html[`SQLExceptionConverter`] to use for converting `SQLExceptions` to Hibernate's `JDBCException` hierarchy. The default is to use the configured https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`]'s preferred `SQLExceptionConverter`. -|=================================================================================================================================================================================================================================== +`*hibernate.jdbc.sql_exception_converter*` (e.g. Fully-qualified name of class implementing `SQLExceptionConverter`):: +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/exception/spi/SQLExceptionConverter.html[`SQLExceptionConverter`] to use for converting `SQLExceptions` to Hibernate's `JDBCException` hierarchy. The default is to use the configured https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`]'s preferred `SQLExceptionConverter`. + +`*hibernate.native_exception_handling_51_compliance*` (e.g. `true` or `false` (default value)):: +Indicates if exception handling for a `SessionFactory` built via Hibernate's native bootstrapping +should behave the same as native exception handling in Hibernate ORM 5.1. When set to `true`, +`HibernateException` will be not wrapped or converted according to the JPA specification. This +setting will be ignored for a `SessionFactory` built via JPA bootstrapping. [[configurations-session-events]] === Session events -[width="100%",cols="20%,20%,60%",] -|=========================================================================================================================== -|Property |Example |Purpose -|`hibernate.session.events.auto` | | Fully qualified class name implementing the `SessionEventListener` interface. -|`hibernate.session_factory.interceptor` or `hibernate.ejb.interceptor` | `org.hibernate.EmptyInterceptor` (default value) a| - -Names a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Interceptor` implementation to be applied to every `Session` created by the current `org.hibernate.SessionFactory` +`*hibernate.session.events.auto*`:: +Fully qualified class name implementing the `SessionEventListener` interface. +`*hibernate.session_factory.interceptor*` (e.g. `org.hibernate.EmptyInterceptor` (default value)):: +Names a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Interceptor[`Interceptor`] implementation to be applied to every `Session` created by the current `org.hibernate.SessionFactory` ++ Can reference: - ++ * `Interceptor` instance * `Interceptor` implementation `Class` object reference * `Interceptor` implementation class name -|`hibernate.ejb.interceptor.session_scoped` | fully-qualified class name or class reference | An optional Hibernate interceptor. - +[line-through]#`*hibernate.ejb.interceptor*`# (e.g. `hibernate.session_factory.interceptor` (default value)):: ++ +WARNING: Deprecated setting. Use `hibernate.session_factory.session_scoped_interceptor` instead. + +`*hibernate.session_factory.session_scoped_interceptor*` (e.g. fully-qualified class name or class reference):: +Names a `org.hibernate.Interceptor` implementation to be applied to the `org.hibernate.SessionFactory` and propagated to each `Session` created from the `SessionFactory`. ++ +This setting identifies an `Interceptor` implementation that is to be applied to every `Session` opened from the `SessionFactory`, +but unlike `hibernate.session_factory.interceptor`, a unique instance of the `Interceptor` is +used for each `Session`. ++ +Can reference: ++ +* `Interceptor` instance +* `Interceptor` implementation `Class` object reference +* `java.util.function.Supplier` instance which is used to retrieve the `Interceptor` instance. ++ +NOTE: Specifically, this setting cannot name an `Interceptor` instance. + +[line-through]#`*hibernate.ejb.interceptor.session_scoped*`# (e.g. fully-qualified class name or class reference):: ++ +WARNING: Deprecated setting. Use `hibernate.session_factory.session_scoped_interceptor` instead. ++ +An optional Hibernate interceptor. ++ The interceptor instance is specific to a given Session instance (and hence is not thread-safe) has to implement `org.hibernate.Interceptor` and have a no-arg constructor. ++ +This property cannot be combined with `hibernate.ejb.interceptor`. -This property can not be combined with `hibernate.ejb.interceptor`. -|`hibernate.ejb.session_factory_observer` | fully-qualified class name or class reference | Specifies a `SessionFactoryObserver` to be applied to the SessionFactory. The class must have a no-arg constructor. -|`hibernate.ejb.event` | `hibernate.ejb.event.pre-load` = `com.acme.SecurityListener,com.acme.AuditListener` | Event listener list for a given event type. The list of event listeners is a comma separated fully qualified class name list. -|=========================================================================================================================== +`*hibernate.ejb.session_factory_observer*` (e.g. fully-qualified class name or class reference):: +Specifies a `SessionFactoryObserver` to be applied to the SessionFactory. The class must have a no-arg constructor. + +`*hibernate.ejb.event*` (e.g. `hibernate.ejb.event.pre-load` = `com.acme.SecurityListener,com.acme.AuditListener`):: +Event listener list for a given event type. The list of event listeners is a comma separated fully qualified class name list. [[configurations-jmx]] === JMX settings -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.jmx.enabled` | `true` or `false` (default value) | Enable JMX. -|`hibernate.jmx.usePlatformServer` | `true` or `false` (default value) | Uses the platform MBeanServer as returned by `ManagementFactory#getPlatformMBeanServer()`. -|`hibernate.jmx.agentId` | | The agent identifier of the associated `MBeanServer`. -|`hibernate.jmx.defaultDomain` | | The domain name of the associated `MBeanServer`. -|`hibernate.jmx.sessionFactoryName` | | The `SessionFactory` name appended to the object name the Manageable Bean is registered with. If null, the `hibernate.session_factory_name` configuration value is used. -|`org.hibernate.core` | | The default object domain appended to the object name the Manageable Bean is registered with. -|=================================================================================================================================================================================================================================== +`*hibernate.jmx.enabled*` (e.g. `true` or `false` (default value)):: +Enable JMX. + +`*hibernate.jmx.usePlatformServer*` (e.g. `true` or `false` (default value)):: +Uses the platform MBeanServer as returned by `ManagementFactory#getPlatformMBeanServer()`. + +`*hibernate.jmx.agentId*`:: +The agent identifier of the associated `MBeanServer`. + +`*hibernate.jmx.defaultDomain*`:: +The domain name of the associated `MBeanServer`. + +`*hibernate.jmx.sessionFactoryName*`:: +The `SessionFactory` name appended to the object name the Manageable Bean is registered with. If null, the `hibernate.session_factory_name` configuration value is used. + +`*org.hibernate.core*`:: +The default object domain appended to the object name the Manageable Bean is registered with. [[configurations-jacc]] === JACC settings -[width="100%",cols="20%,20%,60%",] -|=================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.jacc.enabled` | `true` or `false` (default value) | Is JACC enabled? -|`hibernate.jacc` | `hibernate.jacc.allowed.org.jboss.ejb3.test.jacc.AllEntity` | The property name defines the role (e.g. `allowed`) and the entity class name (e.g. `org.jboss.ejb3.test.jacc.AllEntity`), while the property value defines the authorized actions (e.g. `insert,update,read`). -|`hibernate.jacc_context_id` | | A String identifying the policy context whose PolicyConfiguration interface is to be returned. The value passed to this parameter must not be null. -|=================================================================================================================================================================================================================================== +`*hibernate.jacc.enabled*` (e.g. `true` or `false` (default value)):: +Is JACC enabled? -[[configurations-misc]] +`*hibernate.jacc*` (e.g. `hibernate.jacc.allowed.org.jboss.ejb3.test.jacc.AllEntity`):: +The property name defines the role (e.g. `allowed`) and the entity class name (e.g. `org.jboss.ejb3.test.jacc.AllEntity`), while the property value defines the authorized actions (e.g. `insert,update,read`). + +`*hibernate.jacc_context_id*`:: +A String identifying the policy context whose PolicyConfiguration interface is to be returned. The value passed to this parameter must not be null. + +[[configurations-classloader]] === ClassLoaders properties -[width="100%",cols="20%,20%,60%",] -|===================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.classLoaders` | |Used to define a `java.util.Collection` or the `ClassLoader` instance Hibernate should use for class-loading and resource-lookups. -|`hibernate.classLoader.application` | |Names the `ClassLoader` used to load user application classes. -|`hibernate.classLoader.resources` | |Names the `ClassLoader` Hibernate should use to perform resource loading. -|`hibernate.classLoader.hibernate` | |Names the `ClassLoader` responsible for loading Hibernate classes. By default this is the `ClassLoader` that loaded this class. -|`hibernate.classLoader.environment` | |Names the `ClassLoader` used when Hibernate is unable to locates classes on the `hibernate.classLoader.application` or `hibernate.classLoader.hibernate`. -|===================================================================================================================================================================================================================================================== +`*hibernate.classLoaders*`:: +Used to define a `java.util.Collection` or the `ClassLoader` instance Hibernate should use for class-loading and resource-lookups. + +`*hibernate.classLoader.application*`:: +Names the `ClassLoader` used to load user application classes. + +`*hibernate.classLoader.resources*`:: +Names the `ClassLoader` Hibernate should use to perform resource loading. + +`*hibernate.classLoader.hibernate*`:: +Names the `ClassLoader` responsible for loading Hibernate classes. By default, this is the `ClassLoader` that loaded this class. + +`*hibernate.classLoader.environment*`:: +Names the `ClassLoader` used when Hibernate is unable to locates classes on the `hibernate.classLoader.application` or `hibernate.classLoader.hibernate`. [[configurations-bootstrap]] === Bootstrap properties -[width="100%",cols="20%,20%,60%",] -|===================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.integrator_provider` | The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/IntegratorProvider.html[`IntegratorProvider`] | -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/integrator/spi/Integrator.html[`Integrator`] which are used during bootstrap process to integrate various services. -|`hibernate.strategy_registration_provider` | The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/StrategyRegistrationProviderList.html[`StrategyRegistrationProviderList`] | -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/selector/StrategyRegistrationProvider.html[`StrategyRegistrationProvider`] which are used during bootstrap process to provide registrations of strategy selector(s). -|`hibernate.type_contributors` | The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/TypeContributorList.html[`TypeContributorList`] | -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/TypeContributor.html[`TypeContributor`] which are used during bootstrap process to contribute types. -|`hibernate.persister.resolver` | The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/spi/PersisterClassResolver.html[`PersisterClassResolver`] or a `PersisterClassResolver` instance | +`*hibernate.integrator_provider*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/IntegratorProvider.html[`IntegratorProvider`]):: +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/integrator/spi/Integrator.html[`Integrator`] which is used during the bootstrap process to integrate various services. + +`*hibernate.strategy_registration_provider*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/StrategyRegistrationProviderList.html[`StrategyRegistrationProviderList`]):: +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/selector/StrategyRegistrationProvider.html[`StrategyRegistrationProvider`] which is used during the bootstrap process to provide registrations of strategy selector(s). + +`*hibernate.type_contributors*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/TypeContributorList.html[`TypeContributorList`]):: +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/TypeContributor.html[`TypeContributor`] which is used during the bootstrap process to contribute types. + +`*hibernate.persister.resolver*` (e.g. The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/spi/PersisterClassResolver.html[`PersisterClassResolver`] or a `PersisterClassResolver` instance):: Used to define an implementation of the `PersisterClassResolver` interface which can be used to customize how an entity or a collection is being persisted. -|`hibernate.persister.factory` | The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/spi/PersisterFactory.html[`PersisterFactory`] or a `PersisterFactory` instance | -Like a `PersisterClassResolver`, the `PersisterFactory` can be used to customize how an entity or a collection are being persisted. -|`hibernate.service.allow_crawling` | `true` (default value) or `false` | -Crawl all available service bindings for an alternate registration of a given Hibernate `Service`. +`*hibernate.persister.factory*` (e.g. The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/spi/PersisterFactory.html[`PersisterFactory`] or a `PersisterFactory` instance):: +Like a `PersisterClassResolver`, the `PersisterFactory` can be used to customize how an entity or a collection are being persisted. +`*hibernate.service.allow_crawling*` (e.g. `true` (default value) or `false`):: +Crawl all available service bindings for an alternate registration of a given Hibernate `Service`. -|===================================================================================================================================================================================================================================================== +`*hibernate.metadata_builder_contributor*` (e.g. The instance, the class or the fully qualified class name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`]):: +Used to define an instance, the class or the fully qualified class name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`] which can be used to configure the `MetadataBuilder` when bootstrapping via the JPA `EntityManagerFactory`. [[configurations-misc]] === Miscellaneous properties -[width="100%",cols="20%,20%,60%",] -|===================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.dialect_resolvers` | | Names any additional https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/dialect/spi/DialectResolver.html[`DialectResolver`] implementations to register with the standard https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/dialect/spi/DialectFactory.html[`DialectFactory`] -|`hibernate.session_factory_name` |A JNDI name | +`*hibernate.dialect_resolvers*`:: +Names any additional https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/dialect/spi/DialectResolver.html[`DialectResolver`] implementations to register with the standard https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/dialect/spi/DialectFactory.html[`DialectFactory`] +`*hibernate.session_factory_name*` (e.g. A JNDI name):: Setting used to name the Hibernate `SessionFactory`. Naming the `SessionFactory` allows for it to be properly serialized across JVMs as long as the same name is used on each JVM. - ++ If `hibernate.session_factory_name_is_jndi` is set to `true`, this is also the name under which the `SessionFactory` is bound into JNDI on startup and from which it can be obtained from JNDI. -|`hibernate.session_factory_name_is_jndi` |`true` (default value) or `false` | - +`*hibernate.session_factory_name_is_jndi*` (e.g. `true` (default value) or `false`):: Does the value defined by `hibernate.session_factory_name` represent a JNDI namespace into which the `org.hibernate.SessionFactory` should be bound and made accessible? ++ +Defaults to `true` for backward compatibility. Set this to `false` if naming a SessionFactory is needed for serialization purposes, but no writable JNDI context exists in the runtime environment or if the user simply does not want JNDI to be used. -Defaults to `true` for backwards compatibility. Set this to `false` if naming a SessionFactory is needed for serialization purposes, but no writable JNDI context exists in the runtime environment or if the user simply does not want JNDI to be used. +`*hibernate.ejb.entitymanager_factory_name*` (e.g. By default, the persistence unit name is used, otherwise a randomly generated UUID):: +Internally, Hibernate keeps track of all `EntityManagerFactory` instances using the `EntityManagerFactoryRegistry`. The name is used as a key to identify a given `EntityManagerFactory` reference. -|`hibernate.ejb.entitymanager_factory_name`| By default, the persistence unit name is used, otherwise a randomly generated UUID | Internally, Hibernate keeps track of all `EntityManagerFactory` instances using the `EntityManagerFactoryRegistry`. The name is used as a key to identify a given `EntityManagerFactory` reference. -|`hibernate.ejb.cfgfile`| `hibernate.cfg.xml` (default value) | XML configuration file to use to configure Hibernate. -|`hibernate.ejb.discard_pc_on_close`| `true` or `false` (default value) | +`*hibernate.ejb.cfgfile*` (e.g. `hibernate.cfg.xml` (default value)):: +XML configuration file to use to configure Hibernate. +`*hibernate.ejb.discard_pc_on_close*` (e.g. `true` or `false` (default value)):: If true, the persistence context will be discarded (think `clear()` when the method is called. Otherwise, the persistence context will stay alive till the transaction completion: all objects will remain managed, and any change will be synchronized with the database (default to false, ie wait for transaction completion). -|`hibernate.ejb.metamodel.population`| `enabled` or `disabled`, or `ignoreUnsupported` (default value) a| - +`*hibernate.ejb.metamodel.population*` (e.g. `enabled` or `disabled`, or `ignoreUnsupported` (default value)):: Setting that indicates whether to build the JPA types. - ++ Accepts three values: ++ +enabled::: Do the build +disabled::: Do not do the build +ignoreUnsupported::: Do the build, but ignore any non-JPA features that would otherwise result in a failure (e.g. `@Any` annotation). -enabled:: Do the build -disabled:: Do not do the build -ignoreUnsupported:: Do the build, but ignore any non-JPA features that would otherwise result in a failure (e.g. `@Any` annotation). - -|`hibernate.jpa.static_metamodel.population` | `enabled` or `disabled`, or `skipUnsupported` (default value) a| - +`*hibernate.jpa.static_metamodel.population*` (e.g. `enabled` or `disabled`, or `skipUnsupported` (default value)):: Setting that controls whether we seek out JPA _static metamodel_ classes and populate them. - ++ Accepts three values: - -enabled:: Do the population -disabled:: Do not do the population -skipUnsupported:: Do the population, but ignore any non-JPA features that would otherwise result in the population failing (e.g. `@Any` annotation). - - -|`hibernate.delay_cdi_access`| `true` or `false` (default value) | Defines delayed access to CDI `BeanManager`. Starting in 5.1 the preferred means for CDI bootstrapping is through https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/event/spi/jpa/ExtendedBeanManager.html[`ExtendedBeanManager`]. - -|`hibernate.allow_update_outside_transaction` | `true` or `false` (default value) a| - ++ +enabled::: Do the population +disabled::: Do not do the population +skipUnsupported::: Do the population, but ignore any non-JPA features that would otherwise result in the population failing (e.g. `@Any` annotation). + +`*hibernate.delay_cdi_access*` (e.g. `true` or `false` (default value)):: +Defines delayed access to CDI `BeanManager`. Starting in 5.1 the preferred means for CDI bootstrapping is through https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/event/spi/jpa/ExtendedBeanManager.html[`ExtendedBeanManager`]. + +`*hibernate.resource.beans.container*` (e.g. fully-qualified class name):: +Identifies an explicit `org.hibernate.resource.beans.container.spi.BeanContainer` to be used. ++ +Note that, for CDI-based containers, setting this is not necessary. +Simply pass the `BeanManager` to use via `javax.persistence.bean.manager` and optionally specify `hibernate.delay_cdi_access`. ++ +This setting is more meant to integrate non-CDI bean containers such as Spring. + +`*hibernate.allow_update_outside_transaction*` (e.g. `true` or `false` (default value)):: Setting that allows to perform update operations outside of a transaction boundary. - ++ Accepts two values: ++ +true::: allows to flush an update out of a transaction +false::: does not allow -true:: allows to flush an update out of a transaction -false:: does not allow +`*hibernate.collection_join_subquery*` (e.g. `true` (default value) or `false`):: +Setting which indicates whether or not the new JOINS over collection tables should be rewritten to subqueries. -|`hibernate.collection_join_subquery`| `true` (default value) or `false` | Setting which indicates whether or not the new JOINS over collection tables should be rewritten to subqueries. +`*hibernate.allow_refresh_detached_entity*` (e.g. `true` (default value when using Hibernate native bootstrapping) or `false` (default value when using JPA bootstrapping)):: +Setting that allows to call `javax.persistence.EntityManager#refresh(entity)` or `Session#refresh(entity)` on a detached instance even when the `org.hibernate.Session` is obtained from a JPA `javax.persistence.EntityManager`. -|`hibernate.allow_refresh_detached_entity`| `true` (default value when using Hibernate native bootstrapping) or `false` (default value when using JPA bootstrapping) | Setting that allows to call `javax.persistence.EntityManager#refresh(entity)` or `Session#refresh(entity)` on a detached instance even when the `org.hibernate.Session` is obtained from a JPA `javax.persistence.EntityManager`. -|`hibernate.event.merge.entity_copy_observer`| `disallow` (default value), `allow`, `log` (testing purpose only) or fully-qualified class name a| +`*hibernate.use_entity_where_clause_for_collections*` (e.g., `true` (default) or `false`):: +Setting controls whether an entity's "where" clause, mapped using `@Where(clause="...")` or `, is taken into account when loading one-to-many or many-to-many collections of that type of entity. +`*hibernate.event.merge.entity_copy_observer*` (e.g. `disallow` (default value), `allow`, `log` (testing purpose only) or fully-qualified class name):: Setting that specifies how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging. - ++ The possible values are: - -disallow (the default):: throws `IllegalStateException` if an entity copy is detected -allow:: performs the merge operation on each entity copy that is detected -log:: (provided for testing only) performs the merge operation on each entity copy that is detected and logs information about the entity copies. ++ +disallow (the default)::: throws `IllegalStateException` if an entity copy is detected +allow::: performs the merge operation on each entity copy that is detected +log::: (provided for testing only) performs the merge operation on each entity copy that is detected and logs information about the entity copies. This setting requires DEBUG logging be enabled for https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/event/internal/EntityCopyAllowedLoggedObserver.html[`EntityCopyAllowedLoggedObserver`]. - ++ In addition, the application may customize the behavior by providing an implementation of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/event/spi/EntityCopyObserver.html[`EntityCopyObserver`] and setting `hibernate.event.merge.entity_copy_observer` to the class name. When this property is set to `allow` or `log`, Hibernate will merge each entity copy detected while cascading the merge operation. In the process of merging each entity copy, Hibernate will cascade the merge operation from each entity copy to its associations with `cascade=CascadeType.MERGE` or `CascadeType.ALL`. The entity state resulting from merging an entity copy will be overwritten when another entity copy is merged. - ++ For more details, check out the <> section. -|===================================================================================================================================================================================================================================================== - [[configurations-envers]] === Envers properties -[width="100%",cols="20%,20%,60%",] -|===================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.envers.autoRegisterListeners` | `true` (default value) or `false` |When set to `false`, the Envers entity listeners are no longer auto-registered, so you need to register them manually during the bootstrap process. -|`hibernate.integration.envers.enabled` | `true` (default value) or `false` |Enable or disable the Hibernate Envers `Service` integration. -|`hibernate.listeners.envers.autoRegister` | |Legacy setting. Use `hibernate.envers.autoRegisterListeners` or `hibernate.integration.envers.enabled` instead. -|===================================================================================================================================================================================================================================================== +`*hibernate.envers.autoRegisterListeners*` (e.g. `true` (default value) or `false`):: +When set to `false`, the Envers entity listeners are no longer auto-registered, so you need to register them manually during the bootstrap process. + +`*hibernate.integration.envers.enabled*` (e.g. `true` (default value) or `false`):: +Enable or disable the Hibernate Envers `Service` integration. + +`*hibernate.listeners.envers.autoRegister*`:: +Legacy setting. Use `hibernate.envers.autoRegisterListeners` or `hibernate.integration.envers.enabled` instead. [[configurations-spatial]] === Spatial properties -[width="100%",cols="20%,20%,60%",] -|===================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.integration.spatial.enabled` | `true` (default value) or `false` | Enable or disable the Hibernate Spatial `Service` integration. -|`hibernate.spatial.connection_finder` | `org.geolatte.geom.codec.db.oracle.DefaultConnectionFinder` | Define the fully-qualified name of class implementing the `org.geolatte.geom.codec.db.oracle.ConnectionFinder` interface. -|===================================================================================================================================================================================================================================================== +`*hibernate.integration.spatial.enabled*` (e.g. `true` (default value) or `false`):: +Enable or disable the Hibernate Spatial `Service` integration. + +`*hibernate.spatial.connection_finder*` (e.g. `org.geolatte.geom.codec.db.oracle.DefaultConnectionFinder`):: +Define the fully-qualified name of class implementing the `org.geolatte.geom.codec.db.oracle.ConnectionFinder` interface. [[configurations-internal]] === Internal properties The following configuration properties are used internally, and you shouldn't probably have to configured them in your application. -[width="100%",cols="20%,20%,60%",] -|===================================================================================================================================================================================================================================================== -|Property |Example |Purpose -|`hibernate.enable_specj_proprietary_syntax` | `true` or `false` (default value) | Enable or disable the SpecJ proprietary mapping syntax which differs from JPA specification. Used during performance testing only. -|`hibernate.temp.use_jdbc_metadata_defaults` | `true` (default value) or `false` | +`*hibernate.enable_specj_proprietary_syntax*` (e.g. `true` or `false` (default value)):: +Enable or disable the SpecJ proprietary mapping syntax which differs from JPA specification. Used during performance testing only. + +`*hibernate.temp.use_jdbc_metadata_defaults*` (e.g. `true` (default value) or `false`):: This setting is used to control whether we should consult the JDBC metadata to determine certain Settings default values when the database may not be available (mainly in tools usage). -|`hibernate.connection_provider.injection_data` | `java.util.Map` | Connection provider settings to be injected in the currently configured connection provider. -|`hibernate.jandex_index` | `org.jboss.jandex.Index` | Names a Jandex `org.jboss.jandex.Index` instance to use. -|===================================================================================================================================================================================================================================================== + +`*hibernate.connection_provider.injection_data*` (e.g. `java.util.Map`):: +Connection provider settings to be injected in the currently configured connection provider. + +`*hibernate.jandex_index*` (e.g. `org.jboss.jandex.Index`):: +Names a Jandex `org.jboss.jandex.Index` instance to use. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc index 58a5edcfc308..350d36062c63 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc @@ -41,7 +41,7 @@ There are other ways to specify Configuration information, including: * Place a file named hibernate.properties in a root directory of the classpath * Pass an instance of java.util.Properties to `Configuration#setProperties` * Via a `hibernate.cfg.xml` file -* System properties using java `-Dproperty=value` +* System properties using Java `-Dproperty=value` == Migration diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc index 76b13178e6f5..95e12ea910e6 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc @@ -26,7 +26,7 @@ List cats = crit.list(); ---- [[criteria-entity-name]] -=== JPA vs Hibernate entity name +=== JPA vs. Hibernate entity name When using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/SharedSessionContract.html#createCriteria-java.lang.String-[`Session#createCriteria(String entityName)` or `StatelessSession#createCriteria(String entityName)`], the *entityName* means the fully-qualified name of the underlying entity and not the name denoted by the `name` attribute of the JPA `@Entity` annotation. @@ -228,7 +228,7 @@ List cats = session.createCriteria( Cat.class ) This will return all of the `Cat`s with a mate whose name starts with "good" ordered by their mate's age, and all cats who do not have a mate. This is useful when there is a need to order or limit in the database prior to returning complex/large result sets, -and removes many instances where multiple queries would have to be performed and the results unioned by java in memory. +and removes many instances where multiple queries would have to be performed and the results unioned by Java in memory. Without this feature, first all of the cats without a mate would need to be loaded in one query. @@ -275,7 +275,7 @@ When using criteria against collections, there are two distinct cases. One is if the collection contains entities (eg. `` or ``) or components (`` ), and the second is if the collection contains scalar values (``). In the first case, the syntax is as given above in the section <> where we restrict the `kittens` collection. -Essentially we create a `Criteria` object against the collection property and restrict the entity or component properties using that instance. +Essentially, we create a `Criteria` object against the collection property and restrict the entity or component properties using that instance. For querying a collection of basic values, we still create the `Criteria` object against the collection, but to reference the value, we use the special property "elements". diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc index 300b44a5c33c..43f92dc654d5 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc @@ -37,7 +37,7 @@ include::{sourcedir}/timestamp_version.xml[] |column |The name of the column which holds the timestamp. Optional, defaults to the property name |name |The name of a JavaBeans style property of Java type `Date` or `Timestamp` of the persistent class. |access |The strategy Hibernate uses to access the property value. Optional, defaults to `property`. -|unsaved-value |A version property which indicates than instance is newly instantiated, and unsaved. +|unsaved-value |A version property which indicates that the instance is newly instantiated and unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value of `undefined` indicates that Hibernate uses the identifier property value. |source |Whether Hibernate retrieves the timestamp from the database or the current JVM. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc index 6d6c3c885e7c..955911a3ae6f 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc @@ -102,7 +102,7 @@ You can externalize the resultset mapping information in a `` element ---- ==== -You can, alternatively, use the resultset mapping information in your hbm files directly in java code. +You can, alternatively, use the resultset mapping information in your hbm files directly in Java code. .Programmatically specifying the result mapping information ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc b/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc index dbe41e9a0fa6..58123d50b8e6 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc @@ -16,7 +16,7 @@ As a JPA provider, Hibernate implements the Java Persistence API specifications image:images/architecture/JPA_Hibernate.svg[image] SessionFactory (`org.hibernate.SessionFactory`):: A thread-safe (and immutable) representation of the mapping of the application domain model to a database. -Acts as a factory for `org.hibernate.Session` instances. The `EntityManagerFactory` is the JPA equivalent of a `SessionFactory` and basically those two converge into the same `SessionFactory` implementation. +Acts as a factory for `org.hibernate.Session` instances. The `EntityManagerFactory` is the JPA equivalent of a `SessionFactory` and basically, those two converge into the same `SessionFactory` implementation. + A `SessionFactory` is very expensive to create, so, for any given database, the application should have only one associated `SessionFactory`. The `SessionFactory` maintains services that Hibernate uses across all `Session(s)` such as second level caches, connection pools, transaction system integrations, etc. diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc index 7d24b40aa49d..13404f0d6972 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc @@ -67,7 +67,7 @@ include::{sourcedir}/BatchTest.java[tags=batch-session-batch-example] There are several problems associated with this example: . Hibernate caches all the newly inserted `Customer` instances in the session-level c1ache, so, when the transaction ends, 100 000 entities are managed by the persistence context. - If the maximum memory allocated to the JVM is rather low, this example could fails with an `OutOfMemoryException`. + If the maximum memory allocated to the JVM is rather low, this example could fail with an `OutOfMemoryException`. The Java 1.8 JVM allocated either 1/4 of available RAM or 1Gb, which can easily accommodate 100 000 objects on the heap. . long-running transactions can deplete a connection pool so other transactions don't get a chance to proceed. . JDBC batching is not enabled by default, so every insert statement requires a database roundtrip. @@ -118,7 +118,7 @@ However, it is good practice to close the `ScrollableResults` explicitly. `StatelessSession` is a command-oriented API provided by Hibernate. Use it to stream data to and from the database in the form of detached objects. -A `StatelessSession` has no persistence context associated with it and does not provide many of the higher-level life cycle semantics. +A `StatelessSession` has no persistence context associated with it and does not provide many of the higher-level lifecycle semantics. Some of the things not provided by a `StatelessSession` include: @@ -243,9 +243,9 @@ include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-delete-example] ---- ==== -Method `Query.executeUpdate()` returns an `int` value, which indicates the number of entities effected by the operation. -This may or may not correlate to the number of rows effected in the database. -An JPQL/HQL bulk operation might result in multiple SQL statements being executed, such as for joined-subclass. +Method `Query.executeUpdate()` returns an `int` value, which indicates the number of entities affected by the operation. +This may or may not correlate to the number of rows affected in the database. +A JPQL/HQL bulk operation might result in multiple SQL statements being executed, such as for joined-subclass. In the example of joined-subclass, a `DELETE` against one of the subclasses may actually result in deletes in the tables underlying the join, or further down the inheritance hierarchy. ==== HQL syntax for INSERT @@ -280,10 +280,9 @@ Hibernate generates a value automatically. Automatic generation is only available if you use ID generators which operate on the database. Otherwise, Hibernate throws an exception during parsing. Available in-database generators are `org.hibernate.id.SequenceGenerator` and its subclasses, and objects which implement `org.hibernate.id.PostInsertIdentifierGenerator`. -The most notable exception is `org.hibernate.id.TableHiLoGenerator`, which does not expose a selectable way to get its values. For properties mapped as either version or timestamp, the insert statement gives you two options. -You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions, or omit it from the properties_list, +You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions or omit it from the properties_list, in which case the seed value defined by the org.hibernate.type.VersionType is used. [[batch-bulk-hql-insert-example]] @@ -317,7 +316,7 @@ The `Person` entity is the base class of this entity inheritance model, and is m ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractBulkIdTest.java[tags=batch-bulk-hql-temp-table-base-class-example] +include::{bulkid-sourcedir}/AbstractBulkCompositeIdTest.java[tags=batch-bulk-hql-temp-table-base-class-example] ---- ==== @@ -328,7 +327,7 @@ Both the `Doctor` and `Engineer` entity classes extend the `Person` base class: ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractBulkIdTest.java[tags=batch-bulk-hql-temp-table-sub-classes-example] +include::{bulkid-sourcedir}/AbstractBulkCompositeIdTest.java[tags=batch-bulk-hql-temp-table-sub-classes-example] ---- ==== @@ -342,7 +341,7 @@ Now, when you try to execute a bulk entity delete query: ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractBulkIdTest.java[tags=batch-bulk-hql-temp-table-delete-query-example] +include::{bulkid-sourcedir}/AbstractBulkCompositeIdTest.java[tags=batch-bulk-hql-temp-table-delete-query-example] ---- [source, SQL, indent=0] diff --git a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc index 79d5b15a63b0..ce7f360f4f45 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc @@ -1,8 +1,11 @@ [[bootstrap]] == Bootstrap :sourcedir: ../../../../../test/java/org/hibernate/userguide/bootstrap +:boot-spi-sourcedir: ../../../../../../../hibernate-core/src/test/java/org/hibernate/boot/spi :extrasdir: extras +org.hibernate.boot.spi.metadatabuildercontributor; + The term bootstrapping refers to initializing and starting a software component. In Hibernate, we are specifically talking about the process of building a fully functional `SessionFactory` instance or `EntityManagerFactory` instance, for JPA. The process is very different for each. @@ -19,110 +22,11 @@ Instead, we focus here on the API calls needed to perform the bootstrapping. During the bootstrap process, you might want to customize Hibernate behavior so make sure you check the <> section as well. ==== -[[bootstrap-jpa]] -=== JPA Bootstrapping - -Bootstrapping Hibernate as a JPA provider can be done in a JPA-spec compliant manner or using a proprietary bootstrapping approach. -The standardized approach has some limitations in certain environments, but aside from those, it is *highly* recommended that you use JPA-standardized bootstrapping. - -[[bootstrap-jpa-compliant]] -==== JPA-compliant bootstrapping - -In JPA, we are ultimately interested in bootstrapping a `javax.persistence.EntityManagerFactory` instance. -The JPA specification defines two primary standardized bootstrap approaches depending on how the application intends to access the `javax.persistence.EntityManager` instances from an `EntityManagerFactory`. - -It uses the terms _EE_ and _SE_ for these two approaches, but those terms are very misleading in this context. -What the JPA spec calls EE bootstrapping implies the existence of a container (EE, OSGi, etc), who'll manage and inject the persistence context on behalf of the application. -What it calls SE bootstrapping is everything else. We will use the terms container-bootstrapping and application-bootstrapping in this guide. - -[NOTE] -==== -If you would like additional details on accessing and using `EntityManager` instances, sections 7.6 and 7.7 of the JPA 2.1 specification cover container-managed and application-managed `EntityManagers`, respectively. -==== - -For compliant container-bootstrapping, the container will build an `EntityManagerFactory` for each persistent-unit defined in the `META-INF/persistence.xml` configuration file -and make that available to the application for injection via the `javax.persistence.PersistenceUnit` annotation or via JNDI lookup. - -[[bootstrap-jpa-compliant-PersistenceUnit-example]] -.Injecting a EntityManagerFactory -==== -[source, JAVA, indent=0] ----- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceUnit-example] ----- -==== - -The `META-INF/persistence.xml` file looks as follows: - -[[bootstrap-jpa-compliant-persistence-xml-example]] -.META-INF/persistence.xml configuration file -==== -[source, JAVA, indent=0] ----- -include::{extrasdir}/persistence.xml[] ----- -==== - -For compliant application-bootstrapping, rather than the container building the `EntityManagerFactory` for the application, the application builds the `EntityManagerFactory` itself using the `javax.persistence.Persistence` bootstrap class. -The application creates an `EntityManagerFactory` by calling the `createEntityManagerFactory` method: - -[[bootstrap-jpa-compliant-EntityManagerFactory-example]] -.Application bootstrapped EntityManagerFactory -==== -[source, JAVA, indent=0] ----- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-EntityManagerFactory-example] ----- -==== - -[NOTE] -==== -If you don't want to provide a `persistence.xml` configuration file, JPA allows you to provide all the configuration options in a -http://docs.oracle.com/javaee/7/api/javax/persistence/spi/PersistenceUnitInfo.html[PersistenceUnitInfo] implementation and call -https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/HibernatePersistenceProvider.html#createContainerEntityManagerFactory-javax.persistence.spi.PersistenceUnitInfo-java.util.Map-[HibernatePersistenceProvider.html#createContainerEntityManagerFactory]. -==== - -[[bootstrap-jpa-xml-files]] -==== Externalizing XML mapping files - -JPA offers two mapping options: - -- annotations -- XML mappings - -Although annotations are much more common, there are projects were XML mappings are preferred. -You can even mix annotations and XML mappings so that you can override annotation mappings with XML configurations that can be easily changed without recompiling the project source code. -This is possible because if there are two conflicting mappings, the XML mappings takes precedence over its annotation counterpart. - -The JPA specifications requires the XML mappings to be located on the class path: - -[quote, Section 8.2.1.6.2 of the JPA 2.1 Specification] -____ -An object/relational mapping XML file named `orm.xml` may be specified in the `META-INF` directory in the root of the persistence unit or in the `META-INF` directory of any jar file referenced by the `persistence.xml`. - -Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the class path. -____ - -Therefore, the mapping files can reside in the application jar artifacts, or they can be stored in an external folder location with the cogitation that that location be included in the class path. - -Hibernate is more lenient in this regard so you can use any external location even outside of the application configured class path. - -[[bootstrap-jpa-compliant-persistence-xml-external-mappings-example]] -.META-INF/persistence.xml configuration file for external XML mappings -==== -[source, JAVA, indent=0] ----- -include::{extrasdir}/persistence-external.xml[] ----- -==== - -In the `persistence.xml` configuration file above, the `orm.xml` XML file containing all JPA entity mappings is located in the `/etc/opt/app/mappings/` folder. - [[bootstrap-native]] === Native Bootstrapping This section discusses the process of bootstrapping a Hibernate `SessionFactory`. -Specifically it discusses the bootstrapping APIs as redesigned in 5.0. +Specifically, it addresses the bootstrapping APIs as redesigned in 5.0. For a discussion of the legacy bootstrapping API, see <> [[bootstrap-native-registry]] @@ -206,7 +110,7 @@ include::{sourcedir}/BootstrapTest.java[tags=bootstrap-event-listener-registrati [[bootstrap-native-metadata]] ==== Building the Metadata -The second step in native bootstrapping is the building of a `org.hibernate.boot.Metadata` object containing the parsed representations of an application domain model and its mapping to a database. +The second step in native bootstrapping is the building of an `org.hibernate.boot.Metadata` object containing the parsed representations of an application domain model and its mapping to a database. The first thing we obviously need to build a parsed representation is the source information to be parsed (annotated classes, `hbm.xml` files, `orm.xml` files). This is the purpose of `org.hibernate.boot.MetadataSources`: @@ -229,7 +133,7 @@ If you are ok with the default behavior in building the Metadata then you can si ==== Notice that a `ServiceRegistry` can be passed at a number of points in this bootstrapping process. The suggested approach is to build a `StandardServiceRegistry` yourself and pass that along to the `MetadataSources` constructor. -From there, `MetadataBuilder`, `Metadata`, `SessionFactoryBuilder` and `SessionFactory` will all pick up that same `StandardServiceRegistry`. +From there, `MetadataBuilder`, `Metadata`, `SessionFactoryBuilder`, and `SessionFactory` will all pick up that same `StandardServiceRegistry`. ==== However, if you wish to adjust the process of building `Metadata` from `MetadataSources`, @@ -252,7 +156,7 @@ include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-metadata-builder-e The final step in native bootstrapping is to build the `SessionFactory` itself. Much like discussed above, if you are ok with the default behavior of building a `SessionFactory` from a `Metadata` reference, you can simply call the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#buildSessionFactory--[`buildSessionFactory`] method on the `Metadata` object. -However, if you would like to adjust that building process you will need to use `SessionFactoryBuilder` as obtained via [`Metadata#getSessionFactoryBuilder`. Again, see its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#getSessionFactoryBuilder--[Javadocs] for more details. +However, if you would like to adjust that building process, you will need to use `SessionFactoryBuilder` as obtained via [`Metadata#getSessionFactoryBuilder`. Again, see its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#getSessionFactoryBuilder--[Javadocs] for more details. [[bootstrap-native-SessionFactory-example]] .Native Bootstrapping - Putting it all together @@ -276,4 +180,167 @@ The bootstrapping API is quite flexible, but in most cases it makes the most sen ---- include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-SessionFactoryBuilder-example] ---- -==== \ No newline at end of file +==== + +[[bootstrap-jpa]] +=== JPA Bootstrapping + +Bootstrapping Hibernate as a JPA provider can be done in a JPA-spec compliant manner or using a proprietary bootstrapping approach. +The standardized approach has some limitations in certain environments, but aside from those, it is *highly* recommended that you use JPA-standardized bootstrapping. + +[[bootstrap-jpa-compliant]] +==== JPA-compliant bootstrapping + +In JPA, we are ultimately interested in bootstrapping a `javax.persistence.EntityManagerFactory` instance. +The JPA specification defines two primary standardized bootstrap approaches depending on how the application intends to access the `javax.persistence.EntityManager` instances from an `EntityManagerFactory`. + +It uses the terms _EE_ and _SE_ for these two approaches, but those terms are very misleading in this context. +What the JPA spec calls EE bootstrapping implies the existence of a container (EE, OSGi, etc), who'll manage and inject the persistence context on behalf of the application. +What it calls SE bootstrapping is everything else. We will use the terms container-bootstrapping and application-bootstrapping in this guide. + +For compliant container-bootstrapping, the container will build an `EntityManagerFactory` for each persistent-unit defined in the `META-INF/persistence.xml` configuration file +and make that available to the application for injection via the `javax.persistence.PersistenceUnit` annotation or via JNDI lookup. + +[[bootstrap-jpa-compliant-PersistenceUnit-example]] +.Injecting the default `EntityManagerFactory` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceUnit-example] +---- +==== + +Or, in case you have multiple Persistence Units (e.g. multiple `persistence.xml` configuration files), +you can inject a specific `EntityManagerFactory` by Unit name: + +[[bootstrap-jpa-compliant-PersistenceUnit-configurable-example]] +.Injecting a specific `EntityManagerFactory` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceUnit-configurable-example] +---- +==== + +The `META-INF/persistence.xml` file looks as follows: + +[[bootstrap-jpa-compliant-persistence-xml-example]] +.META-INF/persistence.xml configuration file +==== +[source, JAVA, indent=0] +---- +include::{extrasdir}/persistence.xml[] +---- +==== + +For compliant application-bootstrapping, rather than the container building the `EntityManagerFactory` for the application, the application builds the `EntityManagerFactory` itself using the `javax.persistence.Persistence` bootstrap class. +The application creates an `EntityManagerFactory` by calling the `createEntityManagerFactory` method: + +[[bootstrap-jpa-compliant-EntityManagerFactory-example]] +.Application bootstrapped `EntityManagerFactory` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-EntityManagerFactory-example] +---- +==== + +[NOTE] +==== +If you don't want to provide a `persistence.xml` configuration file, JPA allows you to provide all the configuration options in a +http://docs.oracle.com/javaee/7/api/javax/persistence/spi/PersistenceUnitInfo.html[`PersistenceUnitInfo`] implementation and call +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/HibernatePersistenceProvider.html#createContainerEntityManagerFactory-javax.persistence.spi.PersistenceUnitInfo-java.util.Map-[`HibernatePersistenceProvider.html#createContainerEntityManagerFactory`]. +==== + +To inject the default Persistence Context, you can use the http://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceContext.html[`@PersistenceContext`] annotation. + +[[bootstrap-jpa-compliant-PersistenceContext-example]] +.Inject the default `EntityManager` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceContext-example, indent=0] +---- +==== + +To inject a specific Persistence Context, +you can use the http://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceContext.html[`@PersistenceContext`] annotation, +and you can even pass `EntityManager`-specific properties using the +http://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceProperty.html[`@PersistenceProperty`] annotation. + + +[[bootstrap-jpa-compliant-PersistenceContext-configurable-example]] +.Inject a configurable `EntityManager` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceContext-configurable-example, indent=0] +---- +==== + +[NOTE] +==== +If you would like additional details on accessing and using `EntityManager` instances, sections 7.6 and 7.7 of the JPA 2.1 specification cover container-managed and application-managed `EntityManagers`, respectively. +==== + +[[bootstrap-jpa-xml-files]] +==== Externalizing XML mapping files + +JPA offers two mapping options: + +- annotations +- XML mappings + +Although annotations are much more common, there are projects where XML mappings are preferred. +You can even mix annotations and XML mappings so that you can override annotation mappings with XML configurations that can be easily changed without recompiling the project source code. +This is possible because if there are two conflicting mappings, the XML mappings take precedence over its annotation counterpart. + +The JPA specification requires the XML mappings to be located on the classpath: + +[quote, Section 8.2.1.6.2 of the JPA 2.1 Specification] +____ +An object/relational mapping XML file named `orm.xml` may be specified in the `META-INF` directory in the root of the persistence unit or in the `META-INF` directory of any jar file referenced by the `persistence.xml`. + +Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the classpath. +____ + +Therefore, the mapping files can reside in the application jar artifacts, or they can be stored in an external folder location with the cogitation that that location be included in the classpath. + +Hibernate is more lenient in this regard so you can use any external location even outside of the application configured classpath. + +[[bootstrap-jpa-compliant-persistence-xml-external-mappings-example]] +.META-INF/persistence.xml configuration file for external XML mappings +==== +[source, JAVA, indent=0] +---- +include::{extrasdir}/persistence-external.xml[] +---- +==== + +In the `persistence.xml` configuration file above, the `orm.xml` XML file containing all JPA entity mappings is located in the `/etc/opt/app/mappings/` folder. + +[[bootstrap-jpa-metadata]] +==== Configuring the `SessionFactory` `Metadata` via the JPA bootstrap + +As previously seen, the Hibernate native bootstrap mechanism allows you to customize a great variety of configurations which are passed via the `Metadata` object. + +When using Hibernate as a JPA provider, the `EntityManagerFactory` is backed by a `SessionFactory`. For this reason, you might still want to use the `Metadata` object to pass various settings which cannot be supplied via the standard Hibernate <>. + +For this reason, you can use the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`] class as you can see in the following examples. + +[[bootstrap-jpa-compliant-MetadataBuilderContributor-example]] +.Implementing a `MetadataBuilderContributor` +==== +[source, JAVA, indent=0] +---- +include::{boot-spi-sourcedir}/metadatabuildercontributor/SqlFunctionMetadataBuilderContributor.java[tags=bootstrap-jpa-compliant-MetadataBuilderContributor-example] +---- +==== + +The above `MetadataBuilderContributor` is used to register a `SqlFuction` which is not defined by the currently running Hibernate `Dialect`, but which we need to reference in our JPQL queries. + +By having access to the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/MetadataBuilder.html[`MetadataBuilder`] class that's used by the underlying `SessionFactory`, the JPA bootstrap becomes just as flexible as the Hibernate native bootstrap mechanism. + +You can then pass the custom `MetadataBuilderContributor` via the `hibernate.metadata_builder_contributor` configuration property as explained in the <> diff --git a/documentation/src/main/asciidoc/userguide/chapters/bytecode/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/userguide/chapters/bytecode/BytecodeEnhancement.adoc new file mode 100644 index 000000000000..f79afe18da84 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/bytecode/BytecodeEnhancement.adoc @@ -0,0 +1,23 @@ +[[enhancement]] +== Enhancement +:sourcedir: ../../../../../test/java/org/hibernate/userguide/bytecode +:extrasdir: extras + +Hibernate offers a number of services that can be added into an application's domain model classes +through bytecode enhancement... + + +[[enhancement-laziness]] +=== Laziness + + +[[enhancement-bidir]] +=== Bi-directionality + + +[[enhancement-dirty]] +=== In-line dirty checking + + +[[enhancement-extended]] +=== Extended enhancement \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index 458d0f8a2c8a..b66d8fafc62b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -35,9 +35,9 @@ Detailed information is provided later in this chapter. Besides specific provider configuration, there are a number of configurations options on the Hibernate side of the integration that control various caching behaviors: `hibernate.cache.use_second_level_cache`:: - Enable or disable second level caching overall. Default is true, although the default region factory is `NoCachingRegionFactory`. + Enable or disable second level caching overall. The default is true, although the default region factory is `NoCachingRegionFactory`. `hibernate.cache.use_query_cache`:: - Enable or disable second level caching of query results. Default is false. + Enable or disable second level caching of query results. The default is false. `hibernate.cache.query_cache_factory`:: Query result caching is handled by a special contract that deals with staleness-based invalidation of the results. The default implementation does not allow stale results at all. Use this for applications that would like to relax that. @@ -48,7 +48,7 @@ Besides specific provider configuration, there are a number of configurations op Defines a name to be used as a prefix to all second-level cache region names. `hibernate.cache.default_cache_concurrency_strategy`:: In Hibernate second-level caching, all regions can be configured differently including the concurrency strategy to use when accessing that particular region. - This setting allows to define a default strategy to be used. + This setting allows defining a default strategy to be used. This setting is very rarely required as the pluggable providers do specify the default strategy to use. Valid values include: * read-only, @@ -61,12 +61,12 @@ Besides specific provider configuration, there are a number of configurations op `hibernate.cache.auto_evict_collection_cache`:: Enables or disables the automatic eviction of a bidirectional association's collection cache entry when the association is changed just from the owning side. This is disabled by default, as it has a performance impact to track this state. - However if your application does not manage both sides of bidirectional association where the collection side is cached, + However, if your application does not manage both sides of bidirectional association where the collection side is cached, the alternative is to have stale data in that collection cache. `hibernate.cache.use_reference_entries`:: Enable direct storage of entity references into the second level cache for read-only or immutable entities. `hibernate.cache.keys_factory`:: - When storing entries into second-level cache as key-value pair, the identifiers can be wrapped into tuples + When storing entries into the second-level cache as a key-value pair, the identifiers can be wrapped into tuples to guarantee uniqueness in case that second-level cache stores all entities in single space. These tuples are then used as keys in the cache. When the second-level cache implementation (incl. its configuration) guarantees that different entity types are stored separately and multi-tenancy is not @@ -87,7 +87,7 @@ or by using the `javax.persistence.sharedCache.mode` property in your configurat The following values are possible: `ENABLE_SELECTIVE` (Default and recommended value):: - Entities are not cached unless explicitly marked as cacheable (with the https://docs.oracle.com/javaee/7/api/javax/persistence/Cacheable.html[`@Cacheable`] annotation). + Entities are not cached unless explicitly marked as cacheable (with the https://javaee.github.io/javaee-spec/javadocs/javax/persistence/Cacheable.html[`@Cacheable`] annotation). `DISABLE_SELECTIVE`:: Entities are cached unless explicitly marked as non-cacheable. `ALL`:: @@ -132,51 +132,32 @@ include:: [[caching-mappings-inheritance]] === Entity inheritance and second-level cache mapping -When using inheritance, the JPA `@Cacheable` and the Hibernate-specific `@Cache` annotations should be declared at the root-entity level only. -That being said, it is not possible to customize the base class `@Cacheable` or `@Cache` definition in subclasses. +Traditionally, when using entity inheritance, Hibernate required an entity hierarchy to be either cached entirely or not cached at all. +Therefore, if you wanted to cache a subclass belonging to a given entity hierarchy, +the JPA `@Cacheable` and the Hibernate-specific `@Cache` annotations would have to be declared at the root-entity level only. -Although the JPA 2.1 specification says that the `@Cacheable` annotation could be overwritten by a subclass: +Although we still believe that all entities belonging to a given entity hierarchy should share the same caching semantics, +the JPA specification says that the `@Cacheable` annotation could be overwritten by a subclass: [quote, Section 11.1.7 of the JPA 2.1 Specification] ____ -The value of the `Cacheable` annotation is inherited by subclasses; it can be -overridden by specifying `Cacheable` on a subclass. +The value of the `Cacheable` annotation is inherited by subclasses; it can be overridden by specifying `Cacheable` on a subclass. ____ -Hibernate requires that a given entity hierarchy share the same caching semantics. - -The reasons why Hibernate requires that all entities belonging to an inheritance tree share the same caching definition can be summed as follows: - -- from a performance perspective, adding an additional check on a per entity type level would slow the bootstrap process. -- providing different caching semantics for subclasses would violate the https://en.wikipedia.org/wiki/Liskov_substitution_principle[Liskov substitution principle]. -+ -Assuming we have a base class, `Payment` and a subclass `CreditCardPayment`. -If the `Payment` is not cacheable and the `CreditCardPayment` is cached, what should happen when executing the following code snippet: -+ -[source, JAVA, indent=0] ----- -Payment payment = entityManager.find(Payment.class, creditCardPaymentId); -CreditCardPayment creditCardPayment = (CreditCardPayment) CreditCardPayment; ----- -+ -In this particular case, the second level cache key is formed of the entity class name and the identifier: -+ -[source, JAVA, indent=0] ----- -keyToLoad = {org.hibernate.engine.spi.EntityKey@4712} - identifier = {java.lang.Long@4716} "2" - persister = {org.hibernate.persister.entity.SingleTableEntityPersister@4629} - "SingleTableEntityPersister(org.hibernate.userguide.model.Payment)" ----- -+ -Should Hibernate load the `CreditCardPayment` from the cache as indicated by the actual entity type, or it should not use the cache since the `Payment` is not supposed to be cached? - [NOTE] ==== -Because of all these intricacies, Hibernate only considers the base class `@Cacheable` and `@Cache` definition. +As of Hibernate ORM 5.3, it's now possible to possible to override a base class `@Cacheable` or `@Cache` definition in subclasses. + +However, the Hibernate cache concurrency strategy (e.g. read-only, nonstrict-read-write, read-write, transactional) is still defined at the root entity level +and cannot be overridden. ==== -[[caching-query]] +Nevertheless, the reasons why we advise you to have all entities belonging to an inheritance tree share the same caching definition can be summed as follows: + +- from a performance perspective, adding an additional check on a per entity type level slows the bootstrap process. +- providing different caching semantics for subclasses would violate the https://en.wikipedia.org/wiki/Liskov_substitution_principle[Liskov substitution principle]. + +[[caching-entity]] === Entity cache [[caching-entity-mapping-example]] @@ -335,17 +316,19 @@ Just as with collection caching, the query cache should always be used in conjun This setting creates two new cache regions: -`org.hibernate.cache.internal.StandardQueryCache`:: +`default-query-results-region`:: Holding the cached query results -`org.hibernate.cache.spi.UpdateTimestampsCache`:: +`default-update-timestamps-region`:: Holding timestamps of the most recent updates to queryable tables. These are used to validate the results as they are served from the query cache. [IMPORTANT] ==== -If you configure your underlying cache implementation to use expiration, it's very important that the timeout of the underlying cache region for the `UpdateTimestampsCache` is set to a higher value than the timeouts of any of the query caches. +If you configure your underlying cache implementation to use expiration, it's very important +that the timeout of the underlying cache region for the `default-update-timestamps-region` +is set to a higher value than the timeouts of any of the query caches. -In fact, we recommend that the `UpdateTimestampsCache` region is not configured for expiration (time-based) or eviction (size/memory-based) at all. +In fact, we recommend that the `default-update-timestamps-region` region is not configured for expiration (time-based) or eviction (size/memory-based) at all. Note that an LRU (Least Recently Used) cache eviction policy is never appropriate for this particular cache region. ==== @@ -382,7 +365,7 @@ include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-region-store-m ---- ==== -[[caching-query-region-native-example]] +[[caching-query-region-store-mode-native-example]] .Using custom query cache mode with Hibernate native API ==== [source, JAVA, indent=0] @@ -397,7 +380,7 @@ When using http://docs.oracle.com/javaee/7/api/javax/persistence/CacheStoreMode. Hibernate will selectively force the results cached in that particular region to be refreshed. This is particularly useful in cases where underlying data may have been updated via a separate process -and is a far more efficient alternative to bulk eviction of the region via `SessionFactory` eviction which looks as follows: +and is a far more efficient alternative to the bulk eviction of the region via `SessionFactory` eviction which looks as follows: [source, JAVA, indent=0] ---- @@ -416,14 +399,14 @@ and retrieval (http://docs.oracle.com/javaee/7/api/javax/persistence/CacheRetrie The relationship between Hibernate and JPA cache modes can be seen in the following table: .Cache modes relationships -[cols=",,,",options="header",] +[cols=",,",options="header",] |====================================== |Hibernate | JPA | Description -|`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into cache +|`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into the cache |`CacheMode.REFRESH` |`CacheStoreMode.REFRESH` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache upon loading from the database |`CacheMode.PUT` |`CacheStoreMode.USE` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache as it reads from the database |`CacheMode.GET` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.USE` | Read from the cache, but doesn't write to cache -|`CacheMode.IGNORE` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.BYPASS` | Doesn't read/write data from/into cache +|`CacheMode.IGNORE` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.BYPASS` | Doesn't read/write data from/into the cache |====================================== Setting the cache mode can be done either when loading entities directly or when executing a query. @@ -472,7 +455,7 @@ include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-management-cache-mod Because the second level cache is bound to the `EntityManagerFactory` or the `SessionFactory`, cache eviction must be done through these two interfaces. -JPA only supports entity eviction through the https://docs.oracle.com/javaee/7/api/javax/persistence/Cache.html[`javax.persistence.Cache`] interface: +JPA only supports entity eviction through the https://javaee.github.io/javaee-spec/javadocs/javax/persistence/Cache.html[`javax.persistence.Cache`] interface: [[caching-management-evict-jpa-example]] .Evicting entities with JPA @@ -524,7 +507,7 @@ include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-statistics-example] [NOTE] ==== -Use of the build-in integration for https://jcp.org/en/jsr/detail?id=107[JCache] requires that the `hibernate-jcache` module jar (and all of its dependencies) are on the classpath. +Use of the built-in integration for https://jcp.org/en/jsr/detail?id=107[JCache] requires that the `hibernate-jcache` module jar (and all of its dependencies) are on the classpath. In addition a JCache implementation needs to be added as well. A list of compatible implementations can be found https://jcp.org/aboutJava/communityprocess/implementations/jsr107/index.html[on the JCP website]. An alternative source of compatible implementations can be found through https://github.com/cruftex/jsr107-test-zoo[the JSR-107 test zoo]. @@ -544,7 +527,7 @@ To use the `JCacheRegionFactory`, you need to specify the following configuratio ---- + value="jcache"/> ---- ==== @@ -576,6 +559,36 @@ In order to control which provider to use and specify configuration for the `Cac Only by specifying the second property `hibernate.javax.cache.uri` will you be able to have a `CacheManager` per `SessionFactory`. +[[caching-provider-jcache-missing-cache-strategy]] +==== JCache missing cache strategy + +By default, the JCache region factory +will log a warning when asked to create a cache that is not explicitly configured and pre-started in the underlying cache manager. +Thus if you configure an entity type or a collection as cached, but do not configure the corresponding cache explicitly, +one warning will be logged for each cache that was not configured explicitly. + +You may change this behavior by setting the `hibernate.javax.cache.missing_cache_strategy` property +to one of the following values: + +.Missing cache strategies +[cols=",",options="header",] +|====================================== +| Value | Description +|`fail` | Fail with an exception on missing caches. +|`create-warn` | **Default value**. Create a new cache when a cache is not found (see `create` below), +and also log a warning about the missing cache. +|`create` | Create a new cache when a cache is not found, without logging any warning about the missing cache. +|====================================== + +[WARNING] +==== +Note that caches created this way may be very badly configured (unlimited size and no eviction in particular) +unless the cache provider was explicitly configured to use a specific configuration for default caches. + +Ehcache, in particular, allows to set such default configuration using cache templates, +see http://www.ehcache.org/documentation/3.0/107.html#supplement-jsr-107-configurations +==== + [[caching-provider-ehcache]] === Ehcache @@ -583,7 +596,7 @@ This integration covers Ehcache 2.x, in order to use Ehcache 3.x as second level [NOTE] ==== -Use of the build-in integration for http://www.ehcache.org/[Ehcache] requires that the `hibernate-ehcache` module jar (and all of its dependencies) are on the classpath. +Use of the built-in integration for http://www.ehcache.org/[Ehcache] requires that the `hibernate-ehcache` module jar (and all of its dependencies) are on the classpath. ==== [[caching-provider-ehcache-region-factory]] @@ -603,7 +616,7 @@ To use the `EhCacheRegionFactory`, you need to specify the following configurati ---- + value="ehcache"/> ---- ==== @@ -622,7 +635,7 @@ To use the `SingletonEhCacheRegionFactory`, you need to specify the following co ---- + value="ehcache-singleton"/> ---- ==== @@ -634,404 +647,39 @@ shared among multiple `SessionFactory` instances in the same JVM. http://www.ehcache.org/documentation/2.8/integrations/hibernate#optional[Ehcache documentation] recommends using multiple non-singleton `CacheManager(s)` when there are multiple Hibernate `SessionFactory` instances running in the same JVM. ==== -[[caching-provider-infinispan]] -=== Infinispan - -[NOTE] -==== -Use of the build-in integration for http://infinispan.org/[Infinispan] requires that the `hibernate-infinispan module` jar (and all of its dependencies) are on the classpath. -==== - -How to configure Infinispan to be the second level cache provider varies slightly depending on the deployment scenario: - -==== Single Node Local - -In standalone library mode, a JPA/Hibernate application runs inside a Java SE application or inside containers that don't offer Infinispan integration. - -Enabling Infinispan second level cache provider inside a JPA/Hibernate application that runs in single node is very straightforward. -First, make sure the Hibernate Infinispan cache provider (and its dependencies) are available in the classpath, then modify the persistence.xml to include these properties: - -==== -[source, XML, indent=0] ----- - - - - - ----- -==== - -Plugging in Infinispan as second-level cache provider requires at the bare minimum that `hibernate.cache.region.factory_class` is set to an Infinispan region factory implementation. -Normally, this is `org.hibernate.cache.infinispan.InfinispanRegionFactory` but other region factories are possible in alternative scenarios (see <> section for more info). - -By default, the Infinispan second-level cache provider uses an Infinispan configuration that's designed for clustered environments. -However, since this section is focused on running Infinispan second-level cache provider in a single node, an Infinispan configuration designed for local environments is recommended. -To enable that configuration, set `hibernate.cache.infinispan.cfg` to `org/hibernate/cache/infinispan/builder/infinispan-configs-local.xml` value. - -The next section focuses on analysing how the default local configuration works. -Changing Infinispan configuration options can be done following the instructions in <> section. - -===== Default Local Configuration +[[caching-provider-ehcache-missing-cache-strategy]] +==== Ehcache missing cache strategy -Infinispan second-level cache provider comes with a configuration designed for local, single node, environments. -These are the characteristics of such configuration: +By default, the Ehcache region factory +will log a warning when asked to create a cache that is not explicitly configured and pre-started in the underlying cache manager. +Thus if you configure an entity type or a collection as cached, but do not configure the corresponding cache explicitly, +one warning will be logged for each cache that was not configured explicitly. -* Entities, collections, queries and timestamps are stored in non-transactional local caches. - -* Entities and collections query caches are configured with the following eviction settings. -You can change these settings on a per entity or collection basis or per individual entity or collection type. -More information in the <> section below. - - Eviction wake up interval is 5 seconds. - - Max number of entries are 10,000 - - Max idle time before expiration is 100 seconds - - Default eviction algorithm is LRU, least recently used. - -* _No eviction/expiration is configured for timestamp caches_, nor it's allowed. - -===== Local Cache Strategies - -Before version 5.0, Infinispan only supported `transactional` and `read-only` strategies. - -Starting with version 5.0, all cache strategies are supported: `transactional`, `read-write`, `nonstrict-read-write` and `read-only`. - -==== Multi Node Cluster - -When running a JPA/Hibernate in a multi-node environment and enabling Infinispan second-level cache, it is necessary to cluster the second-level cache so that cache consistency can be guaranteed. -Clustering the Infinispan second-level cache provider is as simple as adding the following properties: - -==== -[source, XML, indent=0] ----- - - ----- -==== - -As with the standalone local mode, at the bare minimum the region factory has to be configured to point to an Infinispan region factory implementation. - -However, the default Infinispan configuration used by the second-level cache provider is already configured to work in a cluster environment, so no need to add any extra properties. - -The next section focuses on analysing how the default cluster configuration works. -Changing Infinispan configuration options can be done following the instructions in <> section. - -===== Default Cluster Configuration [[integrations:infinispan-2lc-cluster-configuration]] - -Infinispan second-level cache provider default configuration is designed for multi-node clustered environments. -The aim of this section is to explain the default settings for each of the different global data type caches (entity, collection, query and timestamps), why these were chosen and what are the available alternatives. -These are the characteristics of such configuration: - -* For all entities and collections, whenever a new _entity or collection is read from database_ and needs to be cached, _it's only cached locally_ in order to reduce intra-cluster traffic. -This option can be changed so that entities/collections are cached cluster wide, by switching the entity/collection cache to be replicated or distributed. -How to change this option is explained in the <> section. - -* All _entities and collections are configured to use a synchronous invalidation_ as clustering mode. -This means that when an entity is updated, the updated cache will send a message to the other members of the cluster telling them that the entity has been modified. -Upon receipt of this message, the other nodes will remove this data from their local cache, if it was stored there. -This option can be changed so that both local and remote nodes contain the updates by configuring entities or collections to use a replicated or distributed cache. -With replicated caches all nodes would contain the update, whereas with distributed caches only a subset of the nodes. -How to change this option is explained in the <> section. - -* All _entities and collections have initial state transfer disabled_ since there's no need for it. - -* Entities and collections are configured with the following eviction settings. -You can change these settings on a per entity or collection basis or per individual entity or collection type. -More information in the <> section below. - - Eviction wake up interval is 5 seconds. - - Max number of entries are 10,000 - - Max idle time before expiration is 100 seconds - - Default eviction algorithm is LRU, least recently used. - -* Assuming that query caching has been enabled for the persistence unit (see <>), the query cache is configured so that _queries are only cached locally_. -Alternatively, you can configure query caching to use replication by selecting the `replicated-query` as query cache name. -However, replication for query cache only makes sense if, and only if, all of this conditions are true: - - Performing the query is quite expensive. - - The same query is very likely to be repeatedly executed on different cluster nodes. - - The query is unlikely to be invalidated out of the cache (Note: Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types targeted by the query. All such query results are invalidated, even if the change made to the specific entity instance would not have affected the query result) - -* _query cache_ uses the _same eviction/expiration settings as for entities/collections_. - -* _query cache has initial state transfer disabled_ . It is not recommended that this is enabled. - -* The _timestamps cache is configured with asynchronous replication_ as clustering mode. -Local or invalidated cluster modes are not allowed, since all cluster nodes must store all timestamps. -As a result, _no eviction/expiration is allowed for timestamp caches either_. - -[IMPORTANT] -==== -Asynchronous replication was selected as default for timestamps cache for performance reasons. -A side effect of this choice is that when an entity/collection is updated, for a very brief period of time stale queries might be returned. -It's important to note that due to how Infinispan deals with asynchronous replication, stale queries might be found even query is done right after an entity/collection update on same node. -The reason why asynchronous replication works this way is because there's a single node that's owner for a given key, and that enables changes to be applied in the same order in all nodes. -Without it, it could happen that an older value could replace a newer value in certain nodes. -==== - -[NOTE] -==== -Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types is modified. All cached query results referencing given entity type are invalidated, even if the change made to the specific entity instance would not have affected the query result. -The timestamps cache plays here an important role - it contains last modification timestamp for each entity type. After a cached query results is loaded, its timestamp is compared to all timestamps of the entity types that are referenced in the query and if any of these is higher, the cached query result is discarded and the query is executed against DB. -==== - -===== Cluster Cache Strategies - -Before version 5.0, Infinispan only supported `transactional` and `read-only` strategies on top of _transactional invalidation_ caches. - -Since version 5.0, Infinispan currently supports all cache concurrency modes in cluster mode, although not all combinations of configurations are compatible: - -* _non-transactional invalidation_ caches are supported as well with `read-write` strategy. The actual setting of cache concurrency mode (`read-write` vs. `transactional`) is not honored, the appropriate strategy is selected based on the cache configuration (_non-transactional_ vs. _transactional_). -* `read-write` mode is supported on _non-transactional distributed/replicated_ caches, however, eviction should not be used in this configuration. Use of eviction can lead to consistency issues. Expiration (with reasonably long max-idle times) can be used. -* `nonstrict-read-write` mode is supported on _non-transactional distributed/replicated_ caches, but the eviction should be turned off as well. In addition to that, the entities must use versioning. This mode mildly relaxes the consistency - between DB commit and end of transaction commit a stale read (see <>) may occur in another transaction. However this strategy uses less RPCs and can be more performant than the other ones. -* `read-only` mode is supported on both _transactional_ and _non-transactional_ _invalidation_ caches and _non-transactional distributed/replicated_ caches, but use of this mode currently does not bring any performance gains. - -The available combinations are summarized in table below: - -[[caching-provider-infinispan-compatibility-table]] -.Cache concurrency strategy/cache mode compatibility table -[options="header"] -|=== -|Concurrency strategy|Cache transactions|Cache mode |Eviction -|transactional |transactional |invalidation |yes -|read-write |non-transactional |invalidation |yes -|read-write |non-transactional |distributed/replicated |no -|nonstrict-read-write|non-transactional |distributed/replicated |no -|=== - -Changing caches to behave different to the default behaviour explained in previous section is explained in <> section. - -[[caching-provider-infinispan-stale-read-example]] -.Stale read with `nonstrict-read-write` strategy -==== -[source, indent=0] ----- -A=0 (non-cached), B=0 (cached in 2LC) -TX1: write A = 1, write B = 1 -TX1: start commit -TX1: commit A, B in DB -TX2: read A = 1 (from DB), read B = 0 (from 2LC) // breaks transactional atomicity -TX1: update A, B in 2LC -TX1: end commit -Tx3: read A = 1, B = 1 // reads after TX1 commit completes are consistent again ----- -==== +You may change this behavior by setting the `hibernate.cache.ehcache.missing_cache_strategy` property +to one of the following values: -[[caching-provider-infinispan-region-factory]] -==== Alternative RegionFactory - -In standalone environments or managed environments with no Infinispan integration, `org.hibernate.cache.infinispan.InfinispanRegionFactory` should be the choice for region factory implementation. -However, it might be sometimes desirable for the Infinispan cache manager to be shared between different JPA/Hibernate applications, for example to share intra-cluster communications channels. -In this case, the Infinispan cache manager could be bound into JNDI and the JPA/Hibernate applications could use an alternative region factory implementation: - -[[caching-provider-infinispan-region-factory-jndi-example]] -.`JndiInfinispanRegionFactory` configuration -==== -[source, XML, indent=0] ----- - - - ----- -==== - -==== Inside Wildfly - -In WildFly, Infinispan is the default second level cache provider for JPA/Hibernate. -When using JPA in WildFly, region factory is automatically set upon configuring `hibernate.cache.use_second_level_cache=true` (by default second-level cache is not used). - -You can find details about its configuration in link:{wildflydocroot}/JPA%20Reference%20Guide[the JPA reference guide], in particular, in the link:{wildflydocroot}/JPA%20Reference%20Guide#JPAReferenceGuide-UsingtheInfinispansecondlevelcache[second level cache] section. - -The default second-level cache configurations used by Wildfly match the configurations explained above both for local and clustered environments. -So, an Infinispan based second-level cache should behave exactly the same standalone and within containers that provide Infinispan second-level cache as default for JPA/Hibernate. - -[IMPORTANT] -==== -Remember that if deploying to Wildfly or Application Server, the way some Infinispan second level cache provider configuration is defined changes slightly because the properties must include deployment and persistence information. -Check the <> section for more details. -==== - -[[caching-provider-infinispan-config]] -==== Configuration properties - -As explained above, Infinispan second-level cache provider comes with default configuration in `infinispan-config.xml` that is suited for clustered use. -If there's only single JVM accessing the DB, you can use more performant `infinispan-config-local.xml` by setting the `hibernate.cache.infinispan.cfg` property. -If you require further tuning of the cache, you can provide your own configuration. -Caches that are not specified in the provided configuration will default to `infinispan-config.xml` (if the provided configuration uses clustering) or `infinispan-config-local.xml`. +.Missing cache strategies +[cols=",",options="header",] +|====================================== +| Value | Description +|`fail` | Fail with an exception on missing caches. +|`create-warn` | **Default value**. Create a new cache when a cache is not found (see `create` below), +and also log a warning about the missing cache. +|`create` | Create a new cache when a cache is not found, without logging any warning about the missing cache. +|====================================== [WARNING] ==== -It is not possible to specify the configuration this way in WildFly. -Cache configuration changes in Wildfly should be done either modifying the cache configurations inside the application server configuration, or creating new caches with the desired tweaks and plugging them accordingly. -See examples below on how entity/collection specific configurations can be applied. -==== - -[[caching-provider-infinispan-config-example]] -.Use custom Infinispan configuration -==== -[source, XML, indent=0] ----- - ----- -==== - -[NOTE] -==== -If the cache is configured as transactional, InfinispanRegionFactory automatically sets transaction manager so that the TM used by Infinispan is the same as TM used by Hibernate. -==== - -Cache configuration can differ for each type of data stored in the cache. In order to override the cache configuration template, use property `hibernate.cache.infinispan._data-type_.cfg` where `_data-type_` can be one of: - -`entity`:: Entities indexed by `@Id` or `@EmbeddedId` attribute. -`immutable-entity`:: Entities tagged with `@Immutable` annotation or set as `mutable=false` in mapping file. -`naturalid`:: Entities indexed by their `@NaturalId` attribute. -`collection`:: All collections. -`timestamps`:: Mapping _entity type_ -> _last modification timestamp_. Used for query caching. -`query`:: Mapping _query_ -> _query result_. -`pending-puts`:: Auxiliary caches for regions using invalidation mode caches. - -For specifying cache template for specific region, use region name instead of the `_data-type_`: - -[[caching-provider-infinispan-config-cache-example]] -.Use custom cache template -==== -[source, XML, indent=0] ----- - - - - ----- -==== - -.Use custom cache template in Wildfly -When applying entity/collection level changes inside JPA applications deployed in Wildfly, it is necessary to specify deployment name and persistence unit name: - -==== -[source, XML, indent=0] ----- - - ----- -==== - -[IMPORTANT] -==== -Cache configurations are used only as a template for the cache created for given region (usually each entity hierarchy or collection has its own region). It is not possible to use the same cache for different regions. -==== - -Some options in the cache configuration can also be overridden directly through properties. These are: - -`hibernate.cache.infinispan._something_.eviction.strategy`:: Available options are `NONE`, `LRU` and `LIRS`. -`hibernate.cache.infinispan._something_.eviction.max_entries`:: Maximum number of entries in the cache. -`hibernate.cache.infinispan._something_.expiration.lifespan`:: Lifespan of entry from insert into cache (in milliseconds) -`hibernate.cache.infinispan._something_.expiration.max_idle`:: Lifespan of entry from last read/modification (in milliseconds) -`hibernate.cache.infinispan._something_.expiration.wake_up_interval`:: Period of thread checking expired entries. -`hibernate.cache.infinispan.statistics`:: Globally enables/disable Infinispan statistics collection, and their exposure via JMX. - -Example: -==== -[source, XML, indent=0] ----- - - - - - ----- -==== - -With the above configuration, you're overriding whatever eviction/expiration settings were defined for the default entity cache name in the Infinispan cache configuration used, regardless of whether it's the default one or user defined. -More specifically, we're defining the following: - -* All entities to use LRU eviction strategy -* The eviction thread to wake up every 2 seconds (2000 milliseconds) -* The maximum number of entities for each entity type to be 5000 entries -* The lifespan of each entity instance to be 1 minute (600000 milliseconds). -* The maximum idle time for each entity instance to be 30 seconds (30000 milliseconds). - -You can also override eviction/expiration settings on a per entity/collection type basis in such way that the overriden settings only afftect that particular entity (i.e. `com.acme.Person`) or collection type (i.e. `com.acme.Person.addresses`). -Example: - -[source,xml] ----- - ----- +Note that caches created this way may be very badly configured (large size in particular) +unless an appropriate `` entry is added to the Ehcache configuration. ==== -Inside of Wildfly, same as with the entity/collection configuration override, eviction/expiration settings would also require deployment name and persistence unit information: - -[source,xml] ----- - - ----- -==== - -[NOTE] -==== -In versions prior to 5.1, `hibernate.cache.infinispan._something_.expiration.wake_up_interval` was called `hibernate.cache.infinispan._something_.eviction.wake_up_interval`. -Eviction settings are checked upon each cache insert, it is expiration that needs to be triggered periodically. -The old property still works, but its use is deprecated. -==== - -[NOTE] -==== -Property `hibernate.cache.infinispan.use_synchronization` that allowed to register Infinispan as XA resource in the transaction has been deprecated in 5.0 and is not honored anymore. Infinispan 2LC must register as synchronizations on transactional caches. Also, non-transactional cache modes hook into the current JTA/JDBC transaction as synchronizations. -==== - -[[caching-provider-infinispan-remote]] -==== Remote Infinispan Caching - -Lately, several questions ( link:http://community.jboss.org/message/575814#575814[here] and link:http://community.jboss.org/message/585841#585841[here] ) have appeared in the Infinispan user forums asking whether it'd be possible to have an Infinispan second level cache that instead of living in the same JVM as the Hibernate code, it resides in a remote server, i.e. an Infinispan Hot Rod server. -It's important to understand that trying to set up second level cache in this way is generally not a good idea for the following reasons: - -* The purpose of a JPA/Hibernate second level cache is to store entities/collections recently retrieved from database or to maintain results of recent queries. -So, part of the aim of the second level cache is to have data accessible locally rather than having to go to the database to retrieve it everytime this is needed. -Hence, if you decide to set the second level cache to be remote as well, you're losing one of the key advantages of the second level cache: the fact that the cache is local to the code that requires it. -* Setting a remote second level cache can have a negative impact in the overall performance of your application because it means that cache misses require accessing a remote location to verify whether a particular entity/collection/query is cached. -With a local second level cache however, these misses are resolved locally and so they are much faster to execute than with a remote second level cache. +[[caching-provider-infinispan]] +=== Infinispan -There are however some edge cases where it might make sense to have a remote second level cache, for example: +Infinispan is a distributed in-memory key/value data store, available as a cache or data grid, which can be used as a Hibernate 2nd-level cache provider as well. -* You are having memory issues in the JVM where JPA/Hibernate code and the second level cache is running. -Off loading the second level cache to remote Hot Rod servers could be an interesting way to separate systems and allow you find the culprit of the memory issues more easily. -* Your application layer cannot be clustered but you still want to run multiple application layer nodes. -In this case, you can't have multiple local second level cache instances running because they won't be able to invalidate each other for example when data in the second level cache is updated. -In this case, having a remote second level cache could be a way out to make sure your second level cache is always in a consistent state, will all nodes in the application layer pointing to it. -* Rather than having the second level cache in a remote server, you want to simply keep the cache in a separate VM still within the same machine. -In this case you would still have the additional overhead of talking across to another JVM, but it wouldn't have the latency of across a network. -+ -The benefit of doing this is that: -+ -** Size the cache separate from the application, since the cache and the application server have very different memory profiles. -One has lots of short lived objects, and the other could have lots of long lived objects. -** To pin the cache and the application server onto different CPU cores (using _numactl_ ), and even pin them to different physically memory based on the NUMA nodes. +It supports advanced functionality such as transactions, events, querying, distributed processing, off-heap and geographical failover. +For more details, check out the +http://infinispan.org/docs/stable/titles/integrating/integrating.html#integrating_jpa_hibernate[Infinispan User Guide]. \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/access.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/access.adoc index 9af70b125034..211898457302 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/access.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/access.adoc @@ -1,15 +1,16 @@ [[access]] ==== Access strategies -:sourcedir: extras +:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/access +:extrasdir: extras As a JPA provider, Hibernate can introspect both the entity attributes (instance fields) or the accessors (instance properties). By default, the placement of the `@Id` annotation gives the default access strategy. When placed on a field, Hibernate will assume field-based access. -Place on the identifier getter, Hibernate will use property-based access. +When placed on the identifier getter, Hibernate will use property-based access. [IMPORTANT] ==== -You should pay attention to https://docs.oracle.com/javase/7/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String)[Java Beans specification] in regard to naming properties to avoid +You should pay attention to https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String)[Java Beans specification] in regard to naming properties to avoid issues such as https://hibernate.atlassian.net/browse/HCANN-63[Property name beginning with at least two uppercase characters has odd functionality in HQL]! ==== @@ -18,11 +19,12 @@ Embeddable types inherit the access strategy from their parent entities. [[field-based-access]] ===== Field-based access +[[access-field-mapping-example]] .Field-based access ==== [source,java] ---- -include::{sourcedir}/access/SimpleEntityFieldAccess.java[] +include::{sourcedir}/FieldAccessTest.java[tag=access-field-mapping-example, indent=0] ---- ==== @@ -32,18 +34,19 @@ To exclude a field from being part of the entity persistent state, the field mus [NOTE] ==== Another advantage of using field-based access is that some entity attributes can be hidden from outside the entity. -An example of such attribute is the entity `@Version` field, which must not be manipulated by the data access layer. +An example of such attribute is the entity `@Version` field, which, usually, does not need to be manipulated by the data access layer. With field-based access, we can simply omit the getter and the setter for this version field, and Hibernate can still leverage the optimistic concurrency control mechanism. ==== [[property-based-access]] ===== Property-based access +[[access-property-mapping-example]] .Property-based access ==== [source,java] ---- -include::{sourcedir}/access/SimpleEntityPropertyAccess.java[] +include::{sourcedir}/PropertyAccessTest.java[tag=access-property-mapping-example, indent=0] ---- ==== @@ -55,11 +58,12 @@ Every other method that will be added to the entity (e.g. helper methods for syn The default access strategy mechanism can be overridden with the JPA `@Access` annotation. In the following example, the `@Version` attribute is accessed by its field and not by its getter, like the rest of entity attributes. +[[access-property-override-mapping-example]] .Overriding access strategy ==== [source,java] ---- -include::{sourcedir}/access/SimpleEntityPropertyAccessOverride.java[] +include::{sourcedir}/PropertyAccessOverrideTest.java[tag=access-property-override-mapping-example, indent=0] ---- ==== @@ -72,30 +76,33 @@ This applies to both simple embeddable types as well as for collection of embedd The embeddable types can overrule the default implicit access strategy (inherited from the owning entity). In the following example, the embeddable uses property-based access, no matter what access strategy the owning entity is choosing: +[[access-embeddable-mapping-example]] .Embeddable with exclusive access strategy ==== [source,java] ---- -include::{sourcedir}/access/EmbeddableAccessType.java[] +include::{sourcedir}/EmbeddableAccessTest.java[tag=access-embeddable-mapping-example, indent=0] ---- ==== -The owning entity can use field-based access, while the embeddable uses property-based access as it has chosen explicitly: +The owning entity can use field-based access while the embeddable uses property-based access as it has chosen explicitly: +[[access-embedded-mapping-example]] .Entity including a single embeddable type ==== [source,java] ---- -include::{sourcedir}/access/EmbeddedAccessType.java[] +include::{sourcedir}/EmbeddableAccessTest.java[tag=access-embedded-mapping-example, indent=0] ---- ==== This works also for collection of embeddable types: +[[access-element-collection-mapping-example]] .Entity including a collection of embeddable types ==== [source,java] ---- -include::{sourcedir}/access/ElementCollectionAccessType.java[] +include::{sourcedir}/ElementCollectionAccessTest.java[tag=access-element-collection-mapping-example, indent=0] ---- ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index 1acaec2e19c2..a8d83ab014d8 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc @@ -123,7 +123,7 @@ include::{extrasdir}/associations-one-to-many-bidirectional-example.sql[] [IMPORTANT] ==== Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times. -The `addPhone()` and `removePhone()` are utilities methods that synchronize both ends whenever a child element is added or removed. +The `addPhone()` and `removePhone()` are utility methods that synchronize both ends whenever a child element is added or removed. ==== Because the `Phone` class has a `@NaturalId` column (the phone number being unique), @@ -146,7 +146,7 @@ include::{extrasdir}/associations-one-to-many-bidirectional-lifecycle-example.sq Unlike the unidirectional `@OneToMany`, the bidirectional association is much more efficient when managing the collection persistence state. Every element removal only requires a single update (in which the foreign key column is set to `NULL`), and, if the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, -then we can annotate the association with the `orphan-removal` attribute and disassociating the child will trigger a delete statement on the actual child table row as well. +then we can annotate the association with the `orphan-removal` attribute and dissociate the child will trigger a delete statement on the actual child table row as well. [[associations-one-to-one]] ==== `@OneToOne` @@ -225,11 +225,36 @@ include::{sourcedir}/OneToOneBidirectionalTest.java[tags=associations-one-to-one ---- ==== +[[associations-one-to-one-bidirectional-lazy]] +====== Bidirectional `@OneToOne` lazy association + +Although you might annotate the parent-side association to be fetched lazily, +Hibernate cannot honor this request since it cannot know whether the association is `null` or not. + +The only way to figure out whether there is an associated record on the child side is to fetch the child association using a secondary query. +Because this can lead to N+1 query issues, it's much more efficient to use unidirectional `@OneToOne` associations with the `@MapsId` annotation in place. + +However, if you really need to use a bidirectional association and want to make sure that this is always going to be fetched lazily, +then you need to enable lazy state initialization bytecode enhancement and use the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/LazyToOne.html[`@LazyToOne`] annotation as well. + +[[associations-one-to-one-bidirectional-lazy-example]] +.Bidirectional `@OneToOne` lazy parent-side association +==== +[source,java] +---- +include::{sourcedir}/OneToOneBidirectionalLazyTest.java[tags=associations-one-to-one-bidirectional-lazy-example,indent=0] +---- +==== + +For more about how to enable Bytecode enhancement, +see the <>. + [[associations-many-to-many]] ==== `@ManyToMany` The `@ManyToMany` association requires a link table that joins two entities. -Like the `@OneToMany` association, `@ManyToMany` can be a either unidirectional or bidirectional. +Like the `@OneToMany` association, `@ManyToMany` can be either unidirectional or bidirectional. [[associations-many-to-many-unidirectional]] ===== Unidirectional `@ManyToMany` @@ -388,3 +413,69 @@ 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 + +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. + +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. + +To ignore non-existing parent entity references, even though not really recommended, it's possible to use the annotation `org.hibernate.annotation.NotFound` annotation with a value of `org.hibernate.annotations.NotFoundAction.IGNORE`. + +Considering the following `City` and `Person` entity mappings: + +[[associations-not-found-domain-model-example]] +.`@NotFound` mapping example +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-domain-model-example,indent=0] +---- +==== + +If we have the following entities in our database: + +[[associations-not-found-persist-example]] +.`@NotFound` mapping example +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-persist-example,indent=0] +---- +==== + +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 +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-find-example,indent=0] +---- +==== + +However, if we change the `cityName` attribute to a non-existing city: + +[[associations-not-found-non-existing-persist-example]] +.`@NotFound` change to non-existing City example +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-non-existing-persist-example,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 +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-non-existing-find-example,indent=0] +---- +==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc index eb18e6893197..97d021bab825 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc @@ -2,6 +2,8 @@ === Basic Types :modeldir: ../../../../../main/java/org/hibernate/userguide/model :sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping +:resourcedir: ../../../../../test/resources/org/hibernate/userguide/ +:converter-sourcedir: ../../../../../../../hibernate-core/src/test/java/org/hibernate/test/converter :extrasdir: extras Basic value types usually map a single database column, to a single, non-aggregated Java type. @@ -38,7 +40,7 @@ Internally Hibernate uses a registry of basic types when it needs to resolve a s |CalendarType |TIMESTAMP |java.util.Calendar |calendar, java.util.Calendar |CalendarDateType |DATE |java.util.Calendar |calendar_date |CalendarTimeType |TIME |java.util.Calendar |calendar_time -|CurrencyType |java.util.Currency |VARCHAR |currency, java.util.Currency +|CurrencyType |VARCHAR |java.util.Currency |currency, java.util.Currency |LocaleType |VARCHAR |java.util.Locale |locale, java.utility.locale |TimeZoneType |VARCHAR, using the TimeZone ID |java.util.TimeZone |timezone, java.util.TimeZone |UrlType |VARCHAR |java.net.URL |url, java.net.URL @@ -46,7 +48,7 @@ Internally Hibernate uses a registry of basic types when it needs to resolve a s |BlobType |BLOB |java.sql.Blob |blob, java.sql.Blob |ClobType |CLOB |java.sql.Clob |clob, java.sql.Clob |BinaryType |VARBINARY |byte[] |binary, byte[] -|MaterializedBlobType |BLOB |byte[] |materized_blob +|MaterializedBlobType |BLOB |byte[] |materialized_blob |ImageType |LONGVARBINARY |byte[] |image |WrapperBinaryType |VARBINARY |java.lang.Byte[] |wrapper-binary, Byte[], java.lang.Byte[] |CharArrayType |VARCHAR |char[] |characters, char[] @@ -75,15 +77,15 @@ Internally Hibernate uses a registry of basic types when it needs to resolve a s |LocalTimeType |TIME |java.time.LocalTime |LocalTime, java.time.LocalTime |OffsetDateTimeType |TIMESTAMP |java.time.OffsetDateTime |OffsetDateTime, java.time.OffsetDateTime |OffsetTimeType |TIME |java.time.OffsetTime |OffsetTime, java.time.OffsetTime -|OffsetTimeType |TIMESTAMP |java.time.ZonedDateTime |ZonedDateTime, java.time.ZonedDateTime +|ZonedDateTimeType |TIMESTAMP |java.time.ZonedDateTime |ZonedDateTime, java.time.ZonedDateTime |================================================================================================= .Hibernate Spatial BasicTypes [cols=",,,",options="header",] |================================================================================================= |Hibernate type (org.hibernate.spatial package) |JDBC type |Java type |BasicTypeRegistry key(s) -|JTSGeometryType |depends on the dialect | com.vividsolutions.jts.geom.Geometry |jts_geometry, or the classname of Geometry or any of its subclasses -|GeolatteGeometryType |depends on the dialect | org.geolatte.geom.Geometry |geolatte_geometry, or the classname of Geometry or any of its subclasses +|JTSGeometryType |depends on the dialect | com.vividsolutions.jts.geom.Geometry |jts_geometry, or the class name of Geometry or any of its subclasses +|GeolatteGeometryType |depends on the dialect | org.geolatte.geom.Geometry |geolatte_geometry, or the class name of Geometry or any of its subclasses |================================================================================================= [NOTE] @@ -149,7 +151,7 @@ The `@Basic` annotation defines 2 attributes. JPA defines this as "a hint", which essentially means that it effect is specifically required. As long as the type is not primitive, Hibernate takes this to mean that the underlying column should be `NULLABLE`. `fetch` - FetchType (defaults to EAGER):: Defines whether this attribute should be fetched eagerly or lazily. -JPA says that EAGER is a requirement to the provider (Hibernate) that the value should be fetched when the owner is fetched, while LAZY is merely a hint that the value be fetched when the attribute is accessed. +JPA says that EAGER is a requirement to the provider (Hibernate) that the value should be fetched when the owner is fetched, while LAZY is merely a hint that the value is fetched when the attribute is accessed. Hibernate ignores this setting for basic types unless you are using bytecode enhancement. See the <> for additional information on fetching and on bytecode enhancement. @@ -186,7 +188,7 @@ or its `org.hibernate.type.IntegerType` for mapping `java.lang.Integer` attribut The answer lies in a service inside Hibernate called the `org.hibernate.type.BasicTypeRegistry`, which essentially maintains a map of `org.hibernate.type.BasicType` (a `org.hibernate.type.Type` specialization) instances keyed by a name. We will see later, in the <> section, that we can explicitly tell Hibernate which BasicType to use for a particular attribute. -But first let's explore how implicit resolution works and how applications can adjust implicit resolution. +But first, let's explore how implicit resolution works and how applications can adjust the implicit resolution. [NOTE] ==== @@ -212,7 +214,7 @@ For more details, see <> section. Sometimes you want a particular attribute to be handled differently. Occasionally Hibernate will implicitly pick a `BasicType` that you do not want (and for some reason you do not want to adjust the `BasicTypeRegistry`). -In these cases you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation. +In these cases, you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation. [[basic-type-annotation-example]] .Using `@org.hibernate.annotations.Type` @@ -313,6 +315,17 @@ include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-BitSetType ---- ==== +Alternatively, you can use the `@TypeDef` and skip the registration phase: + +[[basic-custom-type-BitSetTypeDef-mapping-example]] +.Using `@TypeDef` to register a custom Type +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/BitSetTypeDefTest.java[tags=basic-custom-type-BitSetTypeDef-mapping-example] +---- +==== + To validate this new `BasicType` implementation, we can test it as follows: [[basic-custom-type-BitSetType-persistence-example]] @@ -411,7 +424,7 @@ Hibernate supports the mapping of Java enums as basic value types in a number of [[basic-enums-Enumerated]] ===== `@Enumerated` -The original JPA-compliant way to map enums was via the `@Enumerated` and `@MapKeyEnumerated` for map keys annotations which works on the principle that the enum values are stored according to one of 2 strategies indicated by `javax.persistence.EnumType`: +The original JPA-compliant way to map enums was via the `@Enumerated` or `@MapKeyEnumerated` for map keys annotations, working on the principle that the enum values are stored according to one of 2 strategies indicated by `javax.persistence.EnumType`: `ORDINAL`:: stored according to the enum value's ordinal position within the enum class, as indicated by `java.lang.Enum#ordinal` @@ -428,7 +441,7 @@ include::{modeldir}/PhoneType.java[tags=hql-examples-domain-model-example] ---- ==== -In the ORDINAL example, the `phone_type` column is defined as an (nullable) INTEGER type and would hold: +In the ORDINAL example, the `phone_type` column is defined as a (nullable) INTEGER type and would hold: `NULL`:: For null values `0`:: For the `LAND_LINE` enum @@ -474,7 +487,7 @@ include::{sourcedir}/basic/PhoneTypeEnumeratedStringTest.java[tags=basic-enums-E ---- ==== -Persisting the same entity like in the `@Enumerated(ORDINAL)` example, Hibernate generates the following SQL statement: +Persisting the same entity as in the `@Enumerated(ORDINAL)` example, Hibernate generates the following SQL statement: [[basic-enums-Enumerated-string-persistence-example]] .Persisting an entity with an `@Enumerated(STRING)` mapping @@ -491,7 +504,7 @@ include::{extrasdir}/basic/basic-enums-Enumerated-string-persistence-example.sql Let's consider the following `Gender` enum which stores its values using the `'M'` and `'F'` codes. [[basic-enums-converter-example]] -.Enum with custom constructor +.Enum with a custom constructor ==== [source, JAVA, indent=0] ---- @@ -524,6 +537,119 @@ JPA explicitly disallows the use of an AttributeConverter with an attribute mark So if using the AttributeConverter approach, be sure not to mark the attribute as `@Enumerated`. ==== +[[basic-attribute-converter-query-parameter]] +====== Using the AttributeConverter entity property as a query parameter + +Assuming you have the following entity: + +[[basic-attribute-converter-query-parameter-entity-example]] +.`Photo` entity with `AttributeConverter` +==== +[source, JAVA, indent=0] +---- +include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-entity-example] +---- +==== + +And the `Caption` class looks as follows: + +[[basic-attribute-converter-query-parameter-object-example]] +.`Caption` Java object +==== +[source, JAVA, indent=0] +---- +include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-object-example] +---- +==== + +And we have an `AttributeConverter` to handle the `Caption` Java object: + +[[basic-attribute-converter-query-parameter-converter-example]] +.`Caption` Java object AttributeConverter +==== +[source, JAVA, indent=0] +---- +include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-converter-example] +---- +==== + +Traditionally, you could only use the dbData `Caption` representation, which in our case is a `String`, when referencing the `caption` entity property. + +[[basic-attribute-converter-query-parameter-converter-dbdata-example]] +.Filtering by the `Caption` property using the DB data representation +==== +[source, JAVA, indent=0] +---- +include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-converter-dbdata-example] +---- +==== + +In order to use the Java object `Caption` representation, you have to get the associated Hibernate `Type`. + +[[basic-attribute-converter-query-parameter-converter-object-example]] +.Filtering by the `Caption` property using the Java Object representation +==== +[source, JAVA, indent=0] +---- +include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-converter-object-example] +---- +==== + +By passing the associated Hibernate `Type`, you can use the `Caption` object when binding the query parameter value. + +[[basic-hbm-attribute-converter]] +====== Mapping an AttributeConverter using HBM mappings + +When using HBM mappings, you can still make use of the JPA `AttributeConverter` because Hibernate supports +such mapping via the `type` attribute as demonstrated by the following example. + +Let's consider we have an application-specific `Money` type: + +[[basic-hbm-attribute-converter-mapping-money-example]] +.Application-specific `Money` type +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/converter/hbm/Money.java[tags=basic-hbm-attribute-converter-mapping-money-example] +---- +==== + +Now, we want to use the `Money` type when mapping the `Account` entity: + +[[basic-hbm-attribute-converter-mapping-account-example]] +.`Account` entity using the `Money` type +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/converter/hbm/Account.java[tags=basic-hbm-attribute-converter-mapping-account-example] +---- +==== + +Since Hibernate has no knowledge how to persist the `Money` type, we could use a JPA `AttributeConverter` +to transform the `Money` type as a `Long`. For this purpose, we are going to use the following +`MoneyConverter` utility: + +[[basic-hbm-attribute-converter-mapping-moneyconverter-example]] +.`MoneyConverter` implementing the JPA `AttributeConverter` interface +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/converter/hbm/MoneyConverter.java[tags=basic-hbm-attribute-converter-mapping-moneyconverter-example] +---- +==== + +To map the `MoneyConverter` using HBM configuration files you need to use the `converted::` prefix in the `type` +attribute of the `property` element. + +[[basic-hbm-attribute-converter-mapping-xml-config-example]] +.HBM mapping for `AttributeConverter` +==== +[source, JAVA, indent=0] +---- +include::{resourcedir}/mapping/converter/hbm/MoneyConverterHbmTest.hbm.xml[] +---- +==== + [[basic-enums-custom-type]] ===== Custom type @@ -558,10 +684,10 @@ Mapping LOBs (database Large Objects) come in 2 forms, those using the JDBC loca JDBC LOB locators exist to allow efficient access to the LOB data. They allow the JDBC driver to stream parts of the LOB data as needed, potentially freeing up memory space. -However they can be unnatural to deal with and have certain limitations. +However, they can be unnatural to deal with and have certain limitations. For example, a LOB locator is only portably valid during the duration of the transaction in which it was obtained. -The idea of materialized LOBs is to trade-off the potential efficiency (not all drivers handle LOB data efficiently) for a more natural programming paradigm using familiar Java types such as String or byte[], etc for these LOBs. +The idea of materialized LOBs is to trade-off the potential efficiency (not all drivers handle LOB data efficiently) for a more natural programming paradigm using familiar Java types such as `String` or `byte[]`, etc for these LOBs. Materialized deals with the entire LOB contents in memory, whereas LOB locators (in theory) allow streaming parts of the LOB contents into memory as needed. @@ -572,10 +698,15 @@ The JDBC LOB locator types include: * `java.sql.NClob` Mapping materialized forms of these LOB values would use more familiar Java types such as `String`, `char[]`, `byte[]`, etc. -The trade off for _more familiar_ is usually performance. +The trade-off for _more familiar_ is usually performance. + +[[basic-clob]] +===== Mapping CLOB For a first look, let's assume we have a `CLOB` column that we would like to map (`NCLOB` character `LOB` data will be covered in <> section). +Considering we have the following database table: + [[basic-clob-sql-example]] .CLOB - SQL ==== @@ -596,7 +727,7 @@ include::{sourcedir}/basic/ClobTest.java[tags=basic-clob-example] ---- ==== -To persist such an entity, you have to create a `Clob` using plain JDBC: +To persist such an entity, you have to create a `Clob` using the `ClobProxy` Hibernate utility: [[basic-clob-persist-example]] .Persisting a `java.sql.Clob` entity @@ -633,11 +764,11 @@ include::{sourcedir}/basic/ClobStringTest.java[tags=basic-clob-string-example] ==== How JDBC deals with `LOB` data varies from driver to driver, and Hibernate tries to handle all these variances on your behalf. -However, some drivers are trickier (e.g. PostgreSQL JDBC drivers), and, in such cases, you may have to do some extra to get LOBs working. +However, some drivers are trickier (e.g. PostgreSQL), and, in such cases, you may have to do some extra to get LOBs working. Such discussions are beyond the scope of this guide. ==== -We might even want the materialized data as a char array (for some crazy reason). +We might even want the materialized data as a char array (although this might not be a very good idea). [[basic-clob-char-array-example]] .CLOB - materialized `char[]` mapping @@ -648,8 +779,13 @@ include::{sourcedir}/basic/ClobCharArrayTest.java[tags=basic-clob-char-array-exa ---- ==== +[[basic-blob]] +===== Mapping BLOB + `BLOB` data is mapped in a similar fashion. +Considering we have the following database table: + [[basic-blob-sql-example]] .BLOB - SQL ==== @@ -670,7 +806,7 @@ include::{sourcedir}/basic/BlobTest.java[tags=basic-blob-example] ---- ==== -To persist such an entity, you have to create a `Blob` using plain JDBC: +To persist such an entity, you have to create a `Blob` using the `BlobProxy` Hibernate utility: [[basic-blob-persist-example]] .Persisting a `java.sql.Blob` entity @@ -681,7 +817,7 @@ include::{sourcedir}/basic/BlobTest.java[tags=basic-blob-persist-example] ---- ==== -To retrieve the `Blob` content, you need to transform the underlying `java.io.Reader`: +To retrieve the `Blob` content, you need to transform the underlying `java.io.InputStream`: [[basic-blob-find-example]] .Returning a `java.sql.Blob` entity @@ -707,13 +843,15 @@ include::{sourcedir}/basic/BlobByteArrayTest.java[tags=basic-blob-byte-array-exa ==== Mapping Nationalized Character Data JDBC 4 added the ability to explicitly handle nationalized character data. -To this end it added specific nationalized character data types. +To this end, it added specific nationalized character data types: * `NCHAR` * `NVARCHAR` * `LONGNVARCHAR` * `NCLOB` +Considering we have the following database table: + [[basic-nationalized-sql-example]] .`NVARCHAR` - SQL ==== @@ -756,7 +894,7 @@ include::{sourcedir}/basic/NClobTest.java[tags=basic-nclob-example] ---- ==== -To persist such an entity, you have to create a `NClob` using plain JDBC: +To persist such an entity, you have to create an `NClob` using the `NClobProxy` Hibernate utility: [[basic-nclob-persist-example]] .Persisting a `java.sql.NClob` entity @@ -814,7 +952,7 @@ Hibernate also allows you to map UUID values, again in a number of ways. [NOTE] ==== The default UUID mapping is as binary because it represents more efficient storage. -However many applications prefer the readability of character storage. +However, many applications prefer the readability of character storage. To switch the default mapping, simply call `MetadataBuilder.applyBasicType( UUIDCharType.INSTANCE, UUID.class.getName() )`. ==== @@ -823,13 +961,13 @@ To switch the default mapping, simply call `MetadataBuilder.applyBasicType( UUID As mentioned, the default mapping for UUID attributes. Maps the UUID to a `byte[]` using `java.util.UUID#getMostSignificantBits` and `java.util.UUID#getLeastSignificantBits` and stores that as `BINARY` data. -Chosen as the default simply because it is generally more efficient from storage perspective. +Chosen as the default simply because it is generally more efficient from a storage perspective. ==== UUID as (var)char Maps the UUID to a String using `java.util.UUID#toString` and `java.util.UUID#fromString` and stores that as `CHAR` or `VARCHAR` data. -==== PostgeSQL-specific UUID +==== PostgreSQL-specific UUID [IMPORTANT] ==== @@ -842,7 +980,7 @@ Note that this can cause difficulty as the driver chooses to map many different ==== UUID as identifier -Hibernate supports using UUID values as identifiers, and they can even be generated on user's behalf. +Hibernate supports using UUID values as identifiers, and they can even be generated on the user's behalf. For details, see the discussion of generators in <>. [[basic-datetime]] @@ -989,7 +1127,7 @@ Programmatically:: TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) ); ---- -However, as explained in http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/[this article], this is not always practical especially for front-end nodes. +However, as explained in http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/[this article], this is not always practical, especially for front-end nodes. For this reason, Hibernate offers the `hibernate.jdbc.time_zone` configuration property which can be configured: Declaratively, at the `SessionFactory` level:: @@ -1023,10 +1161,10 @@ it also supports `AttributeConverter` as well. With a custom `AttributeConverter`, the application developer can map a given JDBC type to an entity basic type. -In the following example, the `java.util.Period` is going to be mapped to a `VARCHAR` database column. +In the following example, the `java.time.Period` is going to be mapped to a `VARCHAR` database column. [[basic-jpa-convert-period-string-converter-example]] -.`java.util.Period` custom `AttributeConverter` +.`java.time.Period` custom `AttributeConverter` ==== [source, JAVA, indent=0] ---- @@ -1037,7 +1175,7 @@ include::{sourcedir}/converter/PeriodStringConverter.java[tags=basic-jpa-convert To make use of this custom converter, the `@Convert` annotation must decorate the entity attribute. [[basic-jpa-convert-period-string-converter-mapping-example]] -.Entity using the custom `java.util.Period` `AttributeConverter` mapping +.Entity using the custom `java.time.Period` `AttributeConverter` mapping ==== [source, JAVA, indent=0] ---- @@ -1062,7 +1200,7 @@ include::{extrasdir}/basic/basic-jpa-convert-period-string-converter-sql-example In cases when the Java type specified for the "database side" of the conversion (the second `AttributeConverter` bind parameter) is not known, Hibernate will fallback to a `java.io.Serializable` type. -If the Java type is not know to Hibernate, you will encounter the following message: +If the Java type is not known to Hibernate, you will encounter the following message: > HHH000481: Encountered Java type for which we could not locate a JavaTypeDescriptor and which does not appear to implement equals and/or hashCode. > This can lead to significant performance problems when performing equality/dirty checking involving this Java type. @@ -1153,7 +1291,7 @@ include::{sourcedir}/basic/JpaQuotingTest.java[tags=basic-jpa-quoting-example] ---- ==== -Because `name` and `number` are reserved words, the `Product` entity mapping uses backtricks to quote these column names. +Because `name` and `number` are reserved words, the `Product` entity mapping uses backticks to quote these column names. When saving the following `Product entity`, Hibernate generates the following SQL insert statement: @@ -1222,8 +1360,8 @@ Properties marked as generated must additionally be _non-insertable_ and _non-up Only `@Version` and `@Basic` types can be marked as generated. `NEVER` (the default):: the given property value is not generated within the database. -`INSERT`:: the given property value is generated on insert, but is not regenerated on subsequent updates. Properties like _creationTimestamp_ fall into this category. -`ALWAYS`:: the property value is generated both on insert and on update. +`INSERT`:: the given property value is generated on insert but is not regenerated on subsequent updates. Properties like _creationTimestamp_ fall into this category. +`ALWAYS`:: the property value is generated both on insert and update. To mark a property as generated, use The Hibernate specific `@Generated` annotation. @@ -1370,6 +1508,61 @@ include::{extrasdir}/basic/mapping-generated-CreationTimestamp-persist-example.s ---- ==== +[[mapping-generated-UpdateTimestamp]] +===== `@UpdateTimestamp` annotation + +The `@UpdateTimestamp` annotation instructs Hibernate to set the annotated entity attribute with the current timestamp value of the JVM +when the entity is being persisted. + +The supported property types are: + +- `java.util.Date` +- `java.util.Calendar` +- `java.sql.Date` +- `java.sql.Time` +- `java.sql.Timestamp` + +[[mapping-generated-UpdateTimestamp-example]] +.`@UpdateTimestamp` mapping example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/generated/UpdateTimestampTest.java[tags=mapping-generated-UpdateTimestamp-example] +---- +==== + +When the `Bid` entity is persisted, Hibernate is going to populate the underlying `updated_on` column with the current JVM timestamp value: + +[[mapping-generated-UpdateTimestamp-persist-example]] +.`@UpdateTimestamp` persist example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/generated/UpdateTimestampTest.java[tags=mapping-generated-UpdateTimestamp-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-generated-UpdateTimestamp-persist-example.sql[] +---- +==== + +When updating the `Bid` entity, Hibernate is going to modify the `updated_on` column with the current JVM timestamp value: + +[[mapping-generated-UpdateTimestamp-update-example]] +.`@UpdateTimestamp` update example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/generated/UpdateTimestampTest.java[tags=mapping-generated-UpdateTimestamp-update-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-generated-UpdateTimestamp-update-example.sql[] +---- +==== + [[mapping-generated-ValueGenerationType]] ===== `@ValueGenerationType` meta-annotation @@ -1380,7 +1573,7 @@ But `@ValueGenerationType` exposes more features than what `@Generated` currentl to leverage some of those features, you'd simply wire up a new generator annotation. As you'll see in the following examples, the `@ValueGenerationType` meta-annotation is used when declaring the custom annotation used to mark the entity properties that need a specific generation strategy. -The actual generation logic must be implemented in class that implements the `AnnotationValueGeneration` interface. +The actual generation logic must be added to the class that implements the `AnnotationValueGeneration` interface. [[mapping-database-generated-value]] ====== Database-generated values @@ -1489,7 +1682,7 @@ include::{extrasdir}/basic/mapping-column-read-and-write-composite-type-persiste ==== `@Formula` Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. -You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment) +You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read-only (its value is calculated by your formula fragment) [NOTE] ==== @@ -1529,7 +1722,7 @@ The SQL fragment can be as complex as you want and even include subselects. [[mapping-column-where]] ==== `@Where` -Sometimes, you want to filter out entities or collections using a custom SQL criteria. +Sometimes, you want to filter out entities or collections using custom SQL criteria. This can be achieved using the `@Where` annotation, which can be applied to entities and collections. [[mapping-where-example]] @@ -1544,7 +1737,7 @@ include::{sourcedir}/basic/WhereTest.java[tags=mapping-where-example] If the database contains the following entities: [[mapping-where-persistence-example]] -.Persisting an fetching entities with a `@Where` mapping +.Persisting and fetching entities with a `@Where` mapping ==== [source, JAVA, indent=0] ---- @@ -1589,25 +1782,90 @@ include::{extrasdir}/basic/mapping-where-collection-query-example.sql[] ---- ==== +[[mapping-where-join-table]] +==== `@WhereJoinTable` + +Just like `@Where` annotation, `@WhereJoinTable` is used to filter out collections using a joined table (e.g. @ManyToMany association). + +[[mapping-where-join-table-example]] +.`@WhereJoinTable` mapping example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/WhereJoinTableTest.java[tags=mapping-where-join-table-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-where-join-table-example.sql[] +---- +==== + +In the example above, the current week `Reader` entities are included in the `currentWeekReaders` collection +which uses the `@WhereJoinTable` annotation to filter the joined table rows according to the provided SQL clause. + +Considering that the following two `Book_Reader` entries are added into our system: + +[[mapping-where-join-table-persist-example]] +.`@WhereJoinTable` test data +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/WhereJoinTableTest.java[tags=mapping-where-join-table-persist-example] +---- +==== + +When fetching the `currentWeekReaders` collection, Hibernate is going to find one one entry: + +[[mapping-where-join-table-fetch-example]] +.`@WhereJoinTable` fetch example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/WhereJoinTableTest.java[tags=mapping-where-join-table-fetch-example] +---- +==== + [[mapping-column-filter]] ==== `@Filter` -The `@Filter` annotation is another way to filter out entities or collections using a custom SQL criteria, for both entities and collections. +The `@Filter` annotation is another way to filter out entities or collections using custom SQL criteria. Unlike the `@Where` annotation, `@Filter` allows you to parameterize the filter clause at runtime. -[[mapping-filter-example]] -.`@Filter` mapping usage +Now, considering we have the following `Account` entity: + +[[mapping-filter-account-example]] +.`@Filter` mapping entity-level usage ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-example] +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-Account-example] ---- ==== -If the database contains the following entities: +[NOTE] +==== +Notice that the `active` property is mapped to the `active_status` column. + +This mapping was done to show you that the `@Filter` condition uses a SQL condition and not a JPQL filtering predicate. +==== + +As already explained, we can also apply the `@Filter` annotation for collections as illustrated by the `Client` entity: + +[[mapping-filter-client-example]] +.`@Filter` mapping collection-level usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-Client-example] +---- +==== + +If we persist a `Client` with three associated `Account` entities, +Hibernate will execute the following SQL statements: [[mapping-filter-persistence-example]] -.Persisting an fetching entities with a `@Filter` mapping +.Persisting and fetching entities with a `@Filter` mapping ==== [source, JAVA, indent=0] ---- @@ -1621,6 +1879,21 @@ include::{extrasdir}/basic/mapping-filter-persistence-example.sql[] ==== By default, without explicitly enabling the filter, Hibernate is going to fetch all `Account` entities. + +[[mapping-no-filter-entity-query-example]] +.Query entities mapped without activating the `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-no-filter-entity-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-entity-query-example.sql[] +---- +==== + If the filter is enabled and the filter parameter value is provided, then Hibernate is going to apply the filtering criteria to the associated `Account` entities. @@ -1641,6 +1914,7 @@ include::{extrasdir}/basic/mapping-filter-entity-query-example.sql[] [IMPORTANT] ==== Filters apply to entity queries, but not to direct fetching. + Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context. [[mapping-filter-entity-example]] @@ -1659,7 +1933,22 @@ As you can see from the example above, contrary to an entity query, the filter d ==== Just like with entity queries, collections can be filtered as well, but only if the filter is explicitly enabled on the currently running Hibernate `Session`. -This way, when fetching the `accounts` collections, Hibernate is going to apply the `@Filter` clause filtering criteria to the associated collection entries. + +[[mapping-no-filter-collection-query-example]] +.Traversing collections without activating the `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-no-filter-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-collection-query-example.sql[] +---- +==== + +When activating the `@Filter` and fetching the `accounts` collections, Hibernate is going to apply the filter condition to the associated collection entries. [[mapping-filter-collection-query-example]] .Traversing collections mapped with `@Filter` @@ -1685,7 +1974,7 @@ The main advantage of `@Filter` over the `@Where` clause is that the filtering c It's not possible to combine the `@Filter` and `@Cache` collection annotations. This limitation is due to ensuring consistency and because the filtering information is not stored in the second-level cache. -If caching was allowed for a currently filtered collection, then the second-level cache would store only a subset of the whole collection. +If caching were allowed for a currently filtered collection, then the second-level cache would store only a subset of the whole collection. Afterward, every other Session will get the filtered collection from the cache, even if the Session-level filters have not been explicitly activated. For this reason, the second-level collection cache is limited to storing whole collections, and not subsets. @@ -1697,7 +1986,7 @@ For this reason, the second-level collection cache is limited to storing whole c When using the `@Filter` annotation with collections, the filtering is done against the child entries (entities or embeddables). However, if you have a link table between the parent entity and the child table, then you need to use the `@FilterJoinTable` to filter child entries according to some column contained in the join table. -The `@FilterJoinTable` annotation can be, therefore, applied to a unidirectional `@OneToMany` collection as illustrate din the following mapping: +The `@FilterJoinTable` annotation can be, therefore, applied to a unidirectional `@OneToMany` collection as illustrated in the following mapping: [[mapping-filter-join-table-example]] .`@FilterJoinTable` mapping usage @@ -1708,10 +1997,14 @@ include::{sourcedir}/basic/FilterJoinTableTest.java[tags=mapping-filter-join-tab ---- ==== -If the database contains the following entities: +The `firstAccounts` filter will allow us to get only the `Account` entities that have the `order_id` +(which tells the position of every entry inside the `accounts` collection) +less than a given number (e.g. `maxOrderId`). + +Let's assume our database contains the following entities: [[mapping-filter-join-table-persistence-example]] -.Persisting an fetching entities with a `@FilterJoinTable` mapping +.Persisting and fetching entities with a `@FilterJoinTable` mapping ==== [source, JAVA, indent=0] ---- @@ -1724,10 +2017,26 @@ include::{extrasdir}/basic/mapping-filter-join-table-persistence-example.sql[] ---- ==== -The collections can be filtered if the associated filter is enabled on the currently running Hibernate `Session`. -This way, when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria to the associated collection entries. +The collections can be filtered only if the associated filter is enabled on the currently running Hibernate `Session`. -[[mapping-filter-collection-query-example]] +[[mapping-no-filter-join-table-collection-query-example]] +.Traversing collections mapped with `@FilterJoinTable` without enabling the filter +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterJoinTableTest.java[tags=mapping-no-filter-join-table-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-join-table-collection-query-example.sql[] +---- +==== + +If we enable the filter and set the `maxOrderId` to `1` when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria, and we will get just +`2` `Account` entities, with the `order_id` values of `0` and `1`. + +[[mapping-filter-join-table-collection-query-example]] .Traversing collections mapped with `@FilterJoinTable` ==== [source, JAVA, indent=0] @@ -1741,6 +2050,40 @@ include::{extrasdir}/basic/mapping-filter-join-table-collection-query-example.sq ---- ==== +[[mapping-column-filter-sql-fragment-alias]] +==== `@Filter` with `@SqlFragmentAlias` + +When using the `@Filter` annotation and working with entities that are mapped onto multiple database tables, +you will need to use the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SqlFragmentAlias.html[`@SqlFragmentAlias`] annotation +if the `@Filter` defines a condition that uses predicates across multiple tables. + +[[mapping-filter-sql-fragment-alias-example]] +.`@SqlFragmentAlias` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterSqlFragementAliasTest.java[tags=mapping-filter-sql-fragment-alias-example] +---- +==== + +Now, when fetching the `Account` entities and activating the filter, +Hibernate is going to apply the right table aliases to the filter predicates: + +[[mapping-filter-sql-fragment-alias-query-example]] +.Fetching a collection filtered with `@SqlFragmentAlias` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterSqlFragementAliasTest.java[tags=mapping-filter-sql-fragment-alias-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-filter-sql-fragment-alias-query-example.sql[] +---- +==== + [[mapping-column-any]] ==== `@Any` mapping @@ -1816,19 +2159,40 @@ include::{sourcedir}/basic/any/package-info.java[tags=mapping-column-any-meta-de It is recommended to place the `@AnyMetaDef` mapping as a package metadata. ==== -To see how the `@Any` annotation in action, consider the following example: +To see the `@Any` annotation in action, consider the next examples. -[[mapping-column-any-persistence-example]] -.`@Any` mapping usage +If we persist an `IntegerProperty` as well as a `StringProperty` entity, and associate +the `StringProperty` entity with a `PropertyHolder`, +Hibernate will generate the following SQL queries: + +[[mapping-column-any-persist-example]] +.`@Any` mapping persist example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/any/AnyTest.java[tags=mapping-column-any-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-any-persist-example.sql[] +---- +==== + +When fetching the `PropertyHolder` entity and navigating its `property` association, +Hibernate will fetch the associated `StringProperty` entity like this: + +[[mapping-column-any-query-example]] +.`@Any` mapping query example ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/any/AnyTest.java[tags=mapping-column-any-persistence-example] +include::{sourcedir}/basic/any/AnyTest.java[tags=mapping-column-any-query-example] ---- [source, SQL, indent=0] ---- -include::{extrasdir}/basic/mapping-column-any-persistence-example.sql[] +include::{extrasdir}/basic/mapping-column-any-query-example.sql[] ---- ==== @@ -1839,6 +2203,7 @@ The `@Any` mapping is useful to emulate a `@ManyToOne` association when there ca To emulate a `@OneToMany` association, the `@ManyToAny` annotation must be used. In the following example, the `PropertyRepository` entity has a collection of `Property` entities. + The `repository_properties` link table holds the associations between `PropertyRepository` and `Property` entities. [[mapping-column-many-to-any-example]] @@ -1855,20 +2220,40 @@ include::{extrasdir}/basic/mapping-column-many-to-any-example.sql[] ---- ==== +To see the `@ManyToAny` annotation in action, consider the next examples. -To see how the `@ManyToAny` annotation works, consider the following example: +If we persist an `IntegerProperty` as well as a `StringProperty` entity, +and associate both of them with a `PropertyRepository` parent entity, +Hibernate will generate the following SQL queries: -[[mapping-column-many-to-any-persistence-example]] -.`@Any` mapping usage +[[mapping-column-many-to-any-persist-example]] +.`@ManyToAny` mapping persist example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/any/ManyToAnyTest.java[tags=mapping-column-many-to-any-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-many-to-any-persist-example.sql[] +---- +==== + +When fetching the `PropertyRepository` entity and navigating its `properties` association, +Hibernate will fetch the associated `IntegerProperty` and `StringProperty` entities like this: + +[[mapping-column-many-to-any-query-example]] +.`@ManyToAny` mapping query example ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/any/ManyToAnyTest.java[tags=mapping-column-many-to-any-persistence-example] +include::{sourcedir}/basic/any/ManyToAnyTest.java[tags=mapping-column-many-to-any-query-example] ---- [source, SQL, indent=0] ---- -include::{extrasdir}/basic/mapping-column-many-to-any-persistence-example.sql[] +include::{extrasdir}/basic/mapping-column-many-to-any-query-example.sql[] ---- ==== @@ -1925,7 +2310,7 @@ Therefore, the `@JoinFormula` annotation is used to define a custom join associa [[mapping-JoinColumnOrFormula]] ==== `@JoinColumnOrFormula` mapping -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/JoinColumnOrFormula.html[`@JoinColumnOrFormula`] annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to tak into consideration a column value as well as a `@JoinFormula`. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/JoinColumnOrFormula.html[`@JoinColumnOrFormula`] annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a `@JoinFormula`. [[mapping-JoinColumnOrFormula-example]] .`@JoinColumnOrFormula` mapping usage @@ -1941,12 +2326,12 @@ include::{extrasdir}/basic/mapping-JoinColumnOrFormula-example.sql[] ---- ==== -The `country` association in the `User` entity is mapped by the `language` property value and the the associated `Country` `is_default` column value. +The `country` association in the `User` entity is mapped by the `language` property value and the associated `Country` `is_default` column value. Considering we have the following entities: [[mapping-JoinColumnOrFormula-persistence-example]] -.`@JoinColumnOrFormula` mapping usage +.`@JoinColumnOrFormula` persist example ==== [source, JAVA, indent=0] ---- @@ -1957,7 +2342,7 @@ include::{sourcedir}/basic/JoinColumnOrFormulaTest.java[tags=mapping-JoinColumnO When fetching the `User` entities, the `country` property is mapped by the `@JoinColumnOrFormula` expression: [[mapping-JoinColumnOrFormula-fetching-example]] -.`@JoinColumnOrFormula` mapping usage +.`@JoinColumnOrFormula` fetching example ==== [source, JAVA, indent=0] ---- @@ -1971,3 +2356,98 @@ include::{extrasdir}/basic/mapping-JoinColumnOrFormula-fetching-example.sql[] ==== Therefore, the `@JoinColumnOrFormula` annotation is used to define a custom join association between the parent-child association. + +[[mapping-Target]] +==== `@Target` mapping + +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify the implementation class of a given association that is mapped via an interface. +The +http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html[`@ManyToOne`], +http://docs.oracle.com/javaee/7/api/javax/persistence/OneToOne.html[`@OneToOne`], +http://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html[`@OneToMany`], and +http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToMany.html[`@ManyToMany`] +feature a http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html#targetEntity--[`targetEntity`] attribute to specify the actual class of the entity association when an interface is used for the mapping. + +The http://docs.oracle.com/javaee/7/api/javax/persistence/ElementCollection.html[`@ElementCollection`] association has a http://docs.oracle.com/javaee/7/api/javax/persistence/ElementCollection.html#targetClass--[`targetClass`] attribute for the same purpose. + +However, for simple embeddable types, there is no such construct and so you need to use the Hibernate-specific `@Target` annotation instead. + +[[mapping-Target-example]] +.`@Target` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/TargetTest.java[tags=mapping-Target-example] +---- +==== + +The `coordinates` embeddable type is mapped as the `Coordinates` interface. +However, Hibernate needs to know the actual implementation tye, which is `GPS` in this case, +hence the `@Target` annotation is used to provide this information. + +Assuming we have persisted the following `City` entity: + +[[mapping-Target-persist-example]] +.`@Target` persist example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/TargetTest.java[tags=mapping-Target-persist-example] +---- +==== + +When fetching the `City` entity, the `coordinates` property is mapped by the `@Target` expression: + +[[mapping-Target-fetching-example]] +.`@Target` fetching example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/TargetTest.java[tags=mapping-Target-fetching-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-Target-fetching-example.sql[] +---- +==== + +Therefore, the `@Target` annotation is used to define a custom join association between the parent-child association. + +[[mapping-Parent]] +==== `@Parent` mapping + +The Hibernate-specific `@Parent` annotation allows you to reference the owner entity from within an embeddable. + +[[mapping-Parent-example]] +.`@Parent` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/ParentTest.java[tags=mapping-Parent-example] +---- +==== + +Assuming we have persisted the following `City` entity: + +[[mapping-Parent-persist-example]] +.`@Parent` persist example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/ParentTest.java[tags=mapping-Parent-persist-example] +---- +==== + +When fetching the `City` entity, the `city` property of the embeddable type acts as a back reference to the owning parent entity: + +[[mapping-Parent-fetching-example]] +.`@Parent` fetching example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/ParentTest.java[tags=mapping-Parent-fetching-example] +---- +==== + +Therefore, the `@Parent` annotation is used to define the association between an embeddable type and the owning entity. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc index 0bde60d9d7d3..ae6e9663c76c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc @@ -3,11 +3,11 @@ :sourcedir: ../../../../../test/java/org/hibernate/userguide/collections :extrasdir: extras/collections -Naturally Hibernate also allows to persist collections. -These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, components and references to other entities. +Naturally Hibernate also allows persisting collections. +These persistent collections can contain almost any other Hibernate type, including basic types, custom types, embeddables, and references to other entities. In this context, the distinction between value and reference semantics is very important. -An object in a collection might be handled with _value_ semantics (its life cycle being fully depends on the collection owner), -or it might be a reference to another entity with its own life cycle. +An object in a collection might be handled with _value_ semantics (its lifecycle being fully dependant on the collection owner), +or it might be a reference to another entity with its own lifecycle. In the latter case, only the _link_ between the two objects is considered to be a state held by the collection. The owner of the collection is always an entity, even if the collection is defined by an embeddable type. @@ -46,7 +46,7 @@ The persistent collections injected by Hibernate behave like `ArrayList`, `HashS [[collections-synopsis]] ==== Collections as a value type -Value and embeddable type collections have a similar behavior as simple value types because they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. +Value and embeddable type collections have a similar behavior to basic types since they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. If a collection is passed from one persistent object to another, its elements might be moved from one table to another. [IMPORTANT] @@ -170,7 +170,7 @@ In the following sections, we will go through all these collection types and dis [[collections-bag]] ==== Bags -Bags are unordered lists and we can have unidirectional bags or bidirectional ones. +Bags are unordered lists, and we can have unidirectional bags or bidirectional ones. [[collections-unidirectional-bag]] ===== Unidirectional bags @@ -270,7 +270,7 @@ include::{extrasdir}/collections-bidirectional-bag-orphan-removal-example.sql[] ---- ==== -When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon disassociating the child entity reference. +When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon dissociating the child entity reference. [[collections-list]] ==== Ordered Lists @@ -380,6 +380,72 @@ include::{extrasdir}/collections-bidirectional-ordered-list-order-column-example When fetching the collection, Hibernate will use the fetched ordered columns to sort the elements according to the `@OrderColumn` mapping. +[[collections-customizing-ordered-list-ordinal]] +===== Customizing ordered list ordinal + +You can customize the ordinal of the underlying ordered list by using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/ListIndexBase.html[`@ListIndexBase`] annotation. + +[[collections-customizing-ordered-list-ordinal-mapping-example]] +.`@ListIndexBase` mapping example +==== +[source,java] +---- +include::{sourcedir}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-mapping-example,indent=0] +---- +==== + +When inserting two `Phone` records, Hibernate is going to start the List index from 100 this time. + +[[collections-customizing-ordered-list-ordinal-persist-example]] +.`@ListIndexBase` persist example +==== +[source,java] +---- +include::{sourcedir}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-persist-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-customizing-ordered-list-ordinal-persist-example.sql[] +---- +==== + +[[collections-customizing-ordered-by-sql-clause]] +===== Customizing ORDER BY SQL clause + +While the JPA +http://docs.oracle.com/javaee/7/api/javax/persistence/OrderBy.html[`@OrderBy`] annotation allows you to specify the entity attributes used for sorting +when fetching the current annotated collection, the Hibernate specific +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OrderBy.html[`@OrderBy`] annotation is used to specify a *SQL* clause instead. + +In the following example, the `@OrderBy` annotation uses the `CHAR_LENGTH` SQL function to order the `Article` entities +by the number of characters of the `name` attribute. + +[[collections-customizing-ordered-by-sql-clause-mapping-example]] +.`@OrderBy` mapping example +==== +[source,java] +---- +include::{sourcedir}/OrderedBySQLTest.java[tags=collections-customizing-ordered-by-sql-clause-mapping-example,indent=0] +---- +==== + +When fetching the `articles` collection, Hibernate uses the ORDER BY SQL clause provided by the mapping: + +[[collections-customizing-ordered-by-sql-clause-fetching-example]] +.`@OrderBy` fetching example +==== +[source,java] +---- +include::{sourcedir}/OrderedBySQLTest.java[tags=collections-customizing-ordered-by-sql-clause-fetching-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-customizing-ordered-by-sql-clause-fetching-example.sql[] +---- +==== + [[collections-set]] ==== Sets @@ -400,7 +466,7 @@ include::{sourcedir}/UnidirectionalSetTest.java[tags=collections-unidirectional- ==== The unidirectional set lifecycle is similar to that of the <>, so it can be omitted. -The only difference is that `Set` doesn't allow duplicates, but this constraint is enforced by the Java object contract rather then the database mapping. +The only difference is that `Set` doesn't allow duplicates, but this constraint is enforced by the Java object contract rather than the database mapping. [NOTE] ==== @@ -475,7 +541,7 @@ include::{sourcedir}/UnidirectionalComparatorSortedSetTest.java[lines=75..77,ind [[collections-map]] ==== Maps -A `java.util.Map` is ternary association because it required a parent entity a map key and a value. +A `java.util.Map` is a ternary association because it requires a parent entity, a map key, and a value. An entity can either be a map key or a map value, depending on the mapping. Hibernate allows using the following map keys: @@ -520,12 +586,126 @@ include::{extrasdir}/collections-map-value-type-entity-key-add-example.sql[] ---- ==== +[[collections-map-custom-key-type]] +===== Maps with a custom key type + +Hibernate defines the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/MapKeyType.html[`@MapKeyType`] annotation +which you can use to customize the `Map` key type. + +Considering you have the following tables in your database: + +[source,sql] +---- +include::{extrasdir}/collections-map-custom-key-type-sql-example.sql[] +---- + +The `call_register` records the call history for every `person`. +The `call_timestamp_epoch` column stores the phone call timestamp as a Unix timestamp since the Unix epoch. + +[NOTE] +==== +The `@MapKeyColumn` annotation is used to define the table column holding the key +while the `@Column` mapping gives the value of the `java.util.Map` in question. +==== + +Since we want to map all the calls by their associated `java.util.Date`, not by their timestamp since epoch which is a number, the entity mapping looks as follows: + +[[collections-map-custom-key-type-mapping-example]] +.`@MapKeyType` mapping example +==== +[source,java] +---- +include::{sourcedir}/MapKeyTypeTest.java[tags=collections-map-custom-key-type-mapping-example,indent=0] +---- +==== + +The associated `TimestampEpochType` looks as follows: + +---- +include::{sourcedir}/type/TimestampEpochType.java[tags=collections-map-custom-key-type-mapping-example,indent=0] +---- + +The `TimestampEpochType` allows us to map a Unix timestamp since epoch to a `java.util.Date`. +But, without the `@MapKeyType` Hibernate annotation, it would not be possible to customize the `Map` key type. + +[[collections-map-key-class]] +===== Maps having an interface type as the key + +Considering you have the following `PhoneNumber` interface with an implementation given by the `MobilePhone` class type: + +[[collections-map-key-class-type-mapping-example]] +.`PhoneNumber` interface and the `MobilePhone` class type +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-type-mapping-example,indent=0] +---- +==== + +If you want to use the `PhoneNumber` interface as a `java.util.Map` key, then you need to supply the +http://docs.oracle.com/javaee/7/api/javax/persistence/MapKeyClass.html[`@MapKeyClass`] annotation as well. + +[[collections-map-key-class-mapping-example]] +.`@MapKeyClass` mapping example +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-mapping-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-map-key-class-mapping-example.sql[] +---- +==== + +When inserting a `Person` with a `callRegister` containing 2 `MobilePhone` references, +Hibernate generates the following SQL statements: + +[[collections-map-key-class-persist-example]] +.`@MapKeyClass` persist example +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-persist-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-map-key-class-persist-example.sql[] +---- +==== + +When fetching a `Person` and accessing the `callRegister` `Map`, +Hibernate generates the following SQL statements: + +[[collections-map-key-class-fetch-example]] +.`@MapKeyClass` fetch example +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-fetch-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-map-key-class-fetch-example.sql[] +---- +==== + [[collections-map-unidirectional]] ===== Unidirectional maps A unidirectional map exposes a parent-child association from the parent-side only. + The following example shows a unidirectional map which also uses a `@MapKeyTemporal` annotation. -The map key is a timestamp and it's taken from the child entity table. +The map key is a timestamp, and it's taken from the child entity table. + +[NOTE] +==== +The `@MapKey` annotation is used to define the entity attribute used as a key of the `java.util.Map` in question. +==== [[collections-map-unidirectional-example]] .Unidirectional Map @@ -545,6 +725,7 @@ include::{extrasdir}/collections-map-unidirectional-example.sql[] ===== Bidirectional maps Like most bidirectional associations, this relationship is owned by the child-side while the parent is the inverse side and can propagate its own state transitions to the child entities. + In the following example, you can see that `@MapKeyEnumerated` was used so that the `Phone` enumeration becomes the map key. [[collections-map-bidirectional-example]] @@ -564,9 +745,14 @@ include::{extrasdir}/collections-map-bidirectional-example.sql[] [[collections-array]] ==== Arrays -When it comes to arrays, there is quite a difference between Java arrays and relational database array types (e.g. VARRAY, ARRAY). -First, not all database systems implement the SQL-99 ARRAY type, and, for this reason, Hibernate doesn't support native database array types. -Second, Java arrays are relevant for basic types only since storing multiple embeddables or entities should always be done using the Java Collection API. +When discussing arrays, it is important to understand the distinction between SQL array types and Java arrays that are mapped as part of the application's domain model. + +Not all databases implement the SQL-99 ARRAY type and, for this reason, +Hibernate doesn't support native database array types. + +Hibernate does support the mapping of arrays in the Java domain model - conceptually the same as mapping a List. +However, it is important to realize that it is impossible for Hibernate to offer lazy-loading for arrays of entities and, for this reason, +it is strongly recommended to map a "collection" of entities using a List rather than an array. [[collections-array-binary]] ==== Arrays as binary @@ -574,7 +760,7 @@ Second, Java arrays are relevant for basic types only since storing multiple emb By default, Hibernate will choose a BINARY type, as supported by the current `Dialect`. [[collections-array-binary-example]] -.Binary arrays +.Arrays stored as binary ==== [source,java] ---- @@ -587,6 +773,15 @@ include::{extrasdir}/collections-array-binary-example.sql[] ---- ==== +[NOTE] +==== +If you want to map arrays such as `String[]` or `int[]` to database-specific array types like PostgreSQL `integer[]` or `text[]`, +you need to write a custom Hibernate Type. + +Check out https://vladmihalcea.com/how-to-map-java-and-sql-arrays-with-jpa-and-hibernate/[this article] for an example of how to write +such a custom Hibernate Type. +==== + [[collections-as-basic]] ==== Collections as basic value type @@ -656,7 +851,7 @@ The reason why the `Queue` interface is not used for the entity attribute is bec - `java.util.SortedSet` - `java.util.SortedMap` -However, the custom collection type can still be customized as long as the base type is one of the aformentioned persistent types. +However, the custom collection type can still be customized as long as the base type is one of the aforementioned persistent types. ==== This way, the `Phone` collection can be used as a `java.util.Queue`: diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc index 3fad759fb915..b7517cb8ddeb 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc @@ -1,6 +1,8 @@ [[dynamic-model]] === Dynamic Model -:sourcedir: extras +:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/dynamic +:mappingdir: ../../../../../test/resources/org/hibernate/userguide/mapping/dynamic +:extrasdir: extras [IMPORTANT] ==== @@ -12,25 +14,54 @@ On the other hand, Hibernate can work with both POJO entities as well as with dy ==== Dynamic mapping models Persistent entities do not necessarily have to be represented as POJO/JavaBean classes. -Hibernate also supports dynamic models (using `Map`s of `Map`s at runtime). +Hibernate also supports dynamic models (using `Map` of `Maps` at runtime). With this approach, you do not write persistent classes, only mapping files. A given entity has just one entity mode within a given SessionFactory. This is a change from previous versions which allowed to define multiple entity modes for an entity and to select which to load. -Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity, and vice versa. +Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity and vice versa. -.Working with Dynamic Domain Models +[[mapping-model-dynamic-example]] +.Dynamic domain model Hibernate mapping +==== +[source,xml] +---- +include::{mappingdir}/Book.hbm.xml[tag=mapping-model-dynamic-example, indent=0] +---- +==== + +After you defined your entity mapping, you need to instruct Hibernate to use the dynamic mapping mode: + +[[mapping-model-dynamic-setting-example]] +.Dynamic domain model Hibernate mapping +==== +[source,java] +---- +include::{sourcedir}/DynamicEntityTest.java[tag=mapping-model-dynamic-setting-example, indent=0] +---- +==== + +When you are going to save the following `Book` dynamic entity, +Hibernate is going to generate the following SQL statement: + +[[mapping-model-dynamic-persist-example]] +.Persist dynamic entity ==== [source,java] ---- -include::{sourcedir}/dynamic/listing10.java[] +include::{sourcedir}/DynamicEntityTest.java[tag=mapping-model-dynamic-example, indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/dynamic/mapping-model-dynamic-example.sql[indent=0] ---- ==== [NOTE] ==== -The main advantage of dynamic models is quick turnaround time for prototyping without the need for entity class implementation. -The main down-fall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. +The main advantage of dynamic models is the quick turnaround time for prototyping without the need for entity class implementation. +The main downfall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. However, as a result of the Hibernate mapping, the database schema can easily be normalized and sound, allowing to add a proper domain model implementation on top later on. It is also interesting to note that dynamic models are great for certain integration use cases as well. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc index 8a1dc6305bbf..b9e50148ce6c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc @@ -1,34 +1,33 @@ [[embeddables]] === Embeddable types -:sourcedir: extras +:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/embeddable +:extrasdir: extras Historically Hibernate called these components. JPA calls them embeddables. -Either way the concept is the same: a composition of values. -For example we might have a Name class that is a composition of first-name and last-name, or an Address class that is a composition of street, city, postal code, etc. +Either way, the concept is the same: a composition of values. + +For example, we might have a `Publisher` class that is a composition of `name` and `country`, +or a `Location` class that is a composition of `country` and `city`. .Usage of the word _embeddable_ [NOTE] ==== -To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred as `@Embeddable`. +To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred to as `@Embeddable`. -Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred as _embeddable_. +Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred to as _embeddable_. ==== -.Simple embeddable type example +[[embeddable-type-mapping-example]] +.Embeddable type example ==== [source,java] ---- -include::{sourcedir}/embeddable/Name.java[] ----- - -[source,java] ----- -include::{sourcedir}/embeddable/Address.java[] +include::{sourcedir}/NestedEmbeddableTest.java[tag=embeddable-type-mapping-example, indent=0] ---- ==== -An embeddable type is another form of value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see <>). +An embeddable type is another form of a value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see <>). Embeddable types can be made up of basic values as well as associations, with the caveat that, when used as collection elements, they cannot define collections themselves. @@ -36,58 +35,52 @@ Embeddable types can be made up of basic values as well as associations, with th Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities. -.Simple Embeddedable +[[simple-embeddable-type-mapping-example]] +.Simple Embeddable ==== [source,java] ---- -include::{sourcedir}/embeddable/Person.java[] +include::{sourcedir}/SimpleEmbeddableTest.java[tag=embeddable-type-mapping-example, indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/embeddable/simple-embeddable-type-mapping-example.sql[] ---- ==== [NOTE] ==== JPA defines two terms for working with an embeddable type: `@Embeddable` and `@Embedded`. -`@Embeddable` is used to describe the mapping type itself (e.g. `Name`). -`@Embedded` is for referencing a given embeddable type (e.g. `person.name`). -==== -So, the embeddable type is represented by the `Name` class and the parent makes use of it through the `person.name` object composition. +`@Embeddable` is used to describe the mapping type itself (e.g. `Publisher`). -.Person table -==== -[source,sql] ----- -include::{sourcedir}/embeddable/Person1.sql[] ----- +`@Embedded` is for referencing a given embeddable type (e.g. `book#publisher`). ==== +So, the embeddable type is represented by the `Publisher` class and +the parent entity makes use of it through the `book#publisher` object composition. + The composed values are mapped to the same table as the parent table. -Composition is part of good OO data modeling (idiomatic Java). +Composition is part of good object-oriented data modeling (idiomatic Java). In fact, that table could also be mapped by the following entity type instead. +[[alternative-to-embeddable-type-mapping-example]] .Alternative to embeddable type composition ==== [source,java] ---- -include::{sourcedir}/embeddable/Person_alt.java[] +include::{sourcedir}/SimpleEmbeddableEquivalentTest.java[tag=embeddable-type-mapping-example, indent=0] ---- ==== -The composition form is certainly more Object-oriented, and that becomes more evident as we work with multiple embeddable types. +The composition form is certainly more object-oriented, and that becomes more evident as we work with multiple embeddable types. [[embeddable-multiple]] ==== Multiple embeddable types -.Multiple embeddable types -==== -[source,java] ----- -include::{sourcedir}/embeddable/Contact.java[] ----- -==== - Although from an object-oriented perspective, it's much more convenient to work with embeddable types, this example doesn't work as-is. -When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands setting the associated column names explicitly. +When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands to set the associated column names explicitly. This requirement is due to how object properties are mapped to database columns. By default, JPA expects a database column having the same name with its associated object property. @@ -95,65 +88,108 @@ When including multiple embeddables, the implicit name-based mapping rule doesn' We have a few options to handle this issue. -[[embeddable-multiple-jpa]] -==== JPA's AttributeOverride +[[embeddable-override]] +==== Overriding Embeddable types JPA defines the `@AttributeOverride` annotation to handle this scenario. +This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings. + +If an Embeddable type is used multiple times in some entity, you need to use the +http://docs.oracle.com/javaee/7/api/javax/persistence/AttributeOverride.html[`@AttributeOverride`] and +http://docs.oracle.com/javaee/7/api/javax/persistence/AssociationOverride.html[`@AssociationOverride`] annotations +to override the default column names defined by the Embeddable. + +Considering you have the following `Publisher` embeddable type +which defines a `@ManyToOne` association with the `Country` entity: -.JPA's AttributeOverride +[[embeddable-type-association-mapping-example]] +.Embeddable type with a `@ManyToOne` association ==== [source,java] ---- -include::{sourcedir}/embeddable/Contact-AttributeOverride.java[] +include::{sourcedir}/EmbeddableOverrideTest.java[tag=embeddable-type-association-mapping-example, indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/embeddable/embeddable-type-association-mapping-example.sql[] ---- ==== -This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings. +Now, if you have a `Book` entity which declares two `Publisher` embeddable types for the ebook and paperback version, +you cannot use the default `Publisher` embeddable mapping since there will be a conflict between the two embeddable column mappings. + +Therefore, the `Book` entity needs to override the embeddable type mappings for each `Publisher` attribute: + +[[embeddable-type-override-mapping-example]] +.Overriding embeddable type attributes +==== +[source,java] +---- +include::{sourcedir}/EmbeddableOverrideTest.java[tag=embeddable-type-override-mapping-example, indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/embeddable/embeddable-type-override-mapping-example.sql[] +---- +==== [[embeddable-multiple-namingstrategy]] -==== ImplicitNamingStrategy +==== Embeddables and ImplicitNamingStrategy [IMPORTANT] ==== This is a Hibernate specific feature. -Users concerned with JPA provider portability should instead prefer explicit column naming with <>. +Users concerned with JPA provider portability should instead prefer explicit column naming with `@AttributeOverride`. ==== Hibernate naming strategies are covered in detail in <>. However, for the purposes of this discussion, Hibernate has the capability to interpret implicit column names in a way that is safe for use with multiple embeddable types. -.Enabling embeddable type safe implicit naming +[[embeddable-multiple-namingstrategy-entity-mapping]] +.Implicit multiple embeddable type mapping ==== [source,java] ---- -include::{sourcedir}/embeddable/component-safe-implicit-naming.java[] +include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-namingstrategy-entity-mapping, indent=0] ---- ==== +To make it work, you need to use the `ImplicitNamingStrategyComponentPathImpl` naming strategy. + +[[embeddable-multiple-ImplicitNamingStrategyComponentPathImpl]] +.Enabling implicit embeddable type mapping using the component path naming strategy ==== -[source,sql] +[source,java] ---- -include::{sourcedir}/embeddable/Contact-ImplicitNamingStrategy.sql[] +include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-ImplicitNamingStrategyComponentPathImpl, indent=0] ---- ==== -Now the "path" to attributes are used in the implicit column naming. -You could even develop your own to do special implicit naming. +Now the "path" to attributes are used in the implicit column naming: + +[source,sql] +---- +include::{extrasdir}/embeddable/embeddable-multiple-namingstrategy-entity-mapping.sql[] +---- + +You could even develop your own naming strategy to do other types of implicit naming strategies. [[embeddable-collections]] ==== Collections of embeddable types -Collections of embeddable types are specifically value collections (as embeddable types are a value type). +Collections of embeddable types are specifically valued collections (as embeddable types are a value type). Value collections are covered in detail in <>. [[embeddable-mapkey]] -==== Embeddable types as Map key +==== Embeddable type as a Map key Embeddable types can also be used as `Map` keys. This topic is converted in detail in <>. [[embeddable-identifier]] -==== Embeddable types as identifiers +==== Embeddable type as identifier Embeddable types can also be used as entity type identifiers. This usage is covered in detail in <>. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc index 8b4b50b798b8..5a0ff41dd556 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc @@ -1,15 +1,18 @@ [[entity]] === Entity types -:sourcedir: ../../../../../test/java/org/hibernate/userguide/locking +:sourcedir-locking: ../../../../../test/java/org/hibernate/userguide/locking +:sourcedir-mapping: ../../../../../test/java/org/hibernate/userguide/mapping/ +:sourcedir-proxy: ../../../../../test/java/org/hibernate/userguide/proxy +:sourcedir-persister: ../../../../../test/java/org/hibernate/userguide/persister :extrasdir: extras .Usage of the word _entity_ [NOTE] ==== The entity type describes the mapping between the actual persistable domain model object and a database table row. -To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred as `@Entity`. +To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred to as `@Entity`. -Throughout this chapter and thereafter, entity types will be simply referred as _entity_. +Throughout this chapter and thereafter, entity types will be simply referred to as _entity_. ==== [[entity-pojo]] @@ -68,17 +71,17 @@ That said, the constructor should be defined with at least package visibility if [[entity-pojo-accessors]] ==== Declare getters and setters for persistent attributes -The JPA specification requires this, otherwise the model would prevent accessing the entity persistent state fields directly from outside the entity itself. +The JPA specification requires this, otherwise, the model would prevent accessing the entity persistent state fields directly from outside the entity itself. Although Hibernate does not require it, it is recommended to follow the JavaBean conventions and define getters and setters for entity persistent attributes. Nevertheless, you can still tell Hibernate to directly access the entity fields. Attributes (whether fields or getters/setters) need not be declared public. -Hibernate can deal with attributes declared with public, protected, package or private visibility. +Hibernate can deal with attributes declared with the public, protected, package or private visibility. Again, if wanting to use runtime proxy generation for lazy loading, the getter/setter should grant access to at least package visibility. [[entity-pojo-identifier]] -==== Provide identifier attribute(s) +==== Providing identifier attribute(s) [IMPORTANT] ==== @@ -96,11 +99,12 @@ We recommend that you declare consistently-named identifier attributes on persis The placement of the `@Id` annotation marks the <>. -.Identifier +[[entity-pojo-identifier-mapping-example]] +.Identifier mapping ==== [source,java] ---- -include::{extrasdir}/entity/Identifier.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-identifier-mapping-example, indent=0] ---- ==== @@ -113,11 +117,12 @@ The main piece in mapping the entity is the `javax.persistence.Entity` annotatio The `@Entity` annotation defines just one attribute `name` which is used to give a specific entity name for use in JPQL queries. By default, the entity name represents the unqualified name of the entity class itself. -.Simple `@Entity` +[[entity-pojo-mapping-example]] +.Simple `@Entity` mapping ==== [source,java] ---- -include::{extrasdir}/entity/SimpleEntity.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-mapping-example, indent=0] ---- ==== @@ -126,11 +131,12 @@ The identifier uniquely identifies each row in that table. By default, the name of the table is assumed to be the same as the name of the entity. To explicitly give the name of the table or to specify other information about the table, we would use the `javax.persistence.Table` annotation. +[[entity-pojo-table-mapping-example]] .Simple `@Entity` with `@Table` ==== [source,java] ---- -include::{extrasdir}/entity/SimpleEntityWithTable.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTableTest.java[tag=entity-pojo-table-mapping-example, indent=0] ---- ==== @@ -159,88 +165,109 @@ Hibernate, however, works hard to make sure that does not happen within a given In fact, Hibernate guarantees equivalence of persistent identity (database row) and Java identity inside a particular session scope. So if we ask a Hibernate `Session` to load that specific Person multiple times we will actually get back the same __instance__: +[[entity-pojo-identity-scope-example]] .Scope of identity ==== [source,java] ---- -include::{extrasdir}/entity/listing1.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-identity-scope-example, indent=0] ---- ==== -Consider another example using a persistent `java.util.Set`: +Consider we have a `Library` parent entity which contains a `java.util.Set` of `Book` entities: +[[entity-pojo-set-mapping-example]] +Library entity mapping +==== +[source,java] +---- +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-set-mapping-example, indent=0] +---- +==== + +[[entity-pojo-set-identity-scope-example]] .Set usage with Session-scoped identity ==== [source,java] ---- -include::{extrasdir}/entity/listing3.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-set-identity-scope-example, indent=0] ---- ==== However, the semantic changes when we mix instances loaded from different Sessions: +[[entity-pojo-multi-session-identity-scope-example]] .Mixed Sessions ==== [source,java] ---- -include::{extrasdir}/entity/listing2.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-multi-session-identity-scope-example, indent=0] ---- [source,java] ---- -include::{extrasdir}/entity/listing4.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-multi-session-set-identity-scope-example, indent=0] ---- ==== -Specifically the outcome in this last example will depend on whether the `Person` class implemented equals/hashCode, and, if so, how. +Specifically, the outcome in this last example will depend on whether the `Book` class +implemented equals/hashCode, and, if so, how. + +If the `Book` class did not override the default equals/hashCode, +then the two `Book` object references are not going to be equal since their references are different. Consider yet another case: +[[entity-pojo-transient-set-identity-scope-example]] .Sets with transient entities ==== [source,java] ---- -include::{extrasdir}/entity/listing5.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-transient-set-identity-scope-example, indent=0] ---- ==== -In cases where you will be dealing with entities outside of a Session (whether they be transient or detached), especially in cases where you will be using them in Java collections, +In cases where you will be dealing with entities outside of a Session (whether they be transient or detached), +especially in cases where you will be using them in Java collections, you should consider implementing equals/hashCode. A common initial approach is to use the entity's identifier attribute as the basis for equals/hashCode calculations: +[[entity-pojo-naive-equals-hashcode-example]] .Naive equals/hashCode implementation ==== [source,java] ---- -include::{extrasdir}/entity/listing6.java[] +include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=entity-pojo-naive-equals-hashcode-example, indent=0] ---- ==== -It turns out that this still breaks when adding transient instance of `Person` to a set as we saw in the last example: +It turns out that this still breaks when adding transient instance of `Book` to a set as we saw in the last example: -.Still trouble +[[entity-pojo-naive-equals-hashcode-persist-example]] +.Auto-generated identifiers with Sets and naive equals/hashCode ==== [source,java] ---- -include::{extrasdir}/entity/listing7.java[] +include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=entity-pojo-naive-equals-hashcode-persist-example, indent=0] ---- ==== -The issue here is a conflict between the use of generated identifier, the contract of `Set` and the equals/hashCode implementations. +The issue here is a conflict between the use of the generated identifier, the contract of `Set`, and the equals/hashCode implementations. `Set` says that the equals/hashCode value for an object should not change while the object is part of the `Set`. -But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the `session.getTransaction().commit()` call. +But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed. Note that this is just a concern when using generated identifiers. If you are using assigned identifiers this will not be a problem, assuming the identifier value is assigned prior to adding to the `Set`. Another option is to force the identifier to be generated and set prior to adding to the `Set`: -.Forcing identifier generation +[[entity-pojo-naive-equals-hashcode-persist-force-flush-example]] +.Forcing the flush before adding to the Set ==== [source,java] ---- -include::{extrasdir}/entity/listing8.java[] +include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=entity-pojo-naive-equals-hashcode-persist-force-flush-example, indent=0] ---- ==== @@ -248,11 +275,23 @@ But this is often not feasible. The final approach is to use a "better" equals/hashCode implementation, making use of a natural-id or business-key. -.Better equals/hashCode with natural-id +[[entity-pojo-natural-id-equals-hashcode-example]] +.Natural Id equals/hashCode ==== [source,java] ---- -include::{extrasdir}/entity/listing9.java[] +include::{sourcedir-mapping}/identifier/NaturalIdEqualsHashCodeEntityTest.java[tag=entity-pojo-natural-id-equals-hashcode-example, indent=0] +---- +==== + +This time, when adding a `Book` to the `Library` `Set`, you can retrieve the `Book` even after it's being persisted: + +[[entity-pojo-natural-id-equals-hashcode-persist-example]] +.Natural Id equals/hashCode persist example +==== +[source,java] +---- +include::{sourcedir-mapping}/identifier/NaturalIdEqualsHashCodeEntityTest.java[tag=entity-pojo-natural-id-equals-hashcode-persist-example, indent=0] ---- ==== @@ -270,141 +309,188 @@ It's possible to use the entity identifier for equality check, but it needs a wo For details on mapping the identifier, see the <> chapter. -[[entity-pojo-optlock]] -==== Mapping optimistic locking - -JPA defines support for optimistic locking based on either a version (sequential numeric) or timestamp strategy. -To enable this style of optimistic locking simply add the `javax.persistence.Version` to the persistent attribute that defines the optimistic locking value. -According to JPA, the valid types for these attributes are limited to: +[[entity-sql-query-mapping]] +==== Mapping the entity to a SQL query -* `int` or `Integer` -* `short` or `Short` -* `long` or `Long` -* `java.sql.Timestamp` +You can map an entity to a SQL query using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Subselect.html[`@Subselect`] annotation. -[[entity-pojo-optlock-version-example]] -.`@Version` annotation mapping +[[mapping-Subselect-example]] +.`@Subselect` entity mapping ==== [source,java] ---- -include::{extrasdir}/entity/Version.java[] +include::{sourcedir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-example,indent=0] ---- +==== + +In the example above, the `Account` entity does not retain any balance since every account operation is registered as an `AccountTransaction`. +To find the `Account` balance, we need to query the `AccountSummary` which shares the same identifier with the `Account` entity. +However, the `AccountSummary` is not mapped to a physical table, but to an SQL query. + +So, if we have the following `AccountTransaction` record, the `AccountSummary` balance will match the proper amount of money in this `Account`. + +[[mapping-Subselect-entity-find-example]] +.Finding a `@Subselect` entity +==== [source,java] ---- -include::{extrasdir}/entity/Timestamp.java[] +include::{sourcedir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-entity-find-example,indent=0] ---- +==== + +If we add a new `AccountTransaction` entity and refresh the `AccountSummary` entity, the balance is updated accordingly: +[[mapping-Subselect-refresh-find-example]] +.Refreshing a `@Subselect` entity +==== [source,java] ---- -include::{extrasdir}/entity/Instant.java[] +include::{sourcedir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-entity-refresh-example,indent=0] ---- ==== -[[entity-pojo-optlock-versionless]] -===== Versionless optimistic locking - -Although the default `@Version` property optimistic locking mechanism is sufficient in many situations, -sometimes, you need rely on the actual database row column values to prevent *lost updates*. - -Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". -This is also useful for use with modeling legacy schemas. +[TIP] +==== +The goal of the `@Synchronize` annotation in the `AccountSummary` entity mapping is to instruct Hibernate which database tables are needed by the +underlying `@Subselect` SQL query. This is because, unlike JPQL and HQL queries, Hibernate cannot parse the underlying native SQL query. -The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes, or just the attributes that have changed. -This is achieved through the use of the -https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] -annotation which defines a single attribute of type -https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLockType.html[`org.hibernate.annotations.OptimisticLockType`]. +With the `@Synchronize` annotation in place, +when executing an HQL or JPQL which selects from the `AccountSummary` entity, +Hibernate will trigger a Persistence Context flush if there are pending `Account`, `Client` or `AccountTransaction` entity state transitions. +==== -There are 4 available OptimisticLockTypes: +[[entity-proxy]] +==== Define a custom entity proxy -`NONE`:: - optimistic locking is disabled even if there is a `@Version` annotation present -`VERSION` (the default):: - performs optimistic locking based on a `@Version` as described above -`ALL`:: - performs optimistic locking based on _all_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements -`DIRTY`:: - performs optimistic locking based on _dirty_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements +By default, when it needs to use a proxy instead of the actual Pojo, Hibernate is going to use a Bytecode manipulation library like +http://jboss-javassist.github.io/javassist/[Javassist] or +http://bytebuddy.net/[Byte Buddy]. -[[entity-pojo-optlock-versionless-all]] -====== Versionless optimistic locking using `OptimisticLockType.ALL` +However, if the entity class is final, Javassist will not create a proxy and you will get a Pojo even when you only need a proxy reference. +In this case, you could proxy an interface that this particular entity implements, as illustrated by the following example. -[[locking-optimistic-lock-type-all-example]] -.`OptimisticLockType.ALL` mapping example +[[entity-proxy-interface-mapping]] +.Final entity class implementing the `Identifiable` interface ==== [source,java] ---- -include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-example,indent=0] +include::{sourcedir-proxy}/ProxyInterfaceTest.java[tag=entity-proxy-interface-mapping,indent=0] ---- ==== -When you need to modify the `Person` entity above: +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`] +annotation is used to specify a custom proxy implementation for the current annotated entity. -[[locking-optimistic-lock-type-all-update-example]] -.`OptimisticLockType.ALL` update example +When loading the `Book` entity proxy, Hibernate is going to proxy the `Identifiable` interface instead as illustrated by the following example: + +[[entity-proxy-persist-mapping]] +.Proxying the final entity class implementing the `Identifiable` interface ==== [source,java] ---- -include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-update-example,indent=0] +include::{sourcedir-proxy}/ProxyInterfaceTest.java[tag=entity-proxy-persist-mapping,indent=0] ---- -[source,SQL] +[source,sql] ---- -include::{extrasdir}/locking/locking-optimistic-lock-type-all-update-example.sql[] +include::{extrasdir}/entity/entity-proxy-persist-mapping.sql[] ---- ==== -As you can see, all the columns of the associated database row are used in the `WHERE` clause. -If any column has changed after the row was loaded, there won't be any match, and a `StaleStateException` or an `OptimisticLockException` -is going to be thrown. +As you can see in the associated SQL snippet, Hibernate issues no SQL SELECT query since the proxy can be +constructed without needing to fetch the actual entity Pojo. -[NOTE] -==== -When using `OptimisticLockType.ALL`, you should also use `@DynamicUpdate` because the `UPDATE` statement must take into consideration all the entity property values. -==== +[[entity-tuplizer]] +==== Dynamic entity proxies using the @Tuplizer annotation -[[entity-pojo-optlock-versionless-dirty]] -====== Versionless optimistic locking using `OptimisticLockType.DIRTY` +It is possible to map your entities as dynamic proxies using +the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation. -The `OptimisticLockType.DIRTY` differs from `OptimisticLockType.ALL` -in that it only takes into consideration the entity properties that have changed -since the entity was loaded in the currently running Persistence Context. +In the following entity mapping, both the embeddable and the entity are mapped as interfaces, not Pojos. -[[locking-optimistic-lock-type-dirty-example]] -.`OptimisticLockType.DIRTY` mapping example +[[entity-tuplizer-entity-mapping]] +.Dynamic entity proxy mapping ==== [source,java] ---- -include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-example,indent=0] +include::{sourcedir-proxy}/tuplizer/Cuisine.java[tag=entity-tuplizer-entity-mapping,indent=0] +---- + +[source,java] +---- +include::{sourcedir-proxy}/tuplizer/Country.java[tag=entity-tuplizer-entity-mapping,indent=0] ---- ==== -When you need to modify the `Person` entity above: +The `@Tuplizer` instructs Hibernate to use the `DynamicEntityTuplizer` and `DynamicEmbeddableTuplizer` to handle +the associated entity and embeddable object types. + +Both the `Cuisine` entity and the `Country` embeddable types are going to be instantiated as Java dynamic proxies, +as you can see in the following `DynamicInstantiator` example: -[[locking-optimistic-lock-type-dirty-update-example]] -.`OptimisticLockType.DIRTY` update example +[[entity-tuplizer-instantiator]] +.Instantiating entities and embeddables as dynamic proxies ==== [source,java] ---- -include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-update-example,indent=0] +include::{sourcedir-proxy}/tuplizer/DynamicEntityTuplizer.java[tag=entity-tuplizer-instantiator,indent=0] ---- -[source,SQL] +[source,java] ---- -include::{extrasdir}/locking/locking-optimistic-lock-type-dirty-update-example.sql[] +include::{sourcedir-proxy}/tuplizer/DynamicEmbeddableTuplizer.java[tag=entity-tuplizer-instantiator,indent=0] +---- + +[source,java] +---- +include::{sourcedir-proxy}/tuplizer/DynamicInstantiator.java[tag=entity-tuplizer-instantiator,indent=0] +---- + +[source,java] +---- +include::{sourcedir-proxy}/tuplizer/ProxyHelper.java[tag=entity-tuplizer-instantiator,indent=0] +---- + +[source,java] +---- +include::{sourcedir-proxy}/tuplizer/DataProxyHandler.java[tag=entity-tuplizer-instantiator,indent=0] ---- ==== -This time, only the database column that has changed was used in the `WHERE` clause. +With the `DynamicInstantiator` in place, we can work with the dynamic proxy entities just like with Pojo entities. -[NOTE] +[[entity-tuplizer-dynamic-proxy-example]] +.Persisting entities and embeddables as dynamic proxies ==== -The main advantage of `OptimisticLockType.DIRTY` over `OptimisticLockType.ALL` -and the default `OptimisticLockType.VERSION` used implicitly along with the `@Version` mapping, -is that it allows you to minimize the risk of `OptimisticLockException` across non-overlapping entity property changes. +[source,java] +---- +include::{sourcedir-proxy}/tuplizer/TuplizerTest.java[tag=entity-tuplizer-dynamic-proxy-example,indent=0] +---- +==== + +[[entity-persister]] +==== Define a custom entity persister -When using `OptimisticLockType.DIRTY`, you should also use `@DynamicUpdate` because the `UPDATE` statement must take into consideration all the dirty entity property values, -and also the `@SelectBeforeUpdate` annotation so that detached entities are properly handled by the -https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#update-java.lang.Object-[`Session#update(entity)`] operation. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Persister.html[`@Persister`] annotation is used to specify a custom entity or collection persister. + +For entities, the custom persister must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/entity/EntityPersister.html[`EntityPersister`] interface. + +For collections, the custom persister must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/collection/CollectionPersister.html[`CollectionPersister`] interface. + +[[entity-persister-mapping]] +.Entity persister mapping ==== +[source,java] +---- +include::{sourcedir-persister}/Author.java[tag=entity-persister-mapping,indent=0] +---- + +[source,java] +---- +include::{sourcedir-persister}/Book.java[tag=entity-persister-mapping,indent=0] +---- +==== + +By providing your own `EntityPersister` and `CollectionPersister` implementations, +you can control how entities and collections are persisted in to the database. \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/ElementCollectionAccessType.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/ElementCollectionAccessType.java deleted file mode 100644 index 54de316f220a..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/ElementCollectionAccessType.java +++ /dev/null @@ -1,18 +0,0 @@ -@Entity -public class Patch { - - @Id - private Long id; - - @ElementCollection - @CollectionTable( - name="patch_change", - joinColumns=@JoinColumn(name="patch_id") - ) - @OrderColumn(name = "index_id") - private List changes = new ArrayList<>(); - - public List getChanges() { - return changes; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/EmbeddableAccessType.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/EmbeddableAccessType.java deleted file mode 100644 index bd5b468ec45b..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/EmbeddableAccessType.java +++ /dev/null @@ -1,28 +0,0 @@ -@Embeddable -@Access(AccessType.PROPERTY) -public static class Change { - - private String path; - - private String diff; - - public Change() {} - - @Column(name = "path", nullable = false) - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - @Column(name = "diff", nullable = false) - public String getDiff() { - return diff; - } - - public void setDiff(String diff) { - this.diff = diff; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/EmbeddedAccessType.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/EmbeddedAccessType.java deleted file mode 100644 index 21d6250b2a2d..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/EmbeddedAccessType.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -public class Patch { - - @Id - private Long id; - - @Embedded - private Change change; -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityFieldAccess.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityFieldAccess.java deleted file mode 100644 index 3e424a681304..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityFieldAccess.java +++ /dev/null @@ -1,14 +0,0 @@ -@Entity -public class Simple { - - @Id - private Integer id; - - public Integer getId() { - return id; - } - - public void setId( Integer id ) { - this.id = id; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityPropertyAccess.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityPropertyAccess.java deleted file mode 100644 index 94fc2a8e49e4..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityPropertyAccess.java +++ /dev/null @@ -1,14 +0,0 @@ -@Entity -public class Simple { - - private Integer id; - - @Id - public Integer getId() { - return id; - } - - public void setId( Integer id ) { - this.id = id; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityPropertyAccessOverride.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityPropertyAccessOverride.java deleted file mode 100644 index 5394f05bbe91..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/access/SimpleEntityPropertyAccessOverride.java +++ /dev/null @@ -1,18 +0,0 @@ -@Entity -public class Simple { - - private Integer id; - - @Version - @Access( AccessType.FIELD ) - private Integer version; - - @Id - public Integer getId() { - return id; - } - - public void setId( Integer id ) { - this.id = id; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-bidirectional-lifecycle-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-bidirectional-lifecycle-example.sql index c1bbfdbd1257..6be8a54290b6 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-bidirectional-lifecycle-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-bidirectional-lifecycle-example.sql @@ -1,10 +1,14 @@ +INSERT INTO Person + ( id ) +VALUES ( 1 ) + INSERT INTO Phone - ( number, person_id, id ) -VALUES ( '123-456-7890', NULL, 2 ) + ( "number", person_id, id ) +VALUES ( '123-456-7890', 1, 2 ) INSERT INTO Phone - ( number, person_id, id ) -VALUES ( '321-654-0987', NULL, 3 ) + ( "number", person_id, id ) +VALUES ( '321-654-0987', 1, 3 ) DELETE FROM Phone WHERE id = 2 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-unidirectional-lifecycle-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-unidirectional-lifecycle-example.sql index 52d45dba6e13..3ea0b8c09b2c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-unidirectional-lifecycle-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-many-unidirectional-lifecycle-example.sql @@ -4,11 +4,11 @@ VALUES ( 1 ) INSERT INTO Phone ( number, id ) -VALUES ( '123 - 456 - 7890', 2 ) +VALUES ( '123-456-7890', 2 ) INSERT INTO Phone ( number, id ) -VALUES ( '321 - 654 - 0987', 3 ) +VALUES ( '321-654-0987', 3 ) INSERT INTO Person_Phone ( Person_id, phones_id ) diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-one-bidirectional-lifecycle-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-one-bidirectional-lifecycle-example.sql index 901a6e7069ba..e91f5ff44f66 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-one-bidirectional-lifecycle-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-one-to-one-bidirectional-lifecycle-example.sql @@ -1,5 +1,5 @@ INSERT INTO Phone ( number, id ) -VALUES ( '123 - 456 - 7890', 1 ) +VALUES ( '123-456-7890', 1 ) INSERT INTO PhoneDetails ( phone_id, provider, technology, id ) -VALUES ( 1, 'T - Mobile, GSM', 2 ) \ No newline at end of file +VALUES ( 1, 'T-Mobile', 'GSM', 2 ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/basic-clob-sql-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/basic-clob-sql-example.sql index e1820d87a52e..e939c0f48b9a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/basic-clob-sql-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/basic-clob-sql-example.sql @@ -1,6 +1,6 @@ CREATE TABLE Product ( - id INTEGER NOT NULL - image clob - name VARCHAR(255) - PRIMARY KEY ( id ) + id INTEGER NOT NULL, + name VARCHAR(255), + warranty CLOB, + PRIMARY KEY (id) ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-Target-fetching-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-Target-fetching-example.sql new file mode 100644 index 000000000000..9e362abe0a71 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-Target-fetching-example.sql @@ -0,0 +1,15 @@ +SELECT + c.id as id1_0_0_, + c.latitude as latitude2_0_0_, + c.longitude as longitud3_0_0_, + c.name as name4_0_0_ +FROM + City c +WHERE + c.id = ? + +-- binding parameter [1] as [BIGINT] - [1] + +-- extracted value ([latitude2_0_0_] : [DOUBLE]) - [46.7712] +-- extracted value ([longitud3_0_0_] : [DOUBLE]) - [23.6236] +-- extracted value ([name4_0_0_] : [VARCHAR]) - [Cluj] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persist-example.sql new file mode 100644 index 000000000000..87cf331e49de --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persist-example.sql @@ -0,0 +1,11 @@ +INSERT INTO integer_property + ( "name", "value", id ) +VALUES ( 'age', 23, 1 ) + +INSERT INTO string_property + ( "name", "value", id ) +VALUES ( 'name', 'John Doe', 1 ) + +INSERT INTO property_holder + ( property_type, property_id, id ) +VALUES ( 'S', 1, 1 ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persistence-example.sql deleted file mode 100644 index ca39900bc8bc..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persistence-example.sql +++ /dev/null @@ -1,25 +0,0 @@ -INSERT INTO integer_property - ( "name", "value", id ) -VALUES ( 'age', 23, 1 ) - -INSERT INTO string_property - ( "name", "value", id ) -VALUES ( 'name', 'John Doe', 1 ) - -INSERT INTO property_holder - ( property_type, property_id, id ) -VALUES ( 'S', 1, 1 ) - - -SELECT ph.id AS id1_1_0_, - ph.property_type AS property2_1_0_, - ph.property_id AS property3_1_0_ -FROM property_holder ph -WHERE ph.id = 1 - - -SELECT sp.id AS id1_2_0_, - sp."name" AS name2_2_0_, - sp."value" AS value3_2_0_ -FROM string_property sp -WHERE sp.id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-query-example.sql new file mode 100644 index 000000000000..1467518486f1 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-query-example.sql @@ -0,0 +1,12 @@ +SELECT ph.id AS id1_1_0_, + ph.property_type AS property2_1_0_, + ph.property_id AS property3_1_0_ +FROM property_holder ph +WHERE ph.id = 1 + + +SELECT sp.id AS id1_2_0_, + sp."name" AS name2_2_0_, + sp."value" AS value3_2_0_ +FROM string_property sp +WHERE sp.id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persist-example.sql new file mode 100644 index 000000000000..5bc5fd082e4e --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persist-example.sql @@ -0,0 +1,15 @@ +INSERT INTO integer_property + ( "name", "value", id ) +VALUES ( 'age', 23, 1 ) + +INSERT INTO string_property + ( "name", "value", id ) +VALUES ( 'name', 'John Doe', 1 ) + +INSERT INTO property_repository ( id ) +VALUES ( 1 ) + +INSERT INTO repository_properties + ( repository_id , property_type , property_id ) +VALUES + ( 1 , 'I' , 1 ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persistence-example.sql deleted file mode 100644 index 392de28471b0..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persistence-example.sql +++ /dev/null @@ -1,36 +0,0 @@ -INSERT INTO integer_property - ( "name", "value", id ) -VALUES ( 'age', 23, 1 ) - -INSERT INTO string_property - ( "name", "value", id ) -VALUES ( 'name', 'John Doe', 1 ) - -INSERT INTO property_repository ( id ) -VALUES ( 1 ) - -INSERT INTO repository_properties - ( repository_id , property_type , property_id ) -VALUES - ( 1 , 'I' , 1 ) - -INSERT INTO repository_properties - ( repository_id , property_type , property_id ) -VALUES - ( 1 , 'S' , 1 ) - -SELECT pr.id AS id1_1_0_ -FROM property_repository pr -WHERE pr.id = 1 - -SELECT ip.id AS id1_0_0_ , - integerpro0_."name" AS name2_0_0_ , - integerpro0_."value" AS value3_0_0_ -FROM integer_property integerpro0_ -WHERE integerpro0_.id = 1 - -SELECT sp.id AS id1_3_0_ , - sp."name" AS name2_3_0_ , - sp."value" AS value3_3_0_ -FROM string_property sp -WHERE sp.id = 1 diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-query-example.sql new file mode 100644 index 000000000000..207fd6065302 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-query-example.sql @@ -0,0 +1,15 @@ +SELECT pr.id AS id1_1_0_ +FROM property_repository pr +WHERE pr.id = 1 + +SELECT ip.id AS id1_0_0_ , + ip."name" AS name2_0_0_ , + ip."value" AS value3_0_0_ +FROM integer_property ip +WHERE ip.id = 1 + +SELECT sp.id AS id1_3_0_ , + sp."name" AS name2_3_0_ , + sp."value" AS value3_3_0_ +FROM string_property sp +WHERE sp.id = 1 diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql index d3d216ce8d2c..5282632d416f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql @@ -1,25 +1,3 @@ -SELECT - c.id as id1_1_0_, - c.name as name2_1_0_ -FROM - Client c -WHERE - c.id = 1 - -SELECT - a.id as id1_0_, - a.active as active2_0_, - a.amount as amount3_0_, - a.client_id as client_i6_0_, - a.rate as rate4_0_, - a.account_type as account_5_0_ -FROM - Account a -WHERE - a.client_id = 1 - --- Activate filter [activeAccount] - SELECT c.id as id1_1_0_, c.name as name2_1_0_ @@ -30,7 +8,7 @@ WHERE SELECT a.id as id1_0_, - a.active as active2_0_, + a.active_status as active2_0_, a.amount as amount3_0_, a.client_id as client_i6_0_, a.rate as rate4_0_, @@ -38,5 +16,5 @@ SELECT FROM Account a WHERE - accounts0_.active = true + accounts0_.active_status = true and a.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql index f3d4d61627f5..aeb9bb001ee7 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql @@ -1,6 +1,6 @@ SELECT a.id as id1_0_0_, - a.active as active2_0_0_, + a.active_status as active2_0_0_, a.amount as amount3_0_0_, a.client_id as client_i6_0_0_, a.rate as rate4_0_0_, @@ -8,9 +8,6 @@ SELECT c.id as id1_1_1_, c.name as name2_1_1_ FROM - Account a -LEFT OUTER JOIN - Client c - ON a.client_id=c.id + Account a WHERE a.id = 2 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql index 88c17423ebd0..5bcf7e0c354a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql @@ -1,18 +1,6 @@ SELECT a.id as id1_0_, - a.active as active2_0_, - a.amount as amount3_0_, - a.client_id as client_i6_0_, - a.rate as rate4_0_, - a.account_type as account_5_0_ -FROM - Account a - --- Activate filter [activeAccount] - -SELECT - a.id as id1_0_, - a.active as active2_0_, + a.active_status as active2_0_, a.amount as amount3_0_, a.client_id as client_i6_0_, a.rate as rate4_0_, @@ -20,4 +8,4 @@ SELECT FROM Account a WHERE - a.active = true \ No newline at end of file + a.active_status = true \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql index f6abfd3ec97a..80226c2f508e 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql @@ -3,7 +3,6 @@ SELECT ca.accounts_id as accounts2_2_0_, ca.order_id as order_id3_0_, a.id as id1_0_1_, - a.active as active2_0_1_, a.amount as amount3_0_1_, a.rate as rate4_0_1_, a.account_type as account_5_0_1_ @@ -12,27 +11,6 @@ FROM INNER JOIN Account a ON ca.accounts_id=a.id -WHERE - ca.Client_id = ? - --- binding parameter [1] as [BIGINT] - [1] - --- Activate filter [firstAccounts] - -SELECT - ca.Client_id as Client_i1_2_0_, - ca.accounts_id as accounts2_2_0_, - ca.order_id as order_id3_0_, - a.id as id1_0_1_, - a.active as active2_0_1_, - a.amount as amount3_0_1_, - a.rate as rate4_0_1_, - a.account_type as account_5_0_1_ -FROM - Client_Account ca -INNER JOIN - Account a -ON ca.accounts_id=a.id WHERE ca.order_id <= ? AND ca.Client_id = ? diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql index e9a785d308dd..6bf698e00b3b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql @@ -1,14 +1,14 @@ INSERT INTO Client (name, id) VALUES ('John Doe', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (5000.0, 1, 0.0125, 'CREDIT', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (0.0, 1, 0.0105, 'DEBIT', 2) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (250.0, 1, 0.0105, 'DEBIT', 3) INSERT INTO Client_Account (Client_id, order_id, accounts_id) VALUES (1, 0, 1) diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql index 2f4abf9d6638..750c9f4f70f4 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql @@ -1,11 +1,11 @@ INSERT INTO Client (name, id) VALUES ('John Doe', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-sql-fragment-alias-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-sql-fragment-alias-query-example.sql new file mode 100644 index 000000000000..fbd6c9a48519 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-sql-fragment-alias-query-example.sql @@ -0,0 +1,16 @@ +select + filtersqlf0_.id as id1_0_, + filtersqlf0_.active as active2_0_, + filtersqlf0_.amount as amount3_0_, + filtersqlf0_.rate as rate4_0_, + filtersqlf0_1_.deleted as deleted1_1_ +from + account filtersqlf0_ +left outer join + account_details filtersqlf0_1_ + on filtersqlf0_.id=filtersqlf0_1_.id +where + filtersqlf0_.active = ? + and filtersqlf0_1_.deleted = false + +-- binding parameter [1] as [BOOLEAN] - [true] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-CreationTimestamp-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-CreationTimestamp-persist-example.sql index 6424e880b584..b1cfbc15147f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-CreationTimestamp-persist-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-CreationTimestamp-persist-example.sql @@ -1,4 +1,4 @@ -INSERT INTO Event("timestamp", id) +INSERT INTO Event ("timestamp", id) VALUES (?, ?) -- binding parameter [1] as [TIMESTAMP] - [Tue Nov 15 16:24:20 EET 2016] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-UpdateTimestamp-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-UpdateTimestamp-persist-example.sql new file mode 100644 index 000000000000..74000b4ea9fe --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-UpdateTimestamp-persist-example.sql @@ -0,0 +1,7 @@ +INSERT INTO Bid (cents, updated_by, updated_on, id) +VALUES (?, ?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [15000] +-- binding parameter [2] as [VARCHAR] - [John Doe] +-- binding parameter [3] as [TIMESTAMP] - [Tue Apr 18 17:21:46 EEST 2017] +-- binding parameter [4] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-UpdateTimestamp-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-UpdateTimestamp-update-example.sql new file mode 100644 index 000000000000..7031ebf93876 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-generated-UpdateTimestamp-update-example.sql @@ -0,0 +1,11 @@ +UPDATE Bid SET + cents = ?, + updated_by = ?, + updated_on = ? +where + id = ? + +-- binding parameter [1] as [BIGINT] - [16000] +-- binding parameter [2] as [VARCHAR] - [John Doe Jr.] +-- binding parameter [3] as [TIMESTAMP] - [Tue Apr 18 17:49:24 EEST 2017] +-- binding parameter [4] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql new file mode 100644 index 000000000000..d80b60f607ab --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql @@ -0,0 +1,19 @@ +SELECT + c.id as id1_1_0_, + c.name as name2_1_0_ +FROM + Client c +WHERE + c.id = 1 + +SELECT + a.id as id1_0_, + a.active_status as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a +WHERE + a.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql new file mode 100644 index 000000000000..4d6dbc7655a7 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql @@ -0,0 +1,9 @@ +SELECT + a.id as id1_0_, + a.active_status as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql new file mode 100644 index 000000000000..ab44fcf58102 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql @@ -0,0 +1,17 @@ +SELECT + ca.Client_id as Client_i1_2_0_, + ca.accounts_id as accounts2_2_0_, + ca.order_id as order_id3_0_, + a.id as id1_0_1_, + a.amount as amount3_0_1_, + a.rate as rate4_0_1_, + a.account_type as account_5_0_1_ +FROM + Client_Account ca +INNER JOIN + Account a +ON ca.accounts_id=a.id +WHERE + ca.Client_id = ? + +-- binding parameter [1] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-join-table-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-join-table-example.sql new file mode 100644 index 000000000000..eea7bab7e28b --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-join-table-example.sql @@ -0,0 +1,31 @@ +create table Book ( + id bigint not null, + author varchar(255), + title varchar(255), + primary key (id) +) + +create table Book_Reader ( + book_id bigint not null, + reader_id bigint not null +) + +create table Reader ( + id bigint not null, + name varchar(255), + primary key (id) +) + +alter table Book_Reader + add constraint FKsscixgaa5f8lphs9bjdtpf9g + foreign key (reader_id) + references Reader + +alter table Book_Reader + add constraint FKoyrwu9tnwlukd1616qhck21ra + foreign key (book_id) + references Book + +alter table Book_Reader + add created_on timestamp + default current_timestamp \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-customizing-ordered-by-sql-clause-fetching-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-customizing-ordered-by-sql-clause-fetching-example.sql new file mode 100644 index 000000000000..37c2d86732fc --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-customizing-ordered-by-sql-clause-fetching-example.sql @@ -0,0 +1,12 @@ +select + a.person_id as person_i4_0_0_, + a.id as id1_0_0_, + a.content as content2_0_1_, + a.name as name3_0_1_, + a.person_id as person_i4_0_1_ +from + Article a +where + a.person_id = ? +order by + CHAR_LENGTH(a.name) desc \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-customizing-ordered-list-ordinal-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-customizing-ordered-list-ordinal-persist-example.sql new file mode 100644 index 000000000000..28750928f331 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-customizing-ordered-list-ordinal-persist-example.sql @@ -0,0 +1,13 @@ +INSERT INTO Phone("number", person_id, type, id) +VALUES ('028-234-9876', 1, 'landline', 1) + +INSERT INTO Phone("number", person_id, type, id) +VALUES ('072-122-9876', 1, 'mobile', 2) + +UPDATE Phone +SET order_id = 100 +WHERE id = 1 + +UPDATE Phone +SET order_id = 101 +WHERE id = 2 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-custom-key-type-sql-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-custom-key-type-sql-example.sql new file mode 100644 index 000000000000..6674e0bdf0f4 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-custom-key-type-sql-example.sql @@ -0,0 +1,16 @@ +create table person ( + id int8 not null, + primary key (id) +) + +create table call_register ( + person_id int8 not null, + phone_number int4, + call_timestamp_epoch int8 not null, + primary key (person_id, call_timestamp_epoch) +) + +alter table if exists call_register + add constraint FKsn58spsregnjyn8xt61qkxsub + foreign key (person_id) + references person diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-fetch-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-fetch-example.sql new file mode 100644 index 000000000000..7b89d456ed1a --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-fetch-example.sql @@ -0,0 +1,24 @@ +select + cr.person_id as person_i1_0_0_, + cr.call_register as call_reg2_0_0_, + cr.country_code as country_3_0_, + cr.operator_code as operator4_0_, + cr.subscriber_code as subscrib5_0_ +from + call_register cr +where + cr.person_id = ? + +-- binding parameter [1] as [BIGINT] - [1] + +-- extracted value ([person_i1_0_0_] : [BIGINT]) - [1] +-- extracted value ([call_reg2_0_0_] : [INTEGER]) - [101] +-- extracted value ([country_3_0_] : [VARCHAR]) - [01] +-- extracted value ([operator4_0_] : [VARCHAR]) - [234] +-- extracted value ([subscrib5_0_] : [VARCHAR]) - [567] + +-- extracted value ([person_i1_0_0_] : [BIGINT]) - [1] +-- extracted value ([call_reg2_0_0_] : [INTEGER]) - [102] +-- extracted value ([country_3_0_] : [VARCHAR]) - [01] +-- extracted value ([operator4_0_] : [VARCHAR]) - [234] +-- extracted value ([subscrib5_0_] : [VARCHAR]) - [789] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-mapping-example.sql new file mode 100644 index 000000000000..5f7d89db6be7 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-mapping-example.sql @@ -0,0 +1,18 @@ +create table person ( + id bigint not null, + primary key (id) +) + +create table call_register ( + person_id bigint not null, + call_register integer, + country_code varchar(255) not null, + operator_code varchar(255) not null, + subscriber_code varchar(255) not null, + primary key (person_id, country_code, operator_code, subscriber_code) +) + +alter table call_register + add constraint FKqyj2at6ik010jqckeaw23jtv2 + foreign key (person_id) + references person \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-persist-example.sql new file mode 100644 index 000000000000..ba4d9291581f --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-persist-example.sql @@ -0,0 +1,35 @@ +insert into person (id) values (?) + +-- binding parameter [1] as [BIGINT] - [1] + +insert into call_register( + person_id, + country_code, + operator_code, + subscriber_code, + call_register +) +values + (?, ?, ?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [VARCHAR] - [01] +-- binding parameter [3] as [VARCHAR] - [234] +-- binding parameter [4] as [VARCHAR] - [789] +-- binding parameter [5] as [INTEGER] - [102] + +insert into call_register( + person_id, + country_code, + operator_code, + subscriber_code, + call_register +) +values + (?, ?, ?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [VARCHAR] - [01] +-- binding parameter [3] as [VARCHAR] - [234] +-- binding parameter [4] as [VARCHAR] - [567] +-- binding parameter [5] as [INTEGER] - [101] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/dynamic/listing10.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/dynamic/listing10.java deleted file mode 100644 index 5d7f04298e17..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/dynamic/listing10.java +++ /dev/null @@ -1,20 +0,0 @@ -Session s = openSession(); -Transaction tx = s.beginTransaction(); - -// Create a customer entity -Mapdavid = new HashMap<>(); -david.put( "name","David" ); - -// Create an organization entity -Mapfoobar = new HashMap<>(); -foobar.put( "name","Foobar Inc." ); - -// Link both -david.put( "organization",foobar ); - -// Save both -s.save( "Customer",david ); -s.save( "Organization",foobar ); - -tx.commit(); -s.close(); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/dynamic/mapping-model-dynamic-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/dynamic/mapping-model-dynamic-example.sql new file mode 100644 index 000000000000..ad0ba6705b36 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/dynamic/mapping-model-dynamic-example.sql @@ -0,0 +1,10 @@ +insert +into + Book + (title, author, isbn) +values + (?, ?, ?) + +-- binding parameter [1] as [VARCHAR] - [High-Performance Java Persistence] +-- binding parameter [2] as [VARCHAR] - [Vlad Mihalcea] +-- binding parameter [3] as [VARCHAR] - [978-9730228236] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Address.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Address.java deleted file mode 100644 index 512647a95d95..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Address.java +++ /dev/null @@ -1,22 +0,0 @@ -@Embeddable -public class Address { - - private String line1; - - private String line2; - - @Embedded - private ZipCode zipCode; - - ... - - @Embeddable - public static class Zip { - - private String postalCode; - - private String plus4; - - ... - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact-AttributeOverride.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact-AttributeOverride.java deleted file mode 100644 index 9245ec7569df..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact-AttributeOverride.java +++ /dev/null @@ -1,74 +0,0 @@ -@Entity -public class Contact { - - @Id - private Integer id; - - @Embedded - private Name name; - - @Embedded - @AttributeOverrides( - @AttributeOverride( - name = "line1", - column = @Column( name = "home_address_line1" ), - ), - @AttributeOverride( - name = "line2", - column = @Column( name = "home_address_line2" ) - ), - @AttributeOverride( - name = "zipCode.postalCode", - column = @Column( name = "home_address_postal_cd" ) - ), - @AttributeOverride( - name = "zipCode.plus4", - column = @Column( name = "home_address_postal_plus4" ) - ) - ) - private Address homeAddress; - - @Embedded - @AttributeOverrides( - @AttributeOverride( - name = "line1", - column = @Column( name = "mailing_address_line1" ), - ), - @AttributeOverride( - name = "line2", - column = @Column( name = "mailing_address_line2" ) - ), - @AttributeOverride( - name = "zipCode.postalCode", - column = @Column( name = "mailing_address_postal_cd" ) - ), - @AttributeOverride( - name = "zipCode.plus4", - column = @Column( name = "mailing_address_postal_plus4" ) - ) - ) - private Address mailingAddress; - - @Embedded - @AttributeOverrides( - @AttributeOverride( - name = "line1", - column = @Column( name = "work_address_line1" ), - ), - @AttributeOverride( - name = "line2", - column = @Column( name = "work_address_line2" ) - ), - @AttributeOverride( - name = "zipCode.postalCode", - column = @Column( name = "work_address_postal_cd" ) - ), - @AttributeOverride( - name = "zipCode.plus4", - column = @Column( name = "work_address_postal_plus4" ) - ) - ) - private Address workAddress; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact-ImplicitNamingStrategy.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact-ImplicitNamingStrategy.sql deleted file mode 100644 index 727006c65ccb..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact-ImplicitNamingStrategy.sql +++ /dev/null @@ -1,19 +0,0 @@ -create table Contact( - id integer not null, - name_firstName VARCHAR, - name_middleName VARCHAR, - name_lastName VARCHAR, - homeAddress_line1 VARCHAR, - homeAddress_line2 VARCHAR, - homeAddress_zipCode_postalCode VARCHAR, - homeAddress_zipCode_plus4 VARCHAR, - mailingAddress_line1 VARCHAR, - mailingAddress_line2 VARCHAR, - mailingAddress_zipCode_postalCode VARCHAR, - mailingAddress_zipCode_plus4 VARCHAR, - workAddress_line1 VARCHAR, - workAddress_line2 VARCHAR, - workAddress_zipCode_postalCode VARCHAR, - workAddress_zipCode_plus4 VARCHAR, - ... -) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact.java deleted file mode 100644 index ccf0d3affbc0..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Contact.java +++ /dev/null @@ -1,20 +0,0 @@ -@Entity -public class Contact { - - @Id - private Integer id; - - @Embedded - private Name name; - - @Embedded - private Address homeAddress; - - @Embedded - private Address mailingAddress; - - @Embedded - private Address workAddress; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Name.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Name.java deleted file mode 100644 index 39f998872240..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Name.java +++ /dev/null @@ -1,11 +0,0 @@ -@Embeddable -public class Name { - - private String firstName; - - private String middleName; - - private String lastName; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person.java deleted file mode 100644 index e22dae84f88a..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person.java +++ /dev/null @@ -1,11 +0,0 @@ -@Entity -public class Person { - - @Id - private Integer id; - - @Embedded - private Name name; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person1.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person1.sql deleted file mode 100644 index 7dbc0647c449..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person1.sql +++ /dev/null @@ -1,7 +0,0 @@ -create table Person ( - id integer not null, - firstName VARCHAR, - middleName VARCHAR, - lastName VARCHAR, - ... -) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person_alt.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person_alt.java deleted file mode 100644 index bad381d10050..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/Person_alt.java +++ /dev/null @@ -1,14 +0,0 @@ -@Entity -public class Person { - - @Id - private Integer id; - - private String firstName; - - private String middleName; - - private String lastName; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/component-safe-implicit-naming.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/component-safe-implicit-naming.java deleted file mode 100644 index 39b231dd483c..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/component-safe-implicit-naming.java +++ /dev/null @@ -1,8 +0,0 @@ -MetadataSources sources = ...; -sources.addAnnotatedClass( Address.class ); -sources.addAnnotatedClass( Name.class ); -sources.addAnnotatedClass( Contact.class ); - -Metadata metadata = sources.getMetadataBuilder().applyImplicitNamingStrategy( ImplicitNamingStrategyComponentPathImpl.INSTANCE ) -... -.build(); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-multiple-namingstrategy-entity-mapping.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-multiple-namingstrategy-entity-mapping.sql new file mode 100644 index 000000000000..c303ccbd597d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-multiple-namingstrategy-entity-mapping.sql @@ -0,0 +1,10 @@ +create table Book ( + id bigint not null, + author varchar(255), + ebookPublisher_name varchar(255), + paperBackPublisher_name varchar(255), + title varchar(255), + ebookPublisher_country_id bigint, + paperBackPublisher_country_id bigint, + primary key (id) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-type-association-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-type-association-mapping-example.sql new file mode 100644 index 000000000000..4bdf6715322d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-type-association-mapping-example.sql @@ -0,0 +1,9 @@ +create table Country ( + id bigint not null, + name varchar(255), + primary key (id) +) + +alter table Country + add constraint UK_p1n05aafu73sbm3ggsxqeditd + unique (name) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-type-override-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-type-override-mapping-example.sql new file mode 100644 index 000000000000..bc87dbbf1dc8 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-type-override-mapping-example.sql @@ -0,0 +1,20 @@ +create table Book ( + id bigint not null, + author varchar(255), + ebook_publisher_name varchar(255), + paper_back_publisher_name varchar(255), + title varchar(255), + ebook_publisher_country_id bigint, + paper_back_publisher_country_id bigint, + primary key (id) +) + +alter table Book + add constraint FKm39ibh5jstybnslaoojkbac2g + foreign key (ebook_publisher_country_id) + references Country + +alter table Book + add constraint FK7kqy9da323p7jw7wvqgs6aek7 + foreign key (paper_back_publisher_country_id) + references Country \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/simple-embeddable-type-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/simple-embeddable-type-mapping-example.sql new file mode 100644 index 000000000000..cd57acf6fc35 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/simple-embeddable-type-mapping-example.sql @@ -0,0 +1,8 @@ +create table Book ( + id bigint not null, + author varchar(255), + publisher_country varchar(255), + publisher_name varchar(255), + title varchar(255), + primary key (id) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Identifier.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Identifier.java deleted file mode 100644 index c55468aefddc..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Identifier.java +++ /dev/null @@ -1,2 +0,0 @@ -@Id -private Integer id; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Instant.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Instant.java deleted file mode 100644 index 44d705478d1c..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Instant.java +++ /dev/null @@ -1,10 +0,0 @@ -@Entity -public class Thing2 { - - @Id - private Integer id; - - @Version - private Instant ts; - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntity.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntity.java deleted file mode 100644 index 295c27a3053c..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntity.java +++ /dev/null @@ -1,4 +0,0 @@ -@Entity -public class Simple { - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntityWithTable.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntityWithTable.java deleted file mode 100644 index 386d780ec854..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntityWithTable.java +++ /dev/null @@ -1,5 +0,0 @@ -@Entity -@Table( catalog = "CRM", schema = "purchasing", name = "t_simple" ) -public class Simple { - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Timestamp.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Timestamp.java deleted file mode 100644 index 3136ddbeb877..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Timestamp.java +++ /dev/null @@ -1,11 +0,0 @@ -@Entity -public class Thing { - - @Id - private Integer id; - - @Version - private Timestamp ts; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Version.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Version.java deleted file mode 100644 index bde76d9a1c64..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Version.java +++ /dev/null @@ -1,10 +0,0 @@ -@Entity -public class Course { - - @Id - private Integer id; - - @Version - private Integer version; - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/entity-proxy-persist-mapping.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/entity-proxy-persist-mapping.sql new file mode 100644 index 000000000000..0da513b3f668 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/entity-proxy-persist-mapping.sql @@ -0,0 +1,10 @@ +insert +into + Book + (author, title, id) +values + (?, ?, ?) + +-- binding parameter [1] as [VARCHAR] - [Vlad Mihalcea] +-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence] +-- binding parameter [3] as [BIGINT] - [1] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing1.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing1.java deleted file mode 100644 index 366dd9bb9765..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing1.java +++ /dev/null @@ -1,7 +0,0 @@ -Session session=...; - -Person p1 = session.get( Person.class,1 ); -Person p2 = session.get( Person.class,1 ); - -// this evaluates to true -assert p1==p2; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing2.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing2.java deleted file mode 100644 index 650401d369b2..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing2.java +++ /dev/null @@ -1,8 +0,0 @@ -Session session1=...; -Session session2=...; - -Person p1 = session1.get( Person.class,1 ); -Person p2 = session2.get( Person.class,1 ); - -// this evaluates to false -assert p1==p2; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing3.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing3.java deleted file mode 100644 index cc7a01b398f2..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing3.java +++ /dev/null @@ -1,12 +0,0 @@ -Session session=...; - -Club club = session.get( Club.class,1 ); - -Person p1 = session.get( Person.class,1 ); -Person p2 = session.get( Person.class,1 ); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to true -assert club.getMembers.size()==1; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing4.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing4.java deleted file mode 100644 index a46b8a6c43e9..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing4.java +++ /dev/null @@ -1,13 +0,0 @@ -Session session1=...; -Session session2=...; - -Club club = session1.get( Club.class,1 ); - -Person p1 = session1.get( Person.class,1 ); -Person p2 = session2.get( Person.class,1 ); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to ... well it depends -assert club.getMembers.size()==1; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing5.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing5.java deleted file mode 100644 index d22d415af00d..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing5.java +++ /dev/null @@ -1,12 +0,0 @@ -Session session=...; - -Club club = session.get( Club.class,1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to ... again, it depends -assert club.getMembers.size()==1; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing6.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing6.java deleted file mode 100644 index 6ef093c2f896..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing6.java +++ /dev/null @@ -1,24 +0,0 @@ -@Entity -public class Person { - - @Id - @GeneratedValue - private Integer id; - - @Override - public int hashCode() { - return Objects.hash( id ); - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !( o instanceof Person ) ) { - return false; - } - Person person = (Person) o; - return Objects.equals( id, person.id ); - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing7.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing7.java deleted file mode 100644 index 0cf83a328f56..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing7.java +++ /dev/null @@ -1,15 +0,0 @@ -Session session=...; -session.getTransaction().begin(); - -Club club = session.get( Club.class,1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -session.getTransaction().commit(); - -// will actually resolve to false! -assert club.getMembers().contains( p1 ); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing8.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing8.java deleted file mode 100644 index 5d8eb567ba02..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing8.java +++ /dev/null @@ -1,19 +0,0 @@ -Session session=...; -session.getTransaction().begin(); - -Club club = session.get( Club.class,1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -session.save( p1 ); -session.save( p2 ); -session.flush(); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -session.getTransaction().commit(); - -// will actually resolve to false! -assert club.getMembers().contains( p1 ); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing9.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing9.java deleted file mode 100644 index a0a296efb390..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing9.java +++ /dev/null @@ -1,36 +0,0 @@ -@Entity -public class Person { - - @Id - @GeneratedValue - private Integer id; - - @NaturalId - private String ssn; - - protected Person() { - // Constructor for ORM - } - - public Person( String ssn ) { - // Constructor for app - this.ssn = ssn; - } - - @Override - public int hashCode() { - return Objects.hash( ssn ); - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !( o instanceof Person ) ) { - return false; - } - Person person = (Person) o; - return Objects.equals( ssn, person.ssn ); - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociationPrimaryKeyJoinColumn.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociationPrimaryKeyJoinColumn.java deleted file mode 100644 index d432c8178172..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociationPrimaryKeyJoinColumn.java +++ /dev/null @@ -1,29 +0,0 @@ -@Entity -public class PersonDetails { - - @Id - private Long id; - - private String nickName; - - @ManyToOne - @PrimaryKeyJoinColumn - private Person person; - - public String getNickName() { - return nickName; - } - - public void setNickName(String nickName) { - this.nickName = nickName; - } - - public Person getPerson() { - return person; - } - - public void setPerson(Person person) { - this.person = person; - this.id = person.getId(); - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociations.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociations.java deleted file mode 100644 index 30865b97ca1a..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociations.java +++ /dev/null @@ -1,135 +0,0 @@ -@Entity -public class PersonAddress implements Serializable { - - @Id - @ManyToOne - private Person person; - - @Id - @ManyToOne() - private Address address; - - public PersonAddress() {} - - public PersonAddress(Person person, Address address) { - this.person = person; - this.address = address; - } - - public Person getPerson() { - return person; - } - - public void setPerson(Person person) { - this.person = person; - } - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PersonAddress that = (PersonAddress) o; - return Objects.equals(person, that.person) && - Objects.equals(address, that.address); - } - - @Override - public int hashCode() { - return Objects.hash(person, address); - } -} - -@Entity -public class Person { - - @Id - @GeneratedValue - private Long id; - - @NaturalId - private String registrationNumber; - - public Person() {} - - public Person(String registrationNumber) { - this.registrationNumber = registrationNumber; - } - - public Long getId() { - return id; - } - - @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(registrationNumber, person.registrationNumber); - } - - @Override - public int hashCode() { - return Objects.hash(registrationNumber); - } -} - -@Entity -public class Address { - - @Id - @GeneratedValue - private Long id; - - private String street; - - private String number; - - private String postalCode; - - public Address() {} - - public Address(String street, String number, String postalCode) { - this.street = street; - this.number = number; - this.postalCode = postalCode; - } - - public Long getId() { - return id; - } - - public String getStreet() { - return street; - } - - public String getNumber() { - return number; - } - - public String getPostalCode() { - return postalCode; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Address address = (Address) o; - return Objects.equals(street, address.street) && - Objects.equals(number, address.number) && - Objects.equals(postalCode, address.postalCode); - } - - @Override - public int hashCode() { - return Objects.hash(street, number, postalCode); - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociationsQuery.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociationsQuery.java deleted file mode 100644 index ae9bba97d0e1..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/CompositeIdAssociationsQuery.java +++ /dev/null @@ -1,4 +0,0 @@ -PersonAddress personAddress = entityManager.find( - PersonAddress.class, - new PersonAddress( person, address ) -); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/ConfiguredSequence.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/ConfiguredSequence.java deleted file mode 100644 index 844ecddecdd5..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/ConfiguredSequence.java +++ /dev/null @@ -1,10 +0,0 @@ -@Entity -public class MyEntity { - - @Id - @GeneratedValue( generation = SEQUENCE, name = "my_sequence" ) - @SequenceGenerator( name = "my_sequence", schema = "globals", allocationSize = 30 ) - public Integer id; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/DerivedIdentifier.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/DerivedIdentifier.java deleted file mode 100644 index 5eaac04d232b..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/DerivedIdentifier.java +++ /dev/null @@ -1,53 +0,0 @@ -@Entity -public class Person { - - @Id - @GeneratedValue - private Long id; - - @NaturalId - private String registrationNumber; - - public Person() {} - - public Person(String registrationNumber) { - this.registrationNumber = registrationNumber; - } - - public Long getId() { - return id; - } - - public String getRegistrationNumber() { - return registrationNumber; - } -} - -@Entity -public class PersonDetails { - - @Id - private Long id; - - private String nickName; - - @ManyToOne - @MapsId - private Person person; - - public String getNickName() { - return nickName; - } - - public void setNickName(String nickName) { - this.nickName = nickName; - } - - public Person getPerson() { - return person; - } - - public void setPerson(Person person) { - this.person = person; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/EmbeddedId1.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/EmbeddedId1.java deleted file mode 100644 index f3c61213f53d..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/EmbeddedId1.java +++ /dev/null @@ -1,18 +0,0 @@ -@Entity -public class Login { - - @EmbeddedId - private PK pk; - - @Embeddable - public static class PK implements Serializable { - - private String system; - - private String username; - - ... - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/EmbeddedId2.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/EmbeddedId2.java deleted file mode 100644 index 89857a6becde..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/EmbeddedId2.java +++ /dev/null @@ -1,19 +0,0 @@ -@Entity -public class Login { - - @EmbeddedId - private PK pk; - - @Embeddable - public static class PK implements Serializable { - - @ManyToOne - private System system; - - private String username; - - ... - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass1.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass1.java deleted file mode 100644 index 4d2c810a8b92..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass1.java +++ /dev/null @@ -1,21 +0,0 @@ -@Entity -@IdClass( PK.class ) -public class Login { - - @Id - private String system; - - @Id - private String username; - - public static class PK implements Serializable { - - private String system; - - private String username; - - ... - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass2.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass2.java deleted file mode 100644 index f5b0db3e7a4b..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass2.java +++ /dev/null @@ -1,22 +0,0 @@ -@Entity -@IdClass( PK.class ) -public class Login { - - @Id - @ManyToOne - private System system; - - @Id - private String username; - - public static class PK implements Serializable { - - private System system; - - private String username; - - ... - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass3.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass3.java deleted file mode 100644 index f36022a324d3..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/IdClass3.java +++ /dev/null @@ -1,28 +0,0 @@ -@Entity -@IdClass( PK.class ) -public class LogFile { - - @Id - private String name; - - @Id - private LocalDate date; - - @Id - @GeneratedValue - private Integer uniqueStamp; - - public static class PK implements Serializable { - - private String name; - - private LocalDate date; - - private Integer uniqueStamp; - - ... - } - - ... -} - diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/NamedSequence.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/NamedSequence.java deleted file mode 100644 index 87809c58ae06..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/NamedSequence.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -public class MyEntity { - - @Id - @GeneratedValue( generation = SEQUENCE, name = "my_sequence" ) - public Integer id; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/SimpleAssigned.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/SimpleAssigned.java deleted file mode 100644 index c1443553aa96..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/SimpleAssigned.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class MyEntity { - - @Id - public Integer id; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/SimpleGenerated.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/SimpleGenerated.java deleted file mode 100644 index 1f8ceb01dfce..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/SimpleGenerated.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -public class MyEntity { - - @Id - @GeneratedValue - public Integer id; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/TableGenerator.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/TableGenerator.sql deleted file mode 100644 index d66d067f92a7..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/TableGenerator.sql +++ /dev/null @@ -1,4 +0,0 @@ -create table hibernate_sequences( - sequence_name VARCHAR NOT NULL, - next_val INTEGER NOT NULL -) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UUIDCustomVersionOneStrategy.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UUIDCustomVersionOneStrategy.java deleted file mode 100644 index 3eeb4b77768b..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UUIDCustomVersionOneStrategy.java +++ /dev/null @@ -1,20 +0,0 @@ -@Entity -public class MyEntity { - - @Id - @GeneratedValue( generator = "uuid" ) - @GenericGenerator( - name = "uuid", - strategy = "org.hibernate.id.UUIDGenerator", - parameters = { - @Parameter( - name = "uuid_gen_strategy_class", - value = "org.hibernate.id.uuid.CustomVersionOneStrategy" - ) - } - ) - public UUID id; - - ... - -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UUIDRandom.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UUIDRandom.java deleted file mode 100644 index 7e3c2b23084c..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UUIDRandom.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -public class MyEntity { - - @Id - @GeneratedValue - public UUID id; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UnnamedSequence.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UnnamedSequence.java deleted file mode 100644 index e6e5ffd5780c..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UnnamedSequence.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -public class MyEntity { - - @Id - @GeneratedValue( generation = SEQUENCE ) - public Integer id; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UnnamedTable.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UnnamedTable.java deleted file mode 100644 index 521283d52640..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/UnnamedTable.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -public class MyEntity { - - @Id - @GeneratedValue( generation = TABLE ) - public Integer id; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-configured-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-configured-mapping-example.sql new file mode 100644 index 000000000000..4e7ca0b94c0b --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-configured-mapping-example.sql @@ -0,0 +1,5 @@ +create table table_identifier ( + table_name varchar2(255 char) not null, + product_id number(19,0), + primary key (table_name) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-configured-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-configured-persist-example.sql new file mode 100644 index 000000000000..d4ba98a01b4d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-configured-persist-example.sql @@ -0,0 +1,78 @@ +select + tbl.product_id +from + table_identifier tbl +where + tbl.table_name = ? +for update + +-- binding parameter [1] - [Product] + +insert +into + table_identifier + (table_name, product_id) +values + (?, ?) + +-- binding parameter [1] - [Product] +-- binding parameter [2] - [1] + +update + table_identifier +set + product_id= ? +where + product_id= ? + and table_name= ? + +-- binding parameter [1] - [6] +-- binding parameter [2] - [1] + +select + tbl.product_id +from + table_identifier tbl +where + tbl.table_name= ? for update + +update + table_identifier +set + product_id= ? +where + product_id= ? + and table_name= ? + +-- binding parameter [1] - [11] +-- binding parameter [2] - [6] + +insert +into + Product + (product_name, id) +values + (?, ?) + +-- binding parameter [1] as [VARCHAR] - [Product 1] +-- binding parameter [2] as [BIGINT] - [1] + +insert +into + Product + (product_name, id) +values + (?, ?) + +-- binding parameter [1] as [VARCHAR] - [Product 2] +-- binding parameter [2] as [BIGINT] - [2] + +insert +into + Product + (product_name, id) +values + (?, ?) + +-- binding parameter [1] as [VARCHAR] - [Product 3] +-- binding parameter [2] as [BIGINT] - [3] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-unnamed-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-unnamed-mapping-example.sql new file mode 100644 index 000000000000..82b3326ad28b --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-generators-table-unnamed-mapping-example.sql @@ -0,0 +1,5 @@ +create table hibernate_sequences ( + sequence_name varchar2(255 char) not null, + next_val number(19,0), + primary key (sequence_name) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-rowid-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-rowid-example.sql new file mode 100644 index 000000000000..0acfdea1df69 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/id/identifiers-rowid-example.sql @@ -0,0 +1,27 @@ +SELECT + p.id as id1_0_0_, + p."name" as name2_0_0_, + p."number" as number3_0_0_, + p.ROWID as rowid_0_ +FROM + Product p +WHERE + p.id = ? + +-- binding parameter [1] as [BIGINT] - [1] + +-- extracted value ([name2_0_0_] : [VARCHAR]) - [Mobile phone] +-- extracted value ([number3_0_0_] : [VARCHAR]) - [123-456-7890] +-- extracted ROWID value: AAAwkBAAEAAACP3AAA + +UPDATE + Product +SET + "name" = ?, + "number" = ? +WHERE + ROWID = ? + +-- binding parameter [1] as [VARCHAR] - [Smart phone] +-- binding parameter [2] as [VARCHAR] - [123-456-7890] +-- binding parameter [3] as ROWID - [AAAwkBAAEAAACP3AAA] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/immutability/collection-immutability-update-example.log.txt b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/immutability/collection-immutability-update-example.log.txt new file mode 100644 index 000000000000..158469b17982 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/immutability/collection-immutability-update-example.log.txt @@ -0,0 +1,7 @@ +javax.persistence.RollbackException: Error while committing the transaction + +Caused by: javax.persistence.PersistenceException: org.hibernate.HibernateException: + +Caused by: org.hibernate.HibernateException: changed an immutable collection instance: [ + org.hibernate.userguide.immutability.CollectionImmutabilityTest$Batch.events#1 +] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/MutableNaturalIdMapping.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/MutableNaturalIdMapping.java deleted file mode 100644 index b969be1fa684..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/MutableNaturalIdMapping.java +++ /dev/null @@ -1,11 +0,0 @@ -@Entity -public class Person { - - @Id - private Integer id; - - @NaturalId( mutable = true ) - private String ssn; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/MutableNaturalIdSynchronization.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/MutableNaturalIdSynchronization.java deleted file mode 100644 index e075f6f09e9d..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/MutableNaturalIdSynchronization.java +++ /dev/null @@ -1,16 +0,0 @@ -Session session=...; - -Person person = session.bySimpleNaturalId( Person.class ).load( "123-45-6789" ); -person.setSsn( "987-65-4321" ); - -... - -// returns null! -person = session.bySimpleNaturalId( Person.class ) - .setSynchronizationEnabled( false ) - .load( "987-65-4321" ); - -// returns correctly! -person = session.bySimpleNaturalId( Person.class ) - .setSynchronizationEnabled( true ) - .load( "987-65-4321" ); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NaturalIdCaching.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NaturalIdCaching.java deleted file mode 100644 index a86148ad7372..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NaturalIdCaching.java +++ /dev/null @@ -1,12 +0,0 @@ -@Entity -@NaturalIdCache -public class Company { - - @Id - private Integer id; - - @NaturalId - private String taxIdentifier; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NaturalIdLoadAccessUsage.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NaturalIdLoadAccessUsage.java deleted file mode 100644 index 0386937963c1..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NaturalIdLoadAccessUsage.java +++ /dev/null @@ -1,15 +0,0 @@ -Session session = ...; - -Company company = session.byNaturalId( Company.class ) - .using( "taxIdentifier","abc-123-xyz" ) - .load(); - -PostalCarrier carrier = session.byNaturalId( PostalCarrier.class ) - .using( "postalCode",new PostalCode(... ) ) - .load(); - -Department department = ...; -Course course = session.byNaturalId( Course.class ) - .using( "department",department ) - .using( "code","101" ) - .load(); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NonSimpleNaturalIdMapping.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NonSimpleNaturalIdMapping.java deleted file mode 100644 index 1d3d7a5b8760..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/NonSimpleNaturalIdMapping.java +++ /dev/null @@ -1,15 +0,0 @@ -@Entity -public class Course { - - @Id - private Integer id; - - @NaturalId - @ManyToOne - private Department department; - - @NaturalId - private String code; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleBasicNaturalIdMapping.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleBasicNaturalIdMapping.java deleted file mode 100644 index 373da2020318..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleBasicNaturalIdMapping.java +++ /dev/null @@ -1,11 +0,0 @@ -@Entity -public class Company { - - @Id - private Integer id; - - @NaturalId - private String taxIdentifier; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleCompositeNaturalIdMapping.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleCompositeNaturalIdMapping.java deleted file mode 100644 index 24873992664c..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleCompositeNaturalIdMapping.java +++ /dev/null @@ -1,18 +0,0 @@ -@Entity -public class PostalCarrier { - - @Id - private Integer id; - - @NaturalId - @Embedded - private PostalCode postalCode; - - ... - -} - -@Embeddable -public class PostalCode { - ... -} diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleNaturalIdLoadAccessUsage.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleNaturalIdLoadAccessUsage.java deleted file mode 100644 index e7b8e5778acf..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/natural_id/SimpleNaturalIdLoadAccessUsage.java +++ /dev/null @@ -1,7 +0,0 @@ -Session session = ...; - -Company company = session.bySimpleNaturalId( Company.class ) - .load( "abc-123-xyz" ); - -PostalCarrier carrier = session.bySimpleNaturalId( PostalCarrier.class ) - .load( new PostalCode(... ) ); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc index c3ea9b46ccd0..79d66216f992 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc @@ -1,6 +1,7 @@ [[identifiers]] === Identifiers :sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/identifier +:sourcedir-associations: ../../../../../test/java/org/hibernate/userguide/associations :extrasdir: extras Identifiers model the primary key of an entity. They are used to uniquely identify each specific entity. @@ -20,7 +21,7 @@ See <>. ==== Technically the identifier does not have to map to the column(s) physically defined as the table primary key. They just need to map to column(s) that uniquely identify each row. -However this documentation will continue to use the terms identifier and primary key interchangeably. +However, this documentation will continue to use the terms identifier and primary key interchangeably. ==== Every entity must define an identifier. For entity inheritance hierarchies, the identifier must be defined just on the entity that is the root of the hierarchy. @@ -50,11 +51,12 @@ Any types used for identifier attributes beyond this list will not be portable. Values for simple identifiers can be assigned, as we have seen in the examples above. The expectation for assigned identifier values is that the application assigns (sets them on the entity attribute) prior to calling save/persist. -.Simple assigned identifier +[[identifiers-simple-assigned-mapping-example]] +.Simple assigned entity identifier ==== [source,java] ---- -include::{extrasdir}/id/SimpleAssigned.java[] +include::{sourcedir}/AssignedIdentifierTest.java[tag=identifiers-simple-assigned-mapping-example, indent=0] ---- ==== @@ -63,11 +65,12 @@ include::{extrasdir}/id/SimpleAssigned.java[] Values for simple identifiers can be generated. To denote that an identifier attribute is generated, it is annotated with `javax.persistence.GeneratedValue` +[[identifiers-simple-generated-mapping-example]] .Simple generated identifier ==== [source,java] ---- -include::{extrasdir}/id/SimpleGenerated.java[] +include::{sourcedir}/GeneratedIdentifierTest.java[tag=identifiers-simple-generated-mapping-example, indent=0] ---- ==== @@ -105,28 +108,31 @@ Note especially that collections and one-to-ones are never appropriate. Modeling a composite identifier using an EmbeddedId simply means defining an embeddable to be a composition for the one or more attributes making up the identifier, and then exposing an attribute of that embeddable type on the entity. -.Basic EmbeddedId +[[identifiers-basic-embeddedid-mapping-example]] +.Basic `@EmbeddedId` ==== [source,java] ---- -include::{extrasdir}/id/EmbeddedId1.java[] +include::{sourcedir}/EmbeddedIdTest.java[tag=identifiers-basic-embeddedid-mapping-example, indent=0] ---- ==== -As mentioned before, EmbeddedIds can even contain ManyToOne attributes. +As mentioned before, EmbeddedIds can even contain `@ManyToOne` attributes: -.EmbeddedId with ManyToOne +[[identifiers-basic-embeddedid-manytoone-mapping-example]] +.`@EmbeddedId` with `@ManyToOne` ==== [source,java] ---- -include::{extrasdir}/id/EmbeddedId2.java[] +include::{sourcedir}/EmbeddedIdManyToOneTest.java[tag=identifiers-basic-embeddedid-manytoone-mapping-example, indent=0] ---- ==== [NOTE] ==== -Hibernate supports directly modeling the ManyToOne in the PK class, whether EmbeddedId or IdClass. -However that is not portably supported by the JPA specification. +Hibernate supports directly modeling the ManyToOne in the PK class, whether `@EmbeddedId` or `@IdClass`. + +However, that is not portably supported by the JPA specification. In JPA terms one would use "derived identifiers"; for details, see <>. ==== @@ -136,37 +142,41 @@ In JPA terms one would use "derived identifiers"; for details, see <>. +If the identifier type is UUID, Hibernate is going to use a <>. If the identifier type is numerical (e.g. `Long`, `Integer`), then Hibernate is going to use the `IdGeneratorStrategyInterpreter` to resolve the identifier generator strategy. The `IdGeneratorStrategyInterpreter` has two implementations: `FallbackInterpreter`:: - This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the <> configuration property . + This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the <> configuration property. When using this strategy, `AUTO` always resolves to `SequenceStyleGenerator`. If the underlying database supports sequences, then a SEQUENCE generator is used. Otherwise, a TABLE generator is going to be used instead. `LegacyFallbackInterpreter`:: @@ -256,32 +268,34 @@ The preferred (and portable) way to configure this generator is using the JPA-de The simplest form is to simply request sequence generation; Hibernate will use a single, implicitly-named sequence (`hibernate_sequence`) for all such unnamed definitions. +[[identifiers-generators-sequence-unnamed]] .Unnamed sequence ==== [source,java] ---- -include::{extrasdir}/id/UnnamedSequence.java[] +include::{sourcedir}/SequenceGeneratorUnnamedTest.java[tag=identifiers-generators-sequence-mapping-example, indent=0] ---- ==== -Or a specifically named sequence can be requested. +Using `javax.persistence.SequenceGenerator`, you can specify a specific database sequence name. +[[identifiers-generators-sequence-named]] .Named sequence ==== [source,java] ---- -include::{extrasdir}/id/NamedSequence.java[] +include::{sourcedir}/SequenceGeneratorNamedTest.java[tag=identifiers-generators-sequence-mapping-example, indent=0] ---- ==== -Use `javax.persistence.SequenceGenerator` to specify additional -configuration. +The `javax.persistence.SequenceGenerator` annotation allows you to specify additional configurations as well. +[[identifiers-generators-sequence-configured]] .Configured sequence ==== [source,java] ---- -include::{extrasdir}/id/ConfiguredSequence.java[] +include::{sourcedir}/SequenceGeneratorConfiguredTest.java[tag=identifiers-generators-sequence-mapping-example, indent=0] ---- ==== @@ -289,7 +303,7 @@ include::{extrasdir}/id/ConfiguredSequence.java[] ==== Using IDENTITY columns For implementing identifier value generation based on IDENTITY columns, -Hibernate makes use of its `org.hibernate.id.IdentityGenerator` id generator which expects the identifier to generated by INSERT into the table. +Hibernate makes use of its `org.hibernate.id.IdentityGenerator` id generator which expects the identifier to be generated by INSERT into the table. IdentityGenerator understands 3 different ways that the INSERT-generated value might be retrieved: * If Hibernate believes the JDBC environment supports `java.sql.Statement#getGeneratedKeys`, then that approach will be used for extracting the IDENTITY generated keys. @@ -300,41 +314,74 @@ IdentityGenerator understands 3 different ways that the INSERT-generated value m ==== It is important to realize that this imposes a runtime behavior where the entity row *must* be physically inserted prior to the identifier value being known. This can mess up extended persistence contexts (conversations). -Because of the runtime imposition/inconsistency Hibernate suggest other forms of identifier value generation be used. +Because of the runtime imposition/inconsistency, Hibernate suggests other forms of identifier value generation be used. ==== [NOTE] ==== There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not be able to JDBC batching for inserts of the entities that use IDENTITY generation. -The importance of this depends on the application specific use cases. +The importance of this depends on the application-specific use cases. If the application is not usually creating many new instances of a given type of entity that uses IDENTITY generation, then this is not an important impact since batching would not have been helpful anyway. ==== [[identifiers-generators-table]] -==== Using identifier table +==== Using the table identifier generator -Hibernate achieves table-based identifier generation based on its `org.hibernate.id.enhanced.TableGenerator` id generator which defines a table capable of holding multiple named value segments for any number of entities. +Hibernate achieves table-based identifier generation based on its `org.hibernate.id.enhanced.TableGenerator` which defines a table capable of holding multiple named value segments for any number of entities. -.Table generator table structure + +The basic idea is that a given table-generator table (`hibernate_sequences` for example) can hold multiple segments of identifier generation values. + +[[identifiers-generators-table-unnamed-mapping-example]] +.Unnamed table generator ==== +[source,java] +---- +include::{sourcedir}/TableGeneratorUnnamedTest.java[tag=identifiers-generators-table-mapping-example, indent=0] +---- + [source,sql] ---- -include::{extrasdir}/id/TableGenerator.sql[] +include::{extrasdir}/id/identifiers-generators-table-unnamed-mapping-example.sql[] ---- ==== -The basic idea is that a given table-generator table (`hibernate_sequences` for example) can hold multiple segments of identifier generation values. +If no table name is given Hibernate assumes an implicit name of `hibernate_sequences`. -.Unnamed table generator +Additionally, because no `javax.persistence.TableGenerator#pkColumnValue` is specified, +Hibernate will use the default segment (`sequence_name='default'`) from the hibernate_sequences table. + +However, you can configure the table identifier generator using the http://docs.oracle.com/javaee/7/api/javax/persistence/TableGenerator.html[`@TableGenerator`] annotation. + +[[identifiers-generators-table-configured-mapping-example]] +.Configured table generator ==== [source,java] ---- -include::{extrasdir}/id/UnnamedTable.java[] +include::{sourcedir}/TableGeneratorConfiguredTest.java[tag=identifiers-generators-table-mapping-example, indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/id/identifiers-generators-table-configured-mapping-example.sql[] ---- ==== -If no table name is given Hibernate assumes an implicit name of `hibernate_sequences`. -Additionally, because no `javax.persistence.TableGenerator#pkColumnValue` is specified, Hibernate will use the default segment (`sequence_name='default'`) from the hibernate_sequences table. +Now, when inserting 3 `Product` entities, Hibernate generates the following statements: + +[[identifiers-generators-table-configured-persist-example]] +.Configured table generator persist example +==== +[source,java] +---- +include::{sourcedir}/TableGeneratorConfiguredTest.java[tag=identifiers-generators-table-persist-example, indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/id/identifiers-generators-table-configured-persist-example.sql[] +---- +==== [[identifiers-generators-uuid]] ==== Using UUID generation @@ -345,24 +392,26 @@ This is supported through its `org.hibernate.id.UUIDGenerator` id generator. `UUIDGenerator` supports pluggable strategies for exactly how the UUID is generated. These strategies are defined by the `org.hibernate.id.UUIDGenerationStrategy` contract. The default strategy is a version 4 (random) strategy according to IETF RFC 4122. -Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using ip address rather than mac address). +Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using IP address rather than mac address). +[[identifiers-generators-uuid-mapping-example]] .Implicitly using the random UUID strategy ==== [source,java] ---- -include::{extrasdir}/id/UUIDRandom.java[] +include::{sourcedir}/UuidGeneratedValueTest.java[tag=identifiers-generators-uuid-mapping-example, indent=0] ---- ==== To specify an alternative generation strategy, we'd have to define some configuration via `@GenericGenerator`. Here we choose the RFC 4122 version 1 compliant strategy named `org.hibernate.id.uuid.CustomVersionOneStrategy`. +[[identifiers-generators-custom-uuid-mapping-example]] .Implicitly using the random UUID strategy ==== [source,java] ---- -include::{extrasdir}/id/UUIDCustomVersionOneStrategy.java[] +include::{sourcedir}/UuidCustomGeneratedValueTest.java[tag=identifiers-generators-custom-uuid-mapping-example, indent=0] ---- ==== @@ -378,7 +427,7 @@ Which is, in fact, the role of these optimizers. none:: No optimization is performed. We communicate with the database each and every time an identifier value is needed from the generator. pooled-lo:: The pooled-lo optimizer works on the principle that the increment-value is encoded into the database table/sequence structure. -In sequence-terms this means that the sequence is defined with a greater-that-1 increment size. +In sequence-terms, this means that the sequence is defined with a greater-than-1 increment size. + For example, consider a brand new sequence defined as `create sequence m_sequence start with 1 increment by 20`. This sequence essentially defines a "pool" of 20 usable id values each and every time we ask it for its next-value. @@ -434,7 +483,7 @@ include::{extrasdir}/id/identifiers-generators-pooled-lo-optimizer-persist-examp ---- ==== -As you can see from the list of generated SQL statements, you can insert 3 entities for one database sequence call. +As you can see from the list of generated SQL statements, you can insert 3 entities with just one database sequence call. This way, the pooled and the pooled-lo optimizers allow you to reduce the number of database roundtrips, therefore reducing the overall transaction response time. [[identifiers-derived]] @@ -447,12 +496,22 @@ JPA 2.0 added support for derived identifiers which allow an entity to borrow th ==== [source,java] ---- -include::{extrasdir}/id/DerivedIdentifier.java[] +include::{sourcedir-associations}/OneToOneMapsIdTest.java[tag=identifiers-derived-mapsid, indent=0] ---- ==== -In the example above, the `PersonDetails` entity uses the `id` column for both the entity identifier and for the many-to-one association to the `Person` entity. +In the example above, the `PersonDetails` entity uses the `id` column for both the entity identifier and for the one-to-one association to the `Person` entity. The value of the `PersonDetails` entity identifier is "derived" from the identifier of its parent `Person` entity. + +[[identifiers-derived-mapsid-persist-example]] +.Derived identifier with `@MapsId` persist example +==== +[source,java] +---- +include::{sourcedir-associations}/OneToOneMapsIdTest.java[tag=identifiers-derived-mapsid-persist-example, indent=0] +---- +==== + The `@MapsId` annotation can also reference columns from an `@EmbeddedId` identifier as well. The previous example can also be mapped using `@PrimaryKeyJoinColumn`. @@ -462,11 +521,43 @@ The previous example can also be mapped using `@PrimaryKeyJoinColumn`. ==== [source,java] ---- -include::{extrasdir}/id/CompositeIdAssociationPrimaryKeyJoinColumn.java[] +include::{sourcedir-associations}/OneToOnePrimaryKeyJoinColumnTest.java[tag=identifiers-derived-primarykeyjoincolumn, indent=0] ---- ==== [NOTE] ==== -Unlike `@MapsId`, the application developer is responsible for ensuring that the identifier and the many-to-one (or one-to-one) association are in sync. +Unlike `@MapsId`, the application developer is responsible for ensuring that the identifier and the many-to-one (or one-to-one) association are in sync +as you can see in the `PersonDetails#setPerson` method. +==== + +[[identifiers-rowid]] +==== @RowId + +If you annotate a given entity with the `@RowId` annotation and the underlying database supports fetching a record by ROWID (e.g. Oracle), +then Hibernate can use the `ROWID` pseudo-column for CRUD operations. + +[[identifiers-rowid-mapping]] +.`@RowId` entity maapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/RowIdTest.java[tag=identifiers-rowid-mapping] +---- +==== + +Now, when fetching an entity and modifying it, Hibernate uses the `ROWID` pseudo-column for the UPDATE SQL statement. + +[[identifiers-rowid-example]] +.`@RowId` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/RowIdTest.java[tag=identifiers-rowid-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/id/identifiers-rowid-example.sql[] +---- ==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc index 433d0d1b2cf1..a22c8ab10e60 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc @@ -101,7 +101,7 @@ include::{sourcedir}/CollectionImmutabilityTest.java[tags=collection-immutabilit [source, bash, indent=0] ---- -include::{extrasdir}/collection-immutability-update-example.log[] +include::{extrasdir}/collection-immutability-update-example.log.txt[] ---- ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc index 28a56c6db77e..9e7de6c950d7 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc @@ -5,7 +5,7 @@ Although relational database systems don't provide support for inheritance, Hibernate provides several strategies to leverage this object-oriented trait onto domain model entities: -MappedSuperclass:: Inheritance is implemented in domain model only without reflecting it in the database schema. See <>. +MappedSuperclass:: Inheritance is implemented in the domain model only without reflecting it in the database schema. See <>. Single table:: The domain model class hierarchy is materialized into a single table which contains entities belonging to different class types. See <>. Joined table:: The base class and all the subclasses have their own database tables and fetching a subclass entity requires a join with the parent table as well. See <>. Table per class:: Each subclass has its own table containing both the subclass and the base class properties. See <>. @@ -13,11 +13,11 @@ Table per class:: Each subclass has its own table containing both the subclass a [[entity-inheritance-mapped-superclass]] ==== MappedSuperclass -In the following domain model class hierarchy, a 'DebitAccount' and a 'CreditAccount' share the same 'Account' base class. +In the following domain model class hierarchy, a `DebitAccount` and a `CreditAccount` share the same `Account` base class. image:images/domain/inheritance/inheritance_class_diagram.svg[Inheritance class diagram] -When using `MappedSuperclass`, the inheritance is visible in the domain model only and each database table contains both the base class and the subclass properties. +When using `MappedSuperclass`, the inheritance is visible in the domain model only, and each database table contains both the base class and the subclass properties. [[entity-inheritance-mapped-superclass-example]] .`@MappedSuperclass` inheritance @@ -35,7 +35,7 @@ include::{extrasdir}/entity-inheritance-mapped-superclass-example.sql[] [NOTE] ==== -Because the `@MappedSuperclass` inheritance model is not mirrored at database level, +Because the `@MappedSuperclass` inheritance model is not mirrored at the database level, it's not possible to use polymorphic queries (fetching subclasses by their base class). ==== @@ -123,7 +123,7 @@ Both `@DiscriminatorColumn` and `@DiscriminatorFormula` are to be set on the roo The available options are `force` and `insert`. The `force` attribute is useful if the table contains rows with _extra_ discriminator values that are not mapped to a persistent class. -This could for example occur when working with a legacy database. +This could, for example, occur when working with a legacy database. If `force` is set to true Hibernate will specify the allowed discriminator values in the SELECT query, even when retrieving all instances of the root class. The second option, `insert`, tells Hibernate whether or not to include the discriminator column in SQL INSERTs. @@ -310,4 +310,67 @@ include::{extrasdir}/entity-inheritance-table-per-class-query-example.sql[] [IMPORTANT] ==== Polymorphic queries require multiple UNION queries, so be aware of the performance implications of a large class hierarchy. -==== \ No newline at end of file +==== + +[[entity-inheritance-polymorphism]] +==== Implicit and explicit polymorphism + +By default, when you query a base class entity, +the polymorphic query will fetch all subclasses belonging to the base type. + +However, you can even query +*interfaces or base classes that don't belong to the JPA entity inheritance model*. + +For instance, considering the following `DomainModelEntity` interface: + +[[entity-inheritance-polymorphism-interface-example]] +.Domain Model Entity interface +==== +[source,java] +---- +include::{sourcedir}/polymorphism/DomainModelEntity.java[tags=entity-inheritance-polymorphism-interface-example,indent=0] +---- +==== + +If we have two entity mappings, a `Book` and a `Blog`, +and the `Book` entity is mapped with the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Polymorphism.html[`@Polymorphism`] annotation +and taking the `PolymorphismType.EXPLICIT` setting: + +[[entity-inheritance-polymorphism-mapping-example]] +.`@Polymorphism` entity mapping +==== +[source,java] +---- +include::{sourcedir}/polymorphism/ExplicitPolymorphismTest.java[tags=entity-inheritance-polymorphism-mapping-example,indent=0] +---- +==== + +If we have the following entity objects in our system: + +[[entity-inheritance-polymorphism-persist-example]] +.Domain Model entity objects +==== +[source,java] +---- +include::{sourcedir}/polymorphism/ExplicitPolymorphismTest.java[tags=entity-inheritance-polymorphism-persist-example,indent=0] +---- +==== + +We can now query against the `DomainModelEntity` interface, +and Hibernate is going to fetch only the entities that are either mapped with +`@Polymorphism(type = PolymorphismType.IMPLICIT)` +or they are not annotated at all with the `@Polymorphism` annotation (implying the IMPLICIT behavior): + +[[entity-inheritance-polymorphism-fetch-example]] +.Fetching Domain Model entities using non-mapped base class polymorphism +==== +[source,java] +---- +include::{sourcedir}/polymorphism/ExplicitPolymorphismTest.java[tags=entity-inheritance-polymorphism-fetch-example,indent=0] +---- +==== + +Therefore, only the `Book` was fetched since the `Blog` entity was marked with the +`@Polymorphism(type = PolymorphismType.EXPLICIT)` annotation, which instructs Hibernate +to skip it when executing a polymorphic query against a non-mapped base class. \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc index ff67cb51c37c..7826a95c7444 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc @@ -83,7 +83,7 @@ to specify the ImplicitNamingStrategy to use. See [[PhysicalNamingStrategy]] ==== PhysicalNamingStrategy -Many organizations define rules around the naming of database objects (tables, columns, foreign-keys, etc). +Many organizations define rules around the naming of database objects (tables, columns, foreign keys, etc). The idea of a PhysicalNamingStrategy is to help implement such naming rules without having to hard-code them into the mapping via explicit names. @@ -94,8 +94,8 @@ would be, for example, to say that the physical column name should instead be ab [NOTE] ==== It is true that the resolution to `acct_num` could have been handled in an ImplicitNamingStrategy in this case. -But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether -the attribute explicitly specified the column name or whether we determined that implicitly. The +But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether +the attribute explicitly specified the column name or whether we determined that implicitly. The ImplicitNamingStrategy would only be applied if an explicit name was not given. So it depends on needs and intent. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc index b38372571910..7d00d39e7e49 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc @@ -1,6 +1,7 @@ [[naturalid]] === Natural Ids -:sourcedir: extras +:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/identifier +:extrasdir: extras Natural ids represent domain model unique identifiers that have a meaning in the real world too. Even if a natural id does not make a good primary key (surrogate keys being usually preferred), it's still useful to tell Hibernate about it. @@ -11,27 +12,30 @@ As we will see later, Hibernate provides a dedicated, efficient API for loading Natural ids are defined in terms of one or more persistent attributes. +[[naturalid-simple-basic-attribute-mapping-example]] .Natural id using single basic attribute ==== [source,java] ---- -include::{sourcedir}/natural_id/SimpleBasicNaturalIdMapping.java[] +include::{sourcedir}/SimpleNaturalIdTest.java[tags=naturalid-simple-basic-attribute-mapping-example,indent=0] ---- ==== +[[naturalid-single-embedded-attribute-mapping-example]] .Natural id using single embedded attribute ==== [source,java] ---- -include::{sourcedir}/natural_id/SimpleCompositeNaturalIdMapping.java[] +include::{sourcedir}/CompositeNaturalIdTest.java[tags=naturalid-single-embedded-attribute-mapping-example,indent=0] ---- ==== +[[naturalid-multiple-attribute-mapping-example]] .Natural id using multiple persistent attributes ==== [source,java] ---- -include::{sourcedir}/natural_id/NonSimpleNaturalIdMapping.java[] +include::{sourcedir}/MultipleNaturalIdTest.java[tags=naturalid-multiple-attribute-mapping-example,indent=0] ---- ==== @@ -46,11 +50,22 @@ This is represented by the `org.hibernate.NaturalIdLoadAccess` contract obtained If the entity does not define a natural id, trying to load an entity by its natural id will throw an exception. ==== +[[naturalid-load-access-example]] .Using NaturalIdLoadAccess ==== [source,java] ---- -include::{sourcedir}/natural_id/NaturalIdLoadAccessUsage.java[] +include::{sourcedir}/SimpleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0] +---- + +[source,java] +---- +include::{sourcedir}/CompositeNaturalIdTest.java[tags=naturalid-load-access-example,indent=0] +---- + +[source,java] +---- +include::{sourcedir}/MultipleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0] ---- ==== @@ -68,17 +83,26 @@ We will discuss the last method available on NaturalIdLoadAccess ( `setSynchroni Because the `Company` and `PostalCarrier` entities define "simple" natural ids, we can load them as follows: -.Using SimpleNaturalIdLoadAccess +[[naturalid-simple-load-access-example]] +.Loading by simple natural id ==== [source,java] ---- -include::{sourcedir}/natural_id/SimpleNaturalIdLoadAccessUsage.java[] +include::{sourcedir}/SimpleNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0] +---- + +[source,java] +---- +include::{sourcedir}/CompositeNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0] ---- ==== -Here we see the use of the `org.hibernate.SimpleNaturalIdLoadAccess` contract, obtained via `Session#bySimpleNaturalId(). +Here we see the use of the `org.hibernate.SimpleNaturalIdLoadAccess` contract, +obtained via `Session#bySimpleNaturalId(). + `SimpleNaturalIdLoadAccess` is similar to `NaturalIdLoadAccess` except that it does not define the using method. -Instead, because these "simple" natural ids are defined based on just one attribute we can directly pass the corresponding value of that natural id attribute directly to the `load()` and `getReference()` methods. +Instead, because these _simple_ natural ids are defined based on just one attribute we can directly pass +the corresponding natural id attribute value directly to the `load()` and `getReference()` methods. [NOTE] ==== @@ -90,18 +114,21 @@ If the entity does not define a natural id, or if the natural id is not of a "si A natural id may be mutable or immutable. By default the `@NaturalId` annotation marks an immutable natural id attribute. An immutable natural id is expected to never change its value. + If the value(s) of the natural id attribute(s) change, `@NaturalId(mutable=true)` should be used instead. -.Mutable natural id +[[naturalid-mutable-mapping-example]] +.Mutable natural id mapping ==== [source,java] ---- -include::{sourcedir}/natural_id/MutableNaturalIdMapping.java[] +include::{sourcedir}/MutableNaturalIdTest.java[tags=naturalid-mutable-mapping-example,indent=0] ---- ==== Within the Session, Hibernate maintains a mapping from natural id values to entity identifiers (PK) values. If natural ids values changed, it is possible for this mapping to become out of date until a flush occurs. + To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them when the `load()` or `getReference()` methods are executed. To be clear: this is only pertinent for mutable natural ids. @@ -112,11 +139,12 @@ If an application is certain that none of its mutable natural ids already associ This will force Hibernate to circumvent the checking of mutable natural ids. ==== +[[naturalid-mutable-synchronized-example]] .Mutable natural id synchronization use-case ==== [source,java] ---- -include::{sourcedir}/natural_id/MutableNaturalIdSynchronization.java[] +include::{sourcedir}/MutableNaturalIdTest.java[tags=naturalid-mutable-synchronized-example,indent=0] ---- ==== @@ -127,6 +155,6 @@ Not only can this NaturalId-to-PK resolution be cached in the Session, but we ca ==== [source,java] ---- -include::{sourcedir}/natural_id/NaturalIdCaching.java[] +include::{sourcedir}/CacheableNaturalIdTest.java[tags=naturalid-cacheable-mapping-example,indent=0] ---- ==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc index 8c0a3f6ee4ca..1d58ca3ccda3 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc @@ -6,7 +6,7 @@ Hibernate understands both the Java and JDBC representations of application data. The ability to read/write this data from/to the database is the function of a Hibernate _type_. A type, in this usage, is an implementation of the `org.hibernate.type.Type` interface. -This Hibernate type also describes various aspects of behavior of the Java type such as how to check for equality, how to clone values, etc. +This Hibernate type also describes various behavioral aspects of the Java type such as how to check for equality, how to clone values, etc. .Usage of the word _type_ [NOTE] @@ -20,7 +20,7 @@ When you encounter the term type in discussions of Hibernate, it may refer to th To help understand the type categorizations, let's look at a simple table and domain model that we wish to map. [[mapping-types-basic-example]] -.Simple table and domain model +.A simple table and domain model ==== [source, SQL, indent=0] ---- diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index 55c332de5de6..a381bfd11145 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -1,7 +1,9 @@ [[envers]] == Envers -:sourcedir: extras +:sourcedir: ../../../../../test/java/org/hibernate/userguide/envers +:extrasdir: extras +[[envers-basics]] === Basics To audit changes that are performed on an entity, you only need two things: @@ -18,6 +20,13 @@ Just putting the Envers jar on the classpath is enough because listeners will be And that's all. You can create, modify and delete the entities as always. +[IMPORTANT] +==== +The use of JPA's `CriteriaUpdate` and `CriteriaDelete` bulk operations are not currently supported by Envers +due to how an entity's lifecycle events are dispatched. Such operations should be avoided as they're not +captured by Envers and leads to incomplete audit history. +==== + If you look at the generated schema for your entities, or at the data persisted by Hibernate, you will notice that there are no changes. However, for each audited entity, a new table is introduced - `entity_table_AUD`, which stores the historical data, whenever you commit a transaction. @@ -27,86 +36,267 @@ Envers automatically creates audit tables if `hibernate.hbm2ddl.auto` option is Appropriate DDL statements can also be generated with an Ant task in <>. ==== +Considering we have a `Customer` entity, when annotating it with the `Audited` annotation, +Hibernate is going to generate the following tables using the `hibernate.hbm2ddl.auto` schema tool: + +[[envers-audited-mapping-example]] +.Basic Envers entity mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-mapping-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-mapping-example.sql[] +---- +==== + Instead of annotating the whole class and auditing all properties, you can annotate only some persistent properties with `@Audited`. This will cause only these properties to be audited. -The audit (history) of an entity can be accessed using the `AuditReader` interface, which can be obtained having an open `EntityManager` or `Session` via the `AuditReaderFactory`. -See the [Javadocs](https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/AuditReaderFactory.html) for these classes for details on the functionality offered. +Now, considering the previous `Customer` entity, +let's see how Envers auditing works when inserting, updating, and deleting the entity in question. + +[[envers-audited-insert-example]] +.Auditing the entity `INSERT` operation +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-insert-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-insert-example.sql[] +---- +==== + +[[envers-audited-update-example]] +.Auditing the entity `UPDATE` operation +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-update-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-update-example.sql[] +---- +==== + +[[envers-audited-delete-example]] +.Auditing the entity `DELETE` operation +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-delete-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-delete-example.sql[] +---- +==== + +The `REVTYPE` column value is taken from the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/RevisionType.html[`RevisionType`] Enum. + +[[envers-revtype-column]] +.`REVTYPE` column values +[width="100%",cols="20%,20%,60%",] +|================================= +|Database column value |Associated `RevisionType` Enum value |Description +|0 | `ADD` |A database table row was inserted. +|1 | `MOD` |A database table row was updated. +|2 | `DEL` |A database table row was deleted. +|================================= + +The audit (history) of an entity can be accessed using the `AuditReader` interface, which can be obtained by having an open `EntityManager` or `Session` via the `AuditReaderFactory`. + +[[envers-audited-revisions-example]] +.Getting a list of revisions for the `Customer` entity +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-revisions-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-revisions-example.sql[] +---- +==== + +Using the previously fetched revisions, we can now inspect the state of the `Customer` entity at that particular revision: + +[[envers-audited-rev1-example]] +.Getting the first revision for the `Customer` entity +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-rev1-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-rev1-example.sql[] +---- +==== + +When executing the aforementioned SQL query, there are two parameters: + +revision_number:: +The first parameter marks the revision number we are interested in or the latest one that exists up to this particular revision. +revision_type:: +The second parameter specifies that we are not interested in `DEL` `RevisionType` so that deleted entries are filtered out. + +The same goes for the second revision associated with the `UPDATE` statement. + +[[envers-audited-rev2-example]] +.Getting the second revision for the `Customer` entity +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-rev2-example] +---- +==== + +For the deleted entity revision, Envers throws a `NoResultException` since the entity was no longer valid at that revision. + +[[envers-audited-rev3-example]] +.Getting the third revision for the `Customer` entity +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-rev3-example] +---- +==== + +You can use the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/query/AuditQueryCreator.html#forEntitiesAtRevision-java.lang.Class-java.lang.String-java.lang.Number-boolean-[`forEntitiesAtRevision(Class cls, String entityName, Number revision, boolean includeDeletions)`] +method to get the deleted entity revision so that, instead of a `NoResultException`, +all attributes, except for the entity identifier, are going to be `null`. + +[[envers-audited-rev4-example]] +.Getting the third revision for the `Customer` entity without getting a `NoResultException` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultAuditTest.java[tags=envers-audited-rev4-example] +---- +==== + +See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/AuditReader.html[Javadocs] for details on other functionality offered. [[envers-configuration]] -=== Configuration +=== Configuration Properties It is possible to configure various aspects of Hibernate Envers behavior, such as table names, etc. -.Envers Configuration Properties -[width="100%",cols="34%,33%,33%",options="header",] -|======================================================================= -|Property name |Default value |Description -|`org.hibernate.envers.audit_table_prefix` | |String that will be prepended to the name of an audited entity to create the name of the entity and that will hold audit information. +`*org.hibernate.envers.audit_table_prefix*`:: +String that will be prepended to the name of an audited entity to create the name of the entity and that will hold audit information. -|`org.hibernate.envers.audit_table_suffix` |`_AUD` |String that will be appended to the name of an audited entity to create the name of the entity and that will hold audit information. - If you audit an entity with a table name Person, in the default setting Envers will generate a `Person_AUD` table to store historical data. +`*org.hibernate.envers.audit_table_suffix`* (default: `_AUD`):: +String that will be appended to the name of an audited entity to create the name of the entity and that will hold audit information. ++ +If you audit an entity with a table name Person, in the default setting Envers will generate a `Person_AUD` table to store historical data. -|`org.hibernate.envers.revision_field_name` |`REV` |Name of a field in the audit entity that will hold the revision number. +`*org.hibernate.envers.revision_field_name*` (default: `REV`):: +Name of a field in the audit entity that will hold the revision number. -|`org.hibernate.envers.revision_type_field_name` |`REVTYPE` |Name of a field in the audit entity that will hold the type of the revision (currently, this can be: `add`, `mod`, `del`). +`*org.hibernate.envers.revision_type_field_name*` (default: `REVTYPE` ):: +Name of a field in the audit entity that will hold the type of the revision (currently, this can be: `add`, `mod`, `del`). -|`org.hibernate.envers.revision_on_collection_change` |`true` |Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation, or the field using `mappedBy` attribute in a one-to-one relation). +`*org.hibernate.envers.revision_on_collection_change*` (default: `true` ):: +Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation or the field using `mappedBy` attribute in a one-to-one relation). -|`org.hibernate.envers.do_not_audit_optimistic_locking_field` |`true` |When true, properties to be used for optimistic locking, annotated with `@Version`, will not be automatically audited (their history won't be stored; it normally doesn't make sense to store it). +`*org.hibernate.envers.do_not_audit_optimistic_locking_field*` (default: `true` ):: +When true, properties to be used for optimistic locking, annotated with `@Version`, will not be automatically audited (their history won't be stored; it normally doesn't make sense to store it). -|`org.hibernate.envers.store_data_at_delete` |`false` |Should the entity data be stored in the revision when the entity is deleted (instead of only storing the id and all other properties as null). - This is not normally needed, as the data is present in the last-but-one revision. - Sometimes, however, it is easier and more efficient to access it in the last revision (then the data that the entity contained before deletion is stored twice). +`*org.hibernate.envers.store_data_at_delete*` (default: `false` ):: +Should the entity data be stored in the revision when the entity is deleted (instead of only storing the id and all other properties as null). ++ +This is not normally needed, as the data is present in the last-but-one revision. +Sometimes, however, it is easier and more efficient to access it in the last revision (then the data that the entity contained before deletion is stored twice). -|`org.hibernate.envers.default_schema` |`null` (same schema as table being audited) |The default schema name that should be used for audit tables. - Can be overridden using the `@AuditTable( schema="..." )` annotation. - If not present, the schema will be the same as the schema of the table being audited. +`*org.hibernate.envers.default_schema*` (default: `null` - same schema as the table being audited):: +The default schema name that should be used for audit tables. ++ +Can be overridden using the `@AuditTable( schema="..." )` annotation. ++ +If not present, the schema will be the same as the schema of the table being audited. -|`org.hibernate.envers.default_catalog` |`null` (same catalog as table being audited) |The default catalog name that should be used for audit tables. - Can be overridden using the `@AuditTable( catalog="..." )` annotation. If not present, the catalog will be the same as the catalog of the normal tables. +`*org.hibernate.envers.default_catalog*` (default: `null` - same catalog as the table being audited):: +The default catalog name that should be used for audit tables. ++ +Can be overridden using the `@AuditTable( catalog="..." )` annotation. ++ +If not present, the catalog will be the same as the catalog of the normal tables. -|`org.hibernate.envers.audit_strategy`|`org.hibernate.envers.strategy.DefaultAuditStrategy` |The audit strategy that should be used when persisting audit data. - The default stores only the revision, at which an entity was modified. - An alternative, the `org.hibernate.envers.strategy.ValidityAuditStrategy` stores both the start revision and the end revision. - Together these define when an audit row was valid, hence the name ValidityAuditStrategy. +`*org.hibernate.envers.audit_strategy*`(default: `org.hibernate.envers.strategy.DefaultAuditStrategy` ):: +The audit strategy that should be used when persisting audit data. +The default stores only the revision, at which an entity was modified. ++ +An alternative, the `org.hibernate.envers.strategy.ValidityAuditStrategy` stores both the start revision and the end revision. +Together these define when an audit row was valid, hence the name ValidityAuditStrategy. -|`org.hibernate.envers.audit_strategy_validity_end_rev_field_name` |`REVEND`|The column name that will hold the end revision number in audit entities. - This property is only valid if the validity audit strategy is used. +`*org.hibernate.envers.audit_strategy_validity_end_rev_field_name*` (default: `REVEND`):: +The column name that will hold the end revision number in audit entities. +This property is only valid if the validity audit strategy is used. -|`org.hibernate.envers.audit_strategy_validity_store_revend_timestamp`|`false` |Should the timestamp of the end revision be stored, until which the data was valid, in addition to the end revision itself. - This is useful to be able to purge old Audit records out of a relational database by using table partitioning. - Partitioning requires a column that exists within the table. - This property is only evaluated if the `ValidityAuditStrategy` is used. +`*org.hibernate.envers.audit_strategy_validity_store_revend_timestamp*`(default: `false` ):: +Should the timestamp of the end revision be stored, until which the data was valid, in addition to the end revision itself. +This is useful to be able to purge old Audit records out of a relational database by using table partitioning. ++ +Partitioning requires a column that exists within the table. +This property is only evaluated if the `ValidityAuditStrategy` is used. -|`org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name`|`REVEND_TSTMP` |Column name of the timestamp of the end revision until which the data was valid. - Only used if the 1ValidityAuditStrategy1 is used, and `org.hibernate.envers.audit_strategy_validity_store_revend_timestamp` evaluates to true +`*org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name*`(default: `REVEND_TSTMP` ):: +Column name of the timestamp of the end revision until which the data was valid. +Only used if the `ValidityAuditStrategy` is used, and `org.hibernate.envers.audit_strategy_validity_store_revend_timestamp` evaluates to true -|`org.hibernate.envers.use_revision_entity_with_native_id` |`true` | Boolean flag that determines the strategy of revision number generation. - Default implementation of revision entity uses native identifier generator. - If current database engine does not support identity columns, users are advised to set this property to false. - In this case revision numbers are created by preconfigured `org.hibernate.id.enhanced.SequenceStyleGenerator`. - See: `org.hibernate.envers.DefaultRevisionEntity` and `org.hibernate.envers.enhanced.SequenceIdRevisionEntity`. +`*org.hibernate.envers.use_revision_entity_with_native_id*` (default: `true` ):: +Boolean flag that determines the strategy of revision number generation. +Default implementation of revision entity uses native identifier generator. ++ +If the current database engine does not support identity columns, users are advised to set this property to false. ++ +In this case revision numbers are created by preconfigured `org.hibernate.id.enhanced.SequenceStyleGenerator`. +See: `org.hibernate.envers.DefaultRevisionEntity` and `org.hibernate.envers.enhanced.SequenceIdRevisionEntity`. -|`org.hibernate.envers.track_entities_changed_in_revision` |`false` |Should entity types, that have been modified during each revision, be tracked. - The default implementation creates `REVCHANGES` table that stores entity names of modified persistent objects. - Single record encapsulates the revision identifier (foreign key to `REVINFO` table) and a string value. - For more information, refer to <> and <>. +`*org.hibernate.envers.track_entities_changed_in_revision*` (default: `false` ):: +Should entity types, that have been modified during each revision, be tracked. +The default implementation creates `REVCHANGES` table that stores entity names of modified persistent objects. +Single record encapsulates the revision identifier (foreign key to `REVINFO` table) and a string value. +For more information, refer to <> and <>. -|`org.hibernate.envers.global_with_modified_flag` |`false`, can be individually overridden with `@Audited( withModifiedFlag=true )` |Should property modification flags be stored for all audited entities and all properties. - When set to true, for all properties an additional boolean column in the audit tables will be created, filled with information if the given property changed in the given revision. - When set to false, such column can be added to selected entities or properties using the `@Audited` annotation. - For more information, refer to <> and <>. +`*org.hibernate.envers.global_with_modified_flag*` (default: `false`, can be individually overridden with `@Audited( withModifiedFlag=true )` ):: +Should property modification flags be stored for all audited entities and all properties. ++ +When set to true, for all properties an additional boolean column in the audit tables will be created, filled with information if the given property changed in the given revision. ++ +When set to false, such column can be added to selected entities or properties using the `@Audited` annotation. ++ +For more information, refer to <> and <>. + +`*org.hibernate.envers.modified_flag_suffix*` (default: `_MOD` ):: +The suffix for columns storing "Modified Flags". ++ +For example, a property called "age", will by default get modified flag with column name "age_MOD". -|`org.hibernate.envers.modified_flag_suffix` |`_MOD` |The suffix for columns storing "Modified Flags". - For example: a property called "age", will by default get modified flag with column name "age_MOD". +`*org.hibernate.envers.embeddable_set_ordinal_field_name*` (default: `SETORDINAL` ):: +Name of column used for storing ordinal of the change in sets of embeddable elements. -|`org.hibernate.envers.embeddable_set_ordinal_field_name` |`SETORDINAL` |Name of column used for storing ordinal of the change in sets of embeddable elements. +`*org.hibernate.envers.cascade_delete_revision*` (default: `false` ):: +While deleting revision entry, remove data of associated audited entities. Requires database support for cascade row removal. -|`org.hibernate.envers.cascade_delete_revision` |`false` |While deleting revision entry, remove data of associated audited entities. Requires database support for cascade row removal. +`*org.hibernate.envers.allow_identifier_reuse*` (default: `false` ):: +Guarantees proper validity audit strategy behavior when application reuses identifiers of deleted entities. Exactly one row with `null` end date exists for each identifier. -|`org.hibernate.envers.allow_identifier_reuse` |`false` |Guarantees proper validity audit strategy behavior when application reuses identifiers of deleted entities. Exactly one row with `null` end date exists for each identifier. -|======================================================================= +`*org.hibernate.envers.original_id_prop_name*` (default: `originalId` ):: +Specifies the composite-id key property name used by the audit table mappings. [IMPORTANT] ==== @@ -116,8 +306,10 @@ be regarded as experimental: . `org.hibernate.envers.track_entities_changed_in_revision` . `org.hibernate.envers.using_modified_flag` . `org.hibernate.envers.modified_flag_suffix` +. `org.hibernate.envers.original_id_prop_name` ==== +[[envers-additional-mappings]] === Additional mapping annotations The name of the audit table can be set on a per-entity basis, using the `@AuditTable` annotation. @@ -127,7 +319,7 @@ If you have a mapping with secondary tables, audit tables for them will be gener If you wish to overwrite this behavior, you can use the `@SecondaryAuditTable` and `@SecondaryAuditTables` annotations. If you'd like to override auditing behavior of some fields/properties inherited from `@MappedSuperclass` or in an embedded component, -you can apply the `@AuditOverride( s )` annotation on the subtype or usage site of the component. +you can apply the `@AuditOverride` annotation on the subtype or usage site of the component. If you want to audit a relation mapped with `@OneToMany` and `@JoinColumn`, please see <> for a description of the additional `@AuditJoinTable` annotation that you'll probably want to use. @@ -147,6 +339,7 @@ you can set the `@AuditOverride( forClass = SomeEntity.class, isAudited = true/f The `@Audited` annotation also features an `auditParents` attribute but it's now deprecated in favor of `@AuditOverride`, ==== +[[envers-audit-strategy]] === Choosing an audit strategy After the basic configuration, it is important to choose the audit strategy that will be used to persist and retrieve audit information. @@ -154,126 +347,213 @@ There is a trade-off between the performance of persisting and the performance o Currently, there are two audit strategies. . The default audit strategy persists the audit data together with a start revision. - For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together with the start revision of its validity. - Rows in the audit tables are never updated after insertion. - Queries of audit information use subqueries to select the applicable rows in the audit tables. +For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together with the start revision of its validity. +Rows in the audit tables are never updated after insertion. +Queries of audit information use subqueries to select the applicable rows in the audit tables. + IMPORTANT: These subqueries are notoriously slow and difficult to index. . The alternative is a validity audit strategy. - This strategy stores the start-revision and the end-revision of audit information. - For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together with the start revision of its validity. - But at the same time the end-revision field of the previous audit rows (if available) are set to this revision. - Queries on the audit information can then use 'between start and end revision' instead of subqueries as used by the default audit strategy. +This strategy stores the start-revision and the end-revision of audit information. +For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together with the start revision of its validity. +But at the same, time the end-revision field of the previous audit rows (if available) is set to this revision. +Queries on the audit information can then use 'between start and end revision' instead of subqueries as used by the default audit strategy. ++ +The consequence of this strategy is that persisting audit information will be a bit slower because of the extra updates involved, +but retrieving audit information will be a lot faster. + - The consequence of this strategy is that persisting audit information will be a bit slower because of the extra updates involved, - but retrieving audit information will be a lot faster. - This can be improved even further by adding extra indexes. +IMPORTANT: This can be improved even further by adding extra indexes. + +[[envers-audit-ValidityAuditStrategy]] +==== Configuring the `ValidityAuditStrategy` + +To better visualize how the `ValidityAuditStrategy`, consider the following exercise where +we replay the previous audit logging example for the `Customer` entity. + +First, you need to configure the `ValidityAuditStrategy`: + +[[envers-audited-validity-configuration-example]] +.Configuring the `ValidityAuditStrategy` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/ValidityStrategyAuditTest.java[tags=envers-audited-validity-configuration-example] +---- +==== + +If, you're using the `persistence.xml` configuration file, +then the mapping will looks as follows: + +[source, XML, indent=0] +---- + +---- + +Once you configured the `ValidityAuditStrategy`, the following schema is going to be automatically generated: + +[[envers-audited-validity-mapping-example]] +.Envers schema for the `ValidityAuditStrategy` +==== +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-validity-mapping-example.sql[] +---- +==== + +As you can see, the `REVEND` column is added as well as its Foreign key to the `REVINFO` table. + +When rerunning the previous `Customer` audit log queries against the `ValidityAuditStrategy`, +we get the following results: + +[[envers-audited-validity-rev1-example]] +.Getting the first revision for the `Customer` entity +==== +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-audited-validity-rev1-example.sql[] +---- +==== + +[NOTE] +==== +Compared to the default strategy, the `ValidityAuditStrategy` generates simpler queries that can render better execution plans. +==== [[envers-revisionlog]] === Revision Log When Envers starts a new revision, it creates a new revision entity which stores information about the revision. -By default, that includes just: -revision number:: An integral value (`int/Integer` or `long/Long`). Essentially the primary key of the revision +By default, that includes just: -revision timestamp:: either a `long/Long` or `java.util.Date` value representing the instant at which the revision was made. - When using a `java.util.Date`, instead of a `long/Long` for the revision timestamp, take care not to store it to a column data type which will loose precision. +revision number:: +An integral value (`int/Integer` or `long/Long`). Essentially, the primary key of the revision +revision timestamp:: +Either a `long/Long` or `java.util.Date` value representing the instant at which the revision was made. +When using a `java.util.Date`, instead of a `long/Long` for the revision timestamp, take care not to store it to a column data type which will lose precision. Envers handles this information as an entity. By default it uses its own internal class to act as the entity, mapped to the `REVINFO` table. -You can, however, supply your own approach to collecting this information which might be useful to capture additional details such as who made a change or the ip address from which the request came. +You can, however, supply your own approach to collecting this information which might be useful to capture additional details such as who made a change +or the IP address from which the request came. There are two things you need to make this work: . First, you will need to tell Envers about the entity you wish to use. - Your entity must use the `@org.hibernate.envers.RevisionEntity` annotation. - It must define the two attributes described above annotated with `@org.hibernate.envers.RevisionNumber` and `@org.hibernate.envers.RevisionTimestamp`, respectively. - You can extend from `org.hibernate.envers.DefaultRevisionEntity`, if you wish, to inherit all these required behaviors. +Your entity must use the `@org.hibernate.envers.RevisionEntity` annotation. +It must define the two attributes described above annotated with `@org.hibernate.envers.RevisionNumber` and `@org.hibernate.envers.RevisionTimestamp`, respectively. +You can extend from `org.hibernate.envers.DefaultRevisionEntity`, if you wish, to inherit all these required behaviors. + - Simply add the custom revision entity as you do your normal entities and Envers will _find it_. +Simply add the custom revision entity as you do your normal entities and Envers will *find it*. + NOTE: It is an error for there to be multiple entities marked as `@org.hibernate.envers.RevisionEntity` -. Second, you need to tell Envers how to create instances of your revision entity which is handled by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/RevisionListener.html#newRevision-java.lang.Object-[`newRevision( Object revisionEntity )`] method of the `org.hibernate.envers.RevisionListener` interface. +. Second, you need to tell Envers how to create instances of your revision entity which is handled by the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/RevisionListener.html#newRevision-java.lang.Object-[`newRevision( Object revisionEntity )`] +method of the `org.hibernate.envers.RevisionListener` interface. + - You tell Envers your custom `org.hibernate.envers.RevisionListener` implementation to use by specifying it on the `@org.hibernate.envers.RevisionEntity` annotation, using the value attribute. - If your `RevisionListener` class is inaccessible from `@RevisionEntity` (e.g. it exists in a different module), set `org.hibernate.envers.revision_listener` property to its fully qualified class name. - Class name defined by the configuration parameter overrides revision entity's value attribute. +You tell Envers your custom `org.hibernate.envers.RevisionListener` implementation to use by specifying it on the `@org.hibernate.envers.RevisionEntity` annotation, using the value attribute. +If your `RevisionListener` class is inaccessible from `@RevisionEntity` (e.g. it exists in a different module), +set `org.hibernate.envers.revision_listener` property to its fully qualified class name. +Class name defined by the configuration parameter overrides the revision entity's value attribute. -[source,java] ----- -@RevisionEntity( MyCustomRevisionListener.class ) -public class MyCustomRevisionEntity { - ... -} +Considering we have a `CurrentUser` utility which stores the currently logged user: -public class MyCustomRevisionListener implements RevisionListener { - public void newRevision( Object revisionEntity ) { - MyCustomRevisionEntity customRevisionEntity = ( MyCustomRevisionEntity ) revisionEntity; - } -} ----- - -.ExampleRevEntity.java +[[envers-revisionlog-CurrentUser-example]] +.`CurrentUser` utility ==== -[source,java] +[source, JAVA, indent=0] +---- +include::{sourcedir}/CustomRevisionEntityTest.java[tags=envers-revisionlog-CurrentUser-example] ---- -package `org.hibernate.envers.example;` +==== -import `org.hibernate.envers.RevisionEntity;` -import `org.hibernate.envers.DefaultRevisionEntity;` +Now, we need to provide a custom `@RevisionEntity` to store the currently logged user -import javax.persistence.Entity; +[[envers-revisionlog-RevisionEntity-example]] +.Custom `@RevisionEntity` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CustomRevisionEntityTest.java[tags=envers-revisionlog-RevisionEntity-example] +---- +==== -@Entity -@RevisionEntity( ExampleListener.class ) -public class ExampleRevEntity extends DefaultRevisionEntity { - private String username; +With the custom `RevisionEntity` implementation in place, +we only need to provide the `RevisionEntity` implementation which acts as a factory +of `RevisionEntity` instances. - public String getUsername() { return username; } - public void setUsername( String username ) { this.username = username; } -} +[[envers-revisionlog-RevisionListener-example]] +.Custom `@RevisionListener` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CustomRevisionEntityTest.java[tags=envers-revisionlog-RevisionListener-example] ---- ==== -.ExampleListener.java +When generating the database schema, Envers creates the following `RevisionEntity` table: + +[[envers-revisionlog-custom-revision-entity-table-example]] +.Auto-generated `RevisionEntity` Envers table ==== -[source,java] +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-revisionlog-custom-revision-entity-table-example.sql[] ---- -package `org.hibernate.envers.example;` +==== -import `org.hibernate.envers.RevisionListener;` -import org.jboss.seam.security.Identity; -import org.jboss.seam.Component; +You can see the `username` column in place. -public class ExampleListener implements RevisionListener { +Now, when inserting a `Customer` entity, Envers generates the following statements: - public void newRevision( Object revisionEntity ) { - ExampleRevEntity exampleRevEntity = ( ExampleRevEntity ) revisionEntity; - Identity identity = - (Identity) Component.getInstance( "org.jboss.seam.security.identity" ); +[[envers-revisionlog-RevisionEntity-persist-example]] +.Auditing using the custom `@RevisionEntity` instance +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CustomRevisionEntityTest.java[tags=envers-revisionlog-RevisionEntity-persist-example] +---- - exampleRevEntity.setUsername( identity.getUsername() ); - } -} +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-revisionlog-RevisionEntity-persist-example.sql[] ---- ==== -[NOTE] +As demonstrated by the example above, the username is properly set and propagated to the `CUSTOM_REV_INFO` table. + +[WARNING] ==== -An alternative method to using the `org.hibernate.envers.RevisionListener` is to instead call the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/AuditReader.html#getCurrentRevision-java.lang.Class-boolean-[`getCurrentRevision( Class revisionEntityClass, boolean persist )`] method of the `org.hibernate.envers.AuditReader` interface to obtain the current revision, and fill it with desired information. +**This strategy is deprecated since version 5.2. The alternative is to use dependency injection offered as of version 5.3.** + +An alternative method to using the `org.hibernate.envers.RevisionListener` is to instead call the +[line-through]#https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/envers/AuditReader.html#getCurrentRevision-java.lang.Class-boolean-[`getCurrentRevision( Class revisionEntityClass, boolean persist )`]# +method of the `org.hibernate.envers.AuditReader` interface to obtain the current revision, +and fill it with desired information. + The method accepts a `persist` parameter indicating whether the revision entity should be persisted prior to returning from this method: `true`:: ensures that the returned entity has access to its identifier value (revision number), but the revision entity will be persisted regardless of whether there are any audited entities changed. `false`:: means that the revision number will be `null`, but the revision entity will be persisted only if some audited entities have changed. ==== +[NOTE] +==== +As of Hibernate Envers 5.3, dependency injection is now supported for a `RevisionListener`. + +This feature is up to the various dependency frameworks, such as CDI and Spring, to supply the +necessary implementation during Hibernate ORM bootstrap to support injection. If no qualifying +implementation is supplied, the `RevisionListener` will be constructed without injection. +==== + [[envers-tracking-modified-entities-revchanges]] === Tracking entity names modified during revisions -By default entity types that have been changed in each revision are not being tracked. -This implies the necessity to query all tables storing audited data in order to retrieve changes made during specified revision. +By default, entity types that have been changed in each revision are not being tracked. +This implies the necessity to query all tables storing audited data in order to retrieve changes made during the specified revision. Envers provides a simple mechanism that creates `REVCHANGES` table which stores entity names of modified persistent objects. Single record encapsulates the revision identifier (foreign key to `REVINFO` table) and a string value. @@ -283,132 +563,112 @@ Tracking of modified entity names can be enabled in three different ways: In this case `org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity` will be implicitly used as the revision log entity. . Create a custom revision entity that extends `org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity` class. + -[source,java] +[source, JAVA, indent=0] ---- -@RevisionEntity -public class ExtendedRevisionEntity extends DefaultTrackingModifiedEntitiesRevisionEntity { - ... -} +include::{sourcedir}/EntityTypeChangeAuditDefaultTrackingTest.java[tags=envers-tracking-modified-entities-revchanges-example] ---- + . Mark an appropriate field of a custom revision entity with `@org.hibernate.envers.ModifiedEntityNames` annotation. The property is required to be of `Set` type. + -[source,java] +[source, JAVA, indent=0] ---- -@RevisionEntity -public class AnnotatedTrackingRevisionEntity { - ... - - @ElementCollection - @JoinTable( name = "REVCHANGES", joinColumns = @JoinColumn( name = "REV" ) ) - @Column( name = "ENTITYNAME" ) - @ModifiedEntityNames - private Set modifiedEntityNames; - - ... -} +include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-example] ---- -+ -Users, that have chosen one of the approaches listed above, -can retrieve all entities modified in a specified revision by utilizing API described in <>. +Considering we have a `Customer` entity illustrated by the following example: -Users are also allowed to implement custom mechanism of tracking modified entity types. -In this case, they shall pass their own implementation of `org.hibernate.envers.EntityTrackingRevisionListener` interface as the value of `@org.hibernate.envers.RevisionEntity` annotation. -`EntityTrackingRevisionListener` interface exposes one method that notifies whenever audited entity instance has been added, modified or removed within current revision boundaries. - -.CustomEntityTrackingRevisionListener.java +[[envers-tracking-modified-entities-revchanges-before-rename-example]] +.`Customer` entity before renaming ==== -[source,java] +[source, JAVA, indent=0] ---- -public class CustomEntityTrackingRevisionListener implements EntityTrackingRevisionListener { - - @Override - public void entityChanged( Class entityClass, String entityName, - Serializable entityId, RevisionType revisionType, - Object revisionEntity ) { - String type = entityClass.getName(); - ( ( CustomTrackingRevisionEntity ) revisionEntity ).addModifiedEntityType( type ); - } - - @Override - public void newRevision( Object revisionEntity ) { - } -} +include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-before-rename-example] ---- ==== -.CustomTrackingRevisionEntity.java +If the `Customer` entity class name is changed to `ApplicationCustomer`, +Envers is going to insert a new record in the `REVCHANGES` table with the previous entity class name: + +[[envers-tracking-modified-entities-revchanges-after-rename-example]] +.`Customer` entity after renaming ==== -[source,java] +[source, JAVA, indent=0] +---- +include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-after-rename-example] ---- -@Entity -@RevisionEntity( CustomEntityTrackingRevisionListener.class ) -public class CustomTrackingRevisionEntity { - @Id - @GeneratedValue - @RevisionNumber - private int customId; +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-tracking-modified-entities-revchanges-after-rename-example.sql[] +---- +==== - @RevisionTimestamp - private long customTimestamp; +Users, that have chosen one of the approaches listed above, +can retrieve all entities modified in a specified revision by utilizing API described in <>. - @OneToMany( mappedBy="revision", cascade={ CascadeType.PERSIST, CascadeType.REMOVE } ) - private Set modifiedEntityTypes = new HashSet(); +Users are also allowed to implement custom mechanisms of tracking modified entity types. +In this case, they shall pass their own implementation of `org.hibernate.envers.EntityTrackingRevisionListener` +interface as the value of `@org.hibernate.envers.RevisionEntity` annotation. - public void addModifiedEntityType( String entityClassName ) { - modifiedEntityTypes.add( new ModifiedEntityTypeEntity( this, entityClassName ) ); - } +`EntityTrackingRevisionListener` interface exposes one method that notifies whenever audited entity instance has been +added, modified or removed within current revision boundaries. - ... -} +[[envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example]] +.The `EntityTrackingRevisionListener` implementation +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example] ---- ==== -.ModifiedEntityTypeEntity.java +The `CustomTrackingRevisionListener` adds the fully-qualified class name to the `modifiedEntityTypes` attribute of the `CustomTrackingRevisionEntity`. + +[[envers-tracking-modified-entities-revchanges-RevisionEntity-example]] +.The `RevisionEntity` using the custom `EntityTrackingRevisionListener` ==== -[source,java] +[source, JAVA, indent=0] ---- -@Entity -public class ModifiedEntityTypeEntity { - - @Id - @GeneratedValue - private Integer id; - - @ManyToOne - private CustomTrackingRevisionEntity revision; +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-RevisionEntity-example] +---- +==== - private String entityClassName; +The `CustomTrackingRevisionEntity` contains a `@OneToMany` list of `ModifiedTypeRevisionEntity` - ... -} +[[envers-tracking-modified-entities-revchanges-EntityType-example]] +.The `EntityType` encapsulatets the entity type name before a class name modification +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-EntityType-example] ---- ==== -[source,java] ----- -CustomTrackingRevisionEntity revEntity = - getAuditReader().findRevision( CustomTrackingRevisionEntity.class, revisionNumber ); +Now, when fetching the `CustomTrackingRevisionEntity`, you cna get access to the previous entity class name. -Set modifiedEntityTypes = revEntity.getModifiedEntityTypes(); +[[envers-tracking-modified-entities-revchanges-query-example]] +.Getting the `EntityType` through the `CustomTrackingRevisionEntity` +==== +[source, JAVA, indent=0] ---- +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-query-example] +---- +==== [[envers-tracking-properties-changes]] -=== Tracking entity changes at property level +=== Tracking entity changes at the property level By default, the only information stored by Envers are revisions of modified entities. -This approach lets user create audit queries based on historical values of entity properties. +This approach lets users create audit queries based on historical values of entity properties. Sometimes it is useful to store additional metadata for each revision, when you are interested also in the type of changes, not only about the resulting values. The feature described in <> makes it possible to tell which entities were modified in a given revision. The feature described here takes it one step further. -"Modification Flags" enable Envers to track which properties of audited entities were modified in a given revision. +_Modification Flags_ enable Envers to track which properties of audited entities were modified in a given revision. -Tracking entity changes at property level can be enabled by: +Tracking entity changes at the property level can be enabled by: . setting `org.hibernate.envers.global_with_modified_flag` configuration property to `true`. This global switch will cause adding modification flags to be stored for all audited properties of all audited entities. @@ -417,9 +677,39 @@ Tracking entity changes at property level can be enabled by: The trade-off coming with this functionality is an increased size of audit tables and a very little, almost negligible, performance drop during audit writes. This is due to the fact that every tracked property has to have an accompanying boolean column in the schema that stores information about the property modifications. -Of course it is Envers job to fill these columns accordingly - no additional work by the developer is required. +Of course, it is Enver's job to fill these columns accordingly - no additional work by the developer is required. Because of costs mentioned, it is recommended to enable the feature selectively, when needed with use of the granular configuration means described above. +[[envers-tracking-properties-changes-mapping-example]] +.Mapping for tracking entity changes at the property level +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/ModifiedFlagsAuditTest.java[tags=envers-tracking-properties-changes-mapping-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-tracking-properties-changes-mapping-example.sql[] +---- +==== + +As you can see, every property features a `_MOD` column (e.g. `createdOn_MOD`) in the audit log. + +[[envers-tracking-properties-changes-example]] +.Tracking entity changes at the property level example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/ModifiedFlagsAuditTest.java[tags=envers-tracking-properties-changes-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-tracking-properties-changes-example.sql[] +---- +==== + To see how "Modified Flags" can be utilized, check out the very simple query API that uses them: <>. [[envers-queries]] @@ -427,78 +717,131 @@ To see how "Modified Flags" can be utilized, check out the very simple query API You can think of historic data as having two dimensions: -horizontal:: is the state of the database at a given revision. Thus, you can query for entities as they were at revision N. -vertical:: are the revisions, at which entities changed. Hence, you can query for revisions, in which a given entity changed. +horizontal:: The state of the database at a given revision. Thus, you can query for entities as they were at revision N. +vertical:: The revisions, at which entities changed. Hence, you can query for revisions, in which a given entity changed. The queries in Envers are similar to Hibernate Criteria queries, so if you are common with them, using Envers queries will be much easier. The main limitation of the current queries implementation is that you cannot traverse relations. You can only specify constraints on the ids of the related entities, and only on the "owning" side of the relation. -This however will be changed in future releases. +This, however, will be changed in future releases. -Please note, that queries on the audited data will be in many cases much slower than corresponding queries on "live" data, as they involve correlated subselects. +[NOTE] +==== +The queries on the audited data will be in many cases much slower than corresponding queries on "live" data, +as, especially for the default audit strategy, they involve correlated subselects. -Queries are improved both in terms of speed and possibilities, when using the valid-time audit strategy, that is when storing both start and end revisions for entities. See <>. +Queries are improved both in terms of speed and possibilities when using the validity audit strategy, +which stores both start and end revisions for entities. See <>. +==== [[entities-at-revision]] === Querying for entities of a class at a given revision The entry point for this type of queries is: -[source,java] +[[entities-at-revision-example]] +.Getting the `Customer` entity at a given revision +==== +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader() - .createQuery() - .forEntitiesAtRevision( MyEntity.class, revisionNumber ); +include::{sourcedir}/QueryAuditTest.java[tags=entities-at-revision-example] ---- +==== -You can then specify constraints, which should be met by the entities returned, by adding restrictions, which can be obtained using the `AuditEntity` factory class. -For example, to select only entities where the "name" property is equal to "John": +[[entities-filtering]] +=== Querying for entities using filtering criteria -[source,java] +You can then specify constraints, which should be met by the entities returned, by adding restrictions, +which can be obtained using the `AuditEntity` factory class. + +For example, to select only entities where the `firstName` property is equal to "John": + +[[entities-filtering-example]] +.Getting the `Customer` audit log with a given `firstName` attribute value +==== +[source, JAVA, indent=0] ---- -query.add( AuditEntity.property( "name" ).eq( "John" ) ); +include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-example] ---- +==== -And to select only entities that are related to a given entity: +And, to select only entities whose relationships are related to a given entity, +you can use either the target entity or its identifier. -[source,java] +[[entities-filtering-by-entity-example]] +.Getting the `Customer` entities whose `address` attribute matches the given entity reference +==== +[source, JAVA, indent=0] ---- -query.add( AuditEntity.property( "address" ).eq( relatedEntityInstance ) ); -// or -query.add( AuditEntity.relatedId( "address" ).eq( relatedEntityId ) ); -// or -query.add( AuditEntity.relatedId( "address" ).in( relatedEntityId1, relatedEntityId2 ) ); +include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-by-entity-example] ---- +[source, SQL, indent=0] +---- +include::{extrasdir}/entities-filtering-by-entity-example.sql[] +---- +==== + +The same SQL is generated even if we provide the identifier instead of the target entity reference. + +[[entities-filtering-by-entity-identifier-example]] +.Getting the `Customer` entities whose `address` identifier matches the given entity identifier +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-by-entity-identifier-example] +---- +==== + +Apart for strict equality matching, you can also use an `IN` clause to provide multiple entity identifiers: + +[[entities-in-clause-filtering-by-entity-identifier-example]] +.Getting the `Customer` entities whose `address` identifier matches one of the given entity identifiers +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditTest.java[tags=entities-in-clause-filtering-by-entity-identifier-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/entities-in-clause-filtering-by-entity-identifier-example.sql[] +---- +==== + You can limit the number of results, order them, and set aggregations and projections (except grouping) in the usual way. When your query is complete, you can obtain the results by calling the `getSingleResult()` or `getResultList()` methods. A full query, can look for example like this: -[source,java] +[[entities-filtering-and-pagination]] +.Getting the `Customer` entities using filtering and pagination +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-and-pagination] +---- + +[source, SQL, indent=0] ---- -List personsAtAddress = getAuditReader().createQuery() - .forEntitiesAtRevision( Person.class, 12 ) - .addOrder( AuditEntity.property( "surname" ).desc() ) - .add( AuditEntity.relatedId( "address" ).eq( addressId ) ) - .setFirstResult( 4 ) - .setMaxResults( 2 ) - .getResultList(); +include::{extrasdir}/entities-filtering-and-pagination.sql[] ---- +==== [[revisions-of-entity]] === Querying for revisions, at which entities of a given class changed The entry point for this type of queries is: -[source,java] +[[revisions-of-entity-query-example]] +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forRevisionsOfEntity( MyEntity.class, false, true ); +include::{sourcedir}/QueryAuditTest.java[tags=revisions-of-entity-query-example] ---- You can add constraints to this query in the same way as to the previous one. + There are some additional possibilities: . using `AuditEntity.revisionNumber()` you can specify constraints, projections and order on the revision number, in which the audited entity was modified @@ -509,107 +852,185 @@ There are some additional possibilities: . `AuditEntity.revisionType()` gives you access as above to the type of the revision (`ADD`, `MOD`, `DEL`). Using these methods, you can order the query results by revision number, set projection or constraint the revision number to be greater or less than a specified value, etc. -For example, the following query will select the smallest revision number, at which entity of class `MyEntity` with id `entityId` has changed, after revision number 42: +For example, the following query will select the smallest revision number, at which entity of class `MyEntity` with id `entityId` has changed, after revision number 2: -[source,java] +[[revisions-of-entity-query-by-revision-number-example]] +[source, JAVA, indent=0] ---- -Number revision = (Number) getAuditReader().createQuery() - .forRevisionsOfEntity( MyEntity.class, false, true ) - .setProjection( AuditEntity.revisionNumber().min() ) - .add( AuditEntity.id().eq( entityId ) ) - .add( AuditEntity.revisionNumber().gt( 42 ) ) - .getSingleResult(); +include::{sourcedir}/QueryAuditTest.java[tags=revisions-of-entity-query-by-revision-number-example] ---- The second additional feature you can use in queries for revisions is the ability to _maximize_/_minimize_ a property. -For example, if you want to select the smallest possibler revision at which the value of the `actualDate` for a given entity was larger then a given value: -[source,java] +For example, if you want to select the smallest possibler revision at which the value of the `createdOn` +attribute was larger then a given value, +you can run the following query: + +[[revisions-of-entity-query-minimize-example]] +[source, JAVA, indent=0] ---- -Number revision = (Number) getAuditReader().createQuery() - .forRevisionsOfEntity( MyEntity.class, false, true) // We are only interested in the first revision - .setProjection( AuditEntity.revisionNumber().min() ) - .add( AuditEntity.property( "actualDate" ).minimize() - .add( AuditEntity.property( "actualDate" ).ge( givenDate ) ) - .add( AuditEntity.id().eq( givenEntityId ) )) .getSingleResult(); +include::{sourcedir}/QueryAuditTest.java[tags=revisions-of-entity-query-minimize-example] ---- -The `minimize()` and `maximize()` methods return a criteria, to which you can add constraints, which must be met by the entities with the _maximized_/_minimized_ properties. - -[NOTE] -==== -`AggregatedAuditExpression#computeAggregationInInstanceContext()` enables the possibility to compute aggregated expression in the context of each entity instance separately. -It turns out useful when querying for latest revisions of all entities of a particular type. -==== +The `minimize()` and `maximize()` methods return a criteria, to which you can add constraints, +which must be met by the entities with the _maximized_/_minimized_ properties. You probably also noticed that there are two boolean parameters, passed when creating the query. -`selectEntitiesOnly`:: the first parameter is only valid when you don't set an explicit projection. - If true, the result of the query will be a list of entities (which changed at revisions satisfying the specified constraints). - If false, the result will be a list of three element arrays: +`selectEntitiesOnly`:: The first parameter is only valid when you don't set an explicit projection. ++ +If true, the result of the query will be a list of entities (which changed at revisions satisfying the specified constraints). ++ +If false, the result will be a list of three element arrays: + +* the first element will be the changed entity instance. +* the second will be an entity containing revision data (if no custom entity is used, this will be an instance of `DefaultRevisionEntity`). +* the third will be the type of the revision (one of the values of the `RevisionType` enumeration: `ADD`, `MOD`, `DEL`). + +`selectDeletedEntities`:: The second parameter specifies if revisions, +in which the entity was deleted should be included in the results. ++ +If yes, such entities will have the revision type `DEL` and all attributes, except the `id`, will be set to `null`. + +Another useful feature is `AggregatedAuditExpression#computeAggregationInInstanceContext()`. This can be used to create +an aggregate query based on the entity instance primary key. - * the first element will be the changed entity instance. - * the second will be an entity containing revision data (if no custom entity is used, this will be an instance of `DefaultRevisionEntity`). - * the third will be the type of the revision (one of the values of the `RevisionType` enumeration: `ADD`, `MOD`, `DEL`). +For example, if you wanted to locate all customers but only wanted to retrieve the instances with the +maximum revision number, you would use the following query: -`selectDeletedEntities`:: the second parameter specifies if revisions, in which the entity was deleted should be included in the results. - If yes, such entities will have the revision type `DEL` and all fields, except the id, `null`. +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditTest.java[tags=aggregate-max-revision-with-entity-example] +---- + +In other words, the result set would contain a list of `Customer` instances, one per primary key. Each instance would +hold the audited property data at the _maximum_ revision number for each `Customer` primary key. [[envers-tracking-properties-changes-queries]] -=== Querying for revisions of entity that modified given property +=== Querying for entity revisions that modified a given property For the two types of queries described above it's possible to use special `Audit` criteria called `hasChanged()` and `hasNotChanged()` that makes use of the functionality described in <>. -They're best suited for vertical queries, however existing API doesn't restrict their usage for horizontal ones. -Let's have a look at following examples: +Let's have a look at various queries that can benefit from these two criteria. -[source,java] +First, you must make sure that your entity can track _modification flags_: + +[[envers-tracking-properties-changes-queries-entity-example]] +.Valid only when audit logging tracks entity attribute modification flags +==== +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forRevisionsOfEntity( MyEntity.class, false, true ) - .add( AuditEntity.id().eq( id ) ); - .add( AuditEntity.property( "actualDate" ).hasChanged() ); +include::{sourcedir}/QueryAuditWithModifiedFlagTest.java[tags=envers-tracking-properties-changes-queries-entity-example] ---- +==== -This query will return all revisions of `MyEntity` with given `id`, where the `actualDate` property has been changed. -Using this query we won't get all other revisions in which `actualDate` wasn't touched. -Of course, nothing prevents user from combining `hasChanged` condition with some additional criteria - add method can be used here in a normal way. +The following query will return all revisions of the `Customer` entity with the given `id`, +for which the `lastName` property has changed. -[source,java] +[[envers-tracking-properties-changes-queries-hasChanged-example]] +.Getting all `Customer` revisions for which the `lastName` attribute has changed +==== +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( MyEntity.class, revisionNumber ) - .add( AuditEntity.property( "prop1" ).hasChanged() ) - .add( AuditEntity.property( "prop2" ).hasNotChanged() ); +include::{sourcedir}/QueryAuditWithModifiedFlagTest.java[tags=envers-tracking-properties-changes-queries-hasChanged-example] ---- -This query will return horizontal slice for `MyEntity` at the time `revisionNumber` was generated. -It will be limited to revisions that modified `prop1` but not `prop2`. +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-tracking-properties-changes-queries-hasChanged-example.sql[] +---- +==== -Note that the result set will usually also contain revisions with numbers lower than the `revisionNumber`, -so wem cannot read this query as "Give me all MyEntities changed in `revisionNumber` with `prop1` modified and `prop2` untouched". -To get such result we have to use the `forEntitiesModifiedAtRevision` query: +Using this query we won't get all other revisions in which `lastName` wasn't touched. +From the SQL query you can see that the `lastName_MOD` column is being used in the WHERE clause, +hence the aforementioned requirement for tracking modification flags. -[source,java] +Of course, nothing prevents users from combining `hasChanged` condition with some additional criteria. + +[[envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example]] +.Getting all `Customer` revisions for which the `lastName` attribute has changed and the `firstName` attribute has not changed +==== +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesModifiedAtRevision( MyEntity.class, revisionNumber ) - .add( AuditEntity.property( "prop1" ).hasChanged() ) - .add( AuditEntity.property( "prop2" ).hasNotChanged() ); +include::{sourcedir}/QueryAuditWithModifiedFlagTest.java[tags=envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example] ---- +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql[] +---- +==== + +To get the `Customer` entities changed at a given `revisionNumber` with `lastName` modified and `firstName` untouched, +we have to use the `forEntitiesModifiedAtRevision` query: + +[[envers-tracking-properties-changes-queries-at-revision-example]] +.Getting the `Customer` entity for a given revision if the `lastName` attribute has changed and the `firstName` attribute has not changed +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditWithModifiedFlagTest.java[tags=envers-tracking-properties-changes-queries-at-revision-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-tracking-properties-changes-queries-at-revision-example.sql[] +---- +==== + +[[envers-tracking-obtain-properties-changed-queries]] +=== Querying for revisions of entity including property names that were modified + +[NOTE] +==== +This feature described here is still considered experimental. +It is subject to change in future releases based on user feedback to improve its usefulness. +==== + +Sometimes it may be useful to query entity revisions and also determine all the properties of that revision which +were modified without having to issue multiple queries using `hasChanged()` and `hasNotChanged()` criteria. + +You can now obtain this information easily by using the following query: + +.Querying entity revisions including property names modified. +==== +[source, JAVA, indent=0] +---- +List results = AuditReaderFactory.get( entityManager ) + .createQuery() + .forRevisionsOfEntityWithChanges( Customer.class, false ) + .add( AuditEntity.id().eq( 1L ) ) + .getResultList(); + +for ( Object entry : results ) { + final Object[] array = (Object[]) entry; + final Set propertiesChanged = (Set) array[3]; + for ( String propertyName : propertiesChanged ) { + /* Do something useful with the modified property `propertyName` */ + } +} +---- +==== + [[envers-tracking-modified-entities-queries]] -=== Querying for entities modified in a given revision +=== Querying for entity types modified in a given revision + +[NOTE] +==== +The methods described below can be used only when the default mechanism of tracking changed entity types is enabled (see <>). +==== -The basic query allows retrieving entity names and corresponding Java classes changed in a specified revision: +This basic query allows retrieving entity names and corresponding Java classes changed in a specified revision: -[source,java] +[[envers-tracking-modified-entities-queries-example]] +.Retrieving entity names and corresponding Java classes changed in a specified revision +==== +[source, JAVA, indent=0] ---- -modifiedEntityTypes = getAuditReader() - .getCrossTypeRevisionChangesReader() - .findEntityTypes( revisionNumber ); +include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-queries-example] ---- +==== Other queries (also accessible from `org.hibernate.envers.CrossTypeRevisionChangesReader`): @@ -625,130 +1046,189 @@ Other queries (also accessible from `org.hibernate.envers.CrossTypeRevisionChang Returns a map containing lists of entity snapshots grouped by modification operation (e.g. addition, update and removal). Executes `3N+1` SQL queries, where `N` is a number of different entity classes modified within specified revision. -Note that methods described above can be legally used only when the default mechanism of tracking changed entity names is enabled (see <>). - -[[envers-querying-entity-relation-jobs]] +[[envers-querying-entity-relation-joins]] === Querying for entities using entity relation joins -Audit queries support the ability to apply constraints, projections, and sort operations based on entity relations. In order -to traverse entity relations through an audit query, you must use the relation traversal API with a join type. - -[IMPORTANT] +[WARNING] ==== Relation join queries are considered experimental and may change in future releases. ==== +Audit queries support the ability to apply constraints, projections, and sort operations based on entity relations. In order +to traverse entity relations through an audit query, you must use the relation traversal API with a join type. + [NOTE] ==== -Relation joins can only be applied to `*-to-one` mappings and can only be specified using `JoinType.LEFT` or -`JoinType.INNER`. +Relation joins can be applied to `many-to-one` and `many-to-one` mappings only when using `JoinType.LEFT` or `JoinType.INNER`. ==== The basis for creating an entity relation join query is as follows: -[source,java] +[[envers-querying-entity-relation-inner-join]] +.INNER JOIN entity audit query +==== +[source, JAVA, indent=0] ---- -// create an inner join query -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER ); +include::{sourcedir}/QueryAuditTest.java[tags=envers-querying-entity-relation-inner-join] +---- +==== -// create a left join query -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.LEFT ); +[[envers-querying-entity-relation-left-join]] +.LEFT JOIN entity audit query +==== +[source, JAVA, indent=0] ---- +include::{sourcedir}/QueryAuditTest.java[tags=envers-querying-entity-relation-left-join] +---- +==== -Like any other query, constraints may be added to restrict the results. For example, to find all `Car` entities that -have an owner with a name starting with `Joe`, you would use: +Like any other query, constraints may be added to restrict the results. -[source,java] +For example, to find a `Customers` entities at a given revision whose addresses are in `România`, +you can use the following query: + +[[envers-querying-entity-relation-join-restriction]] +.Filtering the join relation with a WHERE clause predicate +==== +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER ) - .add( AuditEntity.property( "name" ).like( "Joe%" ) ); +include::{sourcedir}/QueryAuditTest.java[tags=envers-querying-entity-relation-join-restriction] ---- -It is also possible to traverse beyond the first relation in an entity graph. For example, to find all `Car` entities -where the owner's address has a street number that equals `1234`: +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-querying-entity-relation-join-restriction.sql[] +---- +==== -[source,java] +It is also possible to traverse beyond the first relation in an entity graph. + +For example, to find all `Customer` entities at a given revision +with the country attribute of the address property being `România`: + +[[envers-querying-entity-relation-nested-join-restriction]] +.Filtering a nested join relation with a WHERE clause predicate +==== +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER ) - .traverseRelation( "address", JoinType.INNER ) - .add( AuditEntity.property( "streetNumber" ).eq( 1234 ) ); +include::{sourcedir}/QueryAuditAdressCountryTest.java[tags=envers-querying-entity-relation-nested-join-restriction] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-querying-entity-relation-nested-join-restriction.sql[] ---- +==== -Complex constraints may also be added that are applicable to properties of nested relations or the base query entity or -relation state, such as testing for `null`. For example, the following query illustrates how to find all `Car` entities where -the owner's age is `20` or that the car has _no_ owner: +Constraints may also be added to the properties of nested joined relations, such as testing for `null`. -[source,java] +For example, the following query illustrates how to find all `Customer` entities at a given revision +having the `address` in `Cluj-Napoca` or the `address` does _not_ have any country entity reference: + +[[envers-querying-entity-relation-join-multiple-restrictions]] +.Filtering a join relation using multiple predicates +==== +[source, JAVA, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.LEFT, "p" ) - .up() - .add( - AuditEntity.or( - AuditEntity.property( "p", "age" ).eq( 20 ), - AuditEntity.relatedId( "owner" ).eq( null ) - ) - ) - .addOrder( AuditEntity.property( "make" ).asc() ); +include::{sourcedir}/QueryAuditAdressCountryTest.java[tags=envers-querying-entity-relation-join-multiple-restrictions] ---- +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-querying-entity-relation-join-multiple-restrictions.sql[] +---- +==== + [NOTE] ==== Queries can use the `up` method to navigate back up the entity graph. ==== -Disjunction criterion may also be applied to relation join queries. For example, the following query will find all -`Car` entities where the owner's age is `20` or that the owner lives at an address where the street number equals `1234`: +Disjunction criterion may also be applied to relation join queries. -[source,java] +For example, the following query will find all `Customer` entities at a given revision +where the country name is `România` or that the `Customer` lives in `Cluj-Napoca`: + +[[envers-querying-entity-relation-nested-join-multiple-restrictions]] +.Filtering a nested join relation using multiple predicates +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditAdressCountryTest.java[tags=envers-querying-entity-relation-nested-join-multiple-restrictions] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-querying-entity-relation-nested-join-multiple-restrictions.sql[] +---- +==== + +Lastly, this example illustrates how related entity properties can be compared in a single constraint. + +Assuming, the `Customer` and the `Address` were previously changed as follows: + +[[envers-querying-entity-relation-nested-join-multiple-restrictions-combined-entities]] +.Changing the `Address` to match the `Country` name +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditAdressCountryTest.java[tags=envers-querying-entity-relation-nested-join-multiple-restrictions-combined-entities] +---- +==== + +The following query shows how to find the `Customer` entities +where the `city` property of the `address` attribute equals the `name` of the associated `country` attribute. + +[[envers-querying-entity-relation-nested-join-multiple-restrictions-combined]] +.Filtering a nested join relation using multiple predicates +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditAdressCountryTest.java[tags=envers-querying-entity-relation-nested-join-multiple-restrictions-combined] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER, "p" ) - .traverseRelation( "address", JoinType.INNER, "a" ) - .up() - .up() - .add( - AuditEntity.disjunction() - .add( AuditEntity.property( "p", "age" ).eq( 20 ) ) - .add( AuditEntity.property( "a", "streetNumber" ).eq( 1234 ) - ) - ) - .addOrder( AuditEntity.property( "make" ).asc() ); ----- - -Lastly, this example illustrates how related entity properties can be compared as a constraint. This query shows how to -find the `Car` entities where the owner's `age` equals the `streetNumber` of where the owner lives: + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-querying-entity-relation-nested-join-multiple-restrictions-combined.sql[] +---- +==== + +[[envers-querying-revision-entities]] +=== Querying for revision information without loading entities + +Sometimes, it may be useful to load information about revisions to find out who performed specific revisions or +to know what entity names were modified but the change log about the related audited entities isn't needed. +This API allows an efficient way to get the revision information entity log without instantiating the actual +entities themselves. + +Here is a simple example: [source,java] ---- AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER, "p" ) - .traverseRelation( "address", JoinType.INNER, "a" ) - .up() - .up() - .add( AuditEntity.property( "p", "age" ).eqProperty( "a", "streetNumber" ) ); + .forRevisionsOfEntity( DefaultRevisionEntity.class, true ) + .add( AuditEntity.revisionNumber().between( 1, 25 ) ); ---- +This query will return all revision information entities for revisions between 1 and 25 including those which are +related to deletions. If deletions are not of interest, you would pass `false` as the second argument. + +Note that this query uses the `DefaultRevisionEntity` class type. The class provided will vary depending on the +configuration properties used to configure Envers or if you supply your own revision entity. Typically users who +will use this API will likely be providing a custom revision entity implementation to obtain custom information +being maintained per revision. + +[[envers-conditional-auditing]] === Conditional auditing -Envers persists audit data in reaction to various Hibernate events (e.g. `post update`, `post insert`, and so on), using a series of event listeners from the `org.hibernate.envers.event.spi` package. +Envers persists audit data in reaction to various Hibernate events (e.g. `post update`, `post insert`, and so on), +using a series of event listeners from the `org.hibernate.envers.event.spi` package. By default, if the Envers jar is in the classpath, the event listeners are auto-registered with Hibernate. Conditional auditing can be implemented by overriding some of the Envers event listeners. To use customized Envers event listeners, the following steps are needed: -. Turn off automatic Envers event listeners registration by setting the `hibernate.listeners.envers.autoRegister` Hibernate property to `false`. +. Turn off automatic Envers event listeners registration by setting the `hibernate.envers.autoRegisterListeners` Hibernate property to `false`. . Create subclasses for appropriate event listeners. For example, if you want to conditionally audit entity insertions, extend the `org.hibernate.envers.event.spi.EnversPostInsertEventListenerImpl` class. @@ -762,10 +1242,11 @@ To use customized Envers event listeners, the following steps are needed: [NOTE] ==== -The use of `hibernate.listeners.envers.autoRegister` has been deprecated. A new configuration setting -`hibernate.envers.autoRegisterListeners` should be used instead. +The use of `hibernate.listeners.envers.autoRegister` has been deprecated. +A new configuration setting `hibernate.envers.autoRegisterListeners` should be used instead. ==== +[[envers-schema]] === Understanding the Envers Schema For each audited entity (that is, for each entity containing at least one audited field), an audit table is created. @@ -776,122 +1257,65 @@ The audit table contains the following columns: id:: `id` of the original entity (this can be more then one column in the case of composite primary keys) revision number:: an integer, which matches to the revision number in the revision entity table. -revision type:: a small integer -audited fields:: propertied from the original entity being audited +revision type:: The `org.hibernate.envers.RevisionType` enumeration ordinal stating if the change represents an INSERT, UPDATE or DELETE. +audited fields:: properties from the original entity being audited -The primary key of the audit table is the combination of the original id of the entity and the revision number, so there can be at most one historic entry for a given entity instance at a given revision. +The primary key of the audit table is the combination of the original id of the entity and the revision number, +so there can be at most one historic entry for a given entity instance at a given revision. The current entity data is stored in the original table and in the audit table. -This is a duplication of data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully this won't be a major drawback for the users. -A row in the audit table with entity id `ID`, revision `N` and data `D` means: entity with id `ID` has data `D` from revision `N` upwards. +This is a duplication of data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully, this won't be a major drawback for the users. + +A row in the audit table with entity id `ID`, revision `N`, and data `D` means: entity with id `ID` has data `D` from revision `N` upwards. Hence, if we want to find an entity at revision `M`, we have to search for a row in the audit table, which has the revision number smaller or equal to `M`, but as large as possible. If no such row is found, or a row with a "deleted" marker is found, it means that the entity didn't exist at that revision. -The "revision type" field can currently have three values: `0`, `1` and `2`, which means `ADD`, `MOD` and `DEL`, respectively. +The "revision type" field can currently have three values: `0`, `1` and `2`, which means `ADD`, `MOD`, and `DEL`, respectively. A row with a revision of type `DEL` will only contain the id of the entity and no data (all fields `NULL`), as it only serves as a marker saying "this entity was deleted at that revision". Additionally, there is a revision entity table which contains the information about the global revision. -By default the generated table is named `REVINFO` and contains just two columns: `ID` and `TIMESTAMP`. +By default, the generated table is named `REVINFO` and contains just two columns: `ID` and `TIMESTAMP`. A row is inserted into this table on each new revision, that is, on each commit of a transaction, which changes audited data. The name of this table can be configured, the name of its columns as well as adding additional columns can be achieved as discussed in <>. [NOTE] ==== While global revisions are a good way to provide correct auditing of relations, some people have pointed out that this may be a bottleneck in systems, where data is very often modified. + One viable solution is to introduce an option to have an entity "locally revisioned", that is revisions would be created for it independently. -This woulld not enable correct versioning of relations, but it would work without the `REVINFO` table. +This would not enable correct versioning of relations, but it would work without the `REVINFO` table. + Another possibility is to introduce a notion of "revisioning groups", which would group entities sharing the same revision numbering. Each such group would have to consist of one or more strongly connected components belonging to the entity graph induced by relations between entities. -Your opinions on the subject are very welcome on the forum! :) + +Your opinions on the subject are very welcome on the forum. ==== [[envers-generateschema]] -=== Generating schema with Ant - -If you would like to generate the database schema file with the Hibernate Tools Ant task, you simply need to use the -`org.hibernate.tool.ant.HibernateToolTask` to do so. This task will generate the definitions of all entities, both of -which are audited by Envers and those which are not. - -For example: - -[source,xml] ----- - - - - - - - - - - - - ----- - -Will generate the following schema: - -[source,sql] ----- -create table Address ( - id integer generated by default as identity (start with 1), - flatNumber integer, - houseNumber integer, - streetName varchar(255), - primary key (id) -); - -create table Address_AUD ( - id integer not null, - REV integer not null, - flatNumber integer, - houseNumber integer, - streetName varchar(255), - REVTYPE tinyint, - primary key (id, REV) -); - -create table Person ( - id integer generated by default as identity (start with 1), - name varchar(255), - surname varchar(255), - address_id integer, - primary key (id) -); - -create table Person_AUD ( - id integer not null, - REV integer not null, - name varchar(255), - surname varchar(255), - REVTYPE tinyint, - address_id integer, - primary key (id, REV) -); - -create table REVINFO ( - REV integer generated by default as identity (start with 1), - REVTSTMP bigint, - primary key (REV) -); - -alter table Person - add constraint FK8E488775E4C3EA63 - foreign key (address_id) - references Address; +=== Generating Envers schema with Hibernate hbm2ddl tool + +If you would like to generate the database schema file with Hibernate, +you simply need to use the hbm2ddl too. + +This task will generate the definitions of all entities, both of which are audited by Envers and those which are not. + +See the <> chapter for more info. + +For the following entities, Hibernate is going to generate the following database schema: + +[[envers-generateschema-example]] +.Filtering a nested join relation using multiple predicates +==== +[source, JAVA, indent=0] ---- +include::{sourcedir}/QueryAuditAdressCountryTest.java[tags=envers-generateschema-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-generateschema-example.sql[] +---- +==== [[envers-mappingexceptions]] === Mapping exceptions @@ -902,7 +1326,7 @@ Bags are not supported because they can contain non-unique elements. Persisting, a bag of `String`s violates the relational database principle that each table is a set of tuples. In case of bags, however (which require a join table), if there is a duplicate element, the two tuples corresponding to the elements will be the same. -Hibernate allows this, however Envers (or more precisely: the database connector) will throw an exception when trying to persist two identical elements because of a unique constraint violation. +Although Hibernate allows this, Envers (or more precisely: the database connector) will throw an exception when trying to persist two identical elements because of a unique constraint violation. There are at least two ways out if you need bag semantics: @@ -920,7 +1344,7 @@ Envers, however, has to do this so that when you read the revisions in which the To be able to name the additional join table, there is a special annotation: `@AuditJoinTable`, which has similar semantics to JPA `@JoinTable`. -One special case are relations mapped with `@OneToMany` with `@JoinColumn` on the one side, and `@ManyToOne` and `@JoinColumn( insertable=false, updatable=false`) on the many side. +One special case is to have relations mapped with `@OneToMany` with `@JoinColumn` on the one side, and `@ManyToOne` and `@JoinColumn( insertable=false, updatable=false`) on the many side. Such relations are, in fact, bidirectional, but the owning side is the collection. To properly audit such relations with Envers, you can use the `@AuditMappedBy` annotation. @@ -946,7 +1370,7 @@ SQL table partitioning offers a lot of advantages including, but certainly not l === Suitable columns for audit table partitioning Generally, SQL tables must be partitioned on a column that exists within the table. -As a rule it makes sense to use either the _end revision_ or the _end revision timestamp_ column for partitioning of audit tables. +As a rule, it makes sense to use either the _end revision_ or the _end revision timestamp_ column for partitioning of audit tables. [NOTE] ==== @@ -1016,17 +1440,19 @@ The following audit information is available, sorted on in order of occurrence: [[envers-partitioning-example-column]] === Determining a suitable partitioning column -To partition this data, the 'level of relevancy' must be defined. Consider the following: +To partition this data, the _level of relevancy_ must be defined. Consider the following: -. For fiscal year 2006 there is only one revision. - It has the oldest _revision timestamp_ of all audit rows, but should still be regarded as relevant because it's the latest modification for this fiscal year in the salary table (its _end revision timestamp_ is null). +. For the fiscal year 2006, there is only one revision. +It has the oldest _revision timestamp_ of all audit rows, +but should still be regarded as relevant because it's the latest modification for this fiscal year in the salary table (its _end revision timestamp_ is null). + - Also, note that it would be very unfortunate if in 2011 there would be an update of the salary for fiscal year 2006 (which is possible in until at least 10 years after the fiscal year), - and the audit information would have been moved to a slow disk (based on the age of the __revision timestamp__). - Remember that, in this case, Envers will have to update the _end revision timestamp_ of the most recent audit row. -. There are two revisions in the salary of fiscal year 2007 which both have nearly the same _revision timestamp_ and a different __end revision timestamp__. - On first sight, it is evident that the first revision was a mistake and probably not relevant. - The only relevant revision for 2007 is the one with _end revision timestamp_ null. +Also, note that it would be very unfortunate if in 2011 there would be an update of the salary for the fiscal year 2006 (which is possible until at least 10 years after the fiscal year), +and the audit information would have been moved to a slow disk (based on the age of the __revision timestamp__). +Remember that, in this case, Envers will have to update the _end revision timestamp_ of the most recent audit row. +. There are two revisions in the salary of the fiscal year 2007 which both have nearly the same _revision timestamp_ and a different __end revision timestamp__. + +On first sight, it is evident that the first revision was a mistake and probably not relevant. +The only relevant revision for 2007 is the one with _end revision timestamp_ null. Based on the above, it is evident that only the _end revision timestamp_ is suitable for audit table partitioning. The _revision timestamp_ is not suitable. @@ -1056,6 +1482,6 @@ And sometime in 2011, the last partition (or 'extension bucket') is split into t . http://hibernate.org[Hibernate main page] . http://community.jboss.org/en/envers?view=discussions[Forum] . https://hibernate.atlassian.net/[JIRA issue tracker] (when adding issues concerning Envers, be sure to select the "envers" component!) -. irc://irc.freenode.net:6667/envers[IRC channel] +. https://hibernate.hipchat.com/chat/room/1238636[HipChat channel] . https://community.jboss.org/wiki/EnversFAQ[FAQ] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-filtering-and-pagination.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-filtering-and-pagination.sql new file mode 100644 index 000000000000..a54249260754 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-filtering-and-pagination.sql @@ -0,0 +1,17 @@ +select + c.id as id1_3_, + c.REV as REV2_3_, + c.REVTYPE as REVTYPE3_3_, + c.REVEND as REVEND4_3_, + c.created_on as created_5_3_, + c.firstName as firstNam6_3_, + c.lastName as lastName7_3_, + c.address_id as address_8_3_ +from + Customer_AUD c +where + c.address_id = ? +order by + c.lastName desc +limit ? +offset ? \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-filtering-by-entity-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-filtering-by-entity-example.sql new file mode 100644 index 000000000000..b005434eb4fa --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-filtering-by-entity-example.sql @@ -0,0 +1,17 @@ +select + c.id as id1_3_, + c.REV as REV2_3_, + c.REVTYPE as REVTYPE3_3_, + c.REVEND as REVEND4_3_, + c.created_on as created_5_3_, + c.firstName as firstNam6_3_, + c.lastName as lastName7_3_, + c.address_id as address_8_3_ +from + Customer_AUD c +where + c.address_id = ? +order by + c.REV asc + +-- binding parameter [1] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-in-clause-filtering-by-entity-identifier-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-in-clause-filtering-by-entity-identifier-example.sql new file mode 100644 index 000000000000..05d3b85f9c58 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/entities-in-clause-filtering-by-entity-identifier-example.sql @@ -0,0 +1,20 @@ +select + c.id as id1_3_, + c.REV as REV2_3_, + c.REVTYPE as REVTYPE3_3_, + c.REVEND as REVEND4_3_, + c.created_on as created_5_3_, + c.firstName as firstNam6_3_, + c.lastName as lastName7_3_, + c.address_id as address_8_3_ +from + Customer_AUD c +where + c.address_id in ( + ? , ? + ) +order by + c.REV asc + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [BIGINT] - [2] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-delete-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-delete-example.sql new file mode 100644 index 000000000000..ce40f7aaa64c --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-delete-example.sql @@ -0,0 +1,31 @@ +delete +from + Customer +where + id = ? + +-- binding parameter [1] as [BIGINT] - [1] + +insert +into + REVINFO + (REV, REVTSTMP) +values + (?, ?) + +-- binding parameter [1] as [BIGINT] - [3] +-- binding parameter [2] as [BIGINT] - [1500906092876] + +insert +into + Customer_AUD + (REVTYPE, created_on, firstName, lastName, id, REV) +values + (?, ?, ?, ?, ?, ?) + +-- binding parameter [1] as [INTEGER] - [2] +-- binding parameter [2] as [TIMESTAMP] - [null] +-- binding parameter [3] as [VARCHAR] - [null] +-- binding parameter [4] as [VARCHAR] - [null] +-- binding parameter [5] as [BIGINT] - [1] +-- binding parameter [6] as [INTEGER] - [3] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-insert-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-insert-example.sql new file mode 100644 index 000000000000..e81e86b44b9d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-insert-example.sql @@ -0,0 +1,35 @@ +insert +into + Customer + (created_on, firstName, lastName, id) +values + (?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [Mon Jul 24 17:21:32 EEST 2017] +-- binding parameter [2] as [VARCHAR] - [John] +-- binding parameter [3] as [VARCHAR] - [Doe] +-- binding parameter [4] as [BIGINT] - [1] + +insert +into + REVINFO + (REV, REVTSTMP) +values + (?, ?) + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [BIGINT] - [1500906092803] + +insert +into + Customer_AUD + (REVTYPE, created_on, firstName, lastName, id, REV) +values + (?, ?, ?, ?, ?, ?) + +-- binding parameter [1] as [INTEGER] - [0] +-- binding parameter [2] as [TIMESTAMP] - [Mon Jul 24 17:21:32 EEST 2017] +-- binding parameter [3] as [VARCHAR] - [John] +-- binding parameter [4] as [VARCHAR] - [Doe] +-- binding parameter [5] as [BIGINT] - [1] +-- binding parameter [6] as [INTEGER] - [1] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-mapping-example.sql new file mode 100644 index 000000000000..6444cfa64b18 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-mapping-example.sql @@ -0,0 +1,28 @@ +create table Customer ( + id bigint not null, + created_on timestamp, + firstName varchar(255), + lastName varchar(255), + primary key (id) +) + +create table Customer_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint, + created_on timestamp, + firstName varchar(255), + lastName varchar(255), + primary key (id, REV) +) + +create table REVINFO ( + REV integer generated by default as identity, + REVTSTMP bigint, + primary key (REV) +) + +alter table Customer_AUD + add constraint FK5ecvi1a0ykunrriib7j28vpdj + foreign key (REV) + references REVINFO \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-rev1-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-rev1-example.sql new file mode 100644 index 000000000000..654b798f09f2 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-rev1-example.sql @@ -0,0 +1,23 @@ +select + c.id as id1_1_, + c.REV as REV2_1_, + c.REVTYPE as REVTYPE3_1_, + c.created_on as created_4_1_, + c.firstName as firstNam5_1_, + c.lastName as lastName6_1_ +from + Customer_AUD c +where + c.REV = ( + select + max( c_max.REV ) + from + Customer_AUD c_max + where + c_max.REV <= ? + and c.id = c_max.id + ) + and c.REVTYPE <> ? + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-revisions-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-revisions-example.sql new file mode 100644 index 000000000000..69f0f6d93933 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-revisions-example.sql @@ -0,0 +1,13 @@ +select + c.REV as col_0_0_ +from + Customer_AUD c +cross join + REVINFO r +where + c.id = ? + and c.REV = r.REV +order by + c.REV asc + +-- binding parameter [1] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-update-example.sql new file mode 100644 index 000000000000..57bb75402f41 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-update-example.sql @@ -0,0 +1,37 @@ +update + Customer +set + created_on=?, + firstName=?, + lastName=? +where + id=? + +-- binding parameter [1] as [TIMESTAMP] - [2017-07-24 17:21:32.757] +-- binding parameter [2] as [VARCHAR] - [John] +-- binding parameter [3] as [VARCHAR] - [Doe Jr.] +-- binding parameter [4] as [BIGINT] - [1] + +insert +into + REVINFO + (REV, REVTSTMP) +values + (?, ?) + +-- binding parameter [1] as [BIGINT] - [2] +-- binding parameter [2] as [BIGINT] - [1500906092853] + +insert +into + Customer_AUD + (REVTYPE, created_on, firstName, lastName, id, REV) +values + (?, ?, ?, ?, ?, ?) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [TIMESTAMP] - [2017-07-24 17:21:32.757] +-- binding parameter [3] as [VARCHAR] - [John] +-- binding parameter [4] as [VARCHAR] - [Doe Jr.] +-- binding parameter [5] as [BIGINT] - [1] +-- binding parameter [6] as [INTEGER] - [2] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-validity-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-validity-mapping-example.sql new file mode 100644 index 000000000000..3e8088a10a39 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-validity-mapping-example.sql @@ -0,0 +1,34 @@ +create table Customer ( + id bigint not null, + created_on timestamp, + firstName varchar(255), + lastName varchar(255), + primary key (id) +) + +create table Customer_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint, + REVEND integer, + created_on timestamp, + firstName varchar(255), + lastName varchar(255), + primary key (id, REV) +) + +create table REVINFO ( + REV integer generated by default as identity, + REVTSTMP bigint, + primary key (REV) +) + +alter table Customer_AUD + add constraint FK5ecvi1a0ykunrriib7j28vpdj + foreign key (REV) + references REVINFO + +alter table Customer_AUD + add constraint FKqd4fy7ww1yy95wi4wtaonre3f + foreign key (REVEND) + references REVINFO \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-validity-rev1-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-validity-rev1-example.sql new file mode 100644 index 000000000000..4d5a7cb9003e --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-audited-validity-rev1-example.sql @@ -0,0 +1,21 @@ +select + c.id as id1_1_, + c.REV as REV2_1_, + c.REVTYPE as REVTYPE3_1_, + c.REVEND as REVEND4_1_, + c.created_on as created_5_1_, + c.firstName as firstNam6_1_, + c.lastName as lastName7_1_ +from + Customer_AUD c +where + c.REV <= ? + and c.REVTYPE <> ? + and ( + c.REVEND > ? + or c.REVEND is null + ) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-generateschema-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-generateschema-example.sql new file mode 100644 index 000000000000..494777730858 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-generateschema-example.sql @@ -0,0 +1,102 @@ +create table Address ( + id bigint not null, + city varchar(255), + street varchar(255), + streetNumber varchar(255), + country_id bigint, + primary key (id) +) + +create table Address_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint, + REVEND integer, + city varchar(255), + street varchar(255), + streetNumber varchar(255), + country_id bigint, + primary key (id, REV) +) + +create table Country ( + id bigint not null, + name varchar(255), + primary key (id) +) + +create table Country_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint, + REVEND integer, + name varchar(255), + primary key (id, REV) +) + +create table Customer ( + id bigint not null, + created_on timestamp, + firstName varchar(255), + lastName varchar(255), + address_id bigint, + primary key (id) +) + +create table Customer_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint, + REVEND integer, + created_on timestamp, + firstName varchar(255), + lastName varchar(255), + address_id bigint, + primary key (id, REV) +) + +create table REVINFO ( + REV integer generated by default as identity, + REVTSTMP bigint, + primary key (REV) +) + +alter table Address +add constraint FKpr4rl83u5fv832kdihl6w3kii +foreign key (country_id) +references Country + +alter table Address_AUD +add constraint FKgwp5sek4pjb4awy66sp184hrv +foreign key (REV) +references REVINFO + +alter table Address_AUD +add constraint FK52pqkpismfxg2b9tmwtncnk0d +foreign key (REVEND) +references REVINFO + +alter table Country_AUD +add constraint FKrix4g8hm9ui6sut5sy86ujggr +foreign key (REV) +references REVINFO + +alter table Country_AUD +add constraint FKpjeqmdccv22y1lbtswjb84ghi +foreign key (REVEND) +references REVINFO + +alter table Customer +add constraint FKfok4ytcqy7lovuiilldbebpd9 +foreign key (address_id) +references Address + +alter table Customer_AUD +add constraint FK5ecvi1a0ykunrriib7j28vpdj +foreign key (REV) +references REVINFO + +alter table Customer_AUD +add constraint FKqd4fy7ww1yy95wi4wtaonre3f +foreign key (REVEND) +references REVINFO \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-multiple-restrictions.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-multiple-restrictions.sql new file mode 100644 index 000000000000..b52fb72caf0f --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-multiple-restrictions.sql @@ -0,0 +1,48 @@ +select + c.id as id1_5_, + c.REV as REV2_5_, + c.REVTYPE as REVTYPE3_5_, + c.REVEND as REVEND4_5_, + c.created_on as created_5_5_, + c.firstName as firstNam6_5_, + c.lastName as lastName7_5_, + c.address_id as address_8_5_ +from + Customer_AUD c +left outer join + Address_AUD a + on ( + c.address_id=a.id + or ( + c.address_id is null + ) + and ( + a.id is null + ) + ) +where + c.REV<=? + and c.REVTYPE<>? + and ( + c.REVEND>? + or c.REVEND is null + ) + and ( + a.REV is null + or a.REV<=? + and ( + a.REVEND>? + or a.REVEND is null + ) + ) + and ( + a.city=? + or a.country_id is null + ) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [1] +-- binding parameter [4] as [INTEGER] - [1] +-- binding parameter [5] as [INTEGER] - [1] +-- binding parameter [6] as [VARCHAR] - [Cluj-Napoca] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-restriction.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-restriction.sql new file mode 100644 index 000000000000..865858d31001 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-restriction.sql @@ -0,0 +1,42 @@ +select + c.id as id1_3_, + c.REV as REV2_3_, + c.REVTYPE as REVTYPE3_3_, + c.REVEND as REVEND4_3_, + c.created_on as created_5_3_, + c.firstName as firstNam6_3_, + c.lastName as lastName7_3_, + c.address_id as address_8_3_ +from + Customer_AUD c +inner join + Address_AUD a + on ( + c.address_id=a.id + or ( + c.address_id is null + ) + and ( + a.id is null + ) + ) +where + c.REV<=? + and c.REVTYPE<>? + and ( + c.REVEND>? + or c.REVEND is null + ) + and a.REV<=? + and a.country=? + and ( + a.REVEND>? + or a.REVEND is null + ) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [1] +-- binding parameter [4] as [INTEGER] - [1] +-- binding parameter [5] as [VARCHAR] - [România] +-- binding parameter [6] as [INTEGER] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-multiple-restrictions-combined.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-multiple-restrictions-combined.sql new file mode 100644 index 000000000000..a41067211b3d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-multiple-restrictions-combined.sql @@ -0,0 +1,59 @@ +select + cu.id as id1_5_, + cu.REV as REV2_5_, + cu.REVTYPE as REVTYPE3_5_, + cu.REVEND as REVEND4_5_, + cu.created_on as created_5_5_, + cu.firstName as firstNam6_5_, + cu.lastName as lastName7_5_, + cu.address_id as address_8_5_ +from + Customer_AUD cu +inner join + Address_AUD a + on ( + cu.address_id=a.id + or ( + cu.address_id is null + ) + and ( + a.id is null + ) + ) +inner join + Country_AUD cr + on ( + a.country_id=cr.id + or ( + a.country_id is null + ) + and ( + cr.id is null + ) + ) +where + cu.REV<=? + and cu.REVTYPE<>? + and a.city=cr.name + and ( + cu.REVEND>? + or cu.REVEND is null + ) + and a.REV<=? + and ( + a.REVEND>? + or a.REVEND is null + ) + and cr.REV<=? + and ( + cr.REVEND>? + or cr.REVEND is null + ) + +-- binding parameter [1] as [INTEGER] - [2] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [2] +-- binding parameter [4] as [INTEGER] - [2] +-- binding parameter [5] as [INTEGER] - [2] +-- binding parameter [6] as [INTEGER] - [2] +-- binding parameter [7] as [INTEGER] - [2] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-multiple-restrictions.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-multiple-restrictions.sql new file mode 100644 index 000000000000..e21eba04abfd --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-multiple-restrictions.sql @@ -0,0 +1,66 @@ +select + cu.id as id1_5_, + cu.REV as REV2_5_, + cu.REVTYPE as REVTYPE3_5_, + cu.REVEND as REVEND4_5_, + cu.created_on as created_5_5_, + cu.firstName as firstNam6_5_, + cu.lastName as lastName7_5_, + cu.address_id as address_8_5_ +from + Customer_AUD cu +inner join + Address_AUD a + on ( + cu.address_id=a.id + or ( + cu.address_id is null + ) + and ( + a.id is null + ) + ) +inner join + Country_AUD co + on ( + a.country_id=co.id + or ( + a.country_id is null + ) + and ( + co.id is null + ) + ) +where + cu.REV<=? + and cu.REVTYPE<>? + and ( + cu.REVEND>? + or cu.REVEND is null + ) + and ( + a.city=? + or co.name=? + ) + and a.REV<=? + and ( + a.REVEND>? + or a.REVEND is null + ) + and co.REV<=? + and ( + co.REVEND>? + or co.REVEND is null + ) +order by + cu.created_on asc + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [1] +-- binding parameter [4] as [VARCHAR] - [Cluj-Napoca] +-- binding parameter [5] as [VARCHAR] - [România] +-- binding parameter [6] as [INTEGER] - [1] +-- binding parameter [7] as [INTEGER] - [1] +-- binding parameter [8] as [INTEGER] - [1] +-- binding parameter [9] as [INTEGER] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-restriction.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-restriction.sql new file mode 100644 index 000000000000..4ca5a8f788d3 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-restriction.sql @@ -0,0 +1,60 @@ +select + cu.id as id1_5_, + cu.REV as REV2_5_, + cu.REVTYPE as REVTYPE3_5_, + cu.REVEND as REVEND4_5_, + cu.created_on as created_5_5_, + cu.firstName as firstNam6_5_, + cu.lastName as lastName7_5_, + cu.address_id as address_8_5_ +from + Customer_AUD cu +inner join + Address_AUD a + on ( + cu.address_id=a.id + or ( + cu.address_id is null + ) + and ( + a.id is null + ) + ) +inner join + Country_AUD co + on ( + a.country_id=co.id + or ( + a.country_id is null + ) + and ( + co.id is null + ) + ) +where + cu.REV<=? + and cu.REVTYPE<>? + and ( + cu.REVEND>? + or cu.REVEND is null + ) + and a.REV<=? + and ( + a.REVEND>? + or a.REVEND is null + ) + and co.REV<=? + and co.name=? + and ( + co.REVEND>? + or co.REVEND is null + ) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [1] +-- binding parameter [4] as [INTEGER] - [1] +-- binding parameter [5] as [INTEGER] - [1] +-- binding parameter [6] as [INTEGER] - [1] +-- binding parameter [7] as [VARCHAR] - [România] +-- binding parameter [8] as [INTEGER] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-revisionlog-RevisionEntity-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-revisionlog-RevisionEntity-persist-example.sql new file mode 100644 index 000000000000..7a3e12ae59be --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-revisionlog-RevisionEntity-persist-example.sql @@ -0,0 +1,36 @@ +insert +into + Customer + (created_on, firstName, lastName, id) +values + (?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [Thu Jul 27 15:45:00 EEST 2017] +-- binding parameter [2] as [VARCHAR] - [John] +-- binding parameter [3] as [VARCHAR] - [Doe] +-- binding parameter [4] as [BIGINT] - [1] + +insert +into + CUSTOM_REV_INFO + (timestamp, username, id) +values + (?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [1501159500888] +-- binding parameter [2] as [VARCHAR] - [Vlad Mihalcea] +-- binding parameter [3] as [INTEGER] - [1] + +insert +into + Customer_AUD + (REVTYPE, created_on, firstName, lastName, id, REV) +values + (?, ?, ?, ?, ?, ?) + +-- binding parameter [1] as [INTEGER] - [0] +-- binding parameter [2] as [TIMESTAMP] - [Thu Jul 27 15:45:00 EEST 2017] +-- binding parameter [3] as [VARCHAR] - [John] +-- binding parameter [4] as [VARCHAR] - [Doe] +-- binding parameter [5] as [BIGINT] - [1] +-- binding parameter [6] as [INTEGER] - [1] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-revisionlog-custom-revision-entity-table-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-revisionlog-custom-revision-entity-table-example.sql new file mode 100644 index 000000000000..5e2884009090 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-revisionlog-custom-revision-entity-table-example.sql @@ -0,0 +1,6 @@ +create table CUSTOM_REV_INFO ( + id integer not null, + timestamp bigint not null, + username varchar(255), + primary key (id) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-modified-entities-revchanges-after-rename-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-modified-entities-revchanges-after-rename-example.sql new file mode 100644 index 000000000000..b66b68764d95 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-modified-entities-revchanges-after-rename-example.sql @@ -0,0 +1,9 @@ +insert +into + REVCHANGES + (REV, ENTITYNAME) +values + (?, ?) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [VARCHAR] - [org.hibernate.userguide.envers.EntityTypeChangeAuditTest$Customer] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-example.sql new file mode 100644 index 000000000000..9be648011932 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-example.sql @@ -0,0 +1,39 @@ +update + Customer +set + created_on = ?, + firstName = ?, + lastName = ? +where + id = ? + +-- binding parameter [1] as [TIMESTAMP] - [2017-07-31 15:58:20.342] +-- binding parameter [2] as [VARCHAR] - [John] +-- binding parameter [3] as [VARCHAR] - [Doe Jr.] +-- binding parameter [4] as [BIGINT] - [1] + +insert +into + REVINFO + (REV, REVTSTMP) +values + (null, ?) + +-- binding parameter [1] as [BIGINT] - [1501505900439] + +insert +into + Customer_AUD + (REVTYPE, created_on, createdOn_MOD, firstName, firstName_MOD, lastName, lastName_MOD, id, REV) +values + (?, ?, ?, ?, ?, ?, ?, ?, ?) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [TIMESTAMP] - [2017-07-31 15:58:20.342] +-- binding parameter [3] as [BOOLEAN] - [false] +-- binding parameter [4] as [VARCHAR] - [John] +-- binding parameter [5] as [BOOLEAN] - [false] +-- binding parameter [6] as [VARCHAR] - [Doe Jr.] +-- binding parameter [7] as [BOOLEAN] - [true] +-- binding parameter [8] as [BIGINT] - [1] +-- binding parameter [9] as [INTEGER] - [2] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-mapping-example.sql new file mode 100644 index 000000000000..8cf1c2896eb3 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-mapping-example.sql @@ -0,0 +1,12 @@ +create table Customer_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint, + created_on timestamp, + createdOn_MOD boolean, + firstName varchar(255), + firstName_MOD boolean, + lastName varchar(255), + lastName_MOD boolean, + primary key (id, REV) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-at-revision-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-at-revision-example.sql new file mode 100644 index 000000000000..33ca9a67aa1e --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-at-revision-example.sql @@ -0,0 +1,25 @@ +select + c.id as id1_3_, + c.REV as REV2_3_, + c.REVTYPE as REVTYPE3_3_, + c.REVEND as REVEND4_3_, + c.created_on as created_5_3_, + c.createdOn_MOD as createdO6_3_, + c.firstName as firstNam7_3_, + c.firstName_MOD as firstNam8_3_, + c.lastName as lastName9_3_, + c.lastName_MOD as lastNam10_3_, + c.address_id as address11_3_, + c.address_MOD as address12_3_ +from + Customer_AUD c +where + c.REV=? + and c.id=? + and c.lastName_MOD=? + and c.firstName_MOD=? + +-- binding parameter [1] as [INTEGER] - [2] +-- binding parameter [2] as [BIGINT] - [1] +-- binding parameter [3] as [BOOLEAN] - [true] +-- binding parameter [4] as [BOOLEAN] - [false] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql new file mode 100644 index 000000000000..f3579a041672 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql @@ -0,0 +1,30 @@ +select + c.id as id1_3_0_, + c.REV as REV2_3_0_, + defaultrev1_.REV as REV1_4_1_, + c.REVTYPE as REVTYPE3_3_0_, + c.REVEND as REVEND4_3_0_, + c.created_on as created_5_3_0_, + c.createdOn_MOD as createdO6_3_0_, + c.firstName as firstNam7_3_0_, + c.firstName_MOD as firstNam8_3_0_, + c.lastName as lastName9_3_0_, + c.lastName_MOD as lastNam10_3_0_, + c.address_id as address11_3_0_, + c.address_MOD as address12_3_0_, + defaultrev1_.REVTSTMP as REVTSTMP2_4_1_ +from + Customer_AUD c cross +join + REVINFO defaultrev1_ +where + c.id=? + and c.lastName_MOD=? + and c.firstName_MOD=? + and c.REV=defaultrev1_.REV +order by + c.REV asc + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [BOOLEAN] - [true] +-- binding parameter [3] as [BOOLEAN] - [false] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-example.sql new file mode 100644 index 000000000000..a466fb8f3ec8 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-example.sql @@ -0,0 +1,28 @@ +select + c.id as id1_3_0_, + c.REV as REV2_3_0_, + defaultrev1_.REV as REV1_4_1_, + c.REVTYPE as REVTYPE3_3_0_, + c.REVEND as REVEND4_3_0_, + c.created_on as created_5_3_0_, + c.createdOn_MOD as createdO6_3_0_, + c.firstName as firstNam7_3_0_, + c.firstName_MOD as firstNam8_3_0_, + c.lastName as lastName9_3_0_, + c.lastName_MOD as lastNam10_3_0_, + c.address_id as address11_3_0_, + c.address_MOD as address12_3_0_, + defaultrev1_.REVTSTMP as REVTSTMP2_4_1_ +from + Customer_AUD c cross +join + REVINFO defaultrev1_ +where + c.id = ? + and c.lastName_MOD = ? + and c.REV=defaultrev1_.REV +order by + c.REV asc + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [BOOLEAN] - [true] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc index 033534787ebb..75333e08f718 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc @@ -1,6 +1,7 @@ [[events]] == Interceptors and events :sourcedir: ../../../../../test/java/org/hibernate/userguide/events +:extrasdir: extras It is useful for the application to react to certain events that occur inside Hibernate. This allows for the implementation of generic functionality and the extension of Hibernate functionality. @@ -41,7 +42,7 @@ include::{sourcedir}/InterceptorTest.java[tags=events-interceptors-session-scope A `SessionFactory`-scoped interceptor is registered with the `Configuration` object prior to building the `SessionFactory`. Unless a session is opened explicitly specifying the interceptor to use, the `SessionFactory`-scoped interceptor will be applied to all sessions opened from that `SessionFactory`. -`SessionFactory`-scoped interceptors must be thread safe. +`SessionFactory`-scoped interceptors must be thread-safe. Ensure that you do not store session-specific states since multiple sessions will use this interceptor potentially concurrently. [[events-interceptors-session-factory-scope-example]] @@ -62,8 +63,8 @@ Many methods of the `Session` interface correlate to an event type. The full range of defined event types is declared as enum values on `org.hibernate.event.spi.EventType`. When a request is made of one of these methods, the Session generates an appropriate event and passes it to the configured event listener(s) for that type. -Applications are free to implement a customization of one of the listener interfaces (i.e., the `LoadEvent` is processed by the registered implementation of the `LoadEventListener` interface), in which case their implementation would -be responsible for processing any `load()` requests made of the `Session`. +Applications can customize the listener interfaces (i.e., the `LoadEvent` is processed by the registered implementation of the `LoadEventListener` interface), in which case their implementations would +be responsible for processing the `load()` requests made of the `Session`. [NOTE] ==== @@ -93,7 +94,7 @@ When you want to customize the entity state transition behavior, you have to opt For example, the `Interceptor#onSave()` method is invoked by Hibernate `AbstractSaveEventListener`. Or, the `Interceptor#onLoad()` is called by the `DefaultPreLoadEventListener`. . you can replace any given default event listener with your own implementation. -When doing this, you should probably extend the default listeners because otherwise you'd have to take care of all the low-level entity state transition logic. +When doing this, you should probably extend the default listeners because otherwise, you'd have to take care of all the low-level entity state transition logic. For example, if you replace the `DefaultPreLoadEventListener` with your own implementation, then, only if you call the `Interceptor#onLoad()` method explicitly, you can mix the custom load event listener with a custom Hibernate interceptor. [[events-declarative-security]] @@ -139,7 +140,7 @@ JPA also defines a more limited set of callbacks through annotations. There are two available approaches defined for specifying callback handling: -* The first approach is to annotate methods on the entity itself to receive notification of particular entity life cycle event(s). +* The first approach is to annotate methods on the entity itself to receive notifications of a particular entity lifecycle event(s). * The second is to use a separate entity listener class. An entity listener is a stateless class with a no-arg constructor. The callback annotations are placed on a method of this class instead of the entity class. @@ -177,3 +178,105 @@ See the `javax.persistence.ExcludeSuperclassListener`s annotation. If a callback type is annotated on both an entity and one or more of its superclasses without method overriding, both would be called, the most general superclass first. An entity class is also allowed to override a callback method defined in a superclass in which case the super callback would not get invoked; the overriding method would get invoked provided it is annotated. +[[events-default-listener]] +=== Default entity listeners + +The JPA specification allows you to define a default entity listener which is going to be applied for every entity in that particular system. +Default entity listeners can only be defined in XML mapping files. + +[[events-default-listener-mapping-example]] +.Default event listner mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListener.java[tags=events-default-listener-mapping-example] +---- + +[source, XML, indent=0] +---- +include::{sourcedir}/DefaultEntityListener-orm.xml[tags=events-default-listener-mapping-example] +---- +==== + +Considering that all entities extend the `BaseEntity` class: + +[source, JAVA, indent=0] +---- +include::{sourcedir}/BaseEntity.java[tags=events-default-listener-mapping-example] +---- + +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-mapping-example] +---- + +When persisting a `Person` or `Book` entity, the `createdOn` is going to be set by the `onPersist` method of the `DefaultEntityListener`. + +[[events-default-listener-persist-example]] +.Default event listner persist event +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/events-default-listener-persist-example.sql[] +---- +==== + +When updating a `Person` or `Book` entity, the `updatedOn` is going to be set by the `onUpdate` method of the `DefaultEntityListener`. + +[[events-default-listener-update-example]] +.Default event listner update event +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-update-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/events-default-listener-update-example.sql[] +---- +==== + +[[events-exclude-default-listener]] +==== Exclude default entity listeners + +If you already registered a default entity listener, but you don't want to apply it to a particular entity, +you can use the +http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeDefaultListeners.html[`@ExcludeDefaultListeners`] and +http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] JPA annotations. + +`@ExcludeDefaultListeners` instructs the current class to ignore the default entity listeners for the current entity +while `@ExcludeSuperclassListeners` is used to ignore the default entity listeners propagated to the `BaseEntity` super-class. + +[[events-exclude-default-listener-mapping-example]] +.Exclude default event listner mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-exclude-default-listener-mapping-example] +---- +==== + +When persisting a `Publisher` entity, +the `createdOn` is not going to be set by the `onPersist` method of the `DefaultEntityListener` +because the `Publisher` entity was marked with the `@ExcludeDefaultListeners` and `@ExcludeSuperclassListeners` annotations. + +[[events-exclude-default-listener-persist-example]] +.Excluding default event listner events +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-exclude-default-listener-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/events-exclude-default-listener-persist-example.sql[] +---- +==== + diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-persist-example.sql new file mode 100644 index 000000000000..f9e724622f01 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-persist-example.sql @@ -0,0 +1,24 @@ +insert +into + Person + (createdOn, updatedOn, name, id) +values + (?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224] +-- binding parameter [2] as [TIMESTAMP] - [null] +-- binding parameter [3] as [VARCHAR] - [Vlad Mihalcea] +-- binding parameter [4] as [BIGINT] - [1] + +insert +into + Book + (createdOn, updatedOn, author_id, title, id) +values + (?, ?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246] +-- binding parameter [2] as [TIMESTAMP] - [null] +-- binding parameter [3] as [BIGINT] - [1] +-- binding parameter [4] as [VARCHAR] - [High-Performance Java Persistence] +-- binding parameter [5] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-update-example.sql new file mode 100644 index 000000000000..36a7502af1e7 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-update-example.sql @@ -0,0 +1,29 @@ +update + Person +set + createdOn=?, + updatedOn=?, + name=? +where + id=? + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224] +-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.316] +-- binding parameter [3] as [VARCHAR] - [Vlad-Alexandru Mihalcea] +-- binding parameter [4] as [BIGINT] - [1] + +update + Book +set + createdOn=?, + updatedOn=?, + author_id=?, + title=? +where + id=? + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246] +-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.317] +-- binding parameter [3] as [BIGINT] - [1] +-- binding parameter [4] as [VARCHAR] - [High-Performance Java Persistence 2nd Edition] +-- binding parameter [5] as [BIGINT] - [1] diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-exclude-default-listener-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-exclude-default-listener-persist-example.sql new file mode 100644 index 000000000000..37837d0bf494 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-exclude-default-listener-persist-example.sql @@ -0,0 +1,11 @@ +insert +into + Publisher + (createdOn, updatedOn, name, id) +values + (?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [null] +-- binding parameter [2] as [TIMESTAMP] - [null] +-- binding parameter [3] as [VARCHAR] - [Amazon] +-- binding parameter [4] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc index 25526e23c696..c6a423f1c60a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc @@ -8,7 +8,7 @@ Tuning how an application does fetching is one of the biggest factors in determi Fetching too much data, in terms of width (values/columns) and/or depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. Fetching too little data might cause additional fetching to be needed. -Tuning how an application fetches data presents a great opportunity to influence the application overall performance. +Tuning how an application fetches data presents a great opportunity to influence the overall application performance. [[fetching-basics]] === The basics @@ -27,7 +27,7 @@ There are a number of scopes for defining fetching: _static_:: Static definition of fetching strategies is done in the mappings. - The statically-defined fetch strategies is used in the absence of any dynamically defined strategies + The statically-defined fetch strategies are used in the absence of any dynamically defined strategies SELECT::: Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). This is the strategy generally termed N+1. @@ -40,13 +40,13 @@ _static_:: Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). _dynamic_ (sometimes referred to as runtime):: - Dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching: + The dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching: _fetch profiles_::: defined in mappings, but can be enabled/disabled on the `Session`. HQL/JPQL::: and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query. entity graphs::: Starting in Hibernate 4.2 (JPA 2.1) this is also an option. [[fetching-direct-vs-query]] -=== Direct fetching vs entity queries +=== Direct fetching vs. entity queries To see the difference between direct fetching and entity queries in regard to eagerly fetched associations, consider the following entities: @@ -109,7 +109,7 @@ For this reason, you should prefer LAZY associations. [[fetching-strategies]] === Applying fetch strategies -Let's consider these topics as it relates to an simple domain model and a few use cases. +Let's consider these topics as it relates to a simple domain model and a few use cases. [[fetching-strategies-domain-model-example]] .Sample domain model @@ -204,8 +204,48 @@ include::{sourcedir}/GraphFetchingTest.java[tags=fetching-strategies-dynamic-fet [NOTE] ==== -Entity graphs are the way to override the EAGER fetching associations at runtime. -With JPQL, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly. +Although the JPA standard specifies that you can override an EAGER fetching association at runtime using the `javax.persistence.fetchgraph` hint, +currently, Hibernate does not implement this feature, so EAGER associations cannot be fetched lazily. +For more info, check out the https://hibernate.atlassian.net/browse/HHH-8776[HHH-8776] Jira issue. + +When executing a JPQL query, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly, +which can lead dto N+1 query issues. + +For this reason, it's better to use LAZY associations, and only fetch them eagerly on a per-query basis. +==== + +[[fetching-strategies-dynamic-fetching-entity-subgraph]] +==== JPA entity subgraphs + +An entity graph specifies which attributes to be fetched, but it limited to a single entity only. +To fetch associations from a child entity, you need to use the http://docs.oracle.com/javaee/7/api/javax/persistence/NamedSubgraph.html[`@NamedSubgraph`] annotation. + +If we have a `Project` parent entity which has an `employees` child associations, +and we'd like to fetch the `department` for the `Employee` child association. + +[[fetching-strategies-dynamic-fetching-entity-subgraph-mapping-example]] +.Fetch graph with a subgraph mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/GraphFetchingTest.java[tags=fetching-strategies-dynamic-fetching-entity-subgraph-mapping-example] +---- +==== + +When fetching this entity graph, Hibernate generates the following SQL query: + +[[fetching-strategies-dynamic-fetching-entity-subgraph-example]] +.Fetch graph with a subgraph mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/GraphFetchingTest.java[tags=fetching-strategies-dynamic-fetching-entity-subgraph-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/fetching-strategies-dynamic-fetching-entity-subgraph-example.sql[] +---- ==== [[fetching-strategies-dynamic-fetching-profile]] @@ -268,7 +308,7 @@ include::{extrasdir}/fetching-batch-fetching-example.sql[] ---- ==== -As you can see in the example above, there are only two SQL statements used to fetch the `Employee` entities associated to multiple `Department` entities. +As you can see in the example above, there are only two SQL statements used to fetch the `Employee` entities associated with multiple `Department` entities. [TIP] ==== @@ -284,16 +324,19 @@ it allows you to fetch all the required data with a single query. === The `@Fetch` annotation mapping Besides the `FetchType.LAZY` or `FetchType.EAGER` JPA annotations, -you can also use the Hibernate-specific `@Fetch` annotation that accepts one of the following `FetchMode`s: +you can also use the Hibernate-specific `@Fetch` annotation that accepts one of the following `FetchMode(s)`: SELECT:: - Use a secondary select for each individual entity, collection, or join load. + The association is going to be fetched lazily using a secondary select for each individual entity, + collection, or join load. + It's equivalent to JPA `FetchType.LAZY` fetching strategy. JOIN:: - Use an outer join to load the related entities, collections or joins. + Use an outer join to load the related entities, collections or joins when using direct fetching. + It's equivalent to JPA `FetchType.EAGER` fetching strategy. SUBSELECT:: - 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. [[fetching-fetchmode-select]] === `FetchMode.SELECT` @@ -345,7 +388,7 @@ include::{sourcedir}/FetchModeSubselectTest.java[tags=fetching-strategies-fetch- ---- ==== -Now, we are going to fetch all `Department` entities that match a given filtering criteria +Now, we are going to fetch all `Department` entities that match a given filtering predicate and then navigate their `employees` collections. Hibernate is going to avoid the N+1 query issue by generating a single SQL statement to initialize all `employees` collections diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-strategies-dynamic-fetching-entity-subgraph-example.sql b/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-strategies-dynamic-fetching-entity-subgraph-example.sql new file mode 100644 index 000000000000..c2d648c2fb88 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-strategies-dynamic-fetching-entity-subgraph-example.sql @@ -0,0 +1,23 @@ +select + p.id as id1_2_0_, e.id as id1_1_1_, d.id as id1_0_2_, + e.accessLevel as accessLe2_1_1_, + e.department_id as departme5_1_1_, + decrypt( 'AES', '00', e.pswd ) as pswd3_1_1_, + e.username as username4_1_1_, + p_e.projects_id as projects1_3_0__, + p_e.employees_id as employee2_3_0__ +from + Project p +inner join + Project_Employee p_e + on p.id=p_e.projects_id +inner join + Employee e + on p_e.employees_id=e.id +inner join + Department d + on e.department_id=d.id +where + p.id = ? + +-- binding parameter [1] as [BIGINT] - [1] diff --git a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc index 784347a61eb2..4876321cc870 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc @@ -7,7 +7,7 @@ Flushing is the process of synchronizing the state of the persistence context wi The `EntityManager` and the Hibernate `Session` expose a set of methods, through which the application developer can change the persistent state of an entity. The persistence context acts as a transactional write-behind cache, queuing any entity state change. -Like any write-behind cache, changes are first applied in-memory and synchronized with the database during flush time. +Like any write-behind cache, changes are first applied in-memory and synchronized with the database during the flush time. The flush operation takes every entity state change and translates it to an `INSERT`, `UPDATE` or `DELETE` statement. [NOTE] @@ -17,11 +17,11 @@ See the <> for more informa ==== The flushing strategy is given by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#getFlushMode--[`flushMode`] of the current running Hibernate `Session`. -Although JPA defines only two flushing strategies (https://docs.oracle.com/javaee/7/api/javax/persistence/FlushModeType.html#AUTO[`AUTO`] and https://docs.oracle.com/javaee/7/api/javax/persistence/FlushModeType.html#COMMIT[`COMMIT`]), +Although JPA defines only two flushing strategies (https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FlushModeType.html#AUTO[`AUTO`] and https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FlushModeType.html#COMMIT[`COMMIT`]), Hibernate has a much broader spectrum of flush types: ALWAYS:: Flushes the `Session` before every query. -AUTO:: This is the default mode and it flushes the `Session` only if necessary. +AUTO:: This is the default mode, and it flushes the `Session` only if necessary. COMMIT:: The `Session` tries to delay the flush until the current `Transaction` is committed, although it might flush prematurely too. MANUAL:: The `Session` flushing is delegated to the application, which must call `Session.flush()` explicitly in order to apply the persistence context changes. @@ -36,7 +36,7 @@ By default, Hibernate uses the `AUTO` flush mode which triggers a flush in the f ==== `AUTO` flush on commit -In the following example, an entity is persisted and then the transaction is committed. +In the following example, an entity is persisted, and then the transaction is committed. [[flushing-auto-flush-commit-example]] .Automatic flushing on commit @@ -79,7 +79,7 @@ include::{extrasdir}/flushing-auto-flush-jpql-example.sql[] ---- ==== -The reason why the `Advertisement` entity query didn't trigger a flush is because there's no overlapping between the `Advertisement` and the `Person` tables: +The reason why the `Advertisement` entity query didn't trigger a flush is that there's no overlapping between the `Advertisement` and the `Person` tables: [[flushing-auto-flush-jpql-entity-example]] .Automatic flushing on JPQL/HQL entities @@ -106,7 +106,7 @@ include::{extrasdir}/flushing-auto-flush-jpql-overlap-example.sql[] ---- ==== -This time, the flush was triggered by a JPQL query because the pending entity persist action overlaps with the query being executed. +This time, the flush was triggered by a JPQL query because the pending entity persists action overlaps with the query being executed. ==== `AUTO` flush on native SQL query @@ -121,14 +121,15 @@ include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-sql-example] ---- ==== -The `Session` API doesn't trigger an `AUTO` flush when executing a native query +If you bootstrap Hibernate natively, and not through JPA, by default, +the `Session` API will trigger a flush automatically when executing a native query. [[flushing-auto-flush-sql-native-example]] .Automatic flushing on native SQL using `Session` ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-sql-native-example] +include::{sourcedir}/HibernateAutoFlushTest.java[tags=flushing-auto-flush-sql-native-example] ---- ==== @@ -213,7 +214,7 @@ include::{extrasdir}/flushing-always-flush-sql-example.sql[] === `MANUAL` flush Both the `EntityManager` and the Hibernate `Session` define a `flush()` method that, when called, triggers a manual flush. -Hibernate also defines a `MANUAL` flush mode so the persistence context can only be flushed manually. +Hibernate also provides a `MANUAL` flush mode so the persistence context can only be flushed manually. [[flushing-manual-flush-example]] .`MANUAL` flushing @@ -233,14 +234,14 @@ The `INSERT` statement was not executed because the persistence context because [NOTE] ==== -This mode is useful when using multi-request logical transactions and only the last request should flush the persistence context. +This mode is useful when using multi-request logical transactions, and only the last request should flush the persistence context. ==== [[flushing-order]] === Flush operation order From a database perspective, a row state can be altered using either an `INSERT`, an `UPDATE` or a `DELETE` statement. -Because entity state changes are automatically converted to SQL statements, it's important to know which entity actions are associated to a given SQL statement. +Because entity state changes are automatically converted to SQL statements, it's important to know which entity actions are associated with a given SQL statement. `INSERT`:: The `INSERT` statement is generated either by the `EntityInsertAction` or `EntityIdentityInsertAction`. These actions are scheduled by the `persist` operation, either explicitly or through cascading the `PersistEvent` from a parent to a child entity. `DELETE`:: The `DELETE` statement is generated by the `EntityDeleteAction` or `OrphanRemovalAction`. diff --git a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc index 7f0ed569883d..b6ea92ca12ed 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc @@ -18,8 +18,10 @@ Hibernate will internally determine which `ConnectionProvider` to use based on t 3. else if any setting prefixed by `hibernate.c3p0.` is set -> <> 4. else if any setting prefixed by `hibernate.proxool.` is set -> <> 5. else if any setting prefixed by `hibernate.hikari.` is set -> <> -6. else if `hibernate.connection.url` is set -> <> -7. else -> <> +6. else if any setting prefixed by `hibernate.vibur.` is set -> <> +7. else if any setting prefixed by `hibernate.agroal.` is set -> <> +8. else if `hibernate.connection.url` is set -> <> +9. else -> <> [[database-connectionprovider-datasource]] === Using DataSources @@ -57,7 +59,7 @@ Any settings prefixed with `hibernate.connection.` (other than the "special ones `hibernate.c3p0.max_size` or `c3p0.maxPoolSize`:: The maximum size of the c3p0 pool. See http://www.mchange.com/projects/c3p0/#maxPoolSize[c3p0 maxPoolSize] `hibernate.c3p0.timeout` or `c3p0.maxIdleTime`:: The Connection idle time. See http://www.mchange.com/projects/c3p0/#maxIdleTime[c3p0 maxIdleTime] `hibernate.c3p0.max_statements` or `c3p0.maxStatements`:: Controls the c3p0 PreparedStatement cache size (if using). See http://www.mchange.com/projects/c3p0/#maxStatements[c3p0 maxStatements] -`hibernate.c3p0.acquire_increment` or `c3p0.acquireIncrement`:: Number of connections c3p0 should acquire at a time when pool is exhausted. See http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 acquireIncrement] +`hibernate.c3p0.acquire_increment` or `c3p0.acquireIncrement`:: Number of connections c3p0 should acquire at a time when the pool is exhausted. See http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 acquireIncrement] `hibernate.c3p0.idle_test_period` or `c3p0.idleConnectionTestPeriod`:: Idle time before a c3p0 pooled connection is validated. See http://www.mchange.com/projects/c3p0/#idleConnectionTestPeriod[c3p0 idleConnectionTestPeriod] `hibernate.c3p0.initialPoolSize`:: The initial c3p0 pool size. If not specified, default is to use the min pool size. See http://www.mchange.com/projects/c3p0/#initialPoolSize[c3p0 initialPoolSize] Any other settings prefixed with `hibernate.c3p0.`:: Will have the `hibernate.` portion stripped and be passed to c3p0. @@ -76,27 +78,27 @@ Hibernate also provides support for applications to use http://proxool.sourcefor Transaction isolation of the Connections is managed by the `ConnectionProvider` itself. See <>. [[database-connectionprovider-proxool-existing]] -=== Using existing Proxool pools +==== Using existing Proxool pools Controlled by the `hibernate.proxool.existing_pool` setting. If set to true, this ConnectionProvider will use an already existing Proxool pool by alias as indicated by the `hibernate.proxool.pool_alias` setting. [[database-connectionprovider-proxool-jaxp]] -=== Configuring Proxool via XML +==== Configuring Proxool via XML The `hibernate.proxool.xml` setting names a Proxool configuration XML file to be loaded as a classpath resource and loaded by Proxool's JAXPConfigurator. See http://proxool.sourceforge.net/configure.html[proxool configuration]. `hibernate.proxool.pool_alias` must be set to indicate which pool to use. [[database-connectionprovider-proxool-properties]] -=== Configuring Proxool via Properties +==== Configuring Proxool via Properties The `hibernate.proxool.properties` setting names a Proxool configuration properties file to be loaded as a classpath resource and loaded by Proxool's `PropertyConfigurator`. See http://proxool.sourceforge.net/configure.html[proxool configuration]. `hibernate.proxool.pool_alias` must be set to indicate which pool to use. [[database-connectionprovider-hikari]] -=== Using Hikari +=== Using HikariCP [IMPORTANT] ==== @@ -116,12 +118,52 @@ Additionally, this `ConnectionProvider` will pick up the following Hibernate-spe Note that Hikari only supports JDBC standard isolation levels (apparently). `hibernate.connection.autocommit`:: Mapped to Hikari's `autoCommit` setting +[[database-connectionprovider-vibur]] +=== Using Vibur DBCP + +[IMPORTANT] +==== +To use this integration, the application must include the hibernate-vibur module jar (as well as its dependencies) on the classpath. +==== + +Hibernate also provides support for applications to use http://www.vibur.org/[Vibur DBCP] connection pool. + +Set all of your Vibur settings in Hibernate prefixed by `hibernate.vibur.` and this `ConnectionProvider` will pick them up and pass them along to Vibur DBCP. +Additionally, this `ConnectionProvider` will pick up the following Hibernate-specific properties and map them to the corresponding Vibur ones (any `hibernate.vibur.` prefixed ones have precedence): + +`hibernate.connection.driver_class`:: Mapped to Vibur's `driverClassName` setting +`hibernate.connection.url`:: Mapped to Vibur's `jdbcUrl` setting +`hibernate.connection.username`:: Mapped to Vibur's `username` setting +`hibernate.connection.password`:: Mapped to Vibur's `password` setting +`hibernate.connection.isolation`:: Mapped to Vibur's `defaultTransactionIsolationValue` setting. See <>. +`hibernate.connection.autocommit`:: Mapped to Vibur's `defaultAutoCommit` setting + +[[database-connectionprovider-agroal]] +=== Using Agroal + +[IMPORTANT] +==== +To use this integration, the application must include the hibernate-agroal module jar (as well as its dependencies) on the classpath. +==== + +Hibernate also provides support for applications to use http://agroal.github.io/[Agroal] connection pool. + +Set all of your Agroal settings in Hibernate prefixed by `hibernate.agroal.` and this `ConnectionProvider` will pick them up and pass them along to Agroal connection pool. +Additionally, this `ConnectionProvider` will pick up the following Hibernate-specific properties and map them to the corresponding Agroal ones (any `hibernate.agroal.` prefixed ones have precedence): + +`hibernate.connection.driver_class`:: Mapped to Agroal's `driverClassName` setting +`hibernate.connection.url`:: Mapped to Agroal's `jdbcUrl` setting +`hibernate.connection.username`:: Mapped to Agroal's `principal` setting +`hibernate.connection.password`:: Mapped to Agroal's `credential` setting +`hibernate.connection.isolation`:: Mapped to Agroal's `jdbcTransactionIsolation` setting. See <>. +`hibernate.connection.autocommit`:: Mapped to Agroal's `autoCommit` setting + [[database-connectionprovider-drivermanager]] === Using Hibernate's built-in (and unsupported) pooling [IMPORTANT] ==== -The built-in connection pool is not supported supported for use. +The built-in connection pool is not supported for use in a production system. ==== This section is here just for completeness. @@ -152,7 +194,7 @@ Although SQL is relatively standardized, each database vendor uses a subset and This is referred to as the database's dialect. Hibernate handles variations across these dialects through its `org.hibernate.dialect.Dialect` class and the various subclasses for each database vendor. -In most cases Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. +In most cases, Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. For information on Hibernate's ability to determine the proper Dialect to use (and your ability to influence that resolution), see <>. If for some reason it is not able to determine the proper one or you want to use a custom Dialect, you will need to set the `hibernate.dialect` setting. @@ -163,7 +205,8 @@ If for some reason it is not able to determine the proper one or you want to use |Dialect (short name) |Remarks |Cache71 |Support for the Caché database, version 2007.1 |CUBRID |Support for the CUBRID database, version 8.3. May work with later versions. -|DB2 |Support for the DB2 database +|DB2 |Support for the DB2 database, version 8.2. +|DB297 |Support for the DB2 database, version 9.7. |DB2390 |Support for DB2 Universal Database for OS/390, also known as DB2/390. |DB2400 |Support for DB2 Universal Database for iSeries, also known as DB2/400. |DerbyTenFive |Support for the Derby database, version 10.5 @@ -172,6 +215,8 @@ If for some reason it is not able to determine the proper one or you want to use |Firebird |Support for the Firebird database |FrontBase |Support for the Frontbase database |H2 |Support for the H2 database +|HANAColumnStore |Support for the SAP HANA database column store. This is the recommended dialect for the SAP HANA database. +|HANARowStore |Support for the SAP HANA database row store |HSQL |Support for the HSQL (HyperSQL) database |Informix |Support for the Informix database |Ingres |Support for the Ingres database, version 9.2 @@ -184,8 +229,8 @@ If for some reason it is not able to determine the proper one or you want to use |MySQL5 |Support for the MySQL database, version 5.x |MySQL5InnoDB |Support for the MySQL database, version 5.x preferring the InnoDB storage engine when exporting tables. |MySQL57InnoDB |Support for the MySQL database, version 5.7 preferring the InnoDB storage engine when exporting tables. May work with newer versions -|MariaDB |Support for the MariadB database. May work with newer versions -|MariaDB53 |Support for the MariadB database, version 5.3 and newer. +|MariaDB |Support for the MariaDB database. May work with newer versions +|MariaDB53 |Support for the MariaDB database, version 5.3 and newer. |Oracle8i |Support for the Oracle database, version 8i |Oracle9i |Support for the Oracle database, version 9i |Oracle10g |Support for the Oracle database, version 10g diff --git a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc index 9de7e08cfe83..faa602627fc2 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc @@ -18,6 +18,7 @@ and requires resources to be locked after they are read and only unlocked after Hibernate provides mechanisms for implementing both types of locking in your applications. +[[locking-optimistic]] === Optimistic When your application uses long transactions or conversations that span several database transactions, @@ -35,17 +36,50 @@ Declaring a nullable version or timestamp property is an easy way to avoid probl especially useful if you use assigned identifiers or composite keys. ==== +[[locking-optimistic-mapping]] +==== Mapping optimistic locking + +JPA defines support for optimistic locking based on either a version (sequential numeric) or timestamp strategy. +To enable this style of optimistic locking simply add the `javax.persistence.Version` to the persistent attribute that defines the optimistic locking value. +According to JPA, the valid types for these attributes are limited to: + +* `int` or `Integer` +* `short` or `Short` +* `long` or `Long` +* `java.sql.Timestamp` + +However, Hibernate allows you to use even Java 8 Date/Time types, such as `Instant`. + +[[locking-optimistic-version-example]] +.`@Version` annotation mapping +==== +[source,java] +---- +include::{sourcedir}/OptimisticLockingTest.java[tags=locking-optimistic-entity-mapping-example,indent=0] +---- + +[source,java] +---- +include::{sourcedir}/OptimisticLockingTimestampTest.java[tags=locking-optimistic-entity-mapping-example,indent=0] +---- + +[source,java] +---- +include::{sourcedir}/OptimisticLockingInstantTest.java[tags=locking-optimistic-entity-mapping-example,indent=0] +---- +==== + [[locking-optimistic-version-number]] -=== Dedicated version number +===== Dedicated version number The version number mechanism for optimistic locking is provided through a `@Version` annotation. [[locking-optimistic-version-number-example]] .@Version annotation ==== -[source, JAVA, indent=0] +[source, JAVA,indent=0] ---- -include::{sourcedir}/OptimisticLockingTest.java[tags=locking-optimistic-version-number-example] +include::{sourcedir}/OptimisticLockingTest.java[tags=locking-optimistic-version-number-example,indent=0] ---- ==== @@ -64,25 +98,204 @@ If the version number is generated by the database, such as a trigger, use the a ==== [[locking-optimistic-timestamp]] -=== Timestamp +===== Timestamp -Timestamps are a less reliable way of optimistic locking than version numbers, but can be used by applications for other purposes as well. +Timestamps are a less reliable way of optimistic locking than version numbers but can be used by applications for other purposes as well. Timestamping is automatically used if you the `@Version` annotation on a `Date` or `Calendar` property type. [[locking-optimistic-version-timestamp-example]] .Using timestamps for optimistic locking ==== -[source, JAVA, indent=0] +[source, JAVA,indent=0] ---- -include::{sourcedir}/OptimisticLockingTest.java[tags=locking-optimistic-version-timestamp-example] +include::{sourcedir}/OptimisticLockingTest.java[tags=locking-optimistic-version-timestamp-example,indent=0] ---- ==== Hibernate can retrieve the timestamp value from the database or the JVM, by reading the value you specify for the `@org.hibernate.annotations.Source` annotation. The value can be either `org.hibernate.annotations.SourceType.DB` or `org.hibernate.annotations.SourceType.VM`. -The default behavior is to use the database, and is also used if you don't specify the annotation at all. +The default behavior is to use the database and is also used if you don't specify the annotation at all. + +The timestamp can also be generated by the database instead of Hibernate +if you use the `@org.hibernate.annotations.Generated(GenerationTime.ALWAYS)` or the `@Source` annotation. + +[[locking-optimistic-version-timestamp-source-mapping-example]] +.Database-generated version timestamp mapping +==== +[source, JAVA,indent=0] +---- +include::{sourcedir}/VersionSourceTest.java[tags=locking-optimistic-version-timestamp-source-mapping-example,indent=0] +---- +==== + +Now, when persisting a `Person` entity, Hibernate calls the database-specific current timestamp retrieval function: + +[[locking-optimistic-version-timestamp-source-persist-example]] +.Database-generated version timestamp example +==== +[source, JAVA,indent=0] +---- +include::{sourcedir}/VersionSourceTest.java[tags=locking-optimistic-version-timestamp-source-persist-example,indent=0] +---- + +[source, SQL,indent=0] +---- +include::{extrasdir}/locking-optimistic-version-timestamp-source-persist-example.sql[] +---- +==== + +[[locking-optimistic-exclude-attribute]] +===== Excluding attributes + +By default, every entity attribute modification is going to trigger a version incrementation. +If there is an entity property which should not bump up the entity version, +then you need to annotate it with the Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLock.html[`@OptimisticLock`] annotation, +as illustrated in the following example. + +[[locking-optimistic-exclude-attribute-mapping-example]] +.@OptimisticLock mapping example +==== +[source, JAVA,indent=0] +---- +include::{sourcedir}/OptimisticLockTest.java[tags=locking-optimistic-exclude-attribute-mapping-example,indent=0] +---- +==== + +This way, if one thread modifies the `Phone` number while a second thread increments the `callCount` attribute, +the two concurrent transactions are not going to conflict as illustrated by the following example. + +[[locking-optimistic-exclude-attribute-example]] +.@OptimisticLock exlude attribute example +==== +[source, JAVA,indent=0] +---- +include::{sourcedir}/OptimisticLockTest.java[tags=locking-optimistic-exclude-attribute-example,indent=0] +---- + +[source, SQL,indent=0] +---- +include::{extrasdir}/locking-optimistic-exclude-attribute-example.sql[] +---- +==== + +When Bob changes the `Phone` entity `callCount`, the entity version is not bumped up. +That's why Alice's UPDATE succeeds since the entity version is still 0, even if Bob has changed the record +since Alice loaded it. + +[WARNING] +==== +Although there is no conflict between Bob and Alice, Alice's UPDATE overrides Bob's change to the `callCount` attribute. -The timestamp can also be generated by the database instead of Hibernate, if you use the `@org.hibernate.annotations.Generated(GenerationTime.ALWAYS)` annotation. +For this reason, you should only use this feature if you can accommodate lost updates on the excluded entity properties. +==== + +[[locking-optimistic-versionless]] +===== Versionless optimistic locking + +Although the default `@Version` property optimistic locking mechanism is sufficient in many situations, +sometimes, you need rely on the actual database row column values to prevent *lost updates*. + +Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". +This is also useful for use with modeling legacy schemas. + +The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes or just the attributes that have changed. +This is achieved through the use of the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] +annotation which defines a single attribute of type +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLockType.html[`org.hibernate.annotations.OptimisticLockType`]. + +There are 4 available OptimisticLockTypes: + +`NONE`:: + optimistic locking is disabled even if there is a `@Version` annotation present +`VERSION` (the default):: + performs optimistic locking based on a `@Version` as described above +`ALL`:: + performs optimistic locking based on _all_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements +`DIRTY`:: + performs optimistic locking based on _dirty_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements + +[[locking-optimistic-versionless-all]] +====== Versionless optimistic locking using `OptimisticLockType.ALL` + +[[locking-optimistic-lock-type-all-example]] +.`OptimisticLockType.ALL` mapping example +==== +[source,java] +---- +include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-example,indent=0] +---- +==== + +When you need to modify the `Person` entity above: + +[[locking-optimistic-lock-type-all-update-example]] +.`OptimisticLockType.ALL` update example +==== +[source,java] +---- +include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-update-example,indent=0] +---- + +[source,SQL] +---- +include::{extrasdir}/locking-optimistic-lock-type-all-update-example.sql[] +---- +==== + +As you can see, all the columns of the associated database row are used in the `WHERE` clause. +If any column has changed after the row was loaded, there won't be any match, and a `StaleStateException` or an `OptimisticLockException` +is going to be thrown. + +[NOTE] +==== +When using `OptimisticLockType.ALL`, you should also use `@DynamicUpdate` because the `UPDATE` statement must take into consideration all the entity property values. +==== + +[[locking-optimistic-versionless-dirty]] +====== Versionless optimistic locking using `OptimisticLockType.DIRTY` + +The `OptimisticLockType.DIRTY` differs from `OptimisticLockType.ALL` +in that it only takes into consideration the entity properties that have changed +since the entity was loaded in the currently running Persistence Context. + +[[locking-optimistic-lock-type-dirty-example]] +.`OptimisticLockType.DIRTY` mapping example +==== +[source,java] +---- +include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-example,indent=0] +---- +==== + +When you need to modify the `Person` entity above: + +[[locking-optimistic-lock-type-dirty-update-example]] +.`OptimisticLockType.DIRTY` update example +==== +[source,java] +---- +include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-update-example,indent=0] +---- + +[source,SQL] +---- +include::{extrasdir}/locking-optimistic-lock-type-dirty-update-example.sql[] +---- +==== + +This time, only the database column that has changed was used in the `WHERE` clause. + +[NOTE] +==== +The main advantage of `OptimisticLockType.DIRTY` over `OptimisticLockType.ALL` +and the default `OptimisticLockType.VERSION` used implicitly along with the `@Version` mapping, +is that it allows you to minimize the risk of `OptimisticLockException` across non-overlapping entity property changes. + +When using `OptimisticLockType.DIRTY`, you should also use `@DynamicUpdate` because the `UPDATE` statement must take into consideration all the dirty entity property values, +and also the `@SelectBeforeUpdate` annotation so that detached entities are properly handled by the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#update-java.lang.Object-[`Session#update(entity)`] operation. +==== [[locking-pessimistic]] === Pessimistic @@ -101,7 +314,7 @@ Hibernate always uses the locking mechanism of the database, and never lock obje Long before JPA 1.0, Hibernate already defined various explicit locking strategies through its `LockMode` enumeration. JPA comes with its own http://docs.oracle.com/javaee/7/api/javax/persistence/LockModeType.html[`LockModeType`] enumeration which defines similar strategies as the Hibernate-native `LockMode`. -[cols=",",, options="header"] +[cols=",,",, options="header"] |======================================================================= |`LockModeType`|`LockMode`|Description @@ -109,7 +322,7 @@ JPA comes with its own http://docs.oracle.com/javaee/7/api/javax/persistence/Loc |`READ` and `OPTIMISTIC`|`READ` | The entity version is checked towards the end of the currently running transaction. |`WRITE` and `OPTIMISTIC_FORCE_INCREMENT`|`WRITE` | The entity version is incremented automatically even if the entity has not changed. |`PESSIMISTIC_FORCE_INCREMENT`|`PESSIMISTIC_FORCE_INCREMENT` | The entity is locked pessimistically and its version is incremented automatically even if the entity has not changed. -|`PESSIMISTIC_READ`|`PESSIMISTIC_READ` | The entity is locked pessimistically using a shared lock, if the database supports such a feature. Otherwise, an explicit lock is used. +|`PESSIMISTIC_READ`|`PESSIMISTIC_READ` | The entity is locked pessimistically using a shared lock if the database supports such a feature. Otherwise, an explicit lock is used. |`PESSIMISTIC_WRITE`|`PESSIMISTIC_WRITE`, `UPGRADE` | The entity is locked using an explicit lock. |`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of 0 |`UPGRADE_NOWAIT` | The lock acquisition request fails fast if the row s already locked. |`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of -2 |`UPGRADE_SKIPLOCKED` | The lock acquisition request skips the already locked rows. It uses a `SELECT ... FOR UPDATE SKIP LOCKED` in Oracle and PostgreSQL 9.5, or `SELECT ... with (rowlock, updlock, readpast) in SQL Server`. @@ -144,12 +357,12 @@ The scope can either be `NORMAL` (default value) or `EXTENDED`. The `EXTENDED` s [[locking-jpa-query-hints-timeout-example]] .`javax.persistence.lock.timeout` example ==== -[source, JAVA, indent=0] +[source, JAVA,indent=0] ---- -include::{sourcedir}/ExplicitLockingTest.java[tags=locking-jpa-query-hints-timeout-example] +include::{sourcedir}/ExplicitLockingTest.java[tags=locking-jpa-query-hints-timeout-example,indent=0] ---- -[source, SQL, indent=0] +[source, SQL,indent=0] ---- include::{extrasdir}/locking-jpa-query-hints-timeout-example.sql[] ---- @@ -172,17 +385,17 @@ The `javax.persistence.lock.scope` is https://hibernate.atlassian.net/browse/HHH Traditionally, Hibernate offered the `Session#lock()` method for acquiring an optimistic or a pessimistic lock on a given entity. Because varying the locking options was difficult when using a single `LockMode` parameter, Hibernate has added the `Session#buildLockRequest()` method API. -The following example shows how to obtain shared database lock without waiting for the lock acquisition request. +The following example shows how to obtain a shared database lock without waiting for the lock acquisition request. [[locking-buildLockRequest-example]] .`buildLockRequest` example ==== -[source, JAVA, indent=0] +[source, JAVA,indent=0] ---- -include::{sourcedir}/ExplicitLockingTest.java[tags=locking-buildLockRequest-example] +include::{sourcedir}/ExplicitLockingTest.java[tags=locking-buildLockRequest-example,indent=0] ---- -[source, SQL, indent=0] +[source, SQL,indent=0] ---- include::{extrasdir}/locking-buildLockRequest-example.sql[] ---- @@ -203,12 +416,12 @@ For this reason, Hibernate uses secondary selects to lock the previously fetched [[locking-follow-on-example]] .Follow-on-locking example ==== -[source, JAVA, indent=0] +[source, JAVA,indent=0] ---- -include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-example] +include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-example,indent=0] ---- -[source, SQL, indent=0] +[source, SQL,indent=0] ---- include::{extrasdir}/locking-follow-on-example.sql[] ---- @@ -222,12 +435,12 @@ To avoid the N+1 query problem, a separate query can be used to apply the lock u [[locking-follow-on-secondary-query-example]] .Secondary query entity locking ==== -[source, JAVA, indent=0] +[source, JAVA,indent=0] ---- -include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-secondary-query-example] +include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-secondary-query-example,indent=0] ---- -[source, SQL, indent=0] +[source, SQL,indent=0] ---- include::{extrasdir}/locking-follow-on-secondary-query-example.sql[] ---- @@ -235,20 +448,20 @@ include::{extrasdir}/locking-follow-on-secondary-query-example.sql[] The lock request was moved from the original query to a secondary one which takes the previously fetched entities to lock their associated database records. -Prior to Hibernate 5.2.1, the the follow-on-locking mechanism was applied uniformly to any locking query executing on Oracle. -Since 5.2.1, the Oracle Dialect tries to figure out if the current query demand the follow-on-locking mechanism. +Prior to Hibernate 5.2.1, the follow-on-locking mechanism was applied uniformly to any locking query executing on Oracle. +Since 5.2.1, the Oracle Dialect tries to figure out if the current query demands the follow-on-locking mechanism. Even more important is that you can overrule the default follow-on-locking detection logic and explicitly enable or disable it on a per query basis. [[locking-follow-on-explicit-example]] .Disabling the follow-on-locking mechanism explicitly ==== -[source, JAVA, indent=0] +[source, JAVA,indent=0] ---- -include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-explicit-example] +include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-explicit-example,indent=0] ---- -[source, SQL, indent=0] +[source, SQL,indent=0] ---- include::{extrasdir}/locking-follow-on-explicit-example.sql[] ---- @@ -256,6 +469,6 @@ include::{extrasdir}/locking-follow-on-explicit-example.sql[] [NOTE] ==== -The follow-on-locking mechanism should be explicitly enabled only if the current executing query fails because the `FOR UPDATE` clause cannot be applied, meaning that the Dialect resolving mechanism needs to be further improved. +The follow-on-locking mechanism should be explicitly enabled only if the currently executing query fails because the `FOR UPDATE` clause cannot be applied, meaning that the Dialect resolving mechanism needs to be further improved. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-exclude-attribute-example.sql b/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-exclude-attribute-example.sql new file mode 100644 index 000000000000..5ce3b481b777 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-exclude-attribute-example.sql @@ -0,0 +1,23 @@ +-- Bob changes the Phone call count + +update + Phone +set + callCount = 1, + "number" = '123-456-7890', + version = 0 +where + id = 1 + and version = 0 + +-- Alice changes the Phone number + +update + Phone +set + callCount = 0, + "number" = '+123-456-7890', + version = 1 +where + id = 1 + and version = 0 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/locking/locking-optimistic-lock-type-all-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-lock-type-all-update-example.sql similarity index 100% rename from documentation/src/main/asciidoc/userguide/chapters/domain/extras/locking/locking-optimistic-lock-type-all-update-example.sql rename to documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-lock-type-all-update-example.sql diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/locking/locking-optimistic-lock-type-dirty-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-lock-type-dirty-update-example.sql similarity index 100% rename from documentation/src/main/asciidoc/userguide/chapters/domain/extras/locking/locking-optimistic-lock-type-dirty-update-example.sql rename to documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-lock-type-dirty-update-example.sql diff --git a/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-version-timestamp-source-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-version-timestamp-source-persist-example.sql new file mode 100644 index 000000000000..beabfb19b3a9 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/locking/extras/locking-optimistic-version-timestamp-source-persist-example.sql @@ -0,0 +1,12 @@ +CALL current_timestamp() + +INSERT INTO + Person + (firstName, lastName, version, id) +VALUES + (?, ?, ?, ?) + +-- binding parameter [1] as [VARCHAR] - [John] +-- binding parameter [2] as [VARCHAR] - [Doe] +-- binding parameter [3] as [TIMESTAMP] - [2017-05-18 12:03:03.808] +-- binding parameter [4] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc index 8b05afbf15f1..16fb19f5de33 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc @@ -75,7 +75,7 @@ include::{sourcedir}/AbstractMultiTenancyTest.java[tags=multitenacy-hibernate-se Additionally, when specifying the configuration, an `org.hibernate.MultiTenancyStrategy` should be named using the `hibernate.multiTenancy` setting. Hibernate will perform validations based on the type of strategy you specify. -The strategy here correlates to the isolation approach discussed above. +The strategy here correlates with the isolation approach discussed above. NONE:: (the default) No multitenancy is expected. @@ -148,7 +148,7 @@ include::{sourcedir}/AbstractMultiTenancyTest.java[tags=multitenacy-multitenacy- ---- ==== -[[multitenacy-hibernate-MultiTenantConnectionProvider]] +[[multitenacy-hibernate-CurrentTenantIdentifierResolver]] ==== CurrentTenantIdentifierResolver `org.hibernate.context.spi.CurrentTenantIdentifierResolver` is a contract for Hibernate to be able to resolve what the application considers the current tenant identifier. diff --git a/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc b/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc index d3f0bd7d423e..5bfb5ceea03d 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc @@ -1,6 +1,7 @@ [[osgi]] == OSGi -:sourcedir: extras +:sourcedir: ../../../../../test/java/org/hibernate/userguide/osgi +:extrasdir: extras === OSGi Specification and Environment @@ -52,10 +53,10 @@ In order to utilize container-managed JPA, an Enterprise OSGi JPA container must In Karaf, this means Aries JPA, which is included out-of-the-box (simply activate the `jpa` and `transaction` features). Originally, we intended to include those dependencies within our own `features.xml`. However, after guidance from the Karaf and Aries teams, it was pulled out. -This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead having the user choose which to use. +This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead of having the user choose which to use. That being said, the QuickStart/Demo projects include a sample https://github.com/hibernate/hibernate-demos/tree/master/hibernate-orm/osgi/managed-jpa/features.xml[features.xml] -showing which features need activated in Karaf in order to support this environment. +showing which features need to be activated in Karaf in order to support this environment. As mentioned, use this purely as a reference! === persistence.xml @@ -73,7 +74,7 @@ You can deploy the `DataSource` manually (Karaf has a `deploy` dir), or through ==== [source,xml] ---- -include::{sourcedir}/datasource-h2.xml[] +include::{extrasdir}/datasource-h2.xml[] ---- ==== @@ -103,7 +104,7 @@ The container takes the name of your persistence unit, then automatically inject ==== [source,xml] ---- -include::{sourcedir}/blueprint.xml[] +include::{extrasdir}/blueprint.xml[] ---- ==== @@ -137,11 +138,12 @@ The service handles the OSGi `ClassLoader`, discovered extension points, scannin Manually creating an `EntityManagerFactory` is guaranteed to NOT work during runtime! ==== +[[osgi-discover-EntityManagerFactory]] .Discover/Use `EntityManagerFactory` ==== [source,java] ---- -include::{sourcedir}/UnmanagedJPAHibernateUtil.java[] +include::{sourcedir}/jpa/HibernateUtil.java[tag=osgi-discover-EntityManagerFactory, indent=0] ---- ==== @@ -160,7 +162,7 @@ Your bundle's manifest will need to import, at a minimum, * `org.osgi.framework`, necessary to discover the `SessionFactory` (described below) * `org.hibernate.*` packages, as necessary (ex: cfg, criterion, service, etc.) -=== Obtaining an SessionFactory +=== Obtaining a SessionFactory `hibernate-osgi` registers an OSGi service, using the `SessionFactory` interface name, that bootstraps and creates a `SessionFactory` specific for OSGi environments. @@ -170,11 +172,12 @@ It is VITAL that your `SessionFactory` be obtained through the service, rather t Manually creating a `SessionFactory` is guaranteed to NOT work during runtime! ==== +[[osgi-discover-SessionFactory]] .Discover/Use `SessionFactory` ==== [source,java] ---- -include::{sourcedir}/NativeHibernateUtil.java[] +include::{sourcedir}/_native/HibernateUtil.java[tag=osgi-discover-SessionFactory, indent=0] ---- ==== @@ -183,7 +186,7 @@ include::{sourcedir}/NativeHibernateUtil.java[] The https://github.com/hibernate/hibernate-demos/tree/master/hibernate-orm/osgi/unmanaged-native[unmanaged-native] demo project displays the use of optional Hibernate modules. Each module adds additional dependency bundles that must first be activated, either manually or through an additional feature. As of ORM 4.2, Envers is fully supported. -Support for C3P0, Proxool, EhCache, and Infinispan were added in 4.3, however none of their 3rd party libraries currently work in OSGi (lots of `ClassLoader` problems, etc.). +Support for C3P0, Proxool, EhCache, and Infinispan were added in 4.3. However, none of their 3rd party libraries currently work in OSGi (lots of `ClassLoader` problems, etc.). We're tracking the issues in JIRA. === Extension Points @@ -198,7 +201,7 @@ The specified interface should be used during service registration. `org.hibernate.integrator.spi.Integrator`:: (as of 4.2) `org.hibernate.boot.registry.selector.StrategyRegistrationProvider`:: (as of 4.3) `org.hibernate.boot.model.TypeContributor`:: (as of 4.3) -JTA's:: `javax.transaction.TransactionManager` and `javax.transaction.UserTransaction` (as of 4.2), however these are typically provided by the OSGi container. +JTA's:: `javax.transaction.TransactionManager` and `javax.transaction.UserTransaction` (as of 4.2). However, these are typically provided by the OSGi container. The easiest way to register extension point implementations is through a `blueprint.xml` file. Add `OSGI-INF/blueprint/blueprint.xml` to your classpath. Envers' blueprint is a great example: @@ -207,7 +210,7 @@ Add `OSGI-INF/blueprint/blueprint.xml` to your classpath. Envers' blueprint is a ==== [source,xml] ---- -include::{sourcedir}/extension_point_blueprint.xml[] +include::{extrasdir}/extension_point_blueprint.xml[] ---- ==== @@ -222,10 +225,10 @@ Extension points can also be registered programmatically with `BundleContext#reg * Scanning is supported to find non-explicitly listed entities and mappings. However, they MUST be in the same bundle as your persistence unit (fairly typical anyway). Our OSGi `ClassLoader` only considers the "requesting bundle" (hence the requirement on using services to create `EntityManagerFactory`/`SessionFactory`), rather than attempting to scan all available bundles. - This is primarily for versioning considerations, collision protections, etc. + This is primarily for versioning considerations, collision protection, etc. * Some containers (ex: Aries) always return true for `PersistenceUnitInfo#excludeUnlistedClasses`, even if your `persistence.xml` explicitly has `exclude-unlisted-classes` set to `false`. They claim it's to protect JPA providers from having to implement scanning ("we handle it for you"), even though we still want to support it in many cases. - The work around is to set `hibernate.archive.autodetection` to, for example, `hbm,class`. + The workaround is to set `hibernate.archive.autodetection` to, for example, `hbm,class`. This tells hibernate to ignore the `excludeUnlistedClasses` value and scan for `*.hbm.xml` and entities regardless. * Scanning does not currently support annotated packages on `package-info.java`. * Currently, Hibernate OSGi is primarily tested using Apache Karaf and Apache Aries JPA. Additional testing is needed with Equinox, Gemini, and other container providers. diff --git a/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/NativeHibernateUtil.java b/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/NativeHibernateUtil.java deleted file mode 100644 index dd53006a76f3..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/NativeHibernateUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -public class HibernateUtil { - - private SessionFactory sf; - - public Session getSession() { - return getSessionFactory().openSession(); - } - - private SessionFactory getSessionFactory() { - if ( sf == null ) { - Bundle thisBundle = FrameworkUtil.getBundle( HibernateUtil.class ); - BundleContext context = thisBundle.getBundleContext(); - - ServiceReference sr = context.getServiceReference( SessionFactory.class.getName() ); - sf = ( SessionFactory ) context.getService( sr ); - } - return sf; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/UnmanagedJPAHibernateUtil.java b/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/UnmanagedJPAHibernateUtil.java deleted file mode 100644 index 1a82ae45a795..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/UnmanagedJPAHibernateUtil.java +++ /dev/null @@ -1,21 +0,0 @@ -public class HibernateUtil { - - private EntityManagerFactory emf; - - public EntityManager getEntityManager() { - return getEntityManagerFactory().createEntityManager(); - } - - private EntityManagerFactory getEntityManagerFactory() { - if ( emf == null ) { - Bundle thisBundle = FrameworkUtil.getBundle( HibernateUtil.class ); - BundleContext context = thisBundle.getBundleContext(); - - ServiceReference serviceReference = context.getServiceReference( PersistenceProvider.class.getName() ); - PersistenceProvider persistenceProvider = ( PersistenceProvider ) context.getService( serviceReference ); - - emf = persistenceProvider.createEntityManagerFactory( "YourPersistenceUnitName", null ); - } - return emf; - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc index ccf69d6a985e..70b52470701f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc @@ -17,11 +17,11 @@ Hibernate supports the enhancement of an application Java domain model for the p ===== Lazy attribute loading Think of this as partial loading support. -Essentially you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. -Note that this is very much different from proxy-based idea of lazy loading which is entity-centric where the entity's state is loaded at once as needed. +Essentially, you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. +Note that this is very much different from the proxy-based idea of lazy loading which is entity-centric where the entity's state is loaded at once as needed. With bytecode enhancement, individual attributes or groups of attributes are loaded as needed. -Lazy attributes can be designated to be loaded together and this is called a "lazy group". +Lazy attributes can be designated to be loaded together, and this is called a "lazy group". By default, all singular attributes are part of a single group, meaning that when one lazy singular attribute is accessed all lazy singular attributes are loaded. Lazy plural attributes, by default, are each a lazy group by themselves. This behavior is explicitly controllable through the `@org.hibernate.annotations.LazyGroup` annotation. @@ -35,9 +35,9 @@ include::{sourcedir}/BytecodeEnhancementTest.java[tags=BytecodeEnhancement-lazy- ---- ==== -In the above example we have 2 lazy attributes: `accountsPayableXrefId` and `image`. +In the above example, we have 2 lazy attributes: `accountsPayableXrefId` and `image`. Each is part of a different fetch group (accountsPayableXrefId is part of the default fetch group), -which means that accessing `accountsPayableXrefId` will not force the loading of image, and vice-versa. +which means that accessing `accountsPayableXrefId` will not force the loading of the `image` attribute, and vice-versa. [NOTE] ==== @@ -52,11 +52,11 @@ Historically Hibernate only supported diff-based dirty calculation for determini This essentially means that Hibernate would keep track of the last known state of an entity in regards to the database (typically the last read or write). Then, as part of flushing the persistence context, Hibernate would walk every entity associated with the persistence context and check its current state against that "last known database state". This is by far the most thorough approach to dirty checking because it accounts for data-types that can change their internal state (`java.util.Date` is the prime example of this). -However, in a persistence context with a large number of associated entities it can also be a performance-inhibiting approach. +However, in a persistence context with a large number of associated entities, it can also be a performance-inhibiting approach. If your application does not need to care about "internal state changing data-type" use cases, bytecode-enhanced dirty tracking might be a worthwhile alternative to consider, especially in terms of performance. In this approach Hibernate will manipulate the bytecode of your classes to add "dirty tracking" directly to the entity, allowing the entity itself to keep track of which of its attributes have changed. -During flush time, Hibernate simply asks your entity what has changed rather that having to perform the state-diff calculations. +During the flush time, Hibernate asks your entity what has changed rather than having to perform the state-diff calculations. [[BytecodeEnhancement-dirty-tracking-bidirectional]] ===== Bidirectional association management @@ -105,22 +105,31 @@ These are hard to discuss without diving into a discussion of Hibernate internal ==== Performing enhancement [[BytecodeEnhancement-enhancement-runtime]] -===== Run-time enhancement +===== Runtime enhancement -Currently, run-time enhancement of the domain model is only supported in managed JPA environments following the JPA-defined SPI for performing class transformations. -Even then, this support is disabled by default. -To enable run-time enhancement, specify `hibernate.ejb.use_class_enhancer`=`true` as a persistent unit property. +Currently, runtime enhancement of the domain model is only supported in managed JPA environments following the JPA-defined SPI for performing class transformations. + +Even then, this support is disabled by default. To enable runtime enhancement, specify one of the following configuration properties: + +`*hibernate.enhancer.enableDirtyTracking*` (e.g. `true` or `false` (default value)):: +Enable dirty tracking feature in runtime bytecode enhancement. + +`*hibernate.enhancer.enableLazyInitialization*` (e.g. `true` or `false` (default value)):: +Enable lazy loading feature in runtime bytecode enhancement. This way, even basic types (e.g. `@Basic(fetch = FetchType.LAZY`)) can be fetched lazily. + +`*hibernate.enhancer.enableAssociationManagement*` (e.g. `true` or `false` (default value)):: +Enable association management feature in runtime bytecode enhancement which automatically synchronizes a bidirectional association when only one side is changed. [NOTE] ==== -Also, at the moment, only annotated classes are supported for run-time enhancement. +Also, at the moment, only annotated classes are supported for runtime enhancement. ==== [[BytecodeEnhancement-enhancement-gradle]] ===== Gradle plugin Hibernate provides a Gradle plugin that is capable of providing build-time enhancement of the domain model as they are compiled as part of a Gradle build. -To use the plugin a project would first need to apply it: +To use the plugin, a project would first need to apply it: .Apply the Gradle plugin ==== @@ -148,7 +157,7 @@ Hibernate provides a Maven plugin capable of providing build-time enhancement of See the section on the <> for details on the configuration settings. Again, the default for those 3 is `false`. The Maven plugin supports one additional configuration settings: failOnError, which controls what happens in case of error. -Default behavior is to fail the build, but it can be set so that only a warning is issued. +The default behavior is to fail the build, but it can be set so that only a warning is issued. .Apply the Maven plugin ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index dc26017b8b23..6af07c251e9b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -1,5 +1,5 @@ [[pc]] -== Persistence Contexts +== Persistence Context :sourcedir: ../../../../../test/java/org/hibernate/userguide/pc :sourcedir-caching: ../../../../../test/java/org/hibernate/userguide/caching :extrasdir: extras @@ -12,8 +12,8 @@ Persistent data has a state in relation to both a persistence context and the un It has no persistent representation in the database and typically no identifier value has been assigned (unless the _assigned_ generator was used). `managed`, or `persistent`:: the entity has an associated identifier and is associated with a persistence context. It may or may not physically exist in the database yet. -`detached`:: the entity has an associated identifier, but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context) -`removed`:: the entity has an associated identifier and is associated with a persistence context, however it is scheduled for removal from the database. +`detached`:: the entity has an associated identifier but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context) +`removed`:: the entity has an associated identifier and is associated with a persistence context, however, it is scheduled for removal from the database. Much of the `org.hibernate.Session` and `javax.persistence.EntityManager` methods deal with moving entities between these states. @@ -78,7 +78,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-remove-jpa-example] ==== [[pc-remove-native-example]] -.Deleting an entity with Hibernate API +.Deleting an entity with the Hibernate API ==== [source, JAVA, indent=0] ---- @@ -91,7 +91,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-remove-native-example] Hibernate itself can handle deleting detached state. JPA, however, disallows it. The implication here is that the entity instance passed to the `org.hibernate.Session` delete method can be either in managed or detached state, -while the entity instance passed to remove on `javax.persistence.EntityManager` must be in managed state. +while the entity instance passed to remove on `javax.persistence.EntityManager` must be in the managed state. ==== [[pc-get-reference]] @@ -121,6 +121,11 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-get-reference-native-ex The above works on the assumption that the entity is defined to allow lazy loading, generally through use of runtime proxies. In both cases an exception will be thrown later if the given entity does not refer to actual database state when the application attempts to use the returned proxy in any way that requires access to its data. +[IMPORTANT] +==== +Unless the entity class is declared `final`, the proxy extends the entity class. If the entity class is `final`, the proxy will implement an interface instead. See the <> section for more info. +==== + [[pc-find]] === Obtain an entity with its data initialized @@ -172,7 +177,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-find-optional-by-id-nat [[pc-find-natural-id]] === Obtain an entity by natural-id -In addition to allowing to load by identifier, Hibernate allows applications to load by declared natural identifier. +In addition to allowing to load the entity by its identifier, Hibernate allows applications to load entities by the declared natural identifier. [[pc-find-by-natural-id-entity-example]] .Natural-id mapping @@ -214,23 +219,23 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-find-optional-by-simple ---- ==== -Hibernate offer a consistent API for accessing persistent data by identifier or by the natural-id. Each of these defines the same two data access methods: +Hibernate offers a consistent API for accessing persistent data by identifier or by the natural-id. Each of these defines the same two data access methods: getReference:: Should be used in cases where the identifier is assumed to exist, where non-existence would be an actual error. Should never be used to test existence. That is because this method will prefer to create and return a proxy if the data is not already associated with the Session rather than hit the database. - The quintessential use-case for using this method is to create foreign-key based associations. + The quintessential use-case for using this method is to create foreign key based associations. load:: Will return the persistent data associated with the given identifier value or null if that identifier does not exist. -Each of these two methods define an overloading variant accepting a `org.hibernate.LockOptions` argument. +Each of these two methods defines an overloading variant accepting a `org.hibernate.LockOptions` argument. Locking is discussed in a separate <>. [[pc-managed-state]] === Modifying managed/persistent state -Entities in managed/persistent state may be manipulated by the application and any changes will be automatically detected and persisted when the persistence context is flushed. +Entities in managed/persistent state may be manipulated by the application, and any changes will be automatically detected and persisted when the persistence context is flushed. There is no need to call a particular method to make your modifications persistent. [[pc-managed-state-jpa-example]] @@ -251,6 +256,83 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-managed-state-native-ex ---- ==== +By default, when you modify an entity, all columns but the identifier are being set during update. + +Therefore, considering you have the following `Product` entity mapping: + +[[pc-managed-state-update-mapping-example]] +.`Product` entity mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/NoDynamicUpdateTest.java[tags=pc-managed-state-update-mapping-example] +---- +==== + +If you persist the following `Product` entity: + +[[pc-managed-state-update-persist-example]] +.Persisting a `Product` entity +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/NoDynamicUpdateTest.java[tags=pc-managed-state-update-persist-example] +---- +==== + +When you modify the `Product` entity, Hibernate generates the following SQL UPDATE statement: + +[[pc-managed-state-update-example]] +.Modifying the `Product` entity +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/NoDynamicUpdateTest.java[tags=pc-managed-state-update-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-managed-state-update-example.sql[] +---- +==== + +The default UPDATE statement containing all columns has two advantages: + +- it allows you to better benefit from JDBC Statement caching. +- it allows you to enable batch updates even if multiple entities modify different properties. + +However, there is also one downside to including all columns in the SQL UPDATE statement. +If you have multiple indexes, the database might update those redundantly even if you don't actually modify all column values. + +To fix this issue, you can use dynamic updates. + +[[pc-managed-state-dynamic-update]] +==== Dynamic updates + +To enable dynamic updates, you need to annotate the entity with the `@DynamicUpdate` annotation: + +[[pc-managed-state-dynamic-update-mapping-example]] +.`Product` entity mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DynamicUpdateTest.java[tags=pc-managed-state-dynamic-update-mapping-example] +---- +==== + +This time, when rerunning the previous test case, Hibernate generates the following SQL UPDATE statement: + +[[pc-managed-state-dynamic-update-example]] +.Modifying the `Product` entity with a dynamic update +==== +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-managed-state-dynamic-update-example.sql[] +---- +==== + +The dynamic update allows you to set just the columns that were modified in the associated entity. + [[pc-refresh]] === Refresh entity state @@ -334,12 +416,12 @@ Clearing the persistence context has the same effect. Evicting a particular entity from the persistence context makes it detached. And finally, serialization will make the deserialized form be detached (the original instance is still managed). -Detached data can still be manipulated, however the persistence context will no longer automatically know about these modification and the application will need to intervene to make the changes persistent again. +Detached data can still be manipulated, however, the persistence context will no longer automatically know about these modifications, and the application will need to intervene to make the changes persistent again. [[pc-detach-reattach]] ==== Reattaching detached data -Reattachment is the process of taking an incoming entity instance that is in detached state and re-associating it with the current persistence context. +Reattachment is the process of taking an incoming entity instance that is in the detached state and re-associating it with the current persistence context. [IMPORTANT] ==== @@ -377,7 +459,7 @@ Provided the entity is detached, `update` and `saveOrUpdate` operate exactly the [[pc-merge]] ==== Merging detached data -Merging is the process of taking an incoming entity instance that is in detached state and copying its data over onto a new managed instance. +Merging is the process of taking an incoming entity instance that is in the detached state and copying its data over onto a new managed instance. Although not exactly per se, the following example is a good visualization of the `merge` operation internals. @@ -619,6 +701,7 @@ Even if just the `Person` parent entity was persisted, Hibernate has managed to The `CascadeType.MERGE` allows us to merge a child entity along with the parent one. +[[pc-cascade-merge-example]] .`CascadeType.MERGE` example ==== [source, JAVA, indent=0] @@ -678,6 +761,7 @@ Such a use case requires the use of the `PessimisticLockScope.EXTENDED` value of However, `CascadeType.LOCK` allows us to reattach a parent entity along with its children to the currently running Persistence Context. +[[pc-cascade-lock-example]] .`CascadeType.LOCK` example ==== [source, JAVA, indent=0] @@ -692,6 +776,7 @@ include::{sourcedir}/CascadeLockTest.java[tags=pc-cascade-lock-example] The `CascadeType.REFRESH` is used to propagate the refresh operation from a parent entity to a child. The refresh operation will discard the current entity state, and it will override it using the one loaded from the database. +[[pc-cascade-refresh-example]] .`CascadeType.REFRESH` example ==== [source, JAVA, indent=0] @@ -713,6 +798,7 @@ In the aforementioned example, you can see that both the `Person` and `Phone` en The `CascadeType.REPLICATE` is to replicate both the parent and the child entities. The replicate operation allows you to synchronize entities coming from different sources of data. +[[pc-cascade-replicate-example]] .`CascadeType.REPLICATE` example ==== [source, JAVA, indent=0] @@ -728,3 +814,108 @@ include::{extrasdir}/pc-cascade-replicate-example.sql[] As illustrated by the SQL statements being generated, both the `Person` and `Phone` entities are replicated to the underlying database rows. +[[pc-cascade-on-delete]] +==== `@OnDelete` cascade + +While the previous cascade types propagate entity state transitions, the `@OnDelete` cascade is a DDL-level FK feature which allows you +to remove a child record whenever the parent row is deleted. + +So, when annotating the `@ManyToOne` association with `@OnDelete( action = OnDeleteAction.CASCADE )`, +the automatic schema generator will apply the ON DELETE CASCADE SQL directive to the Foreign Key declaration, +as illustrated by the following example. + +[[pc-cascade-on-delete-mapping-example]] +.`@OnDelete` mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-Person-example] +---- + +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-Phone-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-cascade-on-delete-mapping-example.sql[] +---- +==== + +Now, you can just remove the `Person` entity, and the associated `Phone` is going to be removed automatically. + +[[pc-cascade-on-delete-example]] +.`@OnDelete` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-cascade-on-delete-example.sql[] +---- +==== + +[[pc-exception-handling]] +=== Exception handling + +If the JPA `EntityManager` or the Hibernate-specific `Session` throws an exception, including any JDBC https://docs.oracle.com/javase/8/docs/api/java/sql/SQLException.html[`SQLException`], you have to immediately rollback the database transaction and close the current `EntityManager` or `Session`. + +Certain methods of the JPA `EntityManager` or the Hibernate `Session` will not leave the Persistence Context in a consistent state. As a rule of thumb, no exception thrown by Hibernate can be treated as recoverable. Ensure that the Session will be closed by calling the `close()` method in a finally block. + +Rolling back the database transaction does not put your business objects back into the state they were at the start of the transaction. This means that the database state and the business objects will be out of sync. Usually, this is not a problem because exceptions are not recoverable and you will have to start over after rollback anyway. + +The JPA https://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceException.html[`PersistenceException`] or the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/HibernateException.html[`HibernateException`] wraps most of the errors that can occur in a Hibernate persistence layer. + +Both the `PersistenceException` and the `HibernateException` are runtime exceptions because, in our opinion, we should not force the application developer to catch an unrecoverable exception at a low layer. In most systems, unchecked and fatal exceptions are handled in one of the first frames of the method call stack (i.e., in higher layers) and either an error message is presented to the application user or some other appropriate action is taken. Note that Hibernate might also throw other unchecked exceptions that are not a `HibernateException`. These are not recoverable either, and appropriate action should be taken. + +Hibernate wraps the JDBC `SQLException`, thrown while interacting with the database, in a +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/JDBCException.html[`JDBCException`]. +In fact, Hibernate will attempt to convert the exception into a more meaningful subclass of `JDBCException`. The underlying `SQLException` is always available via https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/JDBCException.html#getSQLException--[`JDBCException.getSQLException()`]. Hibernate converts the `SQLException` into an appropriate JDBCException subclass using the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/exception/spi/SQLExceptionConverter.html[`SQLExceptionConverter`] +attached to the current `SessionFactory`. + +By default, the `SQLExceptionConverter` is defined by the configured Hibernate `Dialect` via the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html#buildSQLExceptionConversionDelegate--[`buildSQLExceptionConversionDelegate`] method +which is overridden by several database-specific `Dialects`. + +However, it is also possible to plug in a custom implementation. See the +<> configuration property for more details. + +The standard `JDBCException` subtypes are: + +ConstraintViolationException:: + indicates some form of integrity constraint violation. +DataException:: + indicates that evaluation of the valid SQL statement against the given data + resulted in some illegal operation, mismatched types, truncation or incorrect cardinality. +GenericJDBCException:: + a generic exception which did not fall into any of the other categories. +JDBCConnectionException:: + indicates an error with the underlying JDBC communication. +LockAcquisitionException:: + indicates an error acquiring a lock level necessary to perform the requested operation. +LockTimeoutException:: + indicates that the lock acquisition request has timed out. +PessimisticLockException:: + indicates that a lock acquisition request has failed. +QueryTimeoutException:: + indicates that the current executing query has timed out. +SQLGrammarException:: + indicates a grammar or syntax problem with the issued SQL. + +[NOTE] +==== +Starting with Hibernate 5.2, the Hibernate `Session` extends the JPA `EntityManager`. For this reason, when a `SessionFactory` is built via Hibernate's native bootstrapping, +the `HibernateException` or `SQLException` can be wrapped in a JPA https://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceException.html[`PersistenceException`] when thrown +by `Session` methods that implement `EntityManager` methods (e.g., https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#merge-java.lang.Object-[Session.merge(Object object)], +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#flush--[Session.flush()]). + +If your `SessionFactory` is built via Hibernate's native bootstrapping, and you don't want the Hibernate exceptions to be wrapped in the JPA `PersistenceException`, you need to set the +`hibernate.native_exception_handling_51_compliance` configuration property to `true`. See the +<> configuration property for more details. +==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-on-delete-example.sql b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-on-delete-example.sql new file mode 100644 index 000000000000..bcc8c1e021e1 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-on-delete-example.sql @@ -0,0 +1,3 @@ +delete from Person where id = ? + +-- binding parameter [1] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-on-delete-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-on-delete-mapping-example.sql new file mode 100644 index 000000000000..4bf9e79011ba --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-on-delete-mapping-example.sql @@ -0,0 +1,18 @@ +create table Person ( + id bigint not null, + name varchar(255), + primary key (id) +) + +create table Phone ( + id bigint not null, + "number" varchar(255), + owner_id bigint, + primary key (id) +) + +alter table Phone + add constraint FK82m836qc1ss2niru7eogfndhl + foreign key (owner_id) + references Person + on delete cascade \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-managed-state-dynamic-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-managed-state-dynamic-update-example.sql new file mode 100644 index 000000000000..b77fc3a8d838 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-managed-state-dynamic-update-example.sql @@ -0,0 +1,9 @@ +UPDATE + Product +SET + price_cents = ? +WHERE + id = ? + +-- binding parameter [1] as [INTEGER] - [2499] +-- binding parameter [2] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-managed-state-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-managed-state-update-example.sql new file mode 100644 index 000000000000..6773243a6625 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-managed-state-update-example.sql @@ -0,0 +1,15 @@ +UPDATE + Product +SET + description = ?, + name = ?, + price_cents = ?, + quantity = ? +WHERE + id = ? + +-- binding parameter [1] as [VARCHAR] - [Get the most out of your persistence layer] +-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence] +-- binding parameter [3] as [INTEGER] - [2499] +-- binding parameter [4] as [INTEGER] - [10000] +-- binding parameter [5] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc index 8a96fa7d6210..503c19a7c238 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc @@ -24,7 +24,7 @@ Originally, Hibernate would always require that users specify which dialect to u Generally, this required their users to configure the Hibernate dialect or defining their own method of setting that value. Starting with version 3.2, Hibernate introduced the notion of automatically detecting the dialect to use based on the `java.sql.DatabaseMetaData` obtained from a `java.sql.Connection` to that database. -This was much better, expect that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable. +This was much better, except that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable. Starting with version 3.3, Hibernate has a fare more powerful way to automatically determine which dialect to should be used by relying on a series of delegates which implement the `org.hibernate.dialect.resolver.DialectResolver` which defines only a single method: @@ -35,7 +35,7 @@ public Dialect resolveDialect(DatabaseMetaData metaData) throws JDBCConnectionEx The basic contract here is that if the resolver 'understands' the given database metadata then it returns the corresponding Dialect; if not it returns null and the process continues to the next resolver. The signature also identifies `org.hibernate.exception.JDBCConnectionException` as possibly being thrown. -A `JDBCConnectionException` here is interpreted to imply a "non transient" (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution attempts. +A `JDBCConnectionException` here is interpreted to imply a __non-transient__ (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution attempts. All other exceptions result in a warning and continuing on to the next resolver. The cool part about these resolvers is that users can also register their own custom resolvers which will be processed ahead of the built-in Hibernate ones. @@ -50,14 +50,14 @@ To register one or more resolvers, simply specify them (separated by commas, tab === Identifier generation When considering portability between databases, another important decision is selecting the identifier generation strategy you want to use. -Originally Hibernate provided the _native_ generator for this purpose, which was intended to select between a __sequence__, __identity__, or _table_ strategy depending on the capability of the underlying database. +Originally, Hibernate provided the _native_ generator for this purpose, which was intended to select between a __sequence__, __identity__, or _table_ strategy depending on the capability of the underlying database. However, an insidious implication of this approach comes about when targeting some databases which support _identity_ generation and some which do not. _identity_ generation relies on the SQL definition of an IDENTITY (or auto-increment) column to manage the identifier value. It is what is known as a _post-insert_ generation strategy because the insert must actually happen before we can know the identifier value. Because Hibernate relies on this identifier value to uniquely reference entities within a persistence context, -it must then issue the insert immediately when the users requests that the entity be associated with the session (e.g. like via `save()` or `persist()`) , regardless of current transactional semantics. +it must then issue the insert immediately when the user requests that the entity be associated with the session (e.g. like via `save()` or `persist()`), regardless of current transactional semantics. [NOTE] ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc index a6e3e4d4e1d4..cc86b78d9a89 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc @@ -18,9 +18,9 @@ They are type-safe in terms of using interfaces and classes to represent various They can also be type-safe in terms of referencing attributes as we will see in a bit. Users of the older Hibernate `org.hibernate.Criteria` query API will recognize the general approach, though we believe the JPA API to be superior as it represents a clean look at the lessons learned from that API. -Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of query. +Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of the query. The first step in performing a criteria query is building this graph. -The `javax.persistence.criteria.CriteriaBuilder` interface is the first thing with which you need to become acquainted to begin using criteria queries. +The `javax.persistence.criteria.CriteriaBuilder` interface is the first thing with which you need to become acquainted with begin using criteria queries. Its role is that of a factory for all the individual pieces of the criteria. You obtain a `javax.persistence.criteria.CriteriaBuilder` instance by calling the `getCriteriaBuilder()` method of either `javax.persistence.EntityManagerFactory` or `javax.persistence.EntityManager`. @@ -69,7 +69,7 @@ It was done here only for completeness of an example. The `Person_.name` reference is an example of the static form of JPA Metamodel reference. We will use that form exclusively in this chapter. -See the documentation for the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/topical/html/metamodelgen/MetamodelGenerator.html[Hibernate JPA Metamodel Generator] for additional details on the JPA static Metamodel. +See the documentation for the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/topical/html_single/metamodelgen/MetamodelGenerator.html[Hibernate JPA Metamodel Generator] for additional details on the JPA static Metamodel. ==== [[criteria-typedquery-expression]] @@ -148,7 +148,7 @@ Specifically, notice the constructor and its argument types. Since we will be returning `PersonWrapper` objects, we use `PersonWrapper` as the type of our criteria query. This example illustrates the use of the `javax.persistence.criteria.CriteriaBuilder` method construct which is used to build a wrapper expression. -For every row in the result we are saying we would like a `PersonWrapper` instantiated with the remaining arguments by the matching constructor. +For every row in the result, we are saying we would like a `PersonWrapper` instantiated with the remaining arguments by the matching constructor. This wrapper expression is then passed as the select. [[criteria-tuple]] @@ -273,7 +273,7 @@ include::{sourcedir}/CriteriaTest.java[tags=criteria-from-fetch-example] [NOTE] ==== Technically speaking, embedded attributes are always fetched with their owner. -However in order to define the fetching of _Phone#addresses_ we needed a `javax.persistence.criteria.Fetch` because element collections are `LAZY` by default. +However, in order to define the fetching of _Phone#addresses_ we needed a `javax.persistence.criteria.Fetch` because element collections are `LAZY` by default. ==== [[criteria-path]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc index 8d3c32106052..3047cce411ae 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc @@ -6,7 +6,7 @@ The Hibernate Query Language (HQL) and Java Persistence Query Language (JPQL) are both object model focused query languages similar in nature to SQL. JPQL is a heavily-inspired-by subset of HQL. -A JPQL query is always a valid HQL query, the reverse is not true however. +A JPQL query is always a valid HQL query, the reverse is not true, however. Both HQL and JPQL are non-type-safe ways to perform query operations. Criteria queries offer a type-safe approach to querying. See <> for more information. @@ -15,7 +15,7 @@ Criteria queries offer a type-safe approach to querying. See <> for additional details on collection related expressions. +See <> for additional details on collection-related expressions. [[hql-polymorphism]] === Polymorphism @@ -952,20 +968,26 @@ include::{sourcedir}/HQLTest.java[tags=hql-polymorphism-example, indent=0] This query names the `Payment` entity explicitly. However, all subclasses of `Payment` are also available to the query. -So if the `CreditCardPayment` and `WireTransferPayment` entities extend the `Payment` class, all three types would be available to the entity query, +So, if the `CreditCardPayment` and `WireTransferPayment` entities extend the `Payment` class, all three types would be available to the entity query, and the query would return instances of all three. + +This behavior can be altered in two ways: + +- by limiting the query to select only from the subclass entity +- by using either the `org.hibernate.annotations.Polymorphism` annotation (global, and Hibernate-specific). See the <>. + [NOTE] ==== -This can be altered by using either the `org.hibernate.annotations.Polymorphism` annotation (global, and Hibernate-specific) or limiting them using in the query itself using an entity type expression. +The HQL query `from java.lang.Object` is totally valid (although not very practical from a performance perspective)! -The HQL query `from java.lang.Object` is totally valid! It returns every object of every type defined in your application. +It returns every object of every entity type defined by your application mappings. ==== [[hql-expressions]] === Expressions -Essentially expressions are references that resolve to basic or tuple values. +Essentially, expressions are references that resolve to basic or tuple values. [[hql-identification-variable]] === Identification variable @@ -1014,7 +1036,7 @@ The actual suffix is case-insensitive. The boolean literals are `TRUE` and `FALSE`, again case-insensitive. Enums can even be referenced as literals. The fully-qualified enum class name must be used. -HQL can also handle constants in the same manner, though JPQL does not define that as supported. +HQL can also handle constants in the same manner, though JPQL does not define that as being supported. Entity names can also be used as literal. See <>. @@ -1024,7 +1046,7 @@ Date/time literals can be specified using the JDBC escape syntax: * `{t 'hh:mm:ss'}` for times * `{ts 'yyyy-mm-dd hh:mm:ss[.millis]'}` (millis optional) for timestamps. -These Date/time literals only work if you JDBC drivers supports them. +These Date/time literals only work if the underlying JDBC driver supports them. ==== [[hql-numeric-arithmetic]] @@ -1051,13 +1073,13 @@ The following rules apply to the result of arithmetic operations: * else, (the assumption being that both operands are of integral type) the result is `Integer` (except for division, in which case the result type is not further defined) Date arithmetic is also supported, albeit in a more limited fashion. -This is due partially to differences in database support and partially to the lack of support for `INTERVAL` definition in the query language itself. +This is due to differences in database support and partly to the lack of support for `INTERVAL` definition in the query language itself. [[hql-concatenation]] === Concatenation (operation) HQL defines a concatenation operator in addition to supporting the concatenation (`CONCAT`) function. -This is not defined by JPQL, so portable applications should avoid it use. +This is not defined by JPQL, so portable applications should avoid its use. The concatenation operator is taken from the SQL concatenation operator (e.g `||`). [[hql-concatenation-example]] @@ -1356,7 +1378,7 @@ ELEMENTS:: Only allowed in the where clause. Often used in conjunction with `ALL`, `ANY` or `SOME` restrictions. INDICES:: - Similar to `elements` except that `indices` refers to the collections indices (keys/positions) as a whole. + Similar to `elements` except that the `indices` expression refers to the collections indices (keys/positions) as a whole. [[hql-collection-expressions-example]] .Collection-related expressions examples @@ -1385,7 +1407,7 @@ See also <> as there is a good deal of overlap. We can also refer to the type of an entity as an expression. This is mainly useful when dealing with entity inheritance hierarchies. -The type can expressed using a `TYPE` function used to refer to the type of an identification variable representing an entity. +The type can be expressed using a `TYPE` function used to refer to the type of an identification variable representing an entity. The name of the entity also serves as a way to refer to an entity type. Additionally, the entity type can be parameterized, in which case the entity's Java Class reference would be bound as the parameter value. @@ -1479,7 +1501,7 @@ There is a particular expression type that is only valid in the select clause. Hibernate calls this "dynamic instantiation". JPQL supports some of that feature and calls it a "constructor expression". -So rather than dealing with the `Object[]` (again, see <>) here we are wrapping the values in a type-safe java object that will be returned as the results of the query. +So rather than dealing with the `Object[]` (again, see <>) here, we are wrapping the values in a type-safe Java object that will be returned as the results of the query. [[hql-select-clause-dynamic-instantiation-example]] .Dynamic HQL and JPQL instantiation example @@ -1536,7 +1558,7 @@ If the user doesn't assign aliases, the key will be the index of each particular === Predicates Predicates form the basis of the where clause, the having clause and searched case expressions. -They are expressions which resolve to a truth value, generally `TRUE` or `FALSE`, although boolean comparisons involving `NULL` generally resolve to `UNKNOWN`. +They are expressions which resolve to a truth value, generally `TRUE` or `FALSE`, although boolean comparisons involving `NULL` resolve typically to `UNKNOWN`. [[hql-relational-comparisons]] === Relational comparisons @@ -1574,8 +1596,8 @@ It resolves to false if the subquery result is empty. [[hql-null-predicate]] === Nullness predicate -Check a value for nullness. -Can be applied to basic attribute references, entity references and parameters. +It check a value for nullness. +It can be applied to basic attribute references, entity references, and parameters. HQL additionally allows it to be applied to component/embeddable types. [[hql-null-predicate-example]] @@ -1656,9 +1678,9 @@ include::{extrasdir}/predicate_in_bnf.txt[] The types of the `single_valued_expression` and the individual values in the `single_valued_list` must be consistent. -JPQL limits the valid types here to string, numeric, date, time, timestamp, and enum types, and , in JPQL, `single_valued_expression` can only refer to: +JPQL limits the valid types here to string, numeric, date, time, timestamp, and enum types, and, in JPQL, `single_valued_expression` can only refer to: -* "state fields", which is its term for simple attributes. Specifically this excludes association and component/embedded attributes. +* "state fields", which is its term for simple attributes. Specifically, this excludes association and component/embedded attributes. * entity type expressions. See <> In HQL, `single_valued_expression` can refer to a far more broad set of expression types. @@ -1730,7 +1752,7 @@ If the predicate is true, NOT resolves to false. If the predicate is unknown (e. The `AND` operator is used to combine 2 predicate expressions. The result of the AND expression is true if and only if both predicates resolve to true. -If either predicates resolves to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. +If either predicate resolves to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. [[hql-or-predicate]] === OR predicate operator @@ -1795,7 +1817,7 @@ The types of expressions considered valid as part of the `ORDER BY` clause inclu Additionally, JPQL says that all values referenced in the `ORDER BY` clause must be named in the `SELECT` clause. HQL does not mandate that restriction, but applications desiring database portability should be aware that not all databases support referencing values in the `ORDER BY` clause that are not referenced in the select clause. -Individual expressions in the order-by can be qualified with either `ASC` (ascending) or `DESC` (descending) to indicated the desired ordering direction. +Individual expressions in the order-by can be qualified with either `ASC` (ascending) or `DESC` (descending) to indicate the desired ordering direction. Null values can be placed in front or at the end of the sorted set using `NULLS FIRST` or `NULLS LAST` clause respectively. [[hql-order-by-example]] @@ -1811,7 +1833,7 @@ include::{sourcedir}/HQLTest.java[tags=hql-order-by-example] === Read-only entities As explained in <> section, fetching entities in read-only mode is much more efficient than fetching read-write entities. -Even if the entities are mutable, you can still fetch them in read-only mode, and benefit from reducing the memory footprint and sepeding up the flushing process. +Even if the entities are mutable, you can still fetch them in read-only mode, and benefit from reducing the memory footprint and speeding up the flushing process. Read-only entities are skipped by the dirty checking mechanism as illustrated by the following example: @@ -1831,6 +1853,17 @@ include::{extrasdir}/hql-read-only-entities-example.sql[] As you can see, there is no SQL `UPDATE` being executed. +You can also pass the read-only hint to named queries using the JPA http://docs.oracle.com/javaee/7/api/javax/persistence/QueryHint.html[`@QueryHint`] annotation. + +[[jpa-read-only-entities-native-example]] +.Fetching read-only entities using a named query and the read-only hint +==== +[source, JAVA, indent=0] +---- +include::{modeldir}/Person.java[tags=jpa-read-only-entities-native-example] +---- +==== + The Hibernate native API offers a `Query#setReadOnly` method, as an alternative to using a JPA query hint: [[hql-read-only-entities-native-example]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc index 2fa5ab7c1999..3214cba9f912 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc @@ -5,14 +5,14 @@ :extrasdir: extras You may also express queries in the native SQL dialect of your database. -This is useful if you want to utilize database specific features such as window functions, Common Table Expressions (CTE) or the `CONNECT BY` option in Oracle. +This is useful if you want to utilize database-specific features such as window functions, Common Table Expressions (CTE) or the `CONNECT BY` option in Oracle. It also provides a clean migration path from a direct SQL/JDBC based application to Hibernate/JPA. Hibernate also allows you to specify handwritten SQL (including stored procedures) for all create, update, delete, and retrieve operations. [[sql-jpa-query]] === Creating a native query using JPA -Execution of native SQL queries is controlled via the `SQLQuery` interface, which is obtained by calling `Session.createSQLQuery()`. +Execution of native SQL queries is controlled via the `NativeQuery` interface, which is obtained by calling `Session.createNativeQuery()`. The following sections describe how to use this API for querying. [[sql-scalar-query]] @@ -84,7 +84,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-scalar-query-partial-explic ---- ==== -This is essentially the same query as before, but now `ResultSetMetaData` is used to determine the type of `name`, where as the type of `id` is explicitly specified. +This is essentially the same query as before, but now `ResultSetMetaData` is used to determine the type of `name`, whereas the type of `id` is explicitly specified. How the `java.sql.Types` returned from `ResultSetMetaData` is mapped to Hibernate types is controlled by the `Dialect`. If a specific type is not mapped, or does not result in the expected type, it is possible to customize it via calls to `registerHibernateType` in the Dialect. @@ -112,7 +112,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-query-example] ---- ==== -Assuming that `Person` is mapped as a class with the columns `id`, `name`, `nickName`, `address`, `createdOn` and `version`, +Assuming that `Person` is mapped as a class with the columns `id`, `name`, `nickName`, `address`, `createdOn`, and `version`, the following query will also return a `List` where each element is a `Person` entity. [[sql-jpa-entity-query-explicit-result-set-example]] @@ -136,8 +136,9 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-query-explicit-resul [[sql-entity-associations-query]] === Handling associations and collections -If the entity is mapped with a `many-to-one` or a child-side `one-to-one` to another entity, it is required to also return this when performing the native query, -otherwise a database specific _column not found_ error will occur. +If the entity is mapped with a `many-to-one` or a child-side `one-to-one` to another entity, +it is required to also return this when performing the native query, +otherwise, a database-specific _column not found_ error will occur. [[sql-jpa-entity-associations-query-many-to-one-example]] .JPA native query selecting entities with many-to-one association @@ -157,29 +158,11 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-associations-query-m ---- ==== -This will allow the `Phone#person` to function properly. - -[NOTE] -==== -The additional columns will automatically be returned when using the `*` notation. -==== +This will allow the `Phone#person` to function properly since the `many-to-one` or `one-to-one` +association is going to use a proxy that will be initialized when being navigated for the first time. It is possible to eagerly join the `Phone` and the `Person` entities to avoid the possible extra roundtrip for initializing the `many-to-one` association. -[[sql-jpa-entity-associations-query-many-to-one-join-example]] -.JPA native query selecting entities with joined many-to-one association -==== -[source, JAVA, indent=0] ----- -include::{sourcedir}/SQLTest.java[tags=sql-jpa-entity-associations-query-many-to-one-join-example] ----- - -[source, SQL, indent=0] ----- -include::{extrasdir}/sql-jpa-entity-associations-query-many-to-one-join-example.sql[] ----- -==== - [[sql-hibernate-entity-associations-query-many-to-one-join-example]] .Hibernate native query selecting entities with joined many-to-one association ==== @@ -247,7 +230,7 @@ include::{extrasdir}/sql-hibernate-entity-associations-query-one-to-many-join-ex ---- ==== -At this stage you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. +At this stage, you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. Problems can arise when returning multiple entities of the same type or when the default alias/column names are not enough. [[sql-multi-entity-query]] @@ -278,7 +261,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-multi-entity-query-example] The query was intended to return all `Person` and `Partner` instances with the same name. The query fails because there is a conflict of names since the two entities are mapped to the same column names (e.g. `id`, `name`, `version`). -Also, on some databases the returned column aliases will most likely be on the form `pr.id`, `pr.name`, etc. +Also, on some databases, the returned column aliases will most likely be on the form `pr.id`, `pr.name`, etc. which are not equal to the columns specified in the mappings (`id` and `name`). The following form is not vulnerable to column name duplication: @@ -298,13 +281,13 @@ There's no such equivalent in JPA because the `Query` interface doesn't define a ==== The `{pr.*}` and `{pt.*}` notation used above is shorthand for "all properties". -Alternatively, you can list the columns explicitly, but even in this case Hibernate injects the SQL column aliases for each property. +Alternatively, you can list the columns explicitly, but even in this case, Hibernate injects the SQL column aliases for each property. The placeholder for a column alias is just the property name qualified by the table alias. [[sql-alias-references]] === Alias and property references -In most cases the above alias injection is needed. +In most cases, the above alias injection is needed. For queries relating to more complex mappings, like composite properties, inheritance discriminators, collections etc., you can use specific aliases that allow Hibernate to inject the proper aliases. The following table shows the different ways you can use the alias injection. @@ -327,9 +310,9 @@ Please note that the alias names in the result are simply examples, each alias w |A collection key |`{[aliasname].key}` |`ORGID as {coll.key}` -|The id of an collection |`{[aliasname].id}` |`EMPID as {coll.id}` +|The id of a collection |`{[aliasname].id}` |`EMPID as {coll.id}` -|The element of an collection |`{[aliasname].element}` +|The element of a collection |`{[aliasname].element}` |`XID as {coll.element}` |property of the element in the collection @@ -426,7 +409,7 @@ and the Hibernate `org.hibernate.annotations.NamedNativeQuery` annotation extend `timeout()`:: The query timeout (in seconds). By default, there's no timeout. `callable()`:: - Does the SQL query represent a call to a procedure/function? Default is false. + Does the SQL query represent a call to a procedure/function? The default is false. `comment()`:: A comment added to the SQL query for tuning the execution plan. `cacheMode()`:: @@ -536,6 +519,27 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-multiple-scalar-values-dto- ---- ==== +You can also use the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NamedNativeQuery.html[`@NamedNativeQuery`] Hibernate annotation +to customize the named query using various configurations such as fetch mode, cacheability, time out interval. + +[[sql-multiple-scalar-values-dto-NamedNativeQuery-hibernate-example]] +.Multiple scalar values using `ConstructorResult` and Hibernate `NamedNativeQuery` +==== +[source, JAVA, indent=0] +---- +include::{modeldir}/Phone.java[tags=sql-multiple-scalar-values-dto-NamedNativeQuery-hibernate-example] +---- +==== + +[[sql-hibernate-multiple-scalar-values-dto-hibernate-named-query-example]] +.Hibernate `NamedNativeQuery` named native query selecting multiple scalar values into a DTO +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/SQLTest.java[tags=sql-hibernate-multiple-scalar-values-dto-hibernate-named-query-example] +---- +==== + [[sql-entity-named-queries]] ==== Named SQL queries selecting entities @@ -666,7 +670,7 @@ Fortunately, Hibernate allows you to resolve the current global catalog and sche {h-schema}:: resolves the current `hibernate.default_schema` configuration property value. {h-domain}:: resolves the current `hibernate.default_catalog` and `hibernate.default_schema` configuration property values (e.g. catalog.schema). -Withe these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query. +With these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query. So, when running the following native query: @@ -832,16 +836,42 @@ Hibernate will iterate the results and take the first result that is a result se For SQL Server, if you can enable `SET NOCOUNT ON` in your procedure it will probably be more efficient, but this is not a requirement. ==== +[[sql-sp-named-query]] +=== Using named queries to call stored procedures + +Just like with SQL statements, you can also use named queries to call stored procedures. +For this purpose, JPA defines the http://docs.oracle.com/javaee/7/api/javax/persistence/NamedStoredProcedureQuery.html[`@NamedStoredProcedureQuery`] annotation. + +[[sql-sp-ref-cursor-oracle-named-query-example]] +.Oracle `REF_CURSOR` named query stored procedure +==== +[source, JAVA, indent=0] +---- +include::{modeldir}/Person.java[tags=sql-sp-ref-cursor-oracle-named-query-example] +---- +==== + +Calling this stored procedure is straightforward, as illustrated by the following example. + +[[sql-jpa-call-sp-ref-cursor-oracle-named-query-example]] +.Calling an Oracle `REF_CURSOR` stored procedure using a JPA named query +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/OracleStoredProcedureTest.java[tags=sql-jpa-call-sp-ref-cursor-oracle-named-query-example] +---- +==== + [[sql-crud]] -=== Custom SQL for create, update, and delete +=== Custom SQL for CRUD (Create, Read, Update and Delete) -Hibernate can use custom SQL for create, update, and delete operations. +Hibernate can use custom SQL for CRUD operations. The SQL can be overridden at the statement level or individual column level. This section describes statement overrides. For columns, see <>. The following example shows how to define custom SQL operations using annotations. -`@SQLInsert`, `@SQLUpdate` and `@SQLDelete` override the INSERT, UPDATE, DELETE statements of a given entity. +`@SQLInsert`, `@SQLUpdate`, and `@SQLDelete` override the INSERT, UPDATE, DELETE statements of a given entity. For the SELECT clause, a `@Loader` must be defined along with a `@NamedNativeQuery` used for loading the underlying table record. For collections, Hibernate allows defining a custom `@SQLDeleteAll` which is used for removing all child records associated with a given parent entity. @@ -864,7 +894,8 @@ The same is done for the `phones` collection. The `@SQLDeleteAll` and the `SQLIn [NOTE] ==== -You also call a store procedure using the custom CRUD statements; the only requirement is to set the `callable` attribute to `true`. +You can also call a store procedure using the custom CRUD statements. +The only requirement is to set the `callable` attribute to `true`. ==== To check that the execution happens correctly, Hibernate allows you to define one of those three strategies: @@ -897,7 +928,7 @@ include::{sourcedir}/CustomSQLSecondaryTableTest.java[tags=sql-custom-crud-secon [TIP] ==== The SQL is directly executed in your database, so you can use any dialect you like. -This will, however, reduce the portability of your mapping if you use database specific SQL. +This will, however, reduce the portability of your mapping if you use database-specific SQL. ==== You can also use stored procedures for customizing the CRUD statements. diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/native/extras/sql-hibernate-entity-associations-query-many-to-one-join-example.sql b/documentation/src/main/asciidoc/userguide/chapters/query/native/extras/sql-hibernate-entity-associations-query-many-to-one-join-example.sql index a38f8a1a6420..b26442c0acb9 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/native/extras/sql-hibernate-entity-associations-query-many-to-one-join-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/query/native/extras/sql-hibernate-entity-associations-query-many-to-one-join-example.sql @@ -1,5 +1,7 @@ -SELECT id , - number , - type , - person_id -FROM phone \ No newline at end of file +SELECT + * +FROM + Phone ph +JOIN + Person pr +ON ph.person_id = pr.id diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/native/extras/sql-jpa-entity-associations-query-many-to-one-join-example.sql b/documentation/src/main/asciidoc/userguide/chapters/query/native/extras/sql-jpa-entity-associations-query-many-to-one-join-example.sql deleted file mode 100644 index a38f8a1a6420..000000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/query/native/extras/sql-jpa-entity-associations-query-many-to-one-join-example.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT id , - number , - type , - person_id -FROM phone \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc index 2d830ac9c555..226c0793435a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc @@ -11,8 +11,8 @@ Since 5.0, Hibernate Spatial is now part of the Hibernate ORM project, and it allows you to deal with geographic data in a standardized way. Hibernate Spatial provides a standardized, cross-database interface to geographic data storage and query functions. -It supports most of the functions described by the OGC Simple Feature Specification. Supported databases are: Oracle 10g/11g, -PostgreSql/PostGIS, MySQL, Microsoft SQL Server and H2/GeoDB. +It supports most of the functions described by the OGC Simple Feature Specification. Supported databases are Oracle 10g/11g, +PostgreSQL/PostGIS, MySQL, Microsoft SQL Server and H2/GeoDB. Spatial data types are not part of the Java standard library, and they are absent from the JDBC specification. Over the years http://tsusiatsoftware.net/jts/main.html[JTS] has emerged the _de facto_ standard to fill this gap. JTS is @@ -86,43 +86,44 @@ relevant section. :no: icon:times[role="red"] [[spatial-configuration-dialect-features]] .Hibernate Spatial dialect function support -[cols=",,,,,," |options="header",] +[cols=",,,,,,," |options="header",] |================================ -|Function | Description | PostgresSQL | Oracle 10g/11g | MySQL | SQLServer | GeoDB (H2) -|Basic functions on Geometry | | | | | | -|`int dimension(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`String geometrytype(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`int srid(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`Geometry envelope(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`String astext(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`byte[] asbinary(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean isempty(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean issimple(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} -|`Geometry boundary(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {no} | {yes} | {yes} -|Functions for testing Spatial Relations between geometric objects | | | | | | -|`boolean equals(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean disjoint(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean intersects(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean touches(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean crosses(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean within(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean contains(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean overlaps(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} -|`boolean relate(Geometry, Geometry, String)` | SFS §2.1.1.2 | {yes} | {yes} | {no} | {yes} | {yes} -|Functions that support Spatial Analysis | | | | | | -|`double distance(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} -|`Geometry buffer(Geometry, double)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} -|`Geometry convexhull(Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} -|`Geometry intersection(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} -|`Geometry geomunion(Geometry, Geometry)` | SFS §2.1.1.3 (renamed from union) | {yes} | {yes} | {no} | {yes} | {yes} -|`Geometry difference(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} -|`Geometry symdifference(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} -|Common non-SFS functions | | | | | | -|`boolean dwithin(Geometry, Geometry, double)` | Returns true if the geometries are within the specified distance of one another | {yes} | {yes} | {no} | {no} | {yes} -|`Geometry transform(Geometry, int)` | Returns a new geometry with its coordinates transformed to the SRID referenced by the integer parameter | {yes} | {yes} | {no} | {no} | {no} -|Spatial aggregate Functions | | | | | | -|`Geometry extent(Geometry)` | Returns a bounding box that bounds the set of returned geometries | {yes} | {yes} | {no} | {no} | {no} +|Function | Description | PostgresSQL | Oracle 10g/11g | MySQL | SQLServer | GeoDB (H2) | DB2 +|Basic functions on Geometry | | | | | | | +|`int dimension(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`String geometrytype(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`int srid(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`Geometry envelope(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`String astext(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`byte[] asbinary(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean isempty(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean issimple(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`Geometry boundary(Geometry)` | SFS §2.1.1.1 | {yes} | {yes} | {no} | {yes} | {yes} | {yes} +|Functions for testing Spatial Relations between geometric objects | | | | | | | +|`boolean equals(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean disjoint(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean intersects(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean touches(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean crosses(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean within(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean contains(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean overlaps(Geometry, Geometry)` | SFS §2.1.1.2 | {yes} | {yes} | {yes} | {yes} | {yes} | {yes} +|`boolean relate(Geometry, Geometry, String)` | SFS §2.1.1.2 | {yes} | {yes} | {no} | {yes} | {yes} | {yes} +|Functions that support Spatial Analysis | | | | | | | +|`double distance(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} | {yes} +|`Geometry buffer(Geometry, double)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} | {yes} +|`Geometry convexhull(Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} | {yes}^(1)^ +|`Geometry intersection(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} | {yes}^(1)^ +|`Geometry geomunion(Geometry, Geometry)` | SFS §2.1.1.3 (renamed from union) | {yes} | {yes} | {no} | {yes} | {yes} | {yes}^(1)^ +|`Geometry difference(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} | {yes}^(1)^ +|`Geometry symdifference(Geometry, Geometry)` | SFS §2.1.1.3 | {yes} | {yes} | {no} | {yes} | {yes} | {yes}^(1)^ +|Common non-SFS functions | | | | | | | +|`boolean dwithin(Geometry, Geometry, double)` | Returns true if the geometries are within the specified distance of one another | {yes} | {yes} | {no} | {no} | {yes} | {yes} +|`Geometry transform(Geometry, int)` | Returns a new geometry with its coordinates transformed to the SRID referenced by the integer parameter | {yes} | {yes} | {no} | {no} | {no} | {no} +|Spatial aggregate Functions | | | | | | | +|`Geometry extent(Geometry)` | Returns a bounding box that bounds the set of returned geometries | {yes} | {yes} | {no} | {no} | {no} | {no} |================================ +^(1)^ Argument Geometries need to have the same dimensionality. [[spatial-configuration-dialect-postgis]] Postgis:: @@ -137,22 +138,20 @@ There are several dialects for MySQL: `MySQLSpatialDialect`::: a spatially-extended version of Hibernate `MySQLDialect` -`MySQLSpatialInnoDBDialect`::: - a spatially-extended version of Hibernate `MySQLInnoDBDialect` +`MySQL5SpatialDialect`::: + a spatially-extended version of Hibernate `MySQL5Dialect` `MySQLSpatial56Dialect`::: - a spatially-extended version of Hibernate `MySQL5DBDialect`. -`MySQLSpatial5InnoDBDialect`::: - the same as `MySQLSpatial56Dialect`, but with support for the InnoDB storage engine. + a spatially-extended version of Hibernate `MySQL55Dialect`. [NOTE] ==== MySQL versions before 5.6.1 had only limited support for spatial operators. Most operators only took account of the minimum bounding rectangles (MBR) of the geometries, and not the geometries themselves. -This changed in version 5.6.1 were MySQL introduced `ST_*` spatial operators. -The dialects `MySQLSpatial56Dialect` and `MySQLSpatial5InnoDBDialect` use these newer, more precise operators. +This changed in version 5.6.1, when MySQL introduced `ST_*` spatial operators. +The dialect `MySQLSpatial56Dialect` uses these newer, more precise operators. -These dialects may therefore produce results that differ from that of the other spatial dialects. +These dialects may, therefore, produce results that differ from that of the other spatial dialects. For more information, see this page in the MySQL reference guide (esp. the section https://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions.html[Functions That Test Spatial Relations Between Geometry Objects]) ==== @@ -162,24 +161,25 @@ Oracle10g/11g:: There is currently only one Oracle spatial dialect: `OracleSpatial10gDialect` which extends the Hibernate dialect `Oracle10gDialect`. This dialect has been tested on both Oracle 10g and Oracle 11g with the `SDO_GEOMETRY` spatial database type. + -This dialect is the only dialect that can be configured using these Hibernate properties: +This dialect can be configured using the Hibernate property: + `hibernate.spatial.connection_finder`::: -the fully-qualified classname for the Connection finder for this Dialect (see below). +the fully-qualified class name for the implementation of the `ConnectionFinder` to use (see below). .The `ConnectionFinder` interface [NOTE] ==== -The `SDOGeometryType` requires access to an `OracleConnection` object wehen converting a geometry to SDO_GEOMETRY. +The `SDOGeometryType` requires access to an `OracleConnection` object when converting a geometry to SDO_GEOMETRY. In some environments, however, the `OracleConnection` is not available (e.g. because a Java EE container or connection pool proxy wraps the connection object in its own `Connection` implementation). A `ConnectionFinder` knows how to retrieve the `OracleConnection` from the wrapper or proxy Connection object that is passed into prepared statements. -The default implementation will, when the passed object is not already an `OracleConnection`, attempt to retrieve the `OracleConnection` by recursive reflection. +When the passed object is not already an `OracleConnection`, the default implementation will attempt to retrieve the `OracleConnection` by recursive reflection. It will search for methods that return `Connection` objects, execute these methods and check the result. -If the result is of type `OracleConnection` the object is returned, otherwise it recurses on it. +If the result is of type `OracleConnection` the object is returned. +Otherwise, it recurses on it. -In may cases this strategy will suffice. -If not, you can provide your own implementation of this interface on the class path, and configure it in the `hibernate.spatial.connection_finder` property. +In may cases, this strategy will suffice. +If not, you can provide your own implementation of this interface on the classpath, and configure it in the `hibernate.spatial.connection_finder` property. Note that implementations must be thread-safe and have a default no-args constructor. ==== @@ -198,6 +198,54 @@ The `GeoDBDialect` supports the GeoDB a spatial extension of the H2 in-memory da The dialect has been tested with GeoDB version 0.7 ==== +DB2:: +The `DB2SpatialDialect` supports the spatial extensions of the DB2 LUW database. +The dialect has been tested with DB2 LUW 11.1. +The dialect does not support DB2 for z/OS or DB2 column-oriented databases. +[NOTE] +==== + +In order to use the DB2 Hibernate Spatial capabilities, it is necessary to first execute the following +SQL statements which will allow DB2 to accept Extended WellKnown Text (EWKT) data and return EWKT data. +One way to do this is to copy these statements into a file such as ewkt.sql and execute it in a DB2 command window +with a command like 'db2 -tvf ewkt.sql'. + +[source, SQL, indent=0] +---- +create or replace function db2gse.asewkt(geometry db2gse.st_geometry) +returns clob(2G) +specific db2gse.asewkt1 +language sql +deterministic +no external action +reads sql data +return 'srid=' || varchar(db2gse.st_srsid(geometry)) || ';' || db2gse.st_astext(geometry); + +create or replace function db2gse.geomfromewkt(instring varchar(32000)) +returns db2gse.st_geometry +specific db2gse.fromewkt1 +language sql +deterministic +no external action +reads sql data +return db2gse.st_geometry( +substr(instring,posstr(instring,';')+1, length(instring) - posstr(instring,';')), +integer(substr(instring,posstr(instring,'=')+1,posstr(instring,';')-(posstr(instring,'=')+1)))); + +create transform for db2gse.st_geometry ewkt ( + from sql with function db2gse.asewkt(db2gse.st_geometry), + to sql with function db2gse.geomfromewkt(varchar(32000)) ); + +drop transform db2_program for db2gse.st_geometry; +create transform for db2gse.st_geometry db2_program ( + from sql with function db2gse.asewkt(db2gse.st_geometry), + to sql with function db2gse.geomfromewkt(varchar(32000)) ); +---- + +==== + + + [[spatial-types]] === Types @@ -208,7 +256,7 @@ jts_geometry:: geolatte_geometry:: Handled by `org.hibernate.spatial.GeolatteGeometryType`, it maps a database geometry column type to an `org.geolatte.geom.Geometry` entity property type. -It suffices to declare a property as either a JTS or an Geolatte-geom `Geometry` and Hibernate Spatial will map it using the +It suffices to declare a property as either a JTS or a Geolatte-geom `Geometry` and Hibernate Spatial will map it using the relevant type. Here is an example using JTS: diff --git a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc index 2be0ce82b216..0f2bdfffb0e1 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc @@ -119,7 +119,7 @@ include::{extrasdir}/schema-generation-database-checks-persist-example.sql[] ==== [[schema-generation-column-default-value]] -=== Default value for database column +=== Default value for a database column With Hibernate, you can specify a default value for a given database column using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/ColumnDefault.html[`@ColumnDefault`] annotation. @@ -159,3 +159,64 @@ include::{sourcedir}/ColumnDefaultTest.java[tag=schema-generation-column-default include::{extrasdir}/schema-generation-column-default-value-persist-example.sql[] ---- ==== + +[[schema-generation-columns-unique-constraint]] +=== Columns unique constraint + +The http://docs.oracle.com/javaee/7/api/javax/persistence/UniqueConstraint.html[`@UniqueConstraint`] annotation is used to specify a unique constraint to be included by the automated schema generator for the primary or secondary table associated with the current annotated entity. + +Considering the following entity mapping, Hibernate generates the unique constraint DDL when creating the database schema: + +[[schema-generation-columns-unique-constraint-mapping-example]] +.`@UniqueConstraint` mapping example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/UniqueConstraintTest.java[tag=schema-generation-columns-unique-constraint-mapping-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/schema-generation-columns-unique-constraint-mapping-example.sql[] +---- +==== + +With the `uk_book_title_author` unique constraint in place, +it's no longer possible to add two books with the same title and for the same author. + +[[schema-generation-columns-unique-constraint-persist-example]] +.`@UniqueConstraintTest` persist example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/UniqueConstraintTest.java[tag=schema-generation-columns-unique-constraint-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/schema-generation-columns-unique-constraint-persist-example.sql[] +---- +==== + +The second INSERT statement fails because of the unique constraint violation. + +[[schema-generation-columns-index]] +=== Columns index + +The http://docs.oracle.com/javaee/7/api/javax/persistence/Index.html[`@Index`] annotation is used by the automated schema generation tool to create a database index. + +Considering the following entity mapping, Hibernate generates the index when creating the database schema: + +[[schema-generation-columns-index-mapping-example]] +.`@Index` mapping example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/IndexTest.java[tag=schema-generation-columns-index-mapping-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/schema-generation-columns-index-mapping-example.sql[] +---- +==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-index-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-index-mapping-example.sql new file mode 100644 index 000000000000..c84c91892823 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-index-mapping-example.sql @@ -0,0 +1,9 @@ +create table author ( + id bigint not null, + first_name varchar(255), + last_name varchar(255), + primary key (id) +) + +create index idx_author_first_last_name + on author (first_name, last_name) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-unique-constraint-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-unique-constraint-mapping-example.sql new file mode 100644 index 000000000000..904324b0f71d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-unique-constraint-mapping-example.sql @@ -0,0 +1,22 @@ +create table author ( + id bigint not null, + first_name varchar(255), + last_name varchar(255), + primary key (id) +) + +create table book ( + id bigint not null, + title varchar(255), + author_id bigint, + primary key (id) +) + +alter table book + add constraint uk_book_title_author + unique (title, author_id) + +alter table book + add constraint fk_book_author_id + foreign key (author_id) + references author \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-unique-constraint-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-unique-constraint-persist-example.sql new file mode 100644 index 000000000000..db28f62ccc5e --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/schema/extras/schema-generation-columns-unique-constraint-persist-example.sql @@ -0,0 +1,35 @@ +insert +into + author + (first_name, last_name, id) +values + (?, ?, ?) + +-- binding parameter [1] as [VARCHAR] - [Vlad] +-- binding parameter [2] as [VARCHAR] - [Mihalcea] +-- binding parameter [3] as [BIGINT] - [1] + +insert +into + book + (author_id, title, id) +values + (?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence] +-- binding parameter [3] as [BIGINT] - [2] + +insert +into + book + (author_id, title, id) +values + (?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence] +-- binding parameter [3] as [BIGINT] - [3] + +-- SQL Error: 23505, SQLState: 23505 +-- Unique index or primary key violation: "UK_BOOK_TITLE_AUTHOR_INDEX_1 ON PUBLIC.BOOK(TITLE, AUTHOR_ID) VALUES ( /* key:1 */ 3, 'High-Performance Java Persistence', 1)"; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc index 1755a27d37ec..6a7cc0090e66 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc @@ -17,10 +17,10 @@ This documentation largely treats the physical and logic notions of a transactio [[transactions-physical]] === Physical Transactions -Hibernate uses the JDBC API for persistence. In the world of Java there are two well-defined mechanism for dealing with transactions in JDBC: JDBC itself and JTA. +Hibernate uses the JDBC API for persistence. In the world of Java, there are two well-defined mechanisms for dealing with transactions in JDBC: JDBC itself and JTA. Hibernate supports both mechanisms for integrating with transactions and allowing applications to manage physical transactions. -Transaction handling per `Session` is handled by the `org.hibernate.resource.transaction.spi.TransactionCoordinator` contract, +The transaction handling per `Session` is handled by the `org.hibernate.resource.transaction.spi.TransactionCoordinator` contract, which are built by the `org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder` service. `TransactionCoordinatorBuilder` represents a strategy for dealing with transactions whereas TransactionCoordinator represents one instance of that strategy related to a Session. Which `TransactionCoordinatorBuilder` implementation to use is defined by the `hibernate.transaction.coordinator_class` setting. @@ -50,9 +50,9 @@ The Hibernate `Session` acts as a transaction-scoped cache providing repeatable [IMPORTANT] ==== To reduce lock contention in the database, the physical database transaction needs to be as short as possible. -Long database transactions prevent your application from scaling to a highly-concurrent load. +Long-running database transactions prevent your application from scaling to a highly-concurrent load. Do not hold a database transaction open during end-user-level work, but open it after the end-user-level work is finished. -This is concept is referred to as `transactional write-behind`. +This concept is referred to as `transactional write-behind`. ==== [[transactions-physical-jtaplatform]] @@ -81,6 +81,7 @@ Hibernate provides many implementations of the `JtaPlatform` contract, all with `OC4J`:: `JtaPlatform` for Oracle's OC4J container. `Orion`:: `JtaPlatform` for the Orion Application Server. `Resin`:: `JtaPlatform` for the Resin Application Server. +`SapNetWeaver`:: `JtaPlatform` for the SAP NetWeaver Application Server. `SunOne`:: `JtaPlatform` for the SunOne Application Server. `Weblogic`:: `JtaPlatform` for the Weblogic Application Server. `WebSphere`:: `JtaPlatform` for older versions of the WebSphere Application Server. @@ -98,11 +99,11 @@ To use this API, you would obtain the `org.hibernate.Transaction` from the Sessi `markRollbackOnly`:: that works in both JTA and JDBC `getTimeout` and `setTimeout`:: that again work in both JTA and JDBC `registerSynchronization`:: that allows you to register JTA Synchronizations even in non-JTA environments. -In fact in both JTA and JDBC environments, these `Synchronizations` are kept locally by Hibernate. +In fact, in both JTA and JDBC environments, these `Synchronizations` are kept locally by Hibernate. In JTA environments, Hibernate will only ever register one single `Synchronization` with the `TransactionManager` to avoid ordering problems. Additionally, it exposes a getStatus method that returns an `org.hibernate.resource.transaction.spi.TransactionStatus` enum. -This method checks with the underlying transaction system if needed, so care should be taken to minimize its use; it can have a big performance impact in certain JTA set ups. +This method checks with the underlying transaction system if needed, so care should be taken to minimize its use; it can have a big performance impact in certain JTA setups. Let's take a look at using the Transaction API in the various environments. @@ -133,7 +134,7 @@ include::{sourcedir}/TransactionsTest.java[tags=transactions-api-bmt-example] ---- ==== -In the CMT case we really could have omitted all of the Transaction calls. +In the CMT case, we really could have omitted all of the Transaction calls. But the point of the examples was to show that the Transaction API really does insulate your code from the underlying transaction mechanism. In fact, if you strip away the comments and the single configuration setting supplied at bootstrap, the code is exactly the same in all 3 examples. In other words, we could develop that code and drop it, as-is, in any of the 3 transaction environments. @@ -161,11 +162,16 @@ See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hi It defines a single method, `currentSession()`, by which the implementation is responsible for tracking the current contextual session. Out-of-the-box, Hibernate comes with three implementations of this interface: -`org.hibernate.context.internal.JTASessionContext`:: current sessions are tracked and scoped by a `JTA` transaction. -The processing here is exactly the same as in the older JTA-only approach. See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[Javadocs] for more details. -* `org.hibernate.context.internal.ThreadLocalSessionContext`:current sessions are tracked by thread of execution. See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/ThreadLocalSessionContext.html[Javadocs] for more details. -* `org.hibernate.context.internal.ManagedSessionContext`: current sessions are tracked by thread of execution. -However, you are responsible to bind and unbind a `Session` instance with static methods on this class: it does not open, flush, or close a `Session`. See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/ManagedSessionContext.html[Javadocs] for details. +`org.hibernate.context.internal.JTASessionContext`:: +current sessions are tracked and scoped by a `JTA` transaction. +The processing here is exactly the same as in the older JTA-only approach. +See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[Javadocs] for more details. +`org.hibernate.context.internal.ThreadLocalSessionContext`:: +current sessions are tracked by thread of execution. See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/ThreadLocalSessionContext.html[Javadocs] for more details. +`org.hibernate.context.internal.ManagedSessionContext`:: +current sessions are tracked by thread of execution. +However, you are responsible to bind and unbind a `Session` instance with static methods on this class; it does not open, flush, or close a `Session`. +See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/ManagedSessionContext.html[Javadocs] for details. Typically, the value of this parameter would just name the implementation class to use. For the three out-of-the-box implementations, however, there are three corresponding short names: _jta_, _thread_, and _managed_. @@ -178,18 +184,18 @@ If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you execute in an EJB container that supports CMT, transaction boundaries are defined declaratively and you do not need any transaction or session demarcation operations in your code. Refer to <> for more information and code examples. The `hibernate.current_session_context_class` configuration parameter defines which `org.hibernate.context.spi.CurrentSessionContext` implementation should be used. -For backwards compatibility, if this configuration parameter is not set but a `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform` is configured, Hibernate will use the `org.hibernate.context.internal.JTASessionContext`. +For backward compatibility, if this configuration parameter is not set but a `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform` is configured, Hibernate will use the `org.hibernate.context.internal.JTASessionContext`. === Transactional patterns (and anti-patterns) [[session-per-operation]] -=== Session-per-operation anti-pattern +==== Session-per-operation anti-pattern This is an anti-pattern of opening and closing a `Session` for each database call in a single thread. It is also an anti-pattern in terms of database transactions. Group your database calls into a planned sequence. In the same way, do not auto-commit after every SQL statement in your application. -Hibernate disables, or expects the application server to disable, auto-commit mode immediately. +Hibernate disables or expects the application server to disable, auto-commit mode immediately. Database transactions are never optional. All communication with a database must be encapsulated by a transaction. Avoid auto-commit behavior for reading data because many small transactions are unlikely to perform better than one clearly-defined unit of work, and are more difficult to maintain and extend. @@ -202,7 +208,7 @@ It is as if your application called commit after each and every JDBC call. ==== [[session-per-request]] -=== Session-per-request pattern +==== Session-per-request pattern This is the most common transaction pattern. The term request here relates to the concept of a system that reacts to a series of requests from a client/user. @@ -210,7 +216,7 @@ Web applications are a prime example of this type of system, though certainly no At the beginning of handling such a request, the application opens a Hibernate Session, starts a transaction, performs all data related work, ends the transaction and closes the Session. The crux of the pattern is the one-to-one relationship between the transaction and the Session. -Within this pattern there is a common technique of defining a current session to simplify the need of passing this `Session` around to all the application components that may need access to it. +Within this pattern, there is a common technique of defining a current session to simplify the need of passing this `Session` around to all the application components that may need access to it. Hibernate provides support for this technique through the `getCurrentSession` method of the `SessionFactory`. The concept of a _current_ session has to have a scope that defines the bounds in which the notion of _current_ is valid. This is the purpose of the `org.hibernate.context.spi.CurrentSessionContext` contract. @@ -224,7 +230,7 @@ Using this implementation, a `Session` will be opened the first time `getCurrent This is best represented with the `org.hibernate.context.internal.ManagedSessionContext` implementation of the `org.hibernate.context.spi.CurrentSessionContext` contract. Here an external component is responsible for managing the lifecycle and scoping of a _current_ session. At the start of such a scope, `ManagedSessionContext#bind()` method is called passing in the `Session`. -At the end, its `unbind()` method is called. +In the end, its `unbind()` method is called. Some common examples of such _external components_ include: ** `javax.servlet.Filter` implementation ** AOP interceptor with a pointcut on the service methods @@ -239,7 +245,7 @@ Release the underlying database cursor by calling `ScrollableResults#close()` or ==== [[long-conversations]] -=== Conversations +==== Conversations (application-level transactions) The session-per-request pattern is not the only valid way of designing units of work. Many business processes require a whole series of interactions with the user that are interleaved with database accesses. @@ -279,17 +285,17 @@ Automatic versioning is used to isolate concurrent modifications. |Extended `Session` |The Hibernate `Session` can be disconnected from the underlying JDBC connection after the database transaction has been committed and reconnected when a new client request occurs. This pattern is known as session-per-conversation and makes even reattachment unnecessary. -Automatic versioning is used to isolate concurrent modifications and the `Session` will not be allowed to flush automatically, only explicitly. +Automatic versioning is used to isolate concurrent modifications, and the `Session` will not be allowed to flush automatically, only explicitly. |======================================================================= Session-per-request-with-detached-objects and session-per-conversation each have advantages and disadvantages. [[session-per-application]] -=== Session-per-application +==== Session-per-application anti-pattern The _session-per-application_ is also considered an anti-pattern. The Hibernate `Session`, like the JPA `EntityManager`, is not a thread-safe object and it is intended to be confined to a single thread at once. -If the `Session` is shared among multiple threads, there will be race conditions as well as visibility issues , so beware of this. +If the `Session` is shared among multiple threads, there will be race conditions as well as visibility issues, so beware of this. An exception thrown by Hibernate means you have to rollback your database transaction and close the `Session` immediately. If your `Session` is bound to the application, you have to stop the application. @@ -297,6 +303,8 @@ Rolling back the database transaction does not put your business objects back in This means that the database state and the business objects will be out of sync. Usually, this is not a problem because exceptions are not recoverable and you will have to start over after rollback anyway. +For more details, check out the <> chapter. + The `Session` caches every object that is in a persistent state (watched and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too much data, it will grow endlessly until you get an `OutOfMemoryException`. One solution is to call `clear()` and `evict()` to manage the `Session` cache, but you should consider a Stored Procedure if you need mass data operations. diff --git a/documentation/src/main/asciidoc/userguide/images/architecture/JPA_Hibernate.svg b/documentation/src/main/asciidoc/userguide/images/architecture/JPA_Hibernate.svg index 318a312e1aff..b6a240a85a97 100644 --- a/documentation/src/main/asciidoc/userguide/images/architecture/JPA_Hibernate.svg +++ b/documentation/src/main/asciidoc/userguide/images/architecture/JPA_Hibernate.svg @@ -14,7 +14,7 @@ text-rendering="auto" stroke="black" stroke-linecap="square" - width="686" + width="885" stroke-miterlimit="10" shape-rendering="auto" stroke-opacity="1" @@ -22,19 +22,19 @@ stroke-dasharray="none" font-weight="normal" stroke-width="1" - height="385" + height="438" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12" stroke-dashoffset="0" image-rendering="auto" - id="svg3336" version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="JPA_Hibernate.svg"> + id="svg699" + sodipodi:docname="JPA_Hibernate.svg" + inkscape:version="0.92.1 r15371"> + id="metadata703"> @@ -54,21 +54,21 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" - inkscape:window-height="1017" - id="namedview4010" + inkscape:window-height="1001" + id="namedview701" showgrid="false" - inkscape:zoom="2.1268222" - inkscape:cx="343" - inkscape:cy="79.655586" - inkscape:window-x="-8" - inkscape:window-y="-8" + inkscape:zoom="1.0834314" + inkscape:cx="442.5" + inkscape:cy="219" + inkscape:window-x="-9" + inkscape:window-y="-9" inkscape:window-maximized="1" - inkscape:current-layer="g3592" /> + inkscape:current-layer="g697" /> + id="g697"> + id="stop3" /> + id="stop5" /> + id="stop8" /> + id="stop10" /> + id="stop13" /> + id="stop15" /> + id="stop18" /> + id="stop20" /> + id="stop23" /> + id="stop25" /> + id="stop28" /> + id="stop30" /> + id="stop33" /> + id="stop35" /> + id="stop38" /> + id="stop40" /> + id="stop43" /> + id="stop45" /> + id="stop48" /> + id="stop50" /> + + + + + + + + + id="stop63" /> + id="stop65" /> + id="stop68" /> + id="stop70" /> + + + + + + + + + + + + + + + + + d="M0 0 L885 0 L885 438 L0 438 L0 0 Z" + id="path93" /> + d="M-649 -37 L236 -37 L236 401 L-649 401 L-649 -37 Z" + id="path96" /> + d="M-514.5 -21.5 L370.5 -21.5 L370.5 416.5 L-514.5 416.5 L-514.5 -21.5 Z" + id="path99" /> + d="M0 0 L0 37 L159 37 L159 0 Z" + id="path102" /> + d="M0 0 L0 35 L157 35 L157 0 Z" + id="path105" /> + d="M0 0 L0 33 L155 33 L155 0 Z" + id="path108" /> + d="M0 0 L0 25 L147 25 L147 0 Z" + id="path111" /> + d="M-538 -141.5 L347 -141.5 L347 296.5 L-538 296.5 L-538 -141.5 Z" + id="path114" /> + d="M0 0 L0 37 L112 37 L112 0 Z" + id="path117" /> + d="M0 0 L0 35 L110 35 L110 0 Z" + id="path120" /> + d="M0 0 L0 33 L108 33 L108 0 Z" + id="path123" /> + d="M0 0 L0 25 L100 25 L100 0 Z" + id="path126" /> + d="M-20.5 -140.5 L864.5 -140.5 L864.5 297.5 L-20.5 297.5 L-20.5 -140.5 Z" + id="path129" /> + d="M0 0 L0 37 L207 37 L207 0 Z" + id="path132" /> + d="M0 0 L0 35 L205 35 L205 0 Z" + id="path135" /> + d="M0 0 L0 33 L203 33 L203 0 Z" + id="path138" /> + d="M0 0 L0 25 L195 25 L195 0 Z" + id="path141" /> + d="M-44 -20.5 L841 -20.5 L841 417.5 L-44 417.5 L-44 -20.5 Z" + id="path144" /> + d="M0 0 L0 37 L160 37 L160 0 Z" + id="path147" /> + d="M0 0 L0 35 L158 35 L158 0 Z" + id="path150" /> + d="M0 0 L0 33 L156 33 L156 0 Z" + id="path153" /> + d="M0 0 L0 25 L148 25 L148 0 Z" + id="path156" /> + d="M-269 -20.5 L616 -20.5 L616 417.5 L-269 417.5 L-269 -20.5 Z" + id="path159" /> + d="M0 0 L0 37 L190 37 L190 0 Z" + id="path162" /> + d="M0 0 L0 35 L188 35 L188 0 Z" + id="path165" /> + d="M0 0 L0 33 L186 33 L186 0 Z" + id="path168" /> + d="M0 0 L0 25 L178 25 L178 0 Z" + id="path171" /> + d="M-723 -21.5 L162 -21.5 L162 416.5 L-723 416.5 L-723 -21.5 Z" + id="path174" /> + d="M0 0 L0 37 L142 37 L142 0 Z" + id="path177" /> + d="M0 0 L0 35 L140 35 L140 0 Z" + id="path180" /> + d="M0 0 L0 33 L138 33 L138 0 Z" + id="path183" /> + d="M0 0 L0 25 L130 25 L130 0 Z" + id="path186" /> + d="M-36 -380.5 L849 -380.5 L849 57.5 L-36 57.5 L-36 -380.5 Z" + id="path189" /> + d="M0 0 L0 37 L176 37 L176 0 Z" + id="path192" /> + d="M0 0 L0 35 L174 35 L174 0 Z" + id="path195" /> + d="M0 0 L0 33 L172 33 L172 0 Z" + id="path198" /> + d="M0 0 L0 25 L164 25 L164 0 Z" + id="path201" /> + d="M-54.5 -260.5 L830.5 -260.5 L830.5 177.5 L-54.5 177.5 L-54.5 -260.5 Z" + id="path204" /> + d="M0 0 L0 37 L139 37 L139 0 Z" + id="path207" /> + d="M0 0 L0 35 L137 35 L137 0 Z" + id="path210" /> + d="M0 0 L0 33 L135 33 L135 0 Z" + id="path213" /> + d="M0 0 L0 25 L127 25 L127 0 Z" + id="path216" /> + d="M-279.5 -260.5 L605.5 -260.5 L605.5 177.5 L-279.5 177.5 L-279.5 -260.5 Z" + id="path219" /> + d="M0 0 L0 37 L169 37 L169 0 Z" + id="path222" /> + d="M0 0 L0 35 L167 35 L167 0 Z" + id="path225" /> + d="M0 0 L0 33 L165 33 L165 0 Z" + id="path228" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + d="M0 0 L0 25 L157 25 L157 0 Z" + id="path231" /> + id="g237"> + id="rect235" /> - + id="g247"> + id="rect243" /> + id="rect245" /> + id="g251"> + id="rect249" /> + id="g255"> + id="path253" /> + id="g259"> + id="rect257" /> + id="g263"> + id="rect261" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-128.5,-9.5)" + id="g271"> + + EntityManager + + + + + + + + + + + id="image283" /> TransactionImpl + id="text285">EntityManager + id="g293"> + id="rect289" /> + id="rect291" /> + id="g297"> + id="rect295" /> + id="g301"> + id="path299" /> + id="g305"> + id="rect303" /> + transform="matrix(1,0,0,1,649,37) translate(-109,106.5)" + stroke="url(#linearGradient3)" + id="g309"> + id="rect307" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-105,110.5)" + id="g317"> + + Session + + + + + + + + + + + id="image329" /> EntityTransaction + id="text331">Session + id="g339"> + id="rect335" /> + id="rect337" /> + id="g343"> + id="rect341" /> + id="g347"> + id="path345" /> + id="g351"> + id="rect349" /> + transform="matrix(1,0,0,1,649,37) translate(-626.5,105.5)" + stroke="url(#linearGradient5)" + id="g355"> + id="rect353" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-622.5,109.5)" + id="g363"> + id="image357" /> + Transaction + id="text361">EntityManagerFactory + transform="matrix(1,0,0,1,649,37) translate(-626.5,105.5)" + stroke="rgb(232,232,232)" + id="g367"> + + + + + + + + EntityManagerFactory + + + + id="rect381" /> + id="rect383" /> + id="g389"> + id="rect387" /> + id="g393"> + id="path391" /> + id="g397"> + id="rect395" /> + transform="matrix(1,0,0,1,649,37) translate(-603,-14.5)" + stroke="url(#linearGradient7)" + id="g401"> + id="rect399" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-599,-10.5)" + id="g409"> + + SessionFactory + + + + + + + + + + + id="image421" /> SessionFactory + id="text423">SessionFactory + id="g431"> + id="rect427" /> + id="rect429" /> + id="g435"> + id="rect433" /> + id="g439"> + id="path437" /> + id="g443"> + id="rect441" /> + transform="matrix(1,0,0,1,649,37) translate(-378,-14.5)" + stroke="url(#linearGradient9)" + id="g447"> + id="rect445" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-374,-10.5)" + id="g457"> + + + SessionFactoryImpl + + + + + + + + + + + + id="image471" /> EntityManager + id="text473">SessionFactoryImpl + id="g481"> + id="rect477" /> + id="rect479" /> + id="g485"> + id="rect483" /> + id="g489"> + id="path487" /> + id="g493"> + id="rect491" /> + transform="matrix(1,0,0,1,649,37) translate(76,-13.5)" + stroke="url(#linearGradient11)" + id="g497"> + id="rect495" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(80,-9.5)" + id="g507"> + id="image499" /> + + SessionImpl + + + + + + + + + + + + id="image521" /> SessionFactoryImpl + id="text523">SessionImpl + id="g531"> + id="rect527" /> + id="rect529" /> + id="g535"> + id="rect533" /> + id="g539"> + id="path537" /> + id="g543"> + id="rect541" /> + transform="matrix(1,0,0,1,649,37) translate(-611,345.5)" + stroke="url(#linearGradient13)" + id="g547"> + id="rect545" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-607,349.5)" + id="g555"> + id="image549" /> + EntityManagerFactory - - - - + id="text553">EntityTransaction + id="g559"> - - - - - - + id="rect557" /> + transform="matrix(1,0,0,1,649,37) translate(-611,345.5)" + stroke="url(#linearGradient14)" + id="g563"> + id="rect561" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-607,349.5)" + id="g571"> + + id="image567" /> EntityManagerFactoryImpl + id="text569">EntityTransaction + id="g577"> + id="rect573" /> + id="rect575" /> + id="g581"> + id="rect579" /> + id="g585"> + id="path583" /> + id="g589"> + id="rect587" /> + transform="matrix(1,0,0,1,649,37) translate(-592.5,225.5)" + stroke="url(#linearGradient15)" + id="g593"> + id="rect591" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-588.5,229.5)" + id="g601"> + + id="image597" /> SessionFactoryImplementor - - - - - - - - - - + id="text599">Transaction + id="g605"> + id="rect603" /> + transform="matrix(1,0,0,1,649,37) translate(-592.5,225.5)" + stroke="url(#linearGradient16)" + id="g609"> + id="rect607" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-588.5,229.5)" + id="g617"> + + id="image613" /> Session + id="text615">Transaction + id="g623"> + id="rect619" /> + id="rect621" /> + id="g627"> + id="rect625" /> + id="g631"> + id="path629" /> + id="g635"> + id="rect633" /> + transform="matrix(1,0,0,1,649,37) translate(-367.5,225.5)" + stroke="url(#linearGradient17)" + id="g639"> + id="rect637" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-363.5,229.5)" + id="g647"> + id="image641" /> + id="image643" /> SessionImpl - - - - - - - - - - + id="text645">TransactionImpl + id="g651"> + id="rect649" /> + transform="matrix(1,0,0,1,649,37) translate(-367.5,225.5)" + stroke="url(#linearGradient18)" + id="g655"> + id="rect653" /> + font-size="15" + transform="matrix(1,0,0,1,649,37) translate(-363.5,229.5)" + id="g663"> + + id="image659" /> EntityManagerImpl + id="text661">TransactionImpl + stroke="rgb(0,130,0)" + id="g667"> + id="path665" /> + stroke="rgb(0,130,0)" + id="g695"> - - - - - - + id="path669" /> + id="path671" /> + id="path673" /> - - - - - - - - - - - - - - - + id="path675" /> - - - - - + id="path677" /> + id="path679" /> - + id="path681" /> + id="path683" /> + id="path685" /> + id="path687" /> + id="path689" /> + id="path691" /> - - + id="path693" /> diff --git a/documentation/src/main/docbook/.gitignore b/documentation/src/main/docbook/.gitignore deleted file mode 100644 index d7d722ae157a..000000000000 --- a/documentation/src/main/docbook/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore publican output -*/tmp diff --git a/documentation/src/main/docbook/devguide-old/en-US/Author_Group.xml b/documentation/src/main/docbook/devguide-old/en-US/Author_Group.xml deleted file mode 100644 index 564da0dcb5d6..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Author_Group.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - Gavin - King - - - - - Christian - Bauer - - - - - Steve - Ebersole - - - - - Max - Rydahl - Andersen - - - - - Emmanuel - Bernard - - - - - Hardy - Ferentschik - - - - - Adam - Warski - - - - - Gail - Badner - - - - - Brett - Meyer - - - - - - James - Cobb - - - Graphic Design - - - - - Cheyenne - Weaver - - - Graphic Design - - - - - - Misty - Stanley-Jones - - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/Batch_Processing.xml b/documentation/src/main/docbook/devguide-old/en-US/Batch_Processing.xml deleted file mode 100644 index 04b5e880d980..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Batch_Processing.xml +++ /dev/null @@ -1,252 +0,0 @@ - - - - - - Batch Processing - - The following example shows an antipattern for batch inserts. - - - Naive way to insert 100000 lines with Hibernate - - - This fails with exception OutOfMemoryException after around 50000 rows on most - systems. The reason is that Hibernate caches all the newly inserted Customer instances in the session-level - cache. There are several ways to avoid this problem. - - - - Before batch processing, enable JDBC batching. To enable JDBC batching, set the property - hibernate.jdbc.batch_size to an integer between 10 and 50. - - - - Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator. - - - - If the above approach is not appropriate, you can disable the second-level cache, by setting - hibernate.cache.use_second_level_cache to false. - - -
- Batch inserts - - When you make new objects persistent, employ methods flush() and - clear() to the session regularly, to control the size of the first-level cache. - - - Flushing and clearing the <classname>Session</classname> - - -
- -
- Batch updates - - When you retriev and update data, flush() and clear() the - session regularly. In addition, use method scroll() to take advantage of server-side - cursors for queries that return many rows of data. - - - Using <methodname>scroll()</methodname> - - -
- -
- StatelessSession - - StatelessSession is a command-oriented API provided by Hibernate. Use it to stream - data to and from the database in the form of detached objects. A StatelessSession - has no persistence context associated with it and does not provide many of the higher-level life cycle - semantics. Some of the things not provided by a StatelessSession include: - - - Features and behaviors not provided by <interfacename>StatelessSession</interfacename> - - - a first-level cache - - - - - interaction with any second-level or query cache - - - - - transactional write-behind or automatic dirty checking - - - - - Limitations of <interfacename>StatelessSession</interfacename> - - - Operations performed using a stateless session never cascade to associated instances. - - - - - Collections are ignored by a stateless session. - - - - - Operations performed via a stateless session bypass Hibernate's event model and interceptors. - - - - - Due to the lack of a first-level cache, Stateless sessions are vulnerable to data aliasing effects. - - - - - A stateless session is a lower-level abstraction that is much closer to the underlying JDBC. - - - - - Using a <interfacename>StatelessSession</interfacename> - - - The Customer instances returned by the query are immediately detached. They are never - associated with any persistence context. - - - - The insert(), update(), and delete() - operations defined by the StatelessSession interface operate directly on database - rows. They cause the corresponding SQL operations to be executed immediately. They have different semantics from - the save(), saveOrUpdate(), and - delete() operations defined by the Session interface. - -
- -
- Hibernate Query Language for DML - - DML, or Data Markup Language, refers to SQL statements such as INSERT, - UPDATE, and DELETE. Hibernate provides methods for bulk SQL-style DML - statement execution, in the form of Hibernate Query Language (HQL). - -
- HQL for UPDATE and DELETE - - Psuedo-syntax for UPDATE and DELETE statements using HQL - - ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)? - - - The ? suffix indications an optional parameter. The FROM and - WHERE clauses are each optional. - - - - The FROM clause can only refer to a single entity, which can be aliased. If the entity name - is aliased, any property references must be qualified using that alias. If the entity name is not aliased, then - it is illegal for any property references to be qualified. - - - Joins, either implicit or explicit, are prohibited in a bulk HQL query. You can use sub-queries in the - WHERE clause, and the sub-queries themselves can contain joins. - - - Executing an HQL UPDATE, using the <methodname>Query.executeUpdate()</methodname> method - - - - In keeping with the EJB3 specification, HQL UPDATE statements, by default, do not effect the version or the - timestamp property values for the affected entities. You can use a versioned update to force Hibernate to reset - the version or timestamp property values, by adding the VERSIONED keyword after the - UPDATE keyword. - - - Updating the version of timestamp - - - - - If you use the VERSIONED statement, you cannot use custom version types, which use class - org.hibernate.usertype.UserVersionType. - - - - A HQL <literal>DELETE</literal> statement - - - - Method Query.executeUpdate() returns an int value, which indicates the - number of entities effected by the operation. This may or may not correlate to the number of rows effected in - the database. An HQL bulk operation might result in multiple SQL statements being executed, such as for - joined-subclass. In the example of joined-subclass, a DELETE against one of the subclasses - may actually result in deletes in the tables underlying the join, or further down the inheritance hierarchy. - -
- -
- HQL syntax for INSERT - - Pseudo-syntax for INSERT statements - - INSERT INTO EntityName properties_list select_statement - - - - Only the INSERT INTO ... SELECT ... form is supported. You cannot specify explicit values to - insert. - - - The properties_list is analogous to the column specification in the SQL - INSERT statement. For entities involved in mapped inheritance, you can only use properties directly - defined on that given class-level in the properties_list. Superclass properties are - not allowed and subclass properties are irrelevant. In other words, INSERT statements are - inherently non-polymorphic. - - - The select_statement can be any valid HQL select query, but the return types must - match the types expected by the INSERT. Hibernate verifies the return types during query compilation, instead of - expecting the database to check it. Problems might result from Hibernate types which are equivalent, rather than - equal. One such example is a mismatch between a property defined as an org.hibernate.type.DateType - and a property defined as an org.hibernate.type.TimestampType, even though the database may not - make a distinction, or may be capable of handling the conversion. - - - If id property is not specified in the properties_list, - Hibernate generates a value automatically. Automatic generation is only available if you use ID generators which - operate on the database. Otherwise, Hibernate throws an exception during parsing. Available in-database - generators are org.hibernate.id.SequenceGenerator and its subclasses, and objects which - implement org.hibernate.id.PostInsertIdentifierGenerator. The most notable - exception is org.hibernate.id.TableHiLoGenerator, which does not expose a selectable way - to get its values. - - - For properties mapped as either version or timestamp, the insert statement gives you two options. You can either - specify the property in the properties_list, in which case its value is taken from the corresponding select - expressions, or omit it from the properties_list, in which case the seed value defined by the - org.hibernate.type.VersionType is used. - - - HQL INSERT statement - - -
-
- More information on HQL - - This section is only a brief overview of HQL. For more information, see . - -
-
-
- diff --git a/documentation/src/main/docbook/devguide-old/en-US/Caching.xml b/documentation/src/main/docbook/devguide-old/en-US/Caching.xml deleted file mode 100644 index 99aec1183ed1..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Caching.xml +++ /dev/null @@ -1,557 +0,0 @@ - - - - - - - Caching - - -
- The query cache - - If you have queries that run over and over, with the same parameters, query caching provides performance gains. - - - Caching introduces overhead in the area of transactional processing. For example, if you cache results of a query - against an object, Hibernate needs to keep track of whether any changes have been committed against the object, - and invalidate the cache accordingly. In addition, the benefit from caching query results is limited, and highly - dependent on the usage patterns of your application. For these reasons, Hibernate disables the query cache by - default. - - - Enabling the query cache - - Set the <property>hibernate.cache.use_query_cache</property> property to <literal>true</literal>. - - This setting creates two new cache regions: - - - - - org.hibernate.cache.internal.StandardQueryCache holds the cached query results. - - - - - org.hibernate.cache.spi.UpdateTimestampsCache holds timestamps of the most recent updates to - queryable tables. These timestamps validate results served from the query cache. - - - - - - Adjust the cache timeout of the underlying cache region - - If you configure your underlying cache implementation to use expiry or timeouts, set the cache timeout of the - underlying cache region for the UpdateTimestampsCache to a higher value than the timeouts of any - of the query caches. It is possible, and recommended, to set the UpdateTimestampsCache region never to - expire. To be specific, a LRU (Least Recently Used) cache expiry policy is never appropriate. - - - - Enable results caching for specific queries - - Since most queries do not benefit from caching of their results, you need to enable caching for individual - queries, e ven after enabling query caching overall. To enable results caching for a particular query, call - org.hibernate.Query.setCacheable(true). This call allows the query to look for - existing cache results or add its results to the cache when it is executed. - - - - - The query cache does not cache the state of the actual entities in the cache. It caches identifier values and - results of value type. Therefore, always use the query cache in conjunction with the second-level - cache for those entities which should be cached as part of a query result cache. - - -
- Query cache regions - - For fine-grained control over query cache expiration policies, specify a named cache region for a particular - query by calling Query.setCacheRegion(). - - - - Method <methodname>setCacheRegion</methodname> - - - - - To force the query cache to refresh one of its regions and disregard any cached results in the region, call - org.hibernate.Query.setCacheMode(CacheMode.REFRESH). In conjunction with the region defined for the - given query, Hibernate selectively refreshes the results cached in that particular region. This is much more - efficient than bulk eviction of the region via org.hibernate.SessionFactory.evictQueries(). - - -
- -
- -
- Second-level cache providers - - Hibernate is compatible with several second-level cache providers. None of the providers support all of - Hibernate's possible caching strategies. lists the providers, along with - their interfaces and supported caching strategies. For definitions of caching strategies, see . - - -
- Configuring your cache providers - - You can configure your cache providers using either annotations or mapping files. - - - Entities - - By default, entities are not part of the second-level cache, and their use is not recommended. If you - absolutely must use entities, set the shared-cache-mode element in - persistence.xml, or use property javax.persistence.sharedCache.mode - in your configuration. Use one of the values in . - - - - Possible values for Shared Cache Mode - - - - Value - Description - - - - - ENABLE_SELECTIVE - - - Entities are not cached unless you explicitly mark them as cachable. This is the default and - recommended value. - - - - - DISABLE_SELECTIVE - - - Entities are cached unless you explicitly mark them as not cacheable. - - - - - ALL - - - All entities are always cached even if you mark them as not cacheable. - - - - - NONE - - - No entities are cached even if you mark them as cacheable. This option basically disables second-level - caching. - - - - - -
- - Set the global default cache concurrency strategy The cache concurrency strategy with the - hibernate.cache.default_cache_concurrency_strategy configuration property. See for possible values. - - - - When possible, define the cache concurrency strategy per entity rather than globally. Use the - @org.hibernate.annotations.Cache annotation. - - - - Configuring cache providers using annotations - - - You can cache the content of a collection or the identifiers, if the collection contains other entities. Use - the @Cache annotation on the Collection property. - - - @Cache can take several attributes. - - - Attributes of <code>@Cache</code> annotation - - usage - - - The given cache concurrency strategy, which may be: - - - - - NONE - - - - - READ_ONLY - - - - - NONSTRICT_READ_WRITE - - - - - READ_WRITE - - - - - TRANSACTIONAL - - - - - - - region - - - The cache region. This attribute is optional, and defaults to the fully-qualified class name of the - class, or the qually-qualified role name of the collection. - - - - - include - - - Whether or not to include all properties.. Optional, and can take one of two possible values. - - - - - A value of all includes all properties. This is the default. - - - - - A value of non-lazy only includes non-lazy properties. - - - - - - - - - - Configuring cache providers using mapping files - - - Just as in the , you can provide attributes in the - mapping file. There are some specific differences in the syntax for the attributes in a mapping file. - - - - usage - - - The caching strategy. This attribute is required, and can be any of the following values. - - - transactional - read-write - nonstrict-read-write - read-only - - - - - region - - - The name of the second-level cache region. This optional attribute defaults to the class or collection - role name. - - - - - include - - - Whether properties of the entity mapped with lazy=true can be cached when - attribute-level lazy fetching is enabled. Defaults to all and can also be - non-lazy. - - - - - - Instead of <cache>, you can use <class-cache> and - <collection-cache> elements in hibernate.cfg.xml. - - -
-
- Caching strategies - - - read-only - - - A read-only cache is good for data that needs to be read often but not modified. It is simple, performs - well, and is safe to use in a clustered environment. - - - - - nonstrict-read-write - - - Some applications only rarely need to modify data. This is the case if two transactions are unlikely to - try to update the same item simultaneously. In this case, you do not need strict transaction isolation, - and a nonstrict-read-write cache might be appropriate. If the cache is used in a JTA environment, you must - specify hibernate.transaction.manager_lookup_class. In other environments, ensore - that the transaction is complete before you call Session.close() or - Session.disconnect(). - - - - - read-write - - - A read-write cache is appropriate for an application which needs to update data regularly. Do not use a - read-write strategy if you need serializable transaction isolation. In a JTA environment, specify a - strategy for obtaining the JTA TransactionManager by setting the property - hibernate.transaction.manager_lookup_class. In non-JTA environments, be sure the - transaction is complete before you call Session.close() or - Session.disconnect(). - - - - To use the read-write strategy in a clustered environment, the underlying cache implementation must - support locking. The build-in cache providers do not support locking. - - - - - - transactional - - - The transactional cache strategy provides support for transactional cache providers such as JBoss - TreeCache. You can only use such a cache in a JTA environment, and you must first specify - hibernate.transaction.manager_lookup_class. - - - - -
-
- Second-level cache providers for Hibernate - - - - - Cache - Interface - Supported strategies - - - - - HashTable (testing only) - - - - read-only - nonstrict-read-write - read-write - - - - - EHCache - - - - read-only - nonstrict-read-write - read-write - transactional - - - - - Infinispan - - - - read-only - transactional - - - - - - -
-
- -
- Managing the cache - -
- Moving items into and out of the cache - - Actions that add an item to internal cache of the Session - - Saving or updating an item - - - - - save() - - - - - update() - - - - - saveOrUpdate() - - - - - - - Retrieving an item - - - - - load() - - - - - get() - - - - - list() - - - - - iterate() - - - - - scroll() - - - - - - - - Syncing or removing a cached item - - The state of an object is synchronized with the database when you call method - flush(). To avoid this synchronization, you can remove the object and all collections - from the first-level cache with the evict() method. To remove all items from the - Session cache, use method Session.clear(). - - - - Evicting an item from the first-level cache - - - - Determining whether an item belongs to the Session cache - - The Session provides a contains() method to determine if an instance belongs to the - session cache. - - - - - Second-level cache eviction - - You can evict the cached state of an instance, entire class, collection instance or entire collection role, - using methods of SessionFactory. - - - -
- Interactions between a Session and the second-level cache - - The CacheMode controls how a particular session interacts with the second-level cache. - - - - - - CacheMode.NORMAL - reads items from and writes them to the second-level cache. - - - CacheMode.GET - reads items from the second-level cache, but does not write to the second-level cache except to - update data. - - - CacheMode.PUT - writes items to the second-level cache. It does not read from the second-level cache. It bypasses - the effect of hibernate.cache.use_minimal_puts and forces a refresh of the - second-level cache for all items read from the database. - - - - -
- -
- Browsing the contents of a second-level or query cache region - - After enabling statistics, you can browse the contents of a second-level cache or query cache region. - - - Enabling Statistics - - - Set hibernate.generate_statistics to true. - - - - - Optionally, set hibernate.cache.use_structured_entries to true, to cause - Hibernate to store the cache entries in a human-readable format. - - - - - Browsing the second-level cache entries via the Statistics API - - -
-
-
-
diff --git a/documentation/src/main/docbook/devguide-old/en-US/Data_Categorizations.xml b/documentation/src/main/docbook/devguide-old/en-US/Data_Categorizations.xml deleted file mode 100644 index 198b6c7706df..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Data_Categorizations.xml +++ /dev/null @@ -1,458 +0,0 @@ - - - - - - Data categorizations - - Hibernate understands both the Java and JDBC representations of application data. The ability to read and write - object data to a database is called marshalling, and is the function of a Hibernate - type. A type is an implementation of the - org.hibernate.type.Type interface. A Hibernate type describes - various aspects of behavior of the Java type such as how to check for equality and how to clone values. - - - Usage of the word <wordasword>type</wordasword> - - A Hibernate type is neither a Java type nor a SQL datatype. It provides information about - both of these. - - - When you encounter the term type in regards to Hibernate, it may refer to the Java type, - the JDBC type, or the Hibernate type, depending on context. - - - - Hibernate categorizes types into two high-level groups: - and . - - -
- Value types - - A value type does not define its own lifecycle. It is, in effect, owned by an , which defines its - lifecycle. Value types are further classified into three sub-categories. - - - - - - - - -
- Basic types - - Basic value types usually map a single database value, or column, to a single, non-aggregated Java - type. Hibernate provides a number of built-in basic types, which follow the natural mappings recommended in the - JDBC specifications. You can override these mappings and provide and use alternative mappings. These topics are - discussed further on. - - - Basic Type Mappings - - - - Hibernate type - Database type - JDBC type - Type registry - - - - - org.hibernate.type.StringType - string - VARCHAR - string, java.lang.String - - - org.hibernate.type.MaterializedClob - string - CLOB - materialized_clob - - - org.hibernate.type.TextType - string - LONGVARCHAR - text - - - org.hibernate.type.CharacterType - char, java.lang.Character - CHAR - char, java.lang.Character - - - org.hibernate.type.BooleanType - boolean - BIT - boolean, java.lang.Boolean - - - org.hibernate.type.NumericBooleanType - boolean - INTEGER, 0 is false, 1 is true - numeric_boolean - - - org.hibernate.type.YesNoType - boolean - CHAR, 'N'/'n' is false, 'Y'/'y' is true. The uppercase value is written to the database. - yes_no - - - org.hibernate.type.TrueFalseType - boolean - CHAR, 'F'/'f' is false, 'T'/'t' is true. The uppercase value is written to the database. - true_false - - - org.hibernate.type.ByteType - byte, java.lang.Byte - TINYINT - byte, java.lang.Byte - - - org.hibernate.type.ShortType - short, java.lang.Short - SMALLINT - short, java.lang.Short - - - org.hibernate.type.IntegerTypes - int, java.lang.Integer - INTEGER - int, java.lang.Integer - - - org.hibernate.type.LongType - long, java.lang.Long - BIGINT - long, java.lang.Long - - - org.hibernate.type.FloatType - float, java.lang.Float - FLOAT - float, java.lang.Float - - - org.hibernate.type.DoubleType - double, java.lang.Double - DOUBLE - double, java.lang.Double - - - org.hibernate.type.BigIntegerType - java.math.BigInteger - NUMERIC - big_integer - - - org.hibernate.type.BigDecimalType - java.math.BigDecimal - NUMERIC - big_decimal, java.math.bigDecimal - - - org.hibernate.type.TimestampType - java.sql.Timestamp - TIMESTAMP - timestamp, java.sql.Timestamp - - - org.hibernate.type.TimeType - java.sql.Time - TIME - time, java.sql.Time - - - org.hibernate.type.DateType - java.sql.Date - DATE - date, java.sql.Date - - - org.hibernate.type.CalendarType - java.util.Calendar - TIMESTAMP - calendar, java.util.Calendar - - - org.hibernate.type.CalendarDateType - java.util.Calendar - DATE - calendar_date - - - org.hibernate.type.CurrencyType - java.util.Currency - VARCHAR - currency, java.util.Currency - - - org.hibernate.type.LocaleType - java.util.Locale - VARCHAR - locale, java.utility.locale - - - org.hibernate.type.TimeZoneType - java.util.TimeZone - VARCHAR, using the TimeZone ID - timezone, java.util.TimeZone - - - org.hibernate.type.UrlType - java.net.URL - VARCHAR - url, java.net.URL - - - org.hibernate.type.ClassType - java.lang.Class - VARCHAR, using the class name - class, java.lang.Class - - - org.hibernate.type.BlobType - java.sql.Blob - BLOB - blog, java.sql.Blob - - - org.hibernate.type.ClobType - java.sql.Clob - CLOB - clob, java.sql.Clob - - - org.hibernate.type.BinaryType - primitive byte[] - VARBINARY - binary, byte[] - - - org.hibernate.type.MaterializedBlobType - primitive byte[] - BLOB - materized_blob - - - org.hibernate.type.ImageType - primitive byte[] - LONGVARBINARY - image - - - org.hibernate.type.BinaryType - java.lang.Byte[] - VARBINARY - wrapper-binary - - - org.hibernate.type.CharArrayType - char[] - VARCHAR - characters, char[] - - - org.hibernate.type.CharacterArrayType - java.lang.Character[] - VARCHAR - wrapper-characters, Character[], java.lang.Character[] - - - org.hibernate.type.UUIDBinaryType - java.util.UUID - BINARY - uuid-binary, java.util.UUID - - - org.hibernate.type.UUIDCharType - java.util.UUID - CHAR, can also read VARCHAR - uuid-char - - - org.hibernate.type.PostgresUUIDType - java.util.UUID - PostgreSQL UUID, through Types#OTHER, which complies to the PostgreSQL JDBC driver - definition - pg-uuid - - - org.hibernate.type.SerializableType - implementors of java.lang.Serializable - VARBINARY - Unlike the other value types, multiple instances of this type are registered. It is registered - once under java.io.Serializable, and registered under the specific java.io.Serializable implementation - class names. - - - -
-
-
- National Character Types - - National Character types, which is a new feature since JDBC 4.0 API, now available in hibernate type system. - National Language Support enables you retrieve data or insert data into a database in any character - set that the underlying database supports. - - - - Depending on your environment, you might want to set the configuration option hibernate.use_nationalized_character_data - to true and having all string or clob based attributes having this national character support automatically. - There is nothing else to be changed, and you don't have to use any hibernate specific mapping, so it is portable - ( though the national character support feature is not required and may not work on other JPA provider impl ). - - - - The other way of using this feature is having the @Nationalized annotation on the attribute - that should be nationalized. This only works on string based attributes, including string, char, char array and clob. - - - @Entity( name="NationalizedEntity") - public static class NationalizedEntity { - @Id - private Integer id; - - @Nationalized - private String nvarcharAtt; - - @Lob - @Nationalized - private String materializedNclobAtt; - - @Lob - @Nationalized - private NClob nclobAtt; - - @Nationalized - private Character ncharacterAtt; - - @Nationalized - private Character[] ncharArrAtt; - - @Type(type = "ntext") - private String nlongvarcharcharAtt; - } - - - - National Character Type Mappings - - - - Hibernate type - Database type - JDBC type - Type registry - - - - - org.hibernate.type.StringNVarcharType - string - NVARCHAR - nstring - - - org.hibernate.type.NTextType - string - LONGNVARCHAR - materialized_clob - - - org.hibernate.type.NClobType - java.sql.NClob - NCLOB - nclob - - - org.hibernate.type.MaterializedNClobType - string - NCLOB - materialized_nclob - - - org.hibernate.type.PrimitiveCharacterArrayNClobType - char[] - NCHAR - char[] - - - org.hibernate.type.CharacterNCharType - java.lang.Character - NCHAR - ncharacter - - - org.hibernate.type.CharacterArrayNClobType - java.lang.Character[] - NCLOB - Character[], java.lang.Character[] - - - -
-
-
- Composite types - - Composite types, or embedded types, as they are called by the Java - Persistence API, have traditionally been called components in Hibernate. All of these - terms mean the same thing. - - - Components represent aggregations of values into a single Java type. An example is an - Address class, which aggregates street, city, state, and postal code. A composite type - behaves in a similar way to an entity. They are each classes written specifically for an application. They may - both include references to other application-specific classes, as well as to collections and simple JDK - types. The only distinguishing factors are that a component does not have its own lifecycle or define an - identifier. - - -
- -
- Collection types - - A collection type refers to the data type itself, not its contents. - - - A Collection denotes a one-to-one or one-to-many relationship between tables of a database. - - - Refer to the chapter on Collections for more information on collections. - -
-
-
- Entity Types - - Entities are application-specific classes which correlate to rows in a table, using a unique identifier. Because - of the requirement for a unique identifier, ntities exist independently and define their own lifecycle. As an - example, deleting a Membership should not delete the User or the Group. For more information, see the chapter on - Persistent Classes. - -
- -
- Implications of different data categorizations - - NEEDS TO BE WRITTEN - - -
- -
diff --git a/documentation/src/main/docbook/devguide-old/en-US/Database_Access.xml b/documentation/src/main/docbook/devguide-old/en-US/Database_Access.xml deleted file mode 100644 index 976f6b26d37a..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Database_Access.xml +++ /dev/null @@ -1,916 +0,0 @@ - - - - - - Database access - -
- Connecting - - Hibernate connects to databases on behalf of your application. It can connect through a variety of mechanisms, - including: - - - Stand-alone built-in connection pool - javax.sql.DataSource - Connection pools, including support for two different third-party opensource JDBC connection pools: - - c3p0 - proxool - - - - Application-supplied JDBC connections. This is not a recommended approach and exists for legacy reasons - - - - - The built-in connection pool is not intended for production environments. - - - - Hibernate obtains JDBC connections as needed though the - ConnectionProvider interface - which is a service contract. Applications may also supply their own - ConnectionProvider implementation - to define a custom approach for supplying connections to Hibernate (from a different connection pool - implementation, for example). - - - - -
- Configuration - - You can configure database connections using a properties file, an XML deployment descriptor or - programmatically. - - - <filename>hibernate.properties</filename> for a c3p0 connection pool - - - - <filename>hibernate.cfg.xml</filename> for a connection to the bundled HSQL database - - - -
- Programatic configuration - - An instance of object org.hibernate.cfg.Configuration represents an entire set of - mappings of an application's Java types to an SQL database. The - org.hibernate.cfg.Configuration builds an immutable - org.hibernate.SessionFactory, and compiles the mappings from various XML mapping - files. You can specify the mapping files directly, or Hibernate can find them for you. - - - Specifying the mapping files directly - - You can obtain a org.hibernate.cfg.Configuration instance by instantiating it - directly and specifying XML mapping documents. If the mapping files are in the classpath, use method - addResource(). - - - - - Letting Hibernate find the mapping files for you - - The addClass() method directs Hibernate to search the CLASSPATH for the mapping - files, eliminating hard-coded file names. In the following example, it searches for - org/hibernate/auction/Item.hbm.xml and - org/hibernate/auction/Bid.hbm.xml. - - - - - Specifying configuration properties - - - - Other ways to configure Hibernate programmatically - - - Pass an instance of java.util.Properties to - Configuration.setProperties(). - - - - - Set System properties using java - -Dproperty=value - - - -
-
- -
- Obtaining a JDBC connection - - After you configure the , you can use method - openSession of class org.hibernate.SessionFactory to open - sessions. Sessions will obtain JDBC connections as needed based on the provided configuration. - - - Specifying configuration properties - - - - Most important Hibernate JDBC properties - hibernate.connection.driver_class - hibernate.connection.url - hibernate.connection.username - hibernate.connection.password - hibernate.connection.pool_size - - - All available Hibernate settings are defined as constants and discussed on the - org.hibernate.cfg.AvailableSettings interface. See its source code or - JavaDoc for details. - -
-
- -
- Connection pooling - - Hibernate's internal connection pooling algorithm is rudimentary, and is provided for development and testing - purposes. Use a third-party pool for best performance and stability. To use a third-party pool, replace the - hibernate.connection.pool_size property with settings specific to your connection pool of - choice. This disables Hibernate's internal connection pool. - - -
- c3p0 connection pool - - C3P0 is an open source JDBC connection pool distributed along with Hibernate in the lib/ - directory. Hibernate uses its org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider for - connection pooling if you set the hibernate.c3p0.* properties. properties. - - - Important configuration properties for the c3p0 connection pool - hibernate.c3p0.min_size - hibernate.c3p0.max_size - hibernate.c3p0.timeout - hibernate.c3p0.max_statements - -
- -
- Proxool connection pool - - Proxool is another open source JDBC connection pool distributed along with Hibernate in the - lib/ directory. Hibernate uses its - org.hibernate.service.jdbc.connections.internal.ProxoolConnectionProvider for connection pooling if you set the - hibernate.proxool.* properties. Unlike c3p0, proxool requires some additional configuration - parameters, as described by the Proxool documentation available at . - - - Important configuration properties for the Proxool connection pool - - - - - - Property - Description - - - - - hibernate.proxool.xml - Configure Proxool provider using an XML file (.xml is appended automatically) - - - hibernate.proxool.properties - Configure the Proxool provider using a properties file (.properties is appended - automatically) - - - hibernate.proxool.existing_pool - Whether to configure the Proxool provider from an existing pool - - - hibernate.proxool.pool_alias - Proxool pool alias to use. Required. - - - -
-
- - -
- Obtaining connections from an application server, using JNDI - - To use Hibernate inside an application server, configure Hibernate to obtain connections from an application - server javax.sql.Datasource registered in JNDI, by setting at least one of the following - properties: - - - Important Hibernate properties for JNDI datasources - hibernate.connection.datasource (required) - hibernate.jndi.url - hibernate.jndi.class - hibernate.connection.username - hibernate.connection.password - - - JDBC connections obtained from a JNDI datasource automatically participate in the container-managed transactions - of the application server. - -
- -
- Other connection-specific configuration - - You can pass arbitrary connection properties by prepending hibernate.connection to the - connection property name. For example, specify a charSet connection property as - hibernate.connection.charSet. - - - You can define your own plugin strategy for obtaining JDBC connections by implementing the interface - ConnectionProvider and specifying your custom - implementation with the hibernate.connection.provider_class property. - -
- -
- Optional configuration properties - - In addition to the properties mentioned in the previous sections, Hibernate includes many other optional - properties. See for a more complete list. - -
-
- -
- Dialects - - Although SQL is relatively standardized, each database vendor uses a subset of supported syntax. This is referred - to as a dialect. Hibernate handles variations across these dialects through its - org.hibernate.dialect.Dialect class and the various subclasses for each vendor dialect. - - - - Supported database dialects - - - - - - Database - Dialect - - - - - CUBRID 8.3 and later - - - org.hibernate.dialect.CUBRIDDialect - - - - - DB2 - - - org.hibernate.dialect.DB2Dialect - - - - - DB2 AS/400 - - - org.hibernate.dialect.DB2400Dialect - - - - - DB2 OS390 - - - org.hibernate.dialect.DB2390Dialect - - - - - Firebird - - - org.hibernate.dialect.FirebirdDialect - - - - - FrontBase - - - org.hibernate.dialect.FrontbaseDialect - - - - - H2 - - - org.hibernate.dialect.H2Dialect - - - - - HyperSQL (HSQL) - - - org.hibernate.dialect.HSQLDialect - - - - - Informix - - - org.hibernate.dialect.InformixDialect - - - - - Ingres - - - org.hibernate.dialect.IngresDialect - - - - - Ingres 9 - - - org.hibernate.dialect.Ingres9Dialect - - - - - Ingres 10 - - - org.hibernate.dialect.Ingres10Dialect - - - - - Interbase - - - org.hibernate.dialect.InterbaseDialect - - - - - InterSystems Cache 2007.1 - - - org.hibernate.dialect.Cache71Dialect - - - - - JDataStore - - - org.hibernate.dialect.JDataStoreDialect - - - - - Mckoi SQL - - - org.hibernate.dialect.MckoiDialect - - - - - Microsoft SQL Server 2000 - - - org.hibernate.dialect.SQLServerDialect - - - - - Microsoft SQL Server 2005 - - - org.hibernate.dialect.SQLServer2005Dialect - - - - - Microsoft SQL Server 2008 - - - org.hibernate.dialect.SQLServer2008Dialect - - - - - Microsoft SQL Server 2012 - - - org.hibernate.dialect.SQLServer2012Dialect - - - - - Mimer SQL - - - org.hibernate.dialect.MimerSQLDialect - - - - - MySQL - - - org.hibernate.dialect.MySQLDialect - - - - - MySQL with InnoDB - - - org.hibernate.dialect.MySQLInnoDBDialect - - - - - MySQL with MyISAM - - - org.hibernate.dialect.MySQLMyISAMDialect - - - - - MySQL5 - - - org.hibernate.dialect.MySQL5Dialect - - - - - MySQL5 with InnoDB - - - org.hibernate.dialect.MySQL5InnoDBDialect - - - - - Oracle 8i - - - org.hibernate.dialect.Oracle8iDialect - - - - - Oracle 9i - - - org.hibernate.dialect.Oracle9iDialect - - - - - Oracle 10g and later - - - org.hibernate.dialect.Oracle10gDialect - - - - - Oracle TimesTen - - - org.hibernate.dialect.TimesTenDialect - - - - - Pointbase - - - org.hibernate.dialect.PointbaseDialect - - - - - PostgreSQL 8.1 - - - org.hibernate.dialect.PostgreSQL81Dialect - - - - - PostgreSQL 8.2 - - - org.hibernate.dialect.PostgreSQL82Dialect - - - - - PostgreSQL 9 and later - - - org.hibernate.dialect.PostgreSQL9Dialect - - - - - Progress - - - org.hibernate.dialect.ProgressDialect - - - - - SAP DB - - - org.hibernate.dialect.SAPDBDialect - - - - - SAP HANA (column store) - - - org.hibernate.dialect.HANAColumnStoreDialect - - - - - SAP HANA (row store) - - - org.hibernate.dialect.HANARowStoreDialect - - - - - Sybase - - - org.hibernate.dialect.SybaseDialect - - - - - Sybase 11 - - - org.hibernate.dialect.Sybase11Dialect - - - - - Sybase ASE 15.5 - - - org.hibernate.dialect.SybaseASE15Dialect - - - - - Sybase ASE 15.7 - - - org.hibernate.dialect.SybaseASE157Dialect - - - - - Sybase Anywhere - - - org.hibernate.dialect.SybaseAnywhereDialect - - - - - Teradata - - - org.hibernate.dialect.TeradataDialect - - - - - Unisys OS 2200 RDMS - - - org.hibernate.dialect.RDMSOS2200Dialect - - - - -
- -
- Specifying the Dialect to use - - The developer may manually specify the Dialect to use by setting the - hibernate.dialect configuration property to the name of a specific - org.hibernate.dialect.Dialect class to use. - -
- -
- Dialect resolution - - Assuming a ConnectionProvider has been - set up, Hibernate will attempt to automatically determine the Dialect to use based on the - java.sql.DatabaseMetaData reported by a - java.sql.Connection obtained from that - ConnectionProvider. - - - This functionality is provided by a series of - org.hibernate.engine.jdbc.dialect.spi.DialectResolver instances registered - with Hibernate internally. Hibernate comes with a standard set of recognitions. If your application - requires extra Dialect resolution capabilities, it would simply register a custom implementation - of org.hibernate.engine.jdbc.dialect.spi.DialectResolver as follows: - - - - Registered org.hibernate.engine.jdbc.dialect.spi.DialectResolver are - prepended to an internal list of resolvers, so they take precedence - before any already registered resolvers including the standard one. - -
-
- -
- Automatic schema generation with SchemaExport - - SchemaExport is a Hibernate utility which generates DDL from your mapping files. The generated schema includes - referential integrity constraints, primary and foreign keys, for entity and collection tables. It also creates - tables and sequences for mapped identifier generators. - - - - You must specify a SQL Dialect via the hibernate.dialect property when using this tool, - because DDL is highly vendor-specific. See for information. - - - - Before Hibernate can generate your schema, you must customize your mapping files. - - -
- Customizing the mapping files - - Hibernate provides several elements and attributes to customize your mapping files. They are listed in , and a logical order of customization is presented in . - - - Elements and attributes provided for customizing mapping files - - - - - - - Name - Type of value - Description - - - - - length - number - Column length - - - precision - number - Decimal precision of column - - - scale - number - Decimal scale of column - - - not-null - true or false - Whether a column is allowed to hold null values - - - unique - true or false - Whether values in the column must be unique - - - index - string - The name of a multi-column index - - - unique-key - string - The name of a multi-column unique constraint - - - foreign-key - string - The name of the foreign key constraint generated for an association. This applies to - <one-to-one>, <many-to-one>, <key>, and <many-to-many> mapping - elements. inverse="true" sides are skipped by SchemaExport. - - - sql-type - string - Overrides the default column type. This applies to the <column> element only. - - - default - string - Default value for the column - - - check - string - An SQL check constraint on either a column or atable - - - -
- - Customizing the schema - - Set the length, precision, and scale of mapping elements. - - Many Hibernate mapping elements define optional attributes named , - , and . - - - - - Set the <option>not-null</option>, <option>UNIQUE</option>, <option>unique-key</option> attributes. - - The and attributes generate constraints on table columns. - - - The unique-key attribute groups columns in a single, unique key constraint. The attribute overrides - the name of any generated unique key constraint. - - - - - Set the <option>index</option> and <option>foreign-key</option> attributes. - - The attribute specifies the name of an index for Hibernate to create using the mapped - column or columns. You can group multiple columns into the same index by assigning them the same index name. - - - A foreign-key attribute overrides the name of any generated foreign key constraint. - - - - - Set child <option><column></option> elements. - - Many mapping elements accept one or more child <column> elements. This is particularly useful for - mapping types involving multiple columns. - - - - - Set the <option>default</option> attribute. - - The attribute represents a default value for a column. Assign the same value to the - mapped property before saving a new instance of the mapped class. - - - - - Set the <option>sql-type</option> attribure. - - Use the attribute to override the default mapping of a Hibernate type to SQL - datatype. - - - - - Set the <option>check</option> attribute. - - use the attribute to specify a check constraint. - - - - - Add <comment> elements to your schema. - - Use the <comment> element to specify comments for the generated schema. - - - - -
- -
- Running the SchemaExport tool - - The SchemaExport tool writes a DDL script to standard output, executes the DDL statements, or both. - - - SchemaExport syntax - - java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaExport options mapping_files - - - - SchemaExport Options - - - - - - Option - Description - - - - - --quiet - do not output the script to standard output - - - --drop - only drop the tables - - - --create - only create the tables - - - --text - do not export to the database - - - --output=my_schema.ddl - output the ddl script to a file - - - --naming=eg.MyNamingStrategy - select a NamingStrategy - - - --config=hibernate.cfg.xml - read Hibernate configuration from an XML file - - - --properties=hibernate.properties - read database properties from a file - - - --format - format the generated SQL nicely in the script - - - --delimiter=; - set an end-of-line delimiter for the script - - - -
- - Embedding SchemaExport into your application - - -
-
-
diff --git a/documentation/src/main/docbook/devguide-old/en-US/Envers.xml b/documentation/src/main/docbook/devguide-old/en-US/Envers.xml deleted file mode 100644 index d55e045ae0df..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Envers.xml +++ /dev/null @@ -1,1759 +0,0 @@ - - - - - - Envers - - - The aim of Hibernate Envers is to provide historical versioning of your application's entity data. Much - like source control management tools such as Subversion or Git, Hibernate Envers manages a notion of revisions - if your application data through the use of audit tables. Each transaction relates to one global revision number - which can be used to identify groups of changes (much like a change set in source control). As the revisions - are global, having a revision number, you can query for various entities at that revision, retrieving a - (partial) view of the database at that revision. You can find a revision number having a date, and the other - way round, you can get the date at which a revision was committed. - - - - -
- Basics - - - To audit changes that are performed on an entity, you only need two things: the - hibernate-envers jar on the classpath and an @Audited annotation - on the entity. - - - - - Unlike in previous versions, you no longer need to specify listeners in the Hibernate configuration - file. Just putting the Envers jar on the classpath is enough - listeners will be registered - automatically. - - - - - And that's all - you can create, modify and delete the entities as always. If you look at the generated - schema for your entities, or at the data persisted by Hibernate, you will notice that there are no changes. - However, for each audited entity, a new table is introduced - entity_table_AUD, - which stores the historical data, whenever you commit a transaction. Envers automatically creates audit - tables if hibernate.hbm2ddl.auto option is set to create, - create-drop or update. Otherwise, to export complete database schema - programatically, use org.hibernate.envers.tools.hbm2ddl.EnversSchemaGenerator. Appropriate DDL - statements can be also generated with Ant task described later in this manual. - - - - Instead of annotating the whole class and auditing all properties, you can annotate - only some persistent properties with @Audited. This will cause only - these properties to be audited. - - - - The audit (history) of an entity can be accessed using the AuditReader interface, which - can be obtained having an open EntityManager or Session via - the AuditReaderFactory. See the javadocs for these classes for details on the - functionality offered. - -
- -
- Configuration - - It is possible to configure various aspects of Hibernate Envers behavior, such as table names, etc. - - - - Envers Configuration Properties - - - - - - - - Property name - Default value - Description - - - - - - - org.hibernate.envers.audit_table_prefix - - - - - String that will be prepended to the name of an audited entity to create the name of the - entity, that will hold audit information. - - - - - org.hibernate.envers.audit_table_suffix - - - _AUD - - - String that will be appended to the name of an audited entity to create the name of the - entity, that will hold audit information. If you audit an entity with a table name Person, - in the default setting Envers will generate a Person_AUD table to store - historical data. - - - - - org.hibernate.envers.revision_field_name - - - REV - - - Name of a field in the audit entity that will hold the revision number. - - - - - org.hibernate.envers.revision_type_field_name - - - REVTYPE - - - Name of a field in the audit entity that will hold the type of the revision (currently, - this can be: add, mod, del). - - - - - org.hibernate.envers.revision_on_collection_change - - - true - - - Should a revision be generated when a not-owned relation field changes (this can be either - a collection in a one-to-many relation, or the field using "mappedBy" attribute in a - one-to-one relation). - - - - - org.hibernate.envers.do_not_audit_optimistic_locking_field - - - true - - - When true, properties to be used for optimistic locking, annotated with - @Version, will be automatically not audited (their history won't be - stored; it normally doesn't make sense to store it). - - - - - org.hibernate.envers.store_data_at_delete - - - false - - - Should the entity data be stored in the revision when the entity is deleted (instead of only - storing the id and all other properties as null). This is not normally needed, as the data is - present in the last-but-one revision. Sometimes, however, it is easier and more efficient to - access it in the last revision (then the data that the entity contained before deletion is - stored twice). - - - - - org.hibernate.envers.default_schema - - - null (same schema as table being audited) - - - The default schema name that should be used for audit tables. Can be overridden using the - @AuditTable(schema="...") annotation. If not present, the schema will - be the same as the schema of the table being audited. - - - - - org.hibernate.envers.default_catalog - - - null (same catalog as table being audited) - - - The default catalog name that should be used for audit tables. Can be overridden using the - @AuditTable(catalog="...") annotation. If not present, the catalog will - be the same as the catalog of the normal tables. - - - - - org.hibernate.envers.audit_strategy - - - org.hibernate.envers.strategy.DefaultAuditStrategy - - - The audit strategy that should be used when persisting audit data. The default stores only - the revision, at which an entity was modified. An alternative, the - org.hibernate.envers.strategy.ValidityAuditStrategy stores both the - start revision and the end revision. Together these define when an audit row was valid, - hence the name ValidityAuditStrategy. - - - - - org.hibernate.envers.audit_strategy_validity_end_rev_field_name - - - REVEND - - - The column name that will hold the end revision number in audit entities. This property is - only valid if the validity audit strategy is used. - - - - - org.hibernate.envers.audit_strategy_validity_store_revend_timestamp - - - false - - - Should the timestamp of the end revision be stored, until which the data was valid, in - addition to the end revision itself. This is useful to be able to purge old Audit records - out of a relational database by using table partitioning. Partitioning requires a column - that exists within the table. This property is only evaluated if the ValidityAuditStrategy - is used. - - - - - org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name - - - REVEND_TSTMP - - - Column name of the timestamp of the end revision until which the data was valid. Only used - if the ValidityAuditStrategy is used, and - org.hibernate.envers.audit_strategy_validity_store_revend_timestamp - evaluates to true - - - - - org.hibernate.envers.use_revision_entity_with_native_id - - - true - - - Boolean flag that determines the strategy of revision number generation. Default - implementation of revision entity uses native identifier generator. If current database - engine does not support identity columns, users are advised to set this property to false. - In this case revision numbers are created by preconfigured - org.hibernate.id.enhanced.SequenceStyleGenerator. See: - - org.hibernate.envers.DefaultRevisionEntity - org.hibernate.envers.enhanced.SequenceIdRevisionEntity - - - - - - org.hibernate.envers.track_entities_changed_in_revision - - - false - - - Should entity types, that have been modified during each revision, be tracked. The default - implementation creates REVCHANGES table that stores entity names - of modified persistent objects. Single record encapsulates the revision identifier - (foreign key to REVINFO table) and a string value. For more - information refer to - and . - - - - - org.hibernate.envers.global_with_modified_flag - - - false, can be individually overriden with @Audited(withModifiedFlag=true) - - - Should property modification flags be stored for all audited entities and all properties. - When set to true, for all properties an additional boolean column in the audit tables will - be created, filled with information if the given property changed in the given revision. - When set to false, such column can be added to selected entities or properties using the - @Audited annotation. - For more information refer to - and . - - - - - org.hibernate.envers.modified_flag_suffix - - - _MOD - - - The suffix for columns storing "Modified Flags". - For example: a property called "age", will by default get modified flag with column name "age_MOD". - - - - - org.hibernate.envers.embeddable_set_ordinal_field_name - - - SETORDINAL - - - Name of column used for storing ordinal of the change in sets of embeddable elements. - - - - - org.hibernate.envers.cascade_delete_revision - - - false - - - While deleting revision entry, remove data of associated audited entities. - Requires database support for cascade row removal. - - - - - org.hibernate.envers.allow_identifier_reuse - - - false - - - Guarantees proper validity audit strategy behavior when application reuses identifiers - of deleted entities. Exactly one row with null end date exists - for each identifier. - - - - -
- - - - The following configuration options have been added recently and should be regarded as experimental: - - - org.hibernate.envers.track_entities_changed_in_revision - - - org.hibernate.envers.using_modified_flag - - - org.hibernate.envers.modified_flag_suffix - - - - -
- -
- Additional mapping annotations - - - The name of the audit table can be set on a per-entity basis, using the - @AuditTable annotation. It may be tedious to add this - annotation to every audited entity, so if possible, it's better to use a prefix/suffix. - - - - If you have a mapping with secondary tables, audit tables for them will be generated in - the same way (by adding the prefix and suffix). If you wish to overwrite this behaviour, - you can use the @SecondaryAuditTable and - @SecondaryAuditTables annotations. - - - - If you'd like to override auditing behaviour of some fields/properties inherited from - @Mappedsuperclass or in an embedded component, you can - apply the @AuditOverride(s) annotation on the subtype or usage site - of the component. - - - - If you want to audit a relation mapped with @OneToMany+@JoinColumn, - please see for a description of the additional - @AuditJoinTable annotation that you'll probably want to use. - - - - If you want to audit a relation, where the target entity is not audited (that is the case for example with - dictionary-like entities, which don't change and don't have to be audited), just annotate it with - @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED). Then, while reading historic - versions of your entity, the relation will always point to the "current" related entity. By default Envers - throws javax.persistence.EntityNotFoundException when "current" entity does not - exist in the database. Apply @NotFound(action = NotFoundAction.IGNORE) annotation - to silence the exception and assign null value instead. Hereby solution causes implicit eager loading - of to-one relations. - - - - If you'd like to audit properties of a superclass of an entity, which are not explicitly audited (which - don't have the @Audited annotation on any properties or on the class), you can list the - superclasses in the auditParents attribute of the @Audited - annotation. Please note that auditParents feature has been deprecated. Use - @AuditOverride(forClass = SomeEntity.class, isAudited = true/false) instead. - -
- -
- Choosing an audit strategy - - After the basic configuration it is important to choose the audit strategy that will be used to persist - and retrieve audit information. There is a trade-off between the performance of persisting and the - performance of querying the audit information. Currently there two audit strategies. - - - - - The default audit strategy persists the audit data together with a start revision. For each row - inserted, updated or deleted in an audited table, one or more rows are inserted in the audit - tables, together with the start revision of its validity. Rows in the audit tables are never - updated after insertion. Queries of audit information use subqueries to select the applicable - rows in the audit tables. These subqueries are notoriously slow and difficult to index. - - - - - The alternative is a validity audit strategy. This strategy stores the start-revision and the - end-revision of audit information. For each row inserted, updated or deleted in an audited table, - one or more rows are inserted in the audit tables, together with the start revision of its - validity. But at the same time the end-revision field of the previous audit rows (if available) - are set to this revision. Queries on the audit information can then use 'between start and end - revision' instead of subqueries as used by the default audit strategy. - - - The consequence of this strategy is that persisting audit information will be a bit slower, - because of the extra updates involved, but retrieving audit information will be a lot faster. - This can be improved by adding extra indexes. - - - -
- -
- Revision Log - Logging data for revisions - - - When Envers starts a new revision, it creates a new revision entity which stores - information about the revision. By default, that includes just - - - - - revision number - An integral value (int/Integer or - long/Long). Essentially the primary key of the revision - - - - - revision timestamp - either a long/Long or - java.util.Date value representing the instant at which the revision was made. - When using a java.util.Date, instead of a long/Long for - the revision timestamp, take care not to store it to a column data type which will loose precision. - - - - - - Envers handles this information as an entity. By default it uses its own internal class to act as the - entity, mapped to the REVINFO table. - You can, however, supply your own approach to collecting this information which might be useful to - capture additional details such as who made a change or the ip address from which the request came. There - are 2 things you need to make this work. - - - - - First, you will need to tell Envers about the entity you wish to use. Your entity must use the - @org.hibernate.envers.RevisionEntity annotation. It must - define the 2 attributes described above annotated with - @org.hibernate.envers.RevisionNumber and - @org.hibernate.envers.RevisionTimestamp, respectively. You can extend - from org.hibernate.envers.DefaultRevisionEntity, if you wish, to inherit all - these required behaviors. - - - Simply add the custom revision entity as you do your normal entities. Envers will "find it". Note - that it is an error for there to be multiple entities marked as - @org.hibernate.envers.RevisionEntity - - - - - Second, you need to tell Envers how to create instances of your revision entity which is handled - by the newRevision method of the - org.jboss.envers.RevisionListener interface. - - - You tell Envers your custom org.hibernate.envers.RevisionListener - implementation to use by specifying it on the - @org.hibernate.envers.RevisionEntity annotation, using the - value attribute. If your RevisionListener - class is inaccessible from @RevisionEntity (e.g. exists in a different - module), set org.hibernate.envers.revision_listener property to it's fully - qualified name. Class name defined by the configuration parameter overrides revision entity's - value attribute. - - - - - - - An alternative method to using the org.hibernate.envers.RevisionListener - is to instead call the getCurrentRevision method of the - org.hibernate.envers.AuditReader interface to obtain the current revision, - and fill it with desired information. The method accepts a persist parameter indicating - whether the revision entity should be persisted prior to returning from this method. true - ensures that the returned entity has access to its identifier value (revision number), but the revision - entity will be persisted regardless of whether there are any audited entities changed. false - means that the revision number will be null, but the revision entity will be persisted - only if some audited entities have changed. - - - - - Example of storing username with revision - - - ExampleRevEntity.java - - - ExampleListener.java - - - -
- Tracking entity names modified during revisions - - By default entity types that have been changed in each revision are not being tracked. This implies the - necessity to query all tables storing audited data in order to retrieve changes made during - specified revision. Envers provides a simple mechanism that creates REVCHANGES - table which stores entity names of modified persistent objects. Single record encapsulates the revision - identifier (foreign key to REVINFO table) and a string value. - - - Tracking of modified entity names can be enabled in three different ways: - - - - - Set org.hibernate.envers.track_entities_changed_in_revision parameter to - true. In this case - org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity will - be implicitly used as the revision log entity. - - - - - Create a custom revision entity that extends - org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity class. - - - - - - - Mark an appropriate field of a custom revision entity with - @org.hibernate.envers.ModifiedEntityNames annotation. The property is - required to be of ]]> type. - - - modifiedEntityNames; - - ... -}]]> - - - - Users, that have chosen one of the approaches listed above, can retrieve all entities modified in a - specified revision by utilizing API described in . - - - Users are also allowed to implement custom mechanism of tracking modified entity types. In this case, they - shall pass their own implementation of - org.hibernate.envers.EntityTrackingRevisionListener interface as the value - of @org.hibernate.envers.RevisionEntity annotation. - EntityTrackingRevisionListener interface exposes one method that notifies - whenever audited entity instance has been added, modified or removed within current revision boundaries. - - - - Custom implementation of tracking entity classes modified during revisions - - CustomEntityTrackingRevisionListener.java - - - CustomTrackingRevisionEntity.java - modifiedEntityTypes = - new HashSet(); - - public void addModifiedEntityType(String entityClassName) { - modifiedEntityTypes.add(new ModifiedEntityTypeEntity(this, entityClassName)); - } - - ... -} -]]> - - ModifiedEntityTypeEntity.java - - modifiedEntityTypes = revEntity.getModifiedEntityTypes()]]> - -
- -
- -
- Tracking entity changes at property level - - By default the only information stored by Envers are revisions of modified entities. - This approach lets user create audit queries based on historical values of entity's properties. - - Sometimes it is useful to store additional metadata for each revision, when you are interested also in - the type of changes, not only about the resulting values. The feature described in - - makes it possible to tell which entities were modified in given revision. - - Feature described here takes it one step further. "Modification Flags" enable Envers to track which - properties of audited entities were modified in a given revision. - - - Tracking entity changes at property level can be enabled by: - - - - - setting org.hibernate.envers.global_with_modified_flag configuration - property to true. This global switch will cause adding modification flags - for all audited properties in all audited entities. - - - - - using @Audited(withModifiedFlag=true) on a property or on an entity. - - - - - The trade-off coming with this functionality is an increased size of - audit tables and a very little, almost negligible, performance drop - during audit writes. This is due to the fact that every tracked - property has to have an accompanying boolean column in the - schema that stores information about the property's modifications. Of - course it is Envers' job to fill these columns accordingly - no additional work by the - developer is required. Because of costs mentioned, it is recommended - to enable the feature selectively, when needed with use of the - granular configuration means described above. - - - To see how "Modified Flags" can be utilized, check out the very - simple query API that uses them: . - -
- -
- - Queries - - - You can think of historic data as having two dimension. The first - horizontal - - is the state of the database at a given revision. Thus, you can - query for entities as they were at revision N. The second - vertical - are the - revisions, at which entities changed. Hence, you can query for revisions, - in which a given entity changed. - - - - The queries in Envers are similar to Hibernate Criteria queries, so if you are common with them, - using Envers queries will be much easier. - - - - The main limitation of the current queries implementation is that you cannot - traverse relations. You can only specify constraints on the ids of the - related entities, and only on the "owning" side of the relation. This however - will be changed in future releases. - - - - Please note, that queries on the audited data will be in many cases much slower - than corresponding queries on "live" data, as they involve correlated subselects. - - - - In the future, queries will be improved both in terms of speed and possibilities, when using the valid-time - audit strategy, that is when storing both start and end revisions for entities. See - . - - -
- - Querying for entities of a class at a given revision - - - The entry point for this type of queries is: - - - - - - You can then specify constraints, which should be met by the entities returned, by - adding restrictions, which can be obtained using the AuditEntity - factory class. For example, to select only entities, where the "name" property - is equal to "John": - - - - - - And to select only entites that are related to a given entity: - - - - - - You can limit the number of results, order them, and set aggregations and projections - (except grouping) in the usual way. - When your query is complete, you can obtain the results by calling the - getSingleResult() or getResultList() methods. - - - - A full query, can look for example like this: - - - - -
- -
- - Querying for revisions, at which entities of a given class changed - - - The entry point for this type of queries is: - - - - - - You can add constraints to this query in the same way as to the previous one. - There are some additional possibilities: - - - - - - using AuditEntity.revisionNumber() you can specify constraints, projections - and order on the revision number, in which the audited entity was modified - - - - - similarly, using AuditEntity.revisionProperty(propertyName) you can specify constraints, - projections and order on a property of the revision entity, corresponding to the revision - in which the audited entity was modified - - - - - AuditEntity.revisionType() gives you access as above to the type of - the revision (ADD, MOD, DEL). - - - - - - Using these methods, - you can order the query results by revision number, set projection or constraint - the revision number to be greater or less than a specified value, etc. For example, the - following query will select the smallest revision number, at which entity of class - MyEntity with id entityId has changed, after revision - number 42: - - - - - - The second additional feature you can use in queries for revisions is the ability - to maximalize/minimize a property. For example, if you want to select the - revision, at which the value of the actualDate for a given entity - was larger then a given value, but as small as possible: - - - - - - The minimize() and maximize() methods return a criteria, - to which you can add constraints, which must be met by the entities with the - maximized/minimized properties. AggregatedAuditExpression#computeAggregationInInstanceContext() - enables the possibility to compute aggregated expression in the context of each entity instance - separately. It turns out useful when querying for latest revisions of all entities of a particular type. - - - - You probably also noticed that there are two boolean parameters, passed when - creating the query. The first one, selectEntitiesOnly, is only valid when - you don't set an explicit projection. If true, the result of the query will be - a list of entities (which changed at revisions satisfying the specified - constraints). - - - - If false, the result will be a list of three element arrays. The - first element will be the changed entity instance. The second will be an entity - containing revision data (if no custom entity is used, this will be an instance - of DefaultRevisionEntity). The third will be the type of the - revision (one of the values of the RevisionType enumeration: - ADD, MOD, DEL). - - - - The second parameter, selectDeletedEntities, specifies if revisions, - in which the entity was deleted should be included in the results. If yes, such entities - will have the revision type DEL and all fields, except the id, - null. - - -
- -
- - Querying for revisions of entity that modified given property - - - For the two types of queries described above it's possible to use - special Audit criteria called - hasChanged() - and - hasNotChanged() - that makes use of the functionality - described in . - They're best suited for vertical queries, - however existing API doesn't restrict their usage for horizontal - ones. - - Let's have a look at following examples: - - - - - - - This query will return all revisions of MyEntity with given id, - where the - actualDate - property has been changed. - Using this query we won't get all other revisions in which - actualDate - wasn't touched. Of course nothing prevents user from combining - hasChanged condition with some additional criteria - add method - can be used here in a normal way. - - - - - - - This query will return horizontal slice for MyEntity at the time - revisionNumber was generated. It will be limited to revisions - that modified - prop1 - but not prop2. - Note that the result set will usually also contain revisions - with numbers lower than the revisionNumber, so we cannot read - this query as "Give me all MyEntities changed in revisionNumber - with - prop1 - modified and - prop2 - untouched". To get such result we have to use the - forEntitiesModifiedAtRevision query: - - - - - -
- - -
- Querying for entities modified in a given revision - - The basic query allows retrieving entity names and corresponding Java classes changed in a specified revision: - - > modifiedEntityTypes = getAuditReader() - .getCrossTypeRevisionChangesReader().findEntityTypes(revisionNumber);]]> - - Other queries (also accessible from org.hibernate.envers.CrossTypeRevisionChangesReader): - - - - - List]]> findEntities(Number) - - Returns snapshots of all audited entities changed (added, updated and removed) in a given revision. - Executes n+1 SQL queries, where n is a number of different entity - classes modified within specified revision. - - - - - List]]> findEntities(Number, RevisionType) - - Returns snapshots of all audited entities changed (added, updated or removed) in a given revision - filtered by modification type. Executes n+1 SQL queries, where n - is a number of different entity classes modified within specified revision. - - - - - >]]> findEntitiesGroupByRevisionType(Number) - - Returns a map containing lists of entity snapshots grouped by modification operation (e.g. - addition, update and removal). Executes 3n+1 SQL queries, where n - is a number of different entity classes modified within specified revision. - - - - - Note that methods described above can be legally used only when default mechanism of - tracking changed entity names is enabled (see ). - -
- -
- -
- Conditional auditing - - Envers persists audit data in reaction to various Hibernate events (e.g. post update, post insert, and - so on), using a series of even listeners from the org.hibernate.envers.event.spi - package. By default, if the Envers jar is in the classpath, the event listeners are auto-registered with - Hibernate. - - - Conditional auditing can be implemented by overriding some of the Envers event listeners. - To use customized Envers event listeners, the following steps are needed: - - - - Turn off automatic Envers event listeners registration by setting the - hibernate.listeners.envers.autoRegister Hibernate property to - false. - - - - - Create subclasses for appropriate event listeners. For example, if you want to - conditionally audit entity insertions, extend the - org.hibernate.envers.event.spi.EnversPostInsertEventListenerImpl - class. Place the conditional-auditing logic in the subclasses, call the super method if - auditing should be performed. - - - - - Create your own implementation of org.hibernate.integrator.spi.Integrator, - similar to org.hibernate.envers.boot.internal.EnversIntegrator. Use your event - listener classes instead of the default ones. - - - - - For the integrator to be automatically used when Hibernate starts up, you will need to add a - META-INF/services/org.hibernate.integrator.spi.Integrator file to your jar. - The file should contain the fully qualified name of the class implementing the interface. - - - - -
- -
- Understanding the Envers Schema - - - For each audited entity (that is, for each entity containing at least one audited field), an audit table is - created. By default, the audit table's name is created by adding a "_AUD" suffix to the original table name, - but this can be overridden by specifying a different suffix/prefix in the configuration or per-entity using - the @org.hibernate.envers.AuditTable annotation. - - - - Audit table columns - - - id of the original entity (this can be more then one column in the case of composite primary keys) - - - - - revision number - an integer. Matches to the revision number in the revision entity table. - - - - - revision type - a small integer - - - - - audited fields from the original entity - - - - - - The primary key of the audit table is the combination of the original id of the entity and the revision - number - there can be at most one historic entry for a given entity instance at a given revision. - - - - The current entity data is stored in the original table and in the audit table. This is a duplication of - data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully - this won't be a major drawback for the users. A row in the audit table with entity id ID, revision N and - data D means: entity with id ID has data D from revision N upwards. Hence, if we want to find an entity at - revision M, we have to search for a row in the audit table, which has the revision number smaller or equal - to M, but as large as possible. If no such row is found, or a row with a "deleted" marker is found, it means - that the entity didn't exist at that revision. - - - - The "revision type" field can currently have three values: 0, 1, 2, which means ADD, MOD and DEL, - respectively. A row with a revision of type DEL will only contain the id of the entity and no data (all - fields NULL), as it only serves as a marker saying "this entity was deleted at that revision". - - - - Additionally, there is a revision entity table which contains the information about the - global revision. By default the generated table is named REVINFO and - contains just 2 columns: ID and TIMESTAMP. - A row is inserted into this table on each new revision, that is, on each commit of a transaction, which - changes audited data. The name of this table can be configured, the name of its columns as well as adding - additional columns can be achieved as discussed in . - - - - While global revisions are a good way to provide correct auditing of relations, some people have pointed out - that this may be a bottleneck in systems, where data is very often modified. One viable solution is to - introduce an option to have an entity "locally revisioned", that is revisions would be created for it - independently. This wouldn't enable correct versioning of relations, but wouldn't also require the - REVINFO table. Another possibility is to introduce a notion of - "revisioning groups": groups of entities which share revision numbering. Each such group would have to - consist of one or more strongly connected component of the graph induced by relations between entities. - Your opinions on the subject are very welcome on the forum! :) - - -
- -
- Generating schema with Ant - - - If you'd like to generate the database schema file with the Hibernate Tools Ant task, - you'll probably notice that the generated file doesn't contain definitions of audit - tables. To generate also the audit tables, you simply need to use - org.hibernate.tool.ant.EnversHibernateToolTask instead of the usual - org.hibernate.tool.ant.HibernateToolTask. The former class extends - the latter, and only adds generation of the version entities. So you can use the task - just as you used to. - - - - For example: - - - - - - - - - - - - - - -]]> - - - Will generate the following schema: - - - -
- - -
- Mapping exceptions - -
- - What isn't and will not be supported - - - Bags, as they can contain non-unique elements. - The reason is that persisting, for example a bag of String-s, violates a principle - of relational databases: that each table is a set of tuples. In case of bags, - however (which require a join table), if there is a duplicate element, the two - tuples corresponding to the elements will be the same. Hibernate allows this, - however Envers (or more precisely: the database connector) will throw an exception - when trying to persist two identical elements, because of a unique constraint violation. - - - - There are at least two ways out if you need bag semantics: - - - - - - use an indexed collection, with the @IndexColumn annotation, or - - - - - provide a unique id for your elements with the @CollectionId annotation. - - - - -
- -
- - What isn't and <emphasis>will</emphasis> be supported - - - - - Bag style collection which identifier column has been defined using - @CollectionId annotation (JIRA ticket HHH-3950). - - - - -
- -
- - <literal>@OneToMany</literal>+<literal>@JoinColumn</literal> - - - When a collection is mapped using these two annotations, Hibernate doesn't - generate a join table. Envers, however, has to do this, so that when you read the - revisions in which the related entity has changed, you don't get false results. - - - To be able to name the additional join table, there is a special annotation: - @AuditJoinTable, which has similar semantics to JPA's - @JoinTable. - - - - One special case are relations mapped with @OneToMany+@JoinColumn on - the one side, and @ManyToOne+@JoinColumn(insertable=false, updatable=false) - on the many side. Such relations are in fact bidirectional, but the owning side is the collection. - - - To properly audit such relations with Envers, you can use the @AuditMappedBy annotation. - It enables you to specify the reverse property (using the mappedBy element). In case - of indexed collections, the index column must also be mapped in the referenced entity (using - @Column(insertable=false, updatable=false), and specified using - positionMappedBy. This annotation will affect only the way - Envers works. Please note that the annotation is experimental and may change in the future. - - -
-
- -
- Advanced: Audit table partitioning - -
- - Benefits of audit table partitioning - - - Because audit tables tend to grow indefinitely they can quickly become really large. When the audit tables have grown - to a certain limit (varying per RDBMS and/or operating system) it makes sense to start using table partitioning. - SQL table partitioning offers a lot of advantages including, but certainly not limited to: - - - - Improved query performance by selectively moving rows to various partitions (or even purging old rows) - - - - - Faster data loads, index creation, etc. - - - - - -
- -
- - Suitable columns for audit table partitioning - - Generally SQL tables must be partitioned on a column that exists within the table. As a rule it makes sense to use - either the end revision or the end revision timestamp column for - partioning of audit tables. - - - End revision information is not available for the default AuditStrategy. - - - - Therefore the following Envers configuration options are required: - - - org.hibernate.envers.audit_strategy = - org.hibernate.envers.strategy.ValidityAuditStrategy - - - org.hibernate.envers.audit_strategy_validity_store_revend_timestamp = - true - - - - Optionally, you can also override the default values using following properties: - - - org.hibernate.envers.audit_strategy_validity_end_rev_field_name - - - org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name - - - - For more information, see . - - - - - - The reason why the end revision information should be used for audit table partioning is based on the assumption that - audit tables should be partionioned on an 'increasing level of interestingness', like so: - - - - - - - A couple of partitions with audit data that is not very (or no longer) interesting. - This can be stored on slow media, and perhaps even be purged eventually. - - - - - Some partitions for audit data that is potentially interesting. - - - - - One partition for audit data that is most likely to be interesting. - This should be stored on the fastest media, both for reading and writing. - - - - - - -
- -
- - Audit table partitioning example - - In order to determine a suitable column for the 'increasing level of interestingness', - consider a simplified example of a salary registration for an unnamed agency. - - - - Currently, the salary table contains the following rows for a certain person X: - - - Salaries table - - - - - - Year - Salary (USD) - - - - - 2006 - 3300 - - - 2007 - 3500 - - - 2008 - 4000 - - - 2009 - 4500 - - - -
-
- - - The salary for the current fiscal year (2010) is unknown. The agency requires that all changes in registered - salaries for a fiscal year are recorded (i.e. an audit trail). The rationale behind this is that decisions - made at a certain date are based on the registered salary at that time. And at any time it must be possible - reproduce the reason why a certain decision was made at a certain date. - - - - The following audit information is available, sorted on in order of occurrence: - - - Salaries - audit table - - - - - - - - - Year - Revision type - Revision timestamp - Salary (USD) - End revision timestamp - - - - - 2006 - ADD - 2007-04-01 - 3300 - null - - - 2007 - ADD - 2008-04-01 - 35 - 2008-04-02 - - - 2007 - MOD - 2008-04-02 - 3500 - null - - - 2008 - ADD - 2009-04-01 - 3700 - 2009-07-01 - - - 2008 - MOD - 2009-07-01 - 4100 - 2010-02-01 - - - 2008 - MOD - 2010-02-01 - 4000 - null - - - 2009 - ADD - 2010-04-01 - 4500 - null - - - -
-
- -
- - Determining a suitable partitioning column - - To partition this data, the 'level of interestingness' must be defined. - Consider the following: - - - - For fiscal year 2006 there is only one revision. It has the oldest revision timestamp - of all audit rows, but should still be regarded as interesting because it is the latest modification - for this fiscal year in the salary table; its end revision timestamp is null. - - - Also note that it would be very unfortunate if in 2011 there would be an update of the salary for fiscal - year 2006 (which is possible in until at least 10 years after the fiscal year) and the audit - information would have been moved to a slow disk (based on the age of the - revision timestamp). Remember that in this case Envers will have to update - the end revision timestamp of the most recent audit row. - - - - - There are two revisions in the salary of fiscal year 2007 which both have nearly the same - revision timestamp and a different end revision timestamp. - On first sight it is evident that the first revision was a mistake and probably uninteresting. - The only interesting revision for 2007 is the one with end revision timestamp null. - - - - - Based on the above, it is evident that only the end revision timestamp is suitable for - audit table partitioning. The revision timestamp is not suitable. - - -
- -
- - Determining a suitable partitioning scheme - - A possible partitioning scheme for the salary table would be as follows: - - - - end revision timestamp year = 2008 - - - This partition contains audit data that is not very (or no longer) interesting. - - - - - end revision timestamp year = 2009 - - - This partition contains audit data that is potentially interesting. - - - - - end revision timestamp year >= 2010 or null - - - This partition contains the most interesting audit data. - - - - - - - This partitioning scheme also covers the potential problem of the update of the - end revision timestamp, which occurs if a row in the audited table is modified. - Even though Envers will update the end revision timestamp of the audit row to - the system date at the instant of modification, the audit row will remain in the same partition - (the 'extension bucket'). - - - - And sometime in 2011, the last partition (or 'extension bucket') is split into two new partitions: - - - - end revision timestamp year = 2010 - - - This partition contains audit data that is potentially interesting (in 2011). - - - - - end revision timestamp year >= 2011 or null - - - This partition contains the most interesting audit data and is the new 'extension bucket'. - - - - - -
- -
-
- -
- Envers links - - - - - Hibernate main page - - - - - Forum - - - - - JIRA issue tracker - (when adding issues concerning Envers, be sure to select the "envers" component!) - - - - - IRC channel - - - - - Envers Blog - - - - - FAQ - - - - -
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/Hibernate_Development_Guide.ent b/documentation/src/main/docbook/devguide-old/en-US/Hibernate_Development_Guide.ent deleted file mode 100644 index aff71044189b..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Hibernate_Development_Guide.ent +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/Hibernate_Development_Guide.xml b/documentation/src/main/docbook/devguide-old/en-US/Hibernate_Development_Guide.xml deleted file mode 100644 index 870d70d16b7d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Hibernate_Development_Guide.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - -%BOOK_ENTITIES; -]> - - - - Hibernate Developer Guide - &version; - Hibernate O/RM - &version; - &today; - - - - - - - - - - ©rightYear; - ©rightHolder; - - - - - The Hibernate Team - - - The JBoss Visual Design Team - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/JMX.xml b/documentation/src/main/docbook/devguide-old/en-US/JMX.xml deleted file mode 100644 index 4f4f7284a141..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/JMX.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - JMX - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/Locking.xml b/documentation/src/main/docbook/devguide-old/en-US/Locking.xml deleted file mode 100644 index b8bcfa7e6627..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Locking.xml +++ /dev/null @@ -1,332 +0,0 @@ - - - - - Locking - - Locking refers to actions taken to prevent data in a relational database from changing between the time it is read - and the time that it is used. - - - Your locking strategy can be either optimistic or pessimistic. - - - Locking strategies - - Optimistic - - - Optimistic locking assumes that multiple transactions can complete without affecting each other, and that - therefore transactions can proceed without locking the data resources that they affect. Before committing, - each transaction verifies that no other transaction has modified its data. If the check reveals conflicting - modifications, the committing transaction rolls back. - - - - - Pessimistic - - - Pessimistic locking assumes that concurrent transactions will conflict with each other, and requires resources - to be locked after they are read and only unlocked after the application has finished using the data. - - - - - - Hibernate provides mechanisms for implementing both types of locking in your applications. - -
- Optimistic - - When your application uses long transactions or conversations that span several database transactions, you can - store versioning data, so that if the same entity is updated by two conversations, the last to commit changes is - informed of the conflict, and does not override the other conversation's work. This approach guarantees some - isolation, but scales well and works particularly well in Read-Often Write-Sometimes - situations. - - - Hibernate provides two different mechanisms for storing versioning information, a dedicated version number or a - timestamp. - - - - Version number - - - - - - - - Timestamp - - - - - - - - - - A version or timestamp property can never be null for a detached instance. Hibernate detects any instance with a - null version or timestamp as transient, regardless of other unsaved-value strategies that you specify. Declaring - a nullable version or timestamp property is an easy way to avoid problems with transitive reattachment in - Hibernate, especially useful if you use assigned identifiers or composite keys. - - - -
- Dedicated version number - - The version number mechanism for optimistic locking is provided through a @Version - annotation. - - - The @Version annotation - - - Here, the version property is mapped to the OPTLOCK column, and the entity manager uses it - to detect conflicting updates, and prevent the loss of updates that would be overwritten by a - last-commit-wins strategy. - - - - The version column can be any kind of type, as long as you define and implement the appropriate - UserVersionType. - - - Your application is forbidden from altering the version number set by Hibernate. To artificially increase the - version number, see the documentation for properties - LockModeType.OPTIMISTIC_FORCE_INCREMENT or - LockModeType.PESSIMISTIC_FORCE_INCREMENTcheck in the Hibernate Entity Manager reference - documentation. - - - Database-generated version numbers - - If the version number is generated by the database, such as a trigger, use the annotation - @org.hibernate.annotations.Generated(GenerationTime.ALWAYS). - - - - Declaring a version property in <filename>hbm.xml</filename> - - - - - - column - The name of the column holding the version number. Optional, defaults to the property - name. - - - name - The name of a property of the persistent class. - - - type - The type of the version number. Optional, defaults to - integer. - - - access - Hibernate's strategy for accessing the property value. Optional, defaults to - property. - - - unsaved-value - Indicates that an instance is newly instantiated and thus unsaved. This distinguishes it - from detached instances that were saved or loaded in a previous session. The default value, - undefined, indicates that the identifier property value should be - used. Optional. - - - generated - Indicates that the version property value is generated by the database. Optional, defaults - to never. - - - insert - Whether or not to include the version column in SQL insert - statements. Defaults to true, but you can set it to false if the - database column is defined with a default value of 0. - - - - - -
- -
- Timestamp - - Timestamps are a less reliable way of optimistic locking than version numbers, but can be used by applications - for other purposes as well. Timestamping is automatically used if you the @Version annotation on a - Date or Calendar. - - - Using timestamps for optimistic locking - - - - Hibernate can retrieve the timestamp value from the database or the JVM, by reading the value you specify for - the @org.hibernate.annotations.Source annotation. The value can be either - org.hibernate.annotations.SourceType.DB or - org.hibernate.annotations.SourceType.VM. The default behavior is to use the database, and is - also used if you don't specify the annotation at all. - - - The timestamp can also be generated by the database instead of Hibernate, if you use the - @org.hibernate.annotations.Generated(GenerationTime.ALWAYS) annotation. - - - The timestamp element in <filename>hbm.xml</filename> - - - - - - column - The name of the column which holds the timestamp. Optional, defaults to the property - namel - - - name - The name of a JavaBeans style property of Java type Date or Timestamp of the persistent - class. - - - access - The strategy Hibernate uses to access the property value. Optional, defaults to - property. - - - unsaved-value A version property which indicates than instance is newly - instantiated, and unsaved. This distinguishes it from detached instances that were saved or loaded in a - previous session. The default value of undefined indicates that Hibernate uses the - identifier property value. - - - source - Whether Hibernate retrieves the timestamp from the database or the current - JVM. Database-based timestamps incur an overhead because Hibernate needs to query the database each time - to determine the incremental next value. However, database-derived timestamps are safer to use in a - clustered environment. Not all database dialects are known to support the retrieval of the database's - current timestamp. Others may also be unsafe for locking, because of lack of precision. - - - generated - Whether the timestamp property value is generated by the database. Optional, defaults to - never. - - - - - -
- -
- -
- Pessimistic - - Typically, you only need to specify an isolation level for the JDBC connections and let the database handle - locking issues. If you do need to obtain exclusive pessimistic locks or re-obtain locks at the start of a new - transaction, Hibernate gives you the tools you need. - - - - Hibernate always uses the locking mechanism of the database, and never lock objects in memory. - - -
- The <classname>LockMode</classname> class - - The LockMode class defines the different lock levels that Hibernate can acquire. - - - - - - LockMode.WRITE - acquired automatically when Hibernate updates or inserts a row. - - - LockMode.UPGRADE - acquired upon explicit user request using SELECT ... FOR UPDATE on databases - which support that syntax. - - - LockMode.UPGRADE_NOWAIT - acquired upon explicit user request using a SELECT ... FOR UPDATE NOWAIT in - Oracle. - - - LockMode.UPGRADE_SKIPLOCKED - acquired upon explicit user request using a SELECT ... FOR UPDATE SKIP LOCKED in - Oracle, or SELECT ... with (rowlock,updlock,readpast) in SQL Server. - - - LockMode.READ - acquired automatically when Hibernate reads data under Repeatable Read or - Serializable isolation level. It can be re-acquired by explicit user - request. - - - LockMode.NONE - The absence of a lock. All objects switch to this lock mode at the end of a - Transaction. Objects associated with the session via a call to update() or - saveOrUpdate() also start out in this lock mode. - - - - - - The explicit user request mentioned above occurs as a consequence of any of the following actions: - - - - - A call to Session.load(), specifying a LockMode. - - - - - A call to Session.lock(). - - - - - A call to Query.setLockMode(). - - - - - If you call Session.load() with option , - or , and the requested object is not already - loaded by the session, the object is loaded using SELECT ... FOR UPDATE. If you call - load() for an object that is already loaded with a less restrictive lock than the one - you request, Hibernate calls lock() for that object. - - - Session.lock() performs a version number check if the specified lock mode is - READ, UPGRADE, UPGRADE_NOWAIT or - UPGRADE_SKIPLOCKED. In the case of UPGRADE, - UPGRADE_NOWAIT or UPGRADE_SKIPLOCKED, SELECT ... FOR UPDATE - syntax is used. - - - If the requested lock mode is not supported by the database, Hibernate uses an appropriate alternate mode - instead of throwing an exception. This ensures that applications are portable. - -
-
-
diff --git a/documentation/src/main/docbook/devguide-old/en-US/Mapping_Association.xml b/documentation/src/main/docbook/devguide-old/en-US/Mapping_Association.xml deleted file mode 100644 index 089843d1e510..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Mapping_Association.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Mapping associations - - The most basic form of mapping in Hibernate is mapping a persistent entity class to a database table. - You can expand on this concept by mapping associated classes together. - shows a Person class with a - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/Mapping_Entities.xml b/documentation/src/main/docbook/devguide-old/en-US/Mapping_Entities.xml deleted file mode 100644 index 66f7435eb6f0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Mapping_Entities.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - Mapping entities -
- Hierarchies - -
-
diff --git a/documentation/src/main/docbook/devguide-old/en-US/Preface.xml b/documentation/src/main/docbook/devguide-old/en-US/Preface.xml deleted file mode 100644 index 032432abcee0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/Preface.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - Preface - - Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. - Development costs are significantly higher due to a paradigm mismatch between how data is represented in - objects versus relational databases. Hibernate is an Object/Relational Mapping solution for Java environments. - The term Object/Relational Mapping refers to the technique of mapping data from an object model representation - to a relational data model representation (and visa versa). See - for a good high-level discussion. - - - - - While having a strong background in SQL is not required to use Hibernate, having a basic understanding of - the concepts can greatly help you understand Hibernate more fully and quickly. Probably the single - best background is an understanding of data modeling principles. You might want to consider these resources - as a good starting point: - - - - - - - - - - - - - - - - - Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to - SQL data types), but also provides data query and retrieval facilities. It can significantly reduce - development time otherwise spent with manual data handling in SQL and JDBC. Hibernate’s design goal is to - relieve the developer from 95% of common data persistence-related programming tasks by eliminating the need for - manual, hand-crafted data processing using SQL and JDBC. However, unlike many other persistence solutions, - Hibernate does not hide the power of SQL from you and guarantees that your investment in relational technology - and knowledge is as valid as always. - - - - Hibernate may not be the best solution for data-centric applications that only use stored-procedures to - implement the business logic in the database, it is most useful with object-oriented domain models and business - logic in the Java-based middle-tier. However, Hibernate can certainly help you to remove or encapsulate - vendor-specific SQL code and will help with the common task of result set translation from a tabular - representation to a graph of objects. - - -
- Get Involved - - - - Use Hibernate and report any bugs or issues you find. See - for details. - - - - - Try your hand at fixing some bugs or implementing enhancements. Again, see - . - - - - - Engage with the community using mailing lists, forums, IRC, or other ways listed at - . - - - - - Help improve or translate this documentation. Contact us on - the developer mailing list if you have interest. - - - - - Spread the word. Let the rest of your organization know about the benefits of - Hibernate. - - - -
- -
- Getting Started Guide - - New users may want to first look through the - Hibernate Getting Started Guide for basic information as well as - tutorials. Even seasoned veterans may want to considering perusing the sections pertaining to - build artifacts for any changes. - -
- -
- diff --git a/documentation/src/main/docbook/devguide-old/en-US/appendices/legacy_criteria/Legacy_Criteria.xml b/documentation/src/main/docbook/devguide-old/en-US/appendices/legacy_criteria/Legacy_Criteria.xml deleted file mode 100644 index 2001abea708a..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/appendices/legacy_criteria/Legacy_Criteria.xml +++ /dev/null @@ -1,549 +0,0 @@ - - - - - - Legacy Hibernate Criteria Queries - - - - - This appendix covers the legacy Hibernate org.hibernate.Criteria API, which - should be considered deprecated. New development should focus on the JPA - javax.persistence.criteria.CriteriaQuery API. Eventually, - Hibernate-specific criteria features will be ported as extensions to the JPA - javax.persistence.criteria.CriteriaQuery. For details on the JPA APIs, see - . - - - This information is copied as-is from the older Hibernate documentation. - - - - - Hibernate features an intuitive, extensible criteria query API. - - -
- Creating a <literal>Criteria</literal> instance - - - The interface org.hibernate.Criteria represents a query against - a particular persistent class. The Session is a factory for - Criteria instances. - - - - -
- -
- Narrowing the result set - - - An individual query criterion is an instance of the interface - org.hibernate.criterion.Criterion. The class - org.hibernate.criterion.Restrictions defines - factory methods for obtaining certain built-in - Criterion types. - - - - - - Restrictions can be grouped logically. - - - - - - - - There are a range of built-in criterion types (Restrictions - subclasses). One of the most useful allows you to specify SQL directly. - - - - - - The {alias} placeholder will be replaced by the row alias - of the queried entity. - - - - You can also obtain a criterion from a - Property instance. You can create a Property - by calling Property.forName(): - - - - -
- -
- Ordering the results - - - You can order the results using org.hibernate.criterion.Order. - - - - - - -
- -
- Associations - - - By navigating - associations using createCriteria() you can specify constraints upon related entities: - - - - - - The second createCriteria() returns a new - instance of Criteria that refers to the elements of - the kittens collection. - - - - There is also an alternate form that is useful in certain circumstances: - - - - - - (createAlias() does not create a new instance of - Criteria.) - - - - The kittens collections held by the Cat instances - returned by the previous two queries are not pre-filtered - by the criteria. If you want to retrieve just the kittens that match the - criteria, you must use a ResultTransformer. - - - - - - Additionally you may manipulate the result set using a left outer join: - - - - - This will return all of the Cats with a mate whose name starts with "good" - ordered by their mate's age, and all cats who do not have a mate. - This is useful when there is a need to order or limit in the database - prior to returning complex/large result sets, and removes many instances where - multiple queries would have to be performed and the results unioned - by java in memory. - - - Without this feature, first all of the cats without a mate would need to be loaded in one query. - - - A second query would need to retreive the cats with mates who's name started with "good" sorted by the mates age. - - - Thirdly, in memory; the lists would need to be joined manually. - -
- -
- Dynamic association fetching - - - You can specify association fetching semantics at runtime using - setFetchMode(). - - - - - - This query will fetch both mate and kittens - by outer join. See for more information. - - -
- -
- Components - - - To add a restriction against a property of an embedded component, the component property - name should be prepended to the property name when creating the Restriction. - The criteria object should be created on the owning entity, and cannot be created on the component - itself. For example, suppose the Cat has a component property fullName - with sub-properties firstName and lastName: - - - - - - - Note: this does not apply when querying collections of components, for that see below - - - -
- -
- Collections - - When using criteria against collections, there are two distinct cases. One is if - the collection contains entities (eg. <one-to-many/> - or <many-to-many/>) or components - (<composite-element/> ), - and the second is if the collection contains scalar values - (<element/>). - In the first case, the syntax is as given above in the section - where we restrict the kittens - collection. Essentially we create a Criteria object against the collection - property and restrict the entity or component properties using that instance. - - - For queryng a collection of basic values, we still create the Criteria - object against the collection, but to reference the value, we use the special property - "elements". For an indexed collection, we can also reference the index property using - the special property "indices". - - - -
- -
- Example queries - - - The class org.hibernate.criterion.Example allows - you to construct a query criterion from a given instance. - - - - - - Version properties, identifiers and associations are ignored. By default, - null valued properties are excluded. - - - - You can adjust how the Example is applied. - - - - - - You can even use examples to place criteria upon associated objects. - - - - -
- -
- Projections, aggregation and grouping - - The class org.hibernate.criterion.Projections is a - factory for Projection instances. You can apply a - projection to a query by calling setProjection(). - - - - - - - - There is no explicit "group by" necessary in a criteria query. Certain - projection types are defined to be grouping projections, - which also appear in the SQL group by clause. - - - - An alias can be assigned to a projection so that the projected value - can be referred to in restrictions or orderings. Here are two different ways to - do this: - - - - - - - - The alias() and as() methods simply wrap a - projection instance in another, aliased, instance of Projection. - As a shortcut, you can assign an alias when you add the projection to a - projection list: - - - - - - - - You can also use Property.forName() to express projections: - - - - - - -
- -
- Detached queries and subqueries - - The DetachedCriteria class allows you to create a query outside the scope - of a session and then execute it using an arbitrary Session. - - - - - - A DetachedCriteria can also be used to express a subquery. Criterion - instances involving subqueries can be obtained via Subqueries or - Property. - - - - - - - - Correlated subqueries are also possible: - - - - - - Example of multi-column restriction based on a subquery: - - - - -
- - - -
- Queries by natural identifier - - - For most queries, including criteria queries, the query cache is not efficient - because query cache invalidation occurs too frequently. However, there is a special - kind of query where you can optimize the cache invalidation algorithm: lookups by a - constant natural key. In some applications, this kind of query occurs frequently. - The criteria API provides special provision for this use case. - - - - First, map the natural key of your entity using - <natural-id> and enable use of the second-level cache. - - - - - - - - - - - - -]]> - - - This functionality is not intended for use with entities with - mutable natural keys. - - - - Once you have enabled the Hibernate query cache, - the Restrictions.naturalId() allows you to make use of - the more efficient cache algorithm. - - - - -
- -
diff --git a/documentation/src/main/docbook/devguide-old/en-US/appendix-Configuration_Properties.xml b/documentation/src/main/docbook/devguide-old/en-US/appendix-Configuration_Properties.xml deleted file mode 100644 index d340b5e38b07..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/appendix-Configuration_Properties.xml +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - Configuration properties - -
- Strategy configurations - - Many configuration settings define pluggable strategies that Hibernate uses for various purposes. - The configuration of many of these strategy type settings accept definition in various forms. The - documentation of such configuration settings refer here. The types of forms available in such cases - include: - - - - short name (if defined) - - - Certain built-in strategy implementations have a corresponding short name. - - - - - strategy instance - - - An instance of the strategy implementation to use can be specified - - - - - strategy Class reference - - - A java.lang.Class reference of the strategy implementation to use can - be specified - - - - - strategy Class name - - - The class name (java.lang.String) of the strategy implementation to - use can be specified - - - - -
- - -
- General Configuration - - - - - - - - hibernate.dialect - A fully-qualified classname - - - The classname of a Hibernate org.hibernate.dialect.Dialect from which Hibernate - can generate SQL optimized for a particular relational database. - - - In most cases Hibernate can choose the correct org.hibernate.dialect.Dialect - implementation based on the JDBC metadata returned by the JDBC driver. - - - - - hibernate.show_sql - true or false - Write all SQL statements to the console. This is an alternative to setting the log category - org.hibernate.SQL to debug. - - - hibernate.format_sql - true or false - Pretty-print the SQL in the log and console. - - - hibernate.default_schema - A schema name - Qualify unqualified table names with the given schema or tablespace in generated SQL. - - - hibernate.default_catalog - A catalog name - Qualifies unqualified table names with the given catalog in generated SQL. - - - hibernate.session_factory_name - A JNDI name - The org.hibernate.SessionFactory is automatically bound to this name in JNDI - after it is created. - - - hibernate.max_fetch_depth - A value between 0 and 3 - Sets a maximum depth for the outer join fetch tree for single-ended associations. A single-ended - assocation is a one-to-one or many-to-one assocation. A value of 0 disables default outer - join fetching. - - - hibernate.default_batch_fetch_size - 4,8, or 16 - Default size for Hibernate batch fetching of associations. - - - hibernate.default_entity_mode - dynamic-map or pojo - Default mode for entity representation for all sessions opened from this - SessionFactory, defaults to pojo. - - - hibernate.order_updates - true or false - Forces Hibernate to order SQL updates by the primary key value of the items being updated. This - reduces the likelihood of transaction deadlocks in highly-concurrent systems. - - - hibernate.order_by.default_null_ordering - none, first or last - Defines precedence of null values in ORDER BY clause. Defaults to - none which varies between RDBMS implementation. - - - hibernate.generate_statistics - true or false - Causes Hibernate to collect statistics for performance tuning. - - - hibernate.use_identifier_rollback - true or false - If true, generated identifier properties are reset to default values when objects are - deleted. - - - hibernate.use_sql_comments - true or false - If true, Hibernate generates comments inside the SQL, for easier debugging. - - - - -
-
- Database configuration - - JDBC properties - - - - Property - Example - Purpose - - - - - hibernate.jdbc.fetch_size - 0 or an integer - A non-zero value determines the JDBC fetch size, by calling - Statement.setFetchSize(). - - - hibernate.jdbc.batch_size - A value between 5 and 30 - A non-zero value causes Hibernate to use JDBC2 batch updates. - - - hibernate.jdbc.batch_versioned_data - true or false - Set this property to true if your JDBC driver returns correct row counts - from executeBatch(). This option is usually safe, but is disabled by default. If - enabled, Hibernate uses batched DML for automatically versioned data. - - - hibernate.jdbc.factory_class - The fully-qualified class name of the factory - Select a custom org.hibernate.jdbc.Batcher. Irrelevant for most - applications. - - - hibernate.jdbc.use_scrollable_resultset - true or false - Enables Hibernate to use JDBC2 scrollable resultsets. This property is only relevant for - user-supplied JDBC connections. Otherwise, Hibernate uses connection metadata. - - - hibernate.jdbc.use_streams_for_binary - true or false - Use streams when writing or reading binary or serializable types to - or from JDBC. This is a system-level property. - - - hibernate.jdbc.use_get_generated_keys - true or false - Allows Hibernate to use JDBC3 PreparedStatement.getGeneratedKeys() to - retrieve natively-generated keys after insert. You need the JDBC3+ driver and JRE1.4+. Disable this property - if your driver has problems with the Hibernate identifier generators. By default, it tries to detect the - driver capabilities from connection metadata. - - - -
- - Cache Properties - - - - - - - Property - Example - Purpose - - - - - hibernate.cache.provider_class - Fully-qualified classname - The classname of a custom CacheProvider. - - - hibernate.cache.use_minimal_puts - true or false - Optimizes second-level cache operation to minimize writes, at the cost of more frequent reads. This - is most useful for clustered caches and is enabled by default for clustered cache implementations. - - - hibernate.cache.use_query_cache - true or false - Enables the query cache. You still need to set individual queries to be cachable. - - - hibernate.cache.use_second_level_cache true or - false Completely disable the second level cache, which is enabled - by default for classes which specify a <cache> mapping. - - - hibernate.cache.query_cache_factory - Fully-qualified classname - A custom QueryCache interface. The default is the built-in - StandardQueryCache. - - - hibernate.cache.region_prefix - A string - A prefix for second-level cache region names. - - - hibernate.cache.use_structured_entries - true or false - Forces Hibernate to store data in the second-level cache in a more human-readable format. - - - hibernate.cache.auto_evict_collection_cache - true or false (default: false) - Enables the automatic eviction of a bi-directional association's collection cache when an element - in the ManyToOne collection is added/updated/removed without properly managing the change on the OneToMany - side. - - - hibernate.cache.use_reference_entries - true or false - Optimizes second-level cache operation to store immutable entities (aka "reference") which do - not have associations into cache directly, this case, lots of disasseble and deep copy operations - can be avoid. - Default value of this property is false. - - - - -
- - Transactions properties - - - - - - - - Property - Example - Purpose - - - - - hibernate.transaction.factory_class - - jdbc or - - - - Names the org.hibernate.engine.transaction.spi.TransactionFactory - strategy implementation to use. See and - - - - - - jta.UserTransaction - A JNDI name - The JTATransactionFactory needs a JNDI name to obtain the JTA - UserTransaction from the application server. - - - hibernate.transaction.manager_lookup_class - A fully-qualified classname - The classname of a TransactionManagerLookup, which is used in - conjunction with JVM-level or the hilo generator in a JTA environment. - - - hibernate.transaction.flush_before_completion - true or false - Causes the session be flushed during the before completion phase of the - transaction. If possible, use built-in and automatic session context management instead. - - - hibernate.transaction.auto_close_session - true or false - Causes the session to be closed during the after completion phase of the - transaction. If possible, use built-in and automatic session context management instead. - - - -
- - - Each of the properties in the following table are prefixed by hibernate.. It has been removed - in the table to conserve space. - - - - Miscellaneous properties - - - - Property - Example - Purpose - - - - - current_session_context_class - One of jta, thread, managed, or - custom.Class - Supply a custom strategy for the scoping of the Current - Session. - - - factory_class - org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory or - org.hibernate.hql.internal.classic.ClassicQueryTranslatorFactory - Chooses the HQL parser implementation. - - - query.substitutions - hqlLiteral=SQL_LITERAL or hqlFunction=SQLFUNC - - Map from tokens in Hibernate queries to SQL tokens, such as function or literal names. - - - hbm2ddl.auto - validate, update, create, - create-drop - Validates or exports schema DDL to the database when the SessionFactory is - created. With create-drop, the database schema is dropped when the - SessionFactory is closed explicitly. - - - -
-
-
- Connection pool properties - - c3p0 connection pool properties - hibernate.c3p0.min_size - hibernate.c3p0.max_size - hibernate.c3p0.timeout - hibernate.c3p0.max_statements - - - Proxool connection pool properties - - - - - - Property - Description - - - - - hibernate.proxool.xml - Configure Proxool provider using an XML file (.xml is appended automatically) - - - hibernate.proxool.properties - Configure the Proxool provider using a properties file (.properties is appended - automatically) - - - hibernate.proxool.existing_pool - Whether to configure the Proxool provider from an existing pool - - - hibernate.proxool.pool_alias - Proxool pool alias to use. Required. - - - -
- - - For information on specific configuration of Proxool, refer to the Proxool documentation available from - . - - -
-
diff --git a/documentation/src/main/docbook/devguide-old/en-US/appendix-Troubleshooting.xml b/documentation/src/main/docbook/devguide-old/en-US/appendix-Troubleshooting.xml deleted file mode 100644 index 829c705f76b0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/appendix-Troubleshooting.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - Troubleshooting - -
- Log messages - - This section discusses certain log messages you might see from Hibernate and the "meaning" of those - messages. Specifically, it will discuss certain messages having a "message id", which for Hibernate - is always the code HHH followed by a numeric code. The table below is ordered - by this code. - - - Explanation of identified log messages - - - - Key - Explanation - - - - - HHH000002 - - - Indicates that a session was left associated with the - org.hibernate.context.internal.ThreadLocalSessionContext that is used - to implement thread-based current session management. Internally that class uses a - ThreadLocal, and in environments where Threads are pooled this could represent a - potential "bleed through" situation. Consider using a different - org.hibernate.context.spi.CurrentSessionContext - implementation. Otherwise, make sure the sessions always get unbound properly. - - - - - HHH000408 - - - Using workaround for JVM bug in java.sql.Timestamp. Certain - JVMs are known to have a bug in the implementation of - java.sql.Timestamp that causes the following condition to - evaluate to false: new Timestamp(x).getTime() != x. - A huge concern here is to make sure you are not using temporal based optimistic - locking on such JVMs. - - - - - -
-
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/events/Events.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/events/Events.xml deleted file mode 100644 index faefdf73c845..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/events/Events.xml +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - Interceptors and events - - - It is useful for the application to react to certain events that occur inside Hibernate. This allows for the - implementation of generic functionality and the extension of Hibernate functionality. - - -
- Interceptors - - - The org.hibernate.Interceptor interface provides callbacks from the session - to the application, allowing the application to inspect and/or manipulate properties of a persistent object - before it is saved, updated, deleted or loaded. One possible use for this is to track auditing information. - For example, the following example shows an Interceptor implementation - that automatically sets the createTimestamp property when an - Auditable entity is created and updates the - lastUpdateTimestamp property when an Auditable entity is - updated. - - - - - You can either implement Interceptor directly or extend - org.hibernate.EmptyInterceptor. - - - - - An Interceptor can be either Session-scoped or SessionFactory-scoped. - - - - A Session-scoped interceptor is specified when a session is opened. - - - - - - A SessionFactory-scoped interceptor is registered with the Configuration object - prior to building the SessionFactory. Unless a session is opened explicitly specifying the interceptor to - use, the SessionFactory-scoped interceptor will be applied to all sessions opened from that SessionFactory. - SessionFactory-scoped interceptors must be thread safe. Ensure that you do not store session-specific - states, since multiple sessions will use this interceptor potentially concurrently. - - - - -
- -
- Native Event system - - - If you have to react to particular events in the persistence layer, you can also use the Hibernate - event architecture. The event system can be used in place of or in addition to - interceptors. - - - - Many methods of the Session interface correlate to an event type. The - full range of defined event types is declared as enum values on - org.hibernate.event.spi.EventType. When a request is made of one of these methods, - the Session generates an appropriate event and passes it to the configured event listener(s) for that type. - Applications are free to implement a customization of one of the listener interfaces - (i.e., the LoadEvent is processed by the registered implementation - of the LoadEventListener interface), in which case their - implementation would be responsible for processing any load() requests - made of the Session. - - - - - See for information on registering custom event - listeners. - - - - - The listeners should be considered stateless; they are shared between requests, and should not save any - state as instance variables. - - - - A custom listener implements the appropriate interface for the event it wants to process and/or extend one - of the convenience base classes (or even the default event listeners used by Hibernate out-of-the-box as - these are declared non-final for this purpose). Here is an example of a custom load event listener: - - - - - Custom LoadListener example - - - - -
- Hibernate declarative security - - Usually, declarative security in Hibernate applications is managed in a session facade - layer. Hibernate allows certain actions to be permissioned via JACC, and authorized - via JAAS. This is an optional functionality that is built on top of the event architecture. - - - - First, you must configure the appropriate event listeners, to enable the use of JACC authorization. - Again, see for the details. Below is an example of an - appropriate org.hibernate.integrator.spi.Integrator implementation for - this purpose. - - - - - JACC listener registration example - - - - - - You must also decide how to configure your JACC provider. Consult your JACC provider documentation. - -
-
- -
- JPA Callbacks - - JPA also defines a more limited set of callbacks through annotations. - - - - Callback annotations - - - - - - Type - Description - - - - - - @PrePersist - - - Executed before the entity manager persist operation is actually executed or cascaded. - This call is synchronous with the persist operation. - - - - - @PreRemove - - - Executed before the entity manager remove operation is actually executed or cascaded. - This call is synchronous with the remove operation. - - - - - @PostPersist - - - Executed after the entity manager persist operation is actually executed or cascaded. - This call is invoked after the database INSERT is executed. - - - - - @PostRemove - - - Executed after the entity manager remove operation is actually executed or cascaded. - This call is synchronous with the remove operation. - - - - - @PreUpdate - - - Executed before the database UPDATE operation. - - - - - @PostUpdate - - - Executed after the database UPDATE operation. - - - - - @PostLoad - - - Executed after an entity has been loaded into the current persistence context or an entity - has been refreshed. - - - - -
- - - There are 2 available approaches defined for specifying callback handling: - - - - - The first approach is to annotate methods on the entity itself to receive notification of - particular entity life cycle event(s). - - - - - The second is to use a separate entity listener class. An entity listener is a stateless class - with a no-arg constructor. The callback annotations are placed on a method of this class instead - of the entity class. The entity listener class is then associated with the entity using the - javax.persistence.EntityListeners annotation - - - - - - Example of specifying JPA callbacks - - - - - These approaches can be mixed, meaning you can use both together. - - - Regardless of whether the callback method is defined on the entity or on an entity listener, it must have - a void-return signature. The name of the method is irrelevant as it is the placement of the callback - annotations that makes the method a callback. In the case of callback methods defined on the - entity class, the method must additionally have a no-argument signature. For callback methods defined on - an entity listener class, the method must have a single argument signature; the type of that argument can - be either java.lang.Object (to facilitate attachment to multiple entities) or the - specific entity type. - - - A callback method can throw a RuntimeException. If the callback method does - throw a RuntimeException, then the current transaction, if any, must be rolled back. - - - A callback method must not invoke EntityManager or - Query methods! - - - It is possible that multiple callback methods are defined for a particular lifecycle event. When that - is the case, the defined order of execution is well defined by the JPA spec (specifically section 3.5.4): - - - - - Any default listeners associated with the entity are invoked first, in the order they were - specified in the XML. See the javax.persistence.ExcludeDefaultListeners - annotation. - - - - - Next, entity listener class callbacks associated with the entity hierarchy are invoked, in the order - they are defined in the EntityListeners. If multiple classes in the - entity hierarchy define entity listeners, the listeners defined for a superclass are invoked before - the listeners defined for its subclasses. See the - javax.persistence.ExcludeSuperclassListeners annotation. - - - - - Lastly, callback methods defined on the entity hierarchy are invoked. If a callback type is - annotated on both an entity and one or more of its superclasses without method overriding, both - would be called, the most general superclass first. An entity class is also allowed to override - a callback method defined in a superclass in which case the super callback would not get invoked; - the overriding method would get invoked provided it is annotated. - - - -
- -
- diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/events/extras/AuditInterceptor.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/events/extras/AuditInterceptor.java deleted file mode 100644 index 76e8b5bde224..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/events/extras/AuditInterceptor.java +++ /dev/null @@ -1,79 +0,0 @@ -import java.io.Serializable; -import java.util.Date; - -import org.hibernate.EmptyInterceptor; -import org.hibernate.Transaction; -import org.hibernate.type.Type; - -public class AuditInterceptor extends EmptyInterceptor { - - private int updates; - private int creates; - private int loads; - - public void onDelete(Object entity, - Serializable id, - Object[] state, - String[] propertyNames, - Type[] types) { - // do nothing - } - - public boolean onFlushDirty(Object entity, - Serializable id, - Object[] currentState, - Object[] previousState, - String[] propertyNames, - Type[] types) { - - if ( entity instanceof Auditable ) { - updates++; - for ( int i=0; i < propertyNames.length; i++ ) { - if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) { - currentState[i] = new Date(); - return true; - } - } - } - return false; - } - - public boolean onLoad(Object entity, - Serializable id, - Object[] state, - String[] propertyNames, - Type[] types) { - if ( entity instanceof Auditable ) { - loads++; - } - return false; - } - - public boolean onSave(Object entity, - Serializable id, - Object[] state, - String[] propertyNames, - Type[] types) { - - if ( entity instanceof Auditable ) { - creates++; - for ( int i=0; i - - - - - Fetching - - - Fetching, essentially, is the process of grabbing data from the database and making it available to the - application. Tuning how an application does fetching is one of the biggest factors in determining how an - application will perform. Fetching too much data, in terms of width (values/columns) and/or - depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. - Fetching too little data causes additional fetches to be needed. Tuning how an application - fetches data presents a great opportunity to influence the application's overall performance. - - -
- The basics - - - The concept of fetching breaks down into two different questions. - - - - When should the data be fetched? Now? Later? - - - - - How should the data be fetched? - - - - - - - - "now" is generally termed eager or immediate. "later" is - generally termed lazy or delayed. - - - - - There are a number of scopes for defining fetching: - - - - static - Static definition of fetching strategies is done in the - mappings. The statically-defined fetch strategies is used in the absence of any dynamically - defined strategies Except in the case of HQL/JPQL; see xyz. - - - - - dynamic (sometimes referred to as runtime) - Dynamic definition is - really use-case centric. There are 2 main ways to define dynamic fetching: - - - - - fetch profiles - defined in mappings, but can be - enabled/disabled on the Session. - - - - - HQL/JPQL and both Hibernate and JPA Criteria queries have the ability to specify - fetching, specific to said query. - - - - - - - - - The strategies - - SELECT - - - Performs a separate SQL select to load the data. This can either be EAGER (the second select - is issued immediately) or LAZY (the second select is delayed until the data is needed). This - is the strategy generally termed N+1. - - - - - JOIN - - - Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of - an SQL join. - - - - - BATCH - - - Performs a separate SQL select to load a number of related data items using an - IN-restriction as part of the SQL WHERE-clause based on a batch size. Again, this can either - be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until - the data is needed). - - - - - SUBSELECT - - - Performs a separate SQL select to load associated data based on the SQL restriction used to - load the owner. Again, this can either be EAGER (the second select is issued immediately) - or LAZY (the second select is delayed until the data is needed). - - - - -
- -
- Applying fetch strategies - - - Let's consider these topics as it relates to an simple domain model and a few use cases. - - - - Sample domain model - - - - - - - - The Hibernate recommendation is to statically mark all associations lazy and to use dynamic fetching - strategies for eagerness. This is unfortunately at odds with the JPA specification which defines that - all one-to-one and many-to-one associations should be eagerly fetched by default. Hibernate, as a JPA - provider honors that default. - - - -
- No fetching - The login use-case - - For the first use case, consider the application's login process for an Employee. Lets assume that - login only requires access to the Employee information, not Project nor Department information. - - - - No fetching example - - - - - In this example, the application gets the Employee data. However, because all associations from - Employee are declared as LAZY (JPA defines the default for collections as LAZY) no other data is - fetched. - - - - If the login process does not need access to the Employee information specifically, another - fetching optimization here would be to limit the width of the query results. - - - - No fetching (scalar) example - - -
- -
- Dynamic fetching via queries - The projects for an employee use-case - - - For the second use case, consider a screen displaying the Projects for an Employee. Certainly access - to the Employee is needed, as is the collection of Projects for that Employee. Information - about Departments, other Employees or other Projects is not needed. - - - - Dynamic query fetching example - - - - - - In this example we have an Employee and their Projects loaded in a single query shown both as an HQL - query and a JPA Criteria query. In both cases, this resolves to exactly one database query to get - all that information. - -
- -
- Dynamic fetching via profiles - The projects for an employee use-case using natural-id - - - Suppose we wanted to leverage loading by natural-id to obtain the Employee information in the - "projects for and employee" use-case. Loading by natural-id uses the statically defined fetching - strategies, but does not expose a means to define load-specific fetching. So we would leverage a - fetch profile. - - - - Fetch profile example - - - - - - Here the Employee is obtained by natural-id lookup and the Employee's Project data is fetched eagerly. - If the Employee data is resolved from cache, the Project data is resolved on its own. However, - if the Employee data is not resolved in cache, the Employee and Project data is resolved in one - SQL query via join as we saw above. - -
-
- - - - -
\ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Department.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Department.java deleted file mode 100644 index 06eab06a51b9..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Department.java +++ /dev/null @@ -1,10 +0,0 @@ -@Entity -public class Department { - @Id - private Long id; - - @OneToMany(mappedBy="department") - private List employees; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Employee.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Employee.java deleted file mode 100644 index 62ed3e54e06b..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Employee.java +++ /dev/null @@ -1,24 +0,0 @@ -@Entity -public class Employee { - @Id - private Long id; - - @NaturalId - private String userid; - - @Column( name="pswd" ) - @ColumnTransformer( read="decrypt(pswd)" write="encrypt(?)" ) - private String password; - - private int accessLevel; - - @ManyToOne( fetch=LAZY ) - @JoinColumn - private Department department; - - @ManyToMany(mappedBy="employees") - @JoinColumn - private Set projects; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/FetchOverrides.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/FetchOverrides.java deleted file mode 100644 index 4144ea27dcfb..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/FetchOverrides.java +++ /dev/null @@ -1,10 +0,0 @@ -@FetchProfile( - name="employee.projects", - fetchOverrides={ - @FetchOverride( - entity=Employee.class, - association="projects", - mode=JOIN - ) - } -) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Login.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Login.java deleted file mode 100644 index f916bb847a86..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Login.java +++ /dev/null @@ -1,4 +0,0 @@ -String loginHql = "select e from Employee e where e.userid = :userid and e.password = :password"; -Employee employee = (Employee) session.createQuery( loginHql ) - ... - .uniqueResult(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/LoginScalar.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/LoginScalar.java deleted file mode 100644 index 8905b0ce4ad0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/LoginScalar.java +++ /dev/null @@ -1,4 +0,0 @@ -String loginHql = "select e.accessLevel from Employee e where e.userid = :userid and e.password = :password"; -Employee employee = (Employee) session.createQuery( loginHql ) - ... - .uniqueResult(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Project.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Project.java deleted file mode 100644 index 94fe42c0d54b..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/Project.java +++ /dev/null @@ -1,10 +0,0 @@ -@Entity -public class Project { - @Id - private Long id; - - @ManyToMany - private Set employees; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeCriteria.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeCriteria.java deleted file mode 100644 index 384d964e076d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeCriteria.java +++ /dev/null @@ -1,10 +0,0 @@ -String userid = ...; -CriteriaBuilder cb = entityManager.getCriteriaBuilder(); -CriteriaQuery criteria = cb.createQuery( Employee.class ); -Root root = criteria.from( Employee.class ); -root.fetch( Employee_.projects ); -criteria.select( root ); -criteria.where( - cb.equal( root.get( Employee_.userid ), cb.literal( userid ) ) -); -Employee e = entityManager.createQuery( criteria ).getSingleResult(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeFetchProfile.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeFetchProfile.java deleted file mode 100644 index 297cb8cfc631..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeFetchProfile.java +++ /dev/null @@ -1,4 +0,0 @@ -String userid = ...; -session.enableFetchProfile( "employee.projects" ); -Employee e = (Employee) session.bySimpleNaturalId( Employee.class ) - .load( userid ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeHql.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeHql.java deleted file mode 100644 index 11235281d064..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/fetching/extras/ProjectsForAnEmployeeHql.java +++ /dev/null @@ -1,5 +0,0 @@ -String userid = ...; -String hql = "select e from Employee e join fetch e.projects where e.userid = :userid"; -Employee e = (Employee) session.createQuery( hql ) - .setParameter( "userid", userid ) - .uniqueResult(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/Multi_Tenancy.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/Multi_Tenancy.xml deleted file mode 100644 index 94ee9ae1b941..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/Multi_Tenancy.xml +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - Multi-tenancy - -
- What is multi-tenancy? - - The term multi-tenancy in general is applied to software development to indicate an architecture in which - a single running instance of an application simultaneously serves multiple clients (tenants). This is - highly common in SaaS solutions. Isolating information (data, customizations, etc) pertaining to the - various tenants is a particular challenge in these systems. This includes the data owned by each tenant - stored in the database. It is this last piece, sometimes called multi-tenant data, on which we will focus. - -
- -
- Multi-tenant data approaches - - There are 3 main approaches to isolating information in these multi-tenant systems which goes hand-in-hand - with different database schema definitions and JDBC setups. - - - - - Each approach has pros and cons as well as specific techniques and considerations. Such - topics are beyond the scope of this documentation. Many resources exist which delve into these - other topics. One example is - which does a great job of covering these topics. - - - -
- Separate database - - - - - - - - - - - - Each tenant's data is kept in a physically separate database instance. JDBC Connections would point - specifically to each database, so any pooling would be per-tenant. A general application approach - here would be to define a JDBC Connection pool per-tenant and to select the pool to use based on the - tenant identifier associated with the currently logged in user. - -
- -
- Separate schema - - - - - - - - - - - - Each tenant's data is kept in a distinct database schema on a single database instance. There are 2 - different ways to define JDBC Connections here: - - - - Connections could point specifically to each schema, as we saw with the - Separate database approach. This is an option provided that - the driver supports naming the default schema in the connection URL or if the - pooling mechanism supports naming a schema to use for its Connections. Using this - approach, we would have a distinct JDBC Connection pool per-tenant where the pool to use - would be selected based on the tenant identifier associated with the - currently logged in user. - - - - - Connections could point to the database itself (using some default schema) but - the Connections would be altered using the SQL SET SCHEMA (or similar) - command. Using this approach, we would have a single JDBC Connection pool for use to - service all tenants, but before using the Connection it would be altered to reference - the schema named by the tenant identifier associated with the currently - logged in user. - - - - -
- -
- Partitioned (discriminator) data - - - - - - - - - - - - All data is kept in a single database schema. The data for each tenant is partitioned by the use of - partition value or discriminator. The complexity of this discriminator might range from a simple - column value to a complex SQL formula. Again, this approach would use a single Connection pool - to service all tenants. However, in this approach the application needs to alter each and every - SQL statement sent to the database to reference the tenant identifier discriminator. - -
-
- -
- Multi-tenancy in Hibernate - - Using Hibernate with multi-tenant data comes down to both an API and then integration piece(s). As - usual Hibernate strives to keep the API simple and isolated from any underlying integration complexities. - The API is really just defined by passing the tenant identifier as part of opening any session. - - - Specifying tenant identifier from <interfacename>SessionFactory</interfacename> - - - - Additionally, when specifying configuration, a org.hibernate.MultiTenancyStrategy - should be named using the hibernate.multiTenancy setting. Hibernate will perform - validations based on the type of strategy you specify. The strategy here correlates to the isolation - approach discussed above. - - - - NONE - - - (the default) No multi-tenancy is expected. In fact, it is considered an error if a tenant - identifier is specified when opening a session using this strategy. - - - - - SCHEMA - - - Correlates to the separate schema approach. It is an error to attempt to open a session without - a tenant identifier using this strategy. Additionally, a - MultiTenantConnectionProvider - must be specified. - - - - - DATABASE - - - Correlates to the separate database approach. It is an error to attempt to open a session without - a tenant identifier using this strategy. Additionally, a - MultiTenantConnectionProvider - must be specified. - - - - - DISCRIMINATOR - - - Correlates to the partitioned (discriminator) approach. It is an error to attempt to open a - session without a tenant identifier using this strategy. This strategy is not yet implemented - in Hibernate as of 4.0 and 4.1. Its support is planned for 5.0. - - - - - -
- <interfacename>MultiTenantConnectionProvider</interfacename> - - When using either the DATABASE or SCHEMA approach, Hibernate needs to be able to obtain Connections - in a tenant specific manner. That is the role of the - MultiTenantConnectionProvider - contract. Application developers will need to provide an implementation of this - contract. Most of its methods are extremely self-explanatory. The only ones which might not be are - getAnyConnection and releaseAnyConnection. It is - important to note also that these methods do not accept the tenant identifier. Hibernate uses these - methods during startup to perform various configuration, mainly via the - java.sql.DatabaseMetaData object. - - - The MultiTenantConnectionProvider to use can be specified in a number of - ways: - - - - - Use the hibernate.multi_tenant_connection_provider setting. It could - name a MultiTenantConnectionProvider instance, a - MultiTenantConnectionProvider implementation class reference or - a MultiTenantConnectionProvider implementation class name. - - - - - Passed directly to the org.hibernate.boot.registry.StandardServiceRegistryBuilder. - - - - - If none of the above options match, but the settings do specify a - hibernate.connection.datasource value, Hibernate will assume it should - use the specific - DataSourceBasedMultiTenantConnectionProviderImpl - implementation which works on a number of pretty reasonable assumptions when running inside of - an app server and using one javax.sql.DataSource per tenant. - See its javadocs for more details. - - - -
- -
- <interfacename>CurrentTenantIdentifierResolver</interfacename> - - org.hibernate.context.spi.CurrentTenantIdentifierResolver is a contract - for Hibernate to be able to resolve what the application considers the current tenant identifier. - The implementation to use is either passed directly to Configuration via its - setCurrentTenantIdentifierResolver method. It can also be specified via - the hibernate.tenant_identifier_resolver setting. - - - There are 2 situations where CurrentTenantIdentifierResolver is used: - - - - - The first situation is when the application is using the - org.hibernate.context.spi.CurrentSessionContext feature in - conjunction with multi-tenancy. In the case of the current-session feature, Hibernate will - need to open a session if it cannot find an existing one in scope. However, when a session - is opened in a multi-tenant environment the tenant identifier has to be specified. This is - where the CurrentTenantIdentifierResolver comes into play; - Hibernate will consult the implementation you provide to determine the tenant identifier to use - when opening the session. In this case, it is required that a - CurrentTenantIdentifierResolver be supplied. - - - - - The other situation is when you do not want to have to explicitly specify the tenant - identifier all the time as we saw in . If a - CurrentTenantIdentifierResolver has been specified, Hibernate - will use it to determine the default tenant identifier to use when opening the session. - - - - - Additionally, if the CurrentTenantIdentifierResolver implementation - returns true for its validateExistingCurrentSessions - method, Hibernate will make sure any existing sessions that are found in scope have a matching - tenant identifier. This capability is only pertinent when the - CurrentTenantIdentifierResolver is used in current-session settings. - -
- -
- Caching - - Multi-tenancy support in Hibernate works seamlessly with the Hibernate second level cache. The key - used to cache data encodes the tenant identifier. - -
- -
- Odds and ends - - Currently schema export will not really work with multi-tenancy. That may not change. - - - The JPA expert group is in the process of defining multi-tenancy support for the upcoming 2.1 - version of the specification. - -
-
- -
- Strategies for <interfacename>MultiTenantConnectionProvider</interfacename> implementors - - Implementing MultiTenantConnectionProvider using different connection pools - - - - The approach above is valid for the DATABASE approach. It is also valid for the SCHEMA approach - provided the underlying database allows naming the schema to which to connect in the connection URL. - - - Implementing MultiTenantConnectionProvider using single connection pool - - - - This approach is only relevant to the SCHEMA approach. - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-multi-cp.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-multi-cp.java deleted file mode 100644 index 56dcb7a9cda7..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-multi-cp.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Simplisitc implementation for illustration purposes supporting 2 hard coded providers (pools) and leveraging - * the support class {@link org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider} - */ -public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider { - private final ConnectionProvider acmeProvider = ConnectionProviderUtils.buildConnectionProvider( "acme" ); - private final ConnectionProvider jbossProvider = ConnectionProviderUtils.buildConnectionProvider( "jboss" ); - - @Override - protected ConnectionProvider getAnyConnectionProvider() { - return acmeProvider; - } - - @Override - protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) { - if ( "acme".equals( tenantIdentifier ) ) { - return acmeProvider; - } - else if ( "jboss".equals( tenantIdentifier ) ) { - return jbossProvider; - } - throw new HibernateException( "Unknown tenant identifier" ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-single-cp.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-single-cp.java deleted file mode 100644 index 6d41cfd2fd92..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-single-cp.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Simplisitc implementation for illustration purposes showing a single connection pool used to serve - * multiple schemas using "connection altering". Here we use the T-SQL specific USE command; Oracle - * users might use the ALTER SESSION SET SCHEMA command; etc. - */ -public class MultiTenantConnectionProviderImpl - implements MultiTenantConnectionProvider, Stoppable { - private final ConnectionProvider connectionProvider = ConnectionProviderUtils.buildConnectionProvider( "master" ); - - @Override - public Connection getAnyConnection() throws SQLException { - return connectionProvider.getConnection(); - } - - @Override - public void releaseAnyConnection(Connection connection) throws SQLException { - connectionProvider.closeConnection( connection ); - } - - @Override - public Connection getConnection(String tenantIdentifier) throws SQLException { - final Connection connection = getAnyConnection(); - try { - connection.createStatement().execute( "USE " + tenanantIdentifier ); - } - catch ( SQLException e ) { - throw new HibernateException( - "Could not alter JDBC connection to specified schema [" + - tenantIdentifier + "]", - e - ); - } - return connection; - } - - @Override - public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { - try { - connection.createStatement().execute( "USE master" ); - } - catch ( SQLException e ) { - // on error, throw an exception to make sure the connection is not returned to the pool. - // your requirements may differ - throw new HibernateException( - "Could not alter JDBC connection to specified schema [" + - tenantIdentifier + "]", - e - ); - } - connectionProvider.closeConnection( connection ); - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/tenant-identifier-from-SessionFactory.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/tenant-identifier-from-SessionFactory.java deleted file mode 100644 index bbc859be3f0e..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/extras/tenant-identifier-from-SessionFactory.java +++ /dev/null @@ -1,4 +0,0 @@ -Session session = sessionFactory.withOptions() - .tenantIdentifier( yourTenantIdentifier ) - ... - .openSession(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_database.png b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_database.png deleted file mode 100644 index 84822ec4e5b0..000000000000 Binary files a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_database.png and /dev/null differ diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_database.svg b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_database.svg deleted file mode 100644 index f0d9e7baa864..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_database.svg +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -Application - - - - - - - - - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_discriminator.png b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_discriminator.png deleted file mode 100644 index dcc3ad6ed9a1..000000000000 Binary files a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_discriminator.png and /dev/null differ diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_discriminator.svg b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_discriminator.svg deleted file mode 100644 index afb291332695..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_discriminator.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -TENANT_ID VARCHAR -) -Application - - - - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_schema.png b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_schema.png deleted file mode 100644 index 2756ba2ed605..000000000000 Binary files a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_schema.png and /dev/null differ diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_schema.svg b/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_schema.svg deleted file mode 100644 index 6fbb7a40e8d0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/multitenancy/images/multitenacy_schema.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -Application - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/osgi/OSGi.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/osgi/OSGi.xml deleted file mode 100644 index 90f5eb3f9c92..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/osgi/OSGi.xml +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - OSGi - - - The Open Services Gateway initiative (OSGi) specification describes a dynamic, modularized system. "Bundles" - (components) can be installed, activated, deactivated, and uninstalled during runtime, without requiring - a system restart. OSGi frameworks manage bundles' dependencies, packages, and classes. The framework - is also in charge of ClassLoading, managing visibility of packages between bundles. Further, service - registry and discovery is provided through a "whiteboard" pattern. - - - - OSGi environments present numerous, unique challenges. Most notably, the dynamic nature of available - bundles during runtime can require significant architectural considerations. Also, - architectures must allow the OSGi-specific ClassLoading and service registration/discovery. - - - - -
- OSGi Specification and Environment - - - Hibernate targets the OSGi 4.3 spec or later. It was necessary to start with 4.3, over 4.2, due to our - dependency on OSGi's BundleWiring for entity/mapping scanning. - - - - Hibernate supports three types of configurations within OSGi. - - - - Container-Managed JPA: - - - Unmanaged JPA: - - - Unmanaged Native: - - - -
- -
- hibernate-osgi - - - Rather than embed OSGi capabilities into hibernate-core, hibernate-entitymanager, and sub-modules, - hibernate-osgi was created. It's purposefully separated, isolating all OSGi dependencies. It provides an - OSGi-specific ClassLoader (aggregates the container's CL with core and entitymanager CLs), JPA persistence - provider, SF/EMF bootstrapping, entities/mappings scanner, and service management. - -
- -
- Container-Managed JPA - - - The Enterprise OSGi specification includes container-managed JPA. The container is responsible for - discovering persistence units and creating the EntityManagerFactory (one EMF per PU). - It uses the JPA provider (hibernate-osgi) that has registered itself with the OSGi - PersistenceProvider service. - - - - Quickstart tutorial project, demonstrating a container-managed JPA client bundle: - managed-jpa - - -
- Client bundle imports - - Your client bundle's manifest will need to import, at a minimum, - - - javax.persistence - - - - org.hibernate.proxy and javassist.util.proxy, due to - Hibernate's ability to return proxies for lazy initialization (Javassist enhancement - occurs on the entity's ClassLoader during runtime). - - - - -
- -
- JPA 2.1 - - - No Enterprise OSGi JPA container currently supports JPA 2.1 (the spec is not yet released). For - testing, the managed-jpa example makes use of - Brett's fork of Aries. To work - with Hibernate 4.3, clone the fork and build Aries JPA. - -
- -
- DataSource - - Typical Enterprise OSGi JPA usage includes a DataSource installed in the container. The client - bundle's persistence.xml uses the DataSource through JNDI. For an example, - see the QuickStart's DataSource: - datasource-h2.xml - The DataSource is then called out in - - persistence.xml's jta-data-source. - -
- -
- Bundle Ordering - - Hibernate currently requires fairly specific bundle activation ordering. See the managed-jpa - QuickStart's - features.xml - for the best supported sequence. - -
- -
- Obtaining an EntityManger - - The easiest, and most supported, method of obtaining an EntityManager utilizes OSGi's - blueprint.xml. The container takes the name of your persistence unit, then injects - an EntityManager instance into your given bean attribute. See the - dpService bean in the managed-jpa QuickStart's - blueprint.xml - for an example. - -
-
- -
- Unmanaged JPA - - - Hibernate also supports the use of JPA through hibernate-entitymanager, unmanaged by the OSGi - container. The client bundle is responsible for managing the EntityManagerFactory and EntityManagers. - - - - Quickstart tutorial project, demonstrating an unmanaged JPA client bundle: - unmanaged-jpa - - -
- Client bundle imports - - Your client bundle's manifest will need to import, at a minimum, - - - javax.persistence - - - - org.hibernate.proxy and javassist.util.proxy, due to - Hibernate's ability to return proxies for lazy initialization (Javassist enhancement - occurs on the entity's ClassLoader during runtime) - - - - - JDBC driver package (example: org.h2) - - - - - org.osgi.framework, necessary to discover the EMF (described below) - - - - -
- -
- Bundle Ordering - - Hibernate currently requires fairly specific bundle activation ordering. See the unmanaged-jpa - QuickStart's - features.xml - for the best supported sequence. - -
- -
- Obtaining an EntityMangerFactory - - hibernate-osgi registers an OSGi service, using the JPA PersistenceProvider interface - name, that bootstraps and creates an EntityManagerFactory specific for OSGi - environments. It is VITAL that your EMF be obtained through the service, rather than creating it - manually. The service handles the OSGi ClassLoader, discovered extension points, scanning, etc. Manually - creating an EntityManagerFactory is guaranteed to NOT work during runtime! - - - For an example on how to discover and use the service, see the unmanaged-jpa - QuickStart's - HibernateUtil.java. - -
-
- -
- Unmanaged Native - - - Native Hibernate use is also supported. The client bundle is responsible for managing the - SessionFactory and Sessions. - - - - Quickstart tutorial project, demonstrating an unmanaged native client bundle: - unmanaged-native - - -
- Client bundle imports - - Your client bundle's manifest will need to import, at a minimum, - - - javax.persistence - - - - org.hibernate.proxy and javassist.util.proxy, due to - Hibernate's ability to return proxies for lazy initialization (Javassist enhancement - occurs on the entity's ClassLoader during runtime) - - - - - JDBC driver package (example: org.h2) - - - - - org.osgi.framework, necessary to discover the SF (described below) - - - - - org.hibernate.* packages, as necessary (ex: cfg, criterion, service, etc.) - - - - -
- -
- Bundle Ordering - - Hibernate currently requires fairly specific bundle activation ordering. See the unmanaged-native - QuickStart's - features.xml - for the best supported sequence. - -
- -
- Obtaining an SessionFactory - - hibernate-osgi registers an OSGi service, using the SessionFactory interface - name, that bootstraps and creates an SessionFactory specific for OSGi - environments. It is VITAL that your SF be obtained through the service, rather than creating it - manually. The service handles the OSGi ClassLoader, discovered extension points, scanning, etc. Manually - creating an SessionFactory is guaranteed to NOT work during runtime! - - - For an example on how to discover and use the service, see the unmanaged-native - QuickStart's - HibernateUtil.java. - -
-
- -
- Optional Modules - - - The unmanaged-native - QuickStart project demonstrates the use of optional Hibernate modules. Each module adds additional - dependency bundles that must first be activated - (see features.xml). - As of ORM 4.2, Envers is fully supported. Support for C3P0, Proxool, EhCache, and Infinispan were added in - 4.3, however none of their 3rd party libraries currently work in OSGi (lots of ClassLoader problems, etc.). - We're tracking the issues in JIRA. - -
- -
- Extension Points - - - Multiple contracts exist to allow applications to integrate with and extend Hibernate capabilities. Most - apps utilize JDK services to provide their implementations. hibernate-osgi supports the same - extensions through OSGi services. Implement and register them in any of the three configurations. - hibernate-osgi will discover and integrate them during EMF/SF bootstrapping. Supported extension points - are as follows. The specified interface should be used during service registration. - - - - org.hibernate.integrator.spi.Integrator (as of 4.2) - - - org.hibernate.boot.registry.selector.StrategyRegistrationProvider (as of 4.3) - - - org.hibernate.boot.model.TypeContributor (as of 4.3) - - - JTA's javax.transaction.TransactionManager and - javax.transaction.UserTransaction (as of 4.2), however these are typically - provided by the OSGi container. - - - - - - The easiest way to register extension point implementations is through a blueprint.xml - file. Add OSGI-INF/blueprint/blueprint.xml to your classpath. Envers' blueprint - is a great example: - - - - Example extension point registrations in blueprint.xml - - - - - Extension points can also be registered programmatically with - BundleContext#registerService, typically within your - BundleActivator#start. - -
- -
- Caveats - - - - - Technically, multiple persistence units are supported by Enterprise OSGi JPA and unmanaged - Hibernate JPA use. However, we cannot currently support this in OSGi. In Hibernate 4, only one - instance of the OSGi-specific ClassLoader is used per Hibernate bundle, mainly due to heavy use of - static TCCL utilities. We hope to support one OSGi ClassLoader per persistence unit in - Hibernate 5. - - - - - Scanning is supported to find non-explicitly listed entities and mappings. However, they MUST be - in the same bundle as your persistence unit (fairly typical anyway). Our OSGi ClassLoader only - considers the "requesting bundle" (hence the requirement on using services to create EMF/SF), - rather than attempting to scan all available bundles. This is primarily for versioning - considerations, collision protections, etc. - - - - - Some containers (ex: Aries) always return true for - PersistenceUnitInfo#excludeUnlistedClasses, - even if your persistence.xml explicitly has exclude-unlisted-classes set - to false. They claim it's to protect JPA providers from having to implement - scanning ("we handle it for you"), even though we still want to support it in many cases. The work - around is to set hibernate.archive.autodetection to, for example, - hbm,class. This tells hibernate to ignore the excludeUnlistedClasses value and - scan for *.hbm.xml and entities regardless. - - - - - Scanning does not currently support annotated packages on package-info.java. - - - - - Currently, Hibernate OSGi is primarily tested using Apache Karaf and Apache Aries JPA. Additional - testing is needed with Equinox, Gemini, and other container providers. - - - - - Hibernate ORM has many dependencies that do not currently provide OSGi manifests. - The QuickStart tutorials make heavy use of 3rd party bundles (SpringSource, ServiceMix) or the - wrap:... operator. - - - - - As previously mentioned, bundle activation is currently order specific. See the QuickStart - tutorials' features.xml for example sequences. - - - - - No Enterprise OSGi JPA container currently supports JPA 2.1 (the spec is not yet released). For - testing, the managed-jpa example makes use of - Brett's fork of Aries. To work - with Hibernate 4.3, clone the fork and build Aries JPA. - - - -
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/osgi/extras/extension_point_blueprint.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/osgi/extras/extension_point_blueprint.xml deleted file mode 100644 index e9a44529148f..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/osgi/extras/extension_point_blueprint.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/Persistence_Context.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/Persistence_Context.xml deleted file mode 100644 index ef4f98fafe35..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/Persistence_Context.xml +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - Persistence Contexts - - - - Both the org.hibernate.Session API and - javax.persistence.EntityManager API represent a context for dealing with - persistent data. This concept is called a persistence context. Persistent data has a - state in relation to both a persistence context and the underlying database. - - - - Entity states - - - new, or transient - the entity has just been instantiated and is - not associated with a persistence context. It has no persistent representation in the database and no - identifier value has been assigned. - - - - - managed, or persistent - the entity has an associated identifier - and is associated with a persistence context. - - - - - detached - the entity has an associated identifier, but is no longer associated with - a persistence context (usually because the persistence context was closed or the instance was evicted - from the context) - - - - - removed - the entity has an associated identifier and is associated with a persistence - context, however it is scheduled for removal from the database. - - - - - - - - In Hibernate native APIs, the persistence context is defined as the - org.hibernate.Session. In JPA, the persistence context is defined by - javax.persistence.EntityManager. Much of the - org.hibernate.Session and - javax.persistence.EntityManager methods deal with moving entities between these - states. - - -
- Making entities persistent - - - Once you've created a new entity instance (using the standard new operator) it is in - new state. You can make it persistent by associating it to either a - org.hibernate.Session or - javax.persistence.EntityManager - - - - Example of making an entity persistent - - - - - - org.hibernate.Session also has a method named persist - which follows the exact semantic defined in the JPA specification for the persist - method. It is this method on org.hibernate.Session to which the - Hibernate javax.persistence.EntityManager implementation delegates. - - - - If the DomesticCat entity type has a generated identifier, the value is associated - to the instance when the save or persist is called. If the - identifier is not automatically generated, the application-assigned (usually natural) key value has to be - set on the instance before save or persist is called. - -
- -
- Deleting entities - - Entities can also be deleted. - - - Example of deleting an entity - - - - - It is important to note that Hibernate itself can handle deleting detached state. JPA, however, disallows - it. The implication here is that the entity instance passed to the - org.hibernate.Session delete method can be either - in managed or detached state, while the entity instance passed to remove on - javax.persistence.EntityManager must be in managed state. - -
- -
- Obtain an entity reference without initializing its data - - Sometimes referred to as lazy loading, the ability to obtain a reference to an entity without having to - load its data is hugely important. The most common case being the need to create an association between - an entity and another, existing entity. - - - Example of obtaining an entity reference without initializing its data - - - - - The above works on the assumption that the entity is defined to allow lazy loading, generally through - use of runtime proxies. For more information see . In both - cases an exception will be thrown later if the given entity does not refer to actual database state if and - when the application attempts to use the returned proxy in any way that requires access to its data. - -
- -
- Obtain an entity with its data initialized - - - It is also quite common to want to obtain an entity along with with its data, for display for example. - - - Example of obtaining an entity reference with its data initialized - - - - - In both cases null is returned if no matching database row was found. - -
- -
- Obtain an entity by natural-id - - - In addition to allowing to load by identifier, Hibernate allows applications to load by declared - natural identifier. - - - Example of simple natural-id access - - - - Example of natural-id access - - - - Just like we saw above, access entity data by natural id allows both the load - and getReference forms, with the same semantics. - - - - Accessing persistent data by identifier and by natural-id is consistent in the Hibernate API. Each defines - the same 2 data access methods: - - - - getReference - - - Should be used in cases where the identifier is assumed to exist, where non-existence would be - an actual error. Should never be used to test existence. That is because this method will - prefer to create and return a proxy if the data is not already associated with the Session - rather than hit the database. The quintessential use-case for using this method is to create - foreign-key based associations. - - - - - load - - - Will return the persistent data associated with the given identifier value or null if that - identifier does not exist. - - - - - - In addition to those 2 methods, each also defines the method with accepting - a org.hibernate.LockOptions argument. Locking is discussed in a separate - chapter. - -
- -
- Refresh entity state - - - You can reload an entity instance and it's collections at any time. - - - - Example of refreshing entity state - - - - - - One case where this is useful is when it is known that the database state has changed since the data was - read. Refreshing allows the current database state to be pulled into the entity instance and the - persistence context. - - - - Another case where this might be useful is when database triggers are used to initialize some of the - properties of the entity. Note that only the entity instance and its collections are refreshed unless you - specify REFRESH as a cascade style of any associations. However, please note that - Hibernate has the capability to handle this automatically through its notion of generated properties. - See for information. - -
- -
- Modifying managed/persistent state - - - Entities in managed/persistent state may be manipulated by the application and any changes will be - automatically detected and persisted when the persistence context is flushed. There is no need to call a - particular method to make your modifications persistent. - - - - Example of modifying managed state - - - -
- -
- Working with detached data - - - Detachment is the process of working with data outside the scope of any persistence context. Data becomes - detached in a number of ways. Once the persistence context is closed, all data that was associated with it - becomes detached. Clearing the persistence context has the same effect. Evicting a particular entity - from the persistence context makes it detached. And finally, serialization will make the deserialized form - be detached (the original instance is still managed). - - - - Detached data can still be manipulated, however the persistence context will no longer automatically know - about these modification and the application will need to intervene to make the changes persistent. - - -
- Reattaching detached data - - Reattachment is the process of taking an incoming entity instance that is in detached state - and re-associating it with the current persistence context. - - - - JPA does not provide for this model. This is only available through Hibernate - org.hibernate.Session. - - - - Example of reattaching a detached entity - - - - - The method name update is a bit misleading here. It does not mean that an - SQL UPDATE is immediately performed. It does, however, mean that - an SQL UPDATE will be performed when the persistence context is - flushed since Hibernate does not know its previous state against which to compare for changes. Unless - the entity is mapped with select-before-update, in which case Hibernate will - pull the current state from the database and see if an update is needed. - - - Provided the entity is detached, update and saveOrUpdate - operate exactly the same. - -
- -
- Merging detached data - - Merging is the process of taking an incoming entity instance that is in detached state and copying its - data over onto a new instance that is in managed state. - - - Visualizing merge - - - - That is not exactly what happens, but its a good visualization. - - - Example of merging a detached entity - - - -
- -
- - -
- Checking persistent state - - - An application can verify the state of entities and collections in relation to the persistence context. - - - Examples of verifying managed state - - - - - Examples of verifying laziness - - - - - In JPA there is an alternative means to check laziness using the following - javax.persistence.PersistenceUtil pattern. However, the - javax.persistence.PersistenceUnitUtil is recommended where ever possible - - - Alternative JPA means to verify laziness - - - -
- -
- Accessing Hibernate APIs from JPA - - JPA defines an incredibly useful method to allow applications access to the APIs of the underlying provider. - - - Usage of EntityManager.unwrap - - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithHibernate.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithHibernate.java deleted file mode 100644 index 1c166cdca096..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithHibernate.java +++ /dev/null @@ -1,9 +0,0 @@ -if ( Hibernate.isInitialized( customer.getAddress() ) { - //display address if loaded -} -if ( Hibernate.isInitialized( customer.getOrders()) ) ) { - //display orders if loaded -} -if (Hibernate.isPropertyInitialized( customer, "detailedBio" ) ) { - //display property detailedBio if loaded -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithJPA.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithJPA.java deleted file mode 100644 index 50751d25d3f0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithJPA.java +++ /dev/null @@ -1,10 +0,0 @@ -javax.persistence.PersistenceUnitUtil jpaUtil = entityManager.getEntityManagerFactory().getPersistenceUnitUtil(); -if ( jpaUtil.isLoaded( customer.getAddress() ) { - //display address if loaded -} -if ( jpaUtil.isLoaded( customer.getOrders()) ) ) { - //display orders if loaded -} -if (jpaUtil.isLoaded( customer, "detailedBio" ) ) { - //display property detailedBio if loaded -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithJPA2.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithJPA2.java deleted file mode 100644 index 9581b5d0e266..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/CheckingLazinessWithJPA2.java +++ /dev/null @@ -1,10 +0,0 @@ -javax.persistence.PersistenceUtil jpaUtil = javax.persistence.Persistence.getPersistenceUtil(); -if ( jpaUtil.isLoaded( customer.getAddress() ) { - //display address if loaded -} -if ( jpaUtil.isLoaded( customer.getOrders()) ) ) { - //display orders if loaded -} -if (jpaUtil.isLoaded(customer, "detailedBio") ) { - //display property detailedBio if loaded -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ContainsWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ContainsWithEM.java deleted file mode 100644 index 12aaaff765e4..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ContainsWithEM.java +++ /dev/null @@ -1 +0,0 @@ -assert entityManager.contains( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ContainsWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ContainsWithSession.java deleted file mode 100644 index acb40fed3ef7..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ContainsWithSession.java +++ /dev/null @@ -1 +0,0 @@ -assert session.contains( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/DeletingWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/DeletingWithEM.java deleted file mode 100644 index 0938f5a3bf28..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/DeletingWithEM.java +++ /dev/null @@ -1 +0,0 @@ -entityManager.remove( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/DeletingWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/DeletingWithSession.java deleted file mode 100644 index 25831ee34690..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/DeletingWithSession.java +++ /dev/null @@ -1 +0,0 @@ -session.delete( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/GetReferenceWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/GetReferenceWithEM.java deleted file mode 100644 index e45609a42f0d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/GetReferenceWithEM.java +++ /dev/null @@ -1,2 +0,0 @@ -Book book = new Book(); -book.setAuthor( entityManager.getReference( Author.class, authorId ) ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/GetReferenceWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/GetReferenceWithSession.java deleted file mode 100644 index d789191dc953..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/GetReferenceWithSession.java +++ /dev/null @@ -1,2 +0,0 @@ -Book book = new Book(); -book.setAuthor( session.byId( Author.class ).getReference( authorId ) ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/LoadWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/LoadWithEM.java deleted file mode 100644 index 3c3e56f9ff1f..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/LoadWithEM.java +++ /dev/null @@ -1 +0,0 @@ -entityManager.find( Author.class, authorId ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/LoadWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/LoadWithSession.java deleted file mode 100644 index d9801ef3e694..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/LoadWithSession.java +++ /dev/null @@ -1 +0,0 @@ -session.byId( Author.class ).load( authorId ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MakingPersistentWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MakingPersistentWithEM.java deleted file mode 100644 index fc6d6f92bb34..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MakingPersistentWithEM.java +++ /dev/null @@ -1,5 +0,0 @@ -DomesticCat fritz = new DomesticCat(); -fritz.setColor( Color.GINGER ); -fritz.setSex( 'M' ); -fritz.setName( "Fritz" ); -entityManager.persist( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MakingPersistentWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MakingPersistentWithSession.java deleted file mode 100644 index 05b85c02ff21..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MakingPersistentWithSession.java +++ /dev/null @@ -1,5 +0,0 @@ -DomesticCat fritz = new DomesticCat(); -fritz.setColor( Color.GINGER ); -fritz.setSex( 'M' ); -fritz.setName( "Fritz" ); -session.save( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ManagedUpdateWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ManagedUpdateWithEM.java deleted file mode 100644 index 49544b6502e6..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ManagedUpdateWithEM.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = entityManager.find( Cat.class, catId ); -cat.setName( "Garfield" ); -entityManager.flush(); // generally this is not explicitly needed \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ManagedUpdateWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ManagedUpdateWithSession.java deleted file mode 100644 index 5e707244c238..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ManagedUpdateWithSession.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = session.get( Cat.class, catId ); -cat.setName( "Garfield" ); -session.flush(); // generally this is not explicitly needed \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MergeWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MergeWithEM.java deleted file mode 100644 index 340af5cddfaf..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MergeWithEM.java +++ /dev/null @@ -1 +0,0 @@ -Cat theManagedInstance = entityManager.merge( someDetachedCat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MergeWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MergeWithSession.java deleted file mode 100644 index adfad9d69e76..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/MergeWithSession.java +++ /dev/null @@ -1 +0,0 @@ -Cat theManagedInstance = session.merge( someDetachedCat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/NaturalIdLoading.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/NaturalIdLoading.java deleted file mode 100644 index cd5cd431253b..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/NaturalIdLoading.java +++ /dev/null @@ -1,31 +0,0 @@ -import java.lang.String; - -@Entity -public class User { - @Id - @GeneratedValue - Long id; - - @NaturalId - String system; - - @NaturalId - String userName; - - ... -} - -// use getReference() to create associations... -Resource aResource = (Resource) session.byId( Resource.class ).getReference( 123 ); -User aUser = (User) session.byNaturalId( User.class ) - .using( "system", "prod" ) - .using( "userName", "steve" ) - .getReference(); -aResource.assignTo( user ); - - -// use load() to pull initialzed data -return session.byNaturalId( User.class ) - .using( "system", "prod" ) - .using( "userName", "steve" ) - .load(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ReattachingWithSession2.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ReattachingWithSession2.java deleted file mode 100644 index a26c2c98b073..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/ReattachingWithSession2.java +++ /dev/null @@ -1 +0,0 @@ -session.saveOrUpdate( someDetachedCat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/RefreshWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/RefreshWithEM.java deleted file mode 100644 index 8258af31e674..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/RefreshWithEM.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = entityManager.find( Cat.class, catId ); -... -entityManager.refresh( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/RefreshWithSession.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/RefreshWithSession.java deleted file mode 100644 index 3436fe3f881f..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/RefreshWithSession.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = session.get( Cat.class, catId ); -... -session.refresh( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/SimpleNaturalIdLoading.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/SimpleNaturalIdLoading.java deleted file mode 100644 index a07ecf789bb0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/SimpleNaturalIdLoading.java +++ /dev/null @@ -1,20 +0,0 @@ -@Entity -public class User { - @Id - @GeneratedValue - Long id; - - @NaturalId - String userName; - - ... -} - -// use getReference() to create associations... -Resource aResource = (Resource) session.byId( Resource.class ).getReference( 123 ); -User aUser = (User) session.bySimpleNaturalId( User.class ).getReference( "steve" ); -aResource.assignTo( user ); - - -// use load() to pull initialzed data -return session.bySimpleNaturalId( User.class ).load( "steve" ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/UnwrapWithEM.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/UnwrapWithEM.java deleted file mode 100644 index d0c4fac2aa17..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/UnwrapWithEM.java +++ /dev/null @@ -1,2 +0,0 @@ -Session session = entityManager.unwrap( Session.class ); -SessionImplementor sessionImplementor = entityManager.unwrap( SessionImplementor.class ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/VisualizingMerge.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/VisualizingMerge.java deleted file mode 100644 index 3f8697a4aef1..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/pc/extras/VisualizingMerge.java +++ /dev/null @@ -1,5 +0,0 @@ -Object detached = ...; -Object managed = entityManager.find( detached.getClass(), detached.getId() ); -managed.setXyz( detached.getXyz() ); -... -return managed; \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/Criteria.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/Criteria.xml deleted file mode 100644 index db2020d2b67c..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/Criteria.xml +++ /dev/null @@ -1,414 +0,0 @@ - - - - - - Criteria - - - Criteria queries offer a type-safe alternative to HQL, JPQL and native-sql queries. - - - - - Hibernate offers an older, legacy org.hibernate.Criteria API which should be - considered deprecated. No feature development will target those APIs. Eventually, Hibernate-specific - criteria features will be ported as extensions to the JPA - javax.persistence.criteria.CriteriaQuery. For details on the - org.hibernate.Criteria API, see . - - - This chapter will focus on the JPA APIs for declaring type-safe criteria queries. - - - - - - Criteria queries are a programmatic, type-safe way to express a query. They are type-safe in terms of - using interfaces and classes to represent various structural parts of a query such as the query itself, - or the select clause, or an order-by, etc. They can also be type-safe in terms of referencing attributes - as we will see in a bit. Users of the older Hibernate org.hibernate.Criteria - query API will recognize the general approach, though we believe the JPA API to be superior - as it represents a clean look at the lessons learned from that API. - - - - Criteria queries are essentially an object graph, where each part of the graph represents an increasing - (as we navigate down this graph) more atomic part of query. The first step in performing a criteria query - is building this graph. The javax.persistence.criteria.CriteriaBuilder - interface is the first thing with which you need to become acquainted to begin using criteria queries. Its - role is that of a factory for all the individual pieces of the criteria. You obtain a - javax.persistence.criteria.CriteriaBuilder instance by calling the - getCriteriaBuilder method of either - javax.persistence.EntityManagerFactory or - javax.persistence.EntityManager. - - - - The next step is to obtain a javax.persistence.criteria.CriteriaQuery. This - is accomplished using one of the 3 methods on - javax.persistence.criteria.CriteriaBuilder for this purpose: - - - - - - Each serves a different purpose depending on the expected type of the query results. - - - - - Chapter 6 Criteria API of the JPA Specification - already contains a decent amount of reference material pertaining to the various parts of a - criteria query. So rather than duplicate all that content here, lets instead look at some of - the more widely anticipated usages of the API. - - - -
- Typed criteria queries - - - The type of the criteria query (aka the ]]>) indicates the expected types in the query - result. This might be an entity, an Integer, or any other object. - - -
- Selecting an entity - - - This is probably the most common form of query. The application wants to select entity instances. - - - - Selecting the root entity - - - - - The example uses createQuery passing in the Person - class reference as the results of the query will be Person objects. - - - - - The call to the CriteriaQuery.select method in this example is - unnecessary because personRoot will be the implied selection since we - have only a single query root. It was done here only for completeness of an example. - - - The Person_.eyeColor reference is an example of the static form of JPA - metamodel reference. We will use that form exclusively in this chapter. See - the documentation for the Hibernate JPA Metamodel Generator for additional details on - the JPA static metamodel. - - -
- -
- Selecting an expression - - - The simplest form of selecting an expression is selecting a particular attribute from an entity. - But this expression might also represent an aggregation, a mathematical operation, etc. - - - - Selecting an attribute - - - - - In this example, the query is typed as java.lang.Integer because that - is the anticipated type of the results (the type of the Person#age attribute - is java.lang.Integer). Because a query might contain multiple references to - the Person entity, attribute references always need to be qualified. This is accomplished by the - Root#get method call. - -
- - -
- Selecting multiple values - - - There are actually a few different ways to select multiple values using criteria queries. We - will explore 2 options here, but an alternative recommended approach is to use tuples as described in - . Or consider a wrapper query; see - for details. - - - - Selecting an array - - - - - Technically this is classified as a typed query, but you can see from handling the results that - this is sort of misleading. Anyway, the expected result type here is an array. - - - - The example then uses the array method of - javax.persistence.criteria.CriteriaBuilder which explicitly - combines individual selections into a - javax.persistence.criteria.CompoundSelection. - - - - Selecting an array (2) - - - - - Just as we saw in we have a typed criteria - query returning an Object array. Both queries are functionally equivalent. This second example - uses the multiselect method which behaves slightly differently based on - the type given when the criteria query was first built, but in this case it says to select and - return an Object[]. - -
- -
- Selecting a wrapper - - Another alternative to is to instead - select an object that will wrap the multiple values. Going back to the example - query there, rather than returning an array of [Person#id, Person#age] - instead declare a class that holds these values and instead return that. - - - - Selecting an wrapper - - - - - First we see the simple definition of the wrapper object we will be using to wrap our result - values. Specifically notice the constructor and its argument types. Since we will be returning - PersonWrapper objects, we use PersonWrapper as the - type of our criteria query. - - - - This example illustrates the use of the - javax.persistence.criteria.CriteriaBuilder method - construct which is used to build a wrapper expression. For every row in the - result we are saying we would like a PersonWrapper instantiated with - the remaining arguments by the matching constructor. This wrapper expression is then passed as - the select. - -
-
- -
- Tuple criteria queries - - - A better approach to is to use either a - wrapper (which we just saw in ) or using the - javax.persistence.Tuple contract. - - - - Selecting a tuple - - - - - This example illustrates accessing the query results through the - javax.persistence.Tuple interface. The example uses the explicit - createTupleQuery of - javax.persistence.criteria.CriteriaBuilder. An alternate approach - is to use createQuery passing Tuple.class. - - - - Again we see the use of the multiselect method, just like in - . The difference here is that the type of the - javax.persistence.criteria.CriteriaQuery was defined as - javax.persistence.Tuple so the compound selections in this case are - interpreted to be the tuple elements. - - - - The javax.persistence.Tuple contract provides 3 forms of access to - the underlying elements: - - - - - typed - - - The example illustrates this form of access - in the tuple.get( idPath ) and tuple.get( agePath ) calls. - This allows typed access to the underlying tuple values based on the - javax.persistence.TupleElement expressions used to build - the criteria. - - - - - positional - - - Allows access to the underlying tuple values based on the position. The simple - Object get(int position) form is very similar to the access - illustrated in and - . The - X get(int position, Class type]]> form - allows typed positional access, but based on the explicitly supplied type which the tuple - value must be type-assignable to. - - - - - aliased - - - Allows access to the underlying tuple values based an (optionally) assigned alias. The - example query did not apply an alias. An alias would be applied via the - alias method on - javax.persistence.criteria.Selection. Just like - positional access, there is both a typed - (Object get(String alias)) and an untyped - ( X get(String alias, Class type]]> form. - - - - -
- -
- FROM clause - -
- JPA Specification, section 6.5.2 Query Roots, pg 262 - - - A CriteriaQuery object defines a query over one or more entity, embeddable, or basic abstract - schema types. The root objects of the query are entities, from which the other types are reached - by navigation. - -
- - - - All the individual parts of the FROM clause (roots, joins, paths) implement the - javax.persistence.criteria.From interface. - - - -
- Roots - - - Roots define the basis from which all joins, paths and attributes are available in the query. - A root is always an entity type. Roots are defined and added to the criteria by the overloaded - from methods on - javax.persistence.criteria.CriteriaQuery: - - - - - - Adding a root - - - - - Criteria queries may define multiple roots, the effect of which is to create a cartesian - product between the newly added root and the others. Here is an example matching all single - men and all single women: - - - - Adding multiple roots - - -
- -
- Joins - - - Joins allow navigation from other javax.persistence.criteria.From - to either association or embedded attributes. Joins are created by the numerous overloaded - join methods of the - javax.persistence.criteria.From interface - - - - Example with Embedded and ManyToOne - - - - - Example with Collections - - -
- -
- Fetches - - - Just like in HQL and JPQL, criteria queries can specify that associated data be fetched along - with the owner. Fetches are created by the numerous overloaded fetch - methods of the javax.persistence.criteria.From interface. - - - - Example with Embedded and ManyToOne - - - - - - Technically speaking, embedded attributes are always fetched with their owner. However in - order to define the fetching of Address#country we needed a - javax.persistence.criteria.Fetch for its parent path. - - - - - Example with Collections - - -
-
- -
- Path expressions - - - Roots, joins and fetches are themselves paths as well. - - -
- -
- Using parameters - - - Using parameters - - - - - Use the parameter method of - javax.persistence.criteria.CriteriaBuilder to obtain a parameter - reference. Then use the parameter reference to bind the parameter value to the - javax.persistence.Query - -
- -
diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/CriteriaBuilder_query_creation_snippet.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/CriteriaBuilder_query_creation_snippet.java deleted file mode 100644 index a4dc3ef01bc5..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/CriteriaBuilder_query_creation_snippet.java +++ /dev/null @@ -1,3 +0,0 @@ - CriteriaQuery createQuery(Class resultClass); -CriteriaQuery createTupleQuery(); -CriteriaQuery createQuery(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_fetch_example_embedded_and_many2one.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_fetch_example_embedded_and_many2one.java deleted file mode 100644 index fe68976760d3..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_fetch_example_embedded_and_many2one.java +++ /dev/null @@ -1,6 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -// Person.address is an embedded attribute -Fetch personAddress = personRoot.fetch( Person_.address ); -// Address.country is a ManyToOne -Fetch addressCountry = personAddress.fetch( Address_.country ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_fetch_example_plural.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_fetch_example_plural.java deleted file mode 100644 index 5a216e28ef8d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_fetch_example_plural.java +++ /dev/null @@ -1,4 +0,0 @@ -CriteriaQuery<Person> personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -Fetch orders = personRoot.fetch( Person_.orders ); -Fetch orderLines = orders.fetch( Order_.lineItems ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_join_example_embedded_and_many2one.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_join_example_embedded_and_many2one.java deleted file mode 100644 index 478b649cacaa..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_join_example_embedded_and_many2one.java +++ /dev/null @@ -1,6 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -// Person.address is an embedded attribute -Join personAddress = personRoot.join( Person_.address ); -// Address.country is a ManyToOne -Join addressCountry = personAddress.join( Address_.country ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_join_example_plural.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_join_example_plural.java deleted file mode 100644 index 1840bad318f6..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_join_example_plural.java +++ /dev/null @@ -1,4 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -Join orders = personRoot.join( Person_.orders ); -Join orderLines = orders.join( Order_.lineItems ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_example.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_example.java deleted file mode 100644 index 0c73ad04ffa8..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_example.java +++ /dev/null @@ -1,3 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -// create and add the root -person.from( Person.class ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_example_multiple.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_example_multiple.java deleted file mode 100644 index e37cbc17a468..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_example_multiple.java +++ /dev/null @@ -1,12 +0,0 @@ -CriteriaQuery query = builder.createQuery(); -Root men = query.from( Person.class ); -Root women = query.from( Person.class ); -Predicate menRestriction = builder.and( - builder.equal( men.get( Person_.gender ), Gender.MALE ), - builder.equal( men.get( Person_.relationshipStatus ), RelationshipStatus.SINGLE ) -); -Predicate womenRestriction = builder.and( - builder.equal( women.get( Person_.gender ), Gender.FEMALE ), - builder.equal( women.get( Person_.relationshipStatus ), RelationshipStatus.SINGLE ) -); -query.where( builder.and( menRestriction, womenRestriction ) ); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_methods.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_methods.java deleted file mode 100644 index 4a9c915904b3..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/from_root_methods.java +++ /dev/null @@ -1,3 +0,0 @@ - Root from(Class); - - Root from(EntityType) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/parameter_example.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/parameter_example.java deleted file mode 100644 index bcb72ff2b4d4..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/parameter_example.java +++ /dev/null @@ -1,9 +0,0 @@ -CriteriaQuery criteria = build.createQuery( Person.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( personRoot ); -ParameterExpression eyeColorParam = builder.parameter( String.class ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), eyeColorParam ) ); - -TypedQuery query = em.createQuery( criteria ); -query.setParameter( eyeColorParam, "brown" ); -List people = query.getResultList(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_attribute_example.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_attribute_example.java deleted file mode 100644 index 0667a435e916..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_attribute_example.java +++ /dev/null @@ -1,9 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Integer.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( personRoot.get( Person_.age ) ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List ages = em.createQuery( criteria ).getResultList(); -for ( Integer age : ages ) { - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_multiple_values_array.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_multiple_values_array.java deleted file mode 100644 index 7e3fefaa18e1..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_multiple_values_array.java +++ /dev/null @@ -1,13 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Object[].class ); -Root personRoot = criteria.from( Person.class ); -Path idPath = personRoot.get( Person_.id ); -Path agePath = personRoot.get( Person_.age ); -criteria.select( builder.array( idPath, agePath ) ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List valueArray = em.createQuery( criteria ).getResultList(); -for ( Object[] values : valueArray ) { - final Long id = (Long) values[0]; - final Integer age = (Integer) values[1]; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_multiple_values_array2.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_multiple_values_array2.java deleted file mode 100644 index 75c8feeaa38d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_multiple_values_array2.java +++ /dev/null @@ -1,13 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Object[].class ); -Root personRoot = criteria.from( Person.class ); -Path idPath = personRoot.get( Person_.id ); -Path agePath = personRoot.get( Person_.age ); -criteria.multiselect( idPath, agePath ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List valueArray = em.createQuery( criteria ).getResultList(); -for ( Object[] values : valueArray ) { - final Long id = (Long) values[0]; - final Integer age = (Integer) values[1]; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_root_entity_example.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_root_entity_example.java deleted file mode 100644 index c60921d11618..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_root_entity_example.java +++ /dev/null @@ -1,9 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Person.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( personRoot ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List people = em.createQuery( criteria ).getResultList(); -for ( Person person : people ) { - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_tuple.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_tuple.java deleted file mode 100644 index dc651c1cf05f..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_tuple.java +++ /dev/null @@ -1,13 +0,0 @@ -CriteriaQuery criteria = builder.createTupleQuery(); -Root personRoot = criteria.from( Person.class ); -Path idPath = personRoot.get( Person_.id ); -Path agePath = personRoot.get( Person_.age ); -criteria.multiselect( idPath, agePath ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List tuples = em.createQuery( criteria ).getResultList(); -for ( Tuple tuple : valueArray ) { - assert tuple.get( 0 ) == tuple.get( idPath ); - assert tuple.get( 1 ) == tuple.get( agePath ); - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_wrapper.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_wrapper.java deleted file mode 100644 index f82398829fd5..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_criteria/extras/select_wrapper.java +++ /dev/null @@ -1,27 +0,0 @@ -public class PersonWrapper { - private final Long id; - private final Integer age; - public PersonWrapper(Long id, Integer age) { - this.id = id; - this.age = age; - } - ... -} - -... - -CriteriaQuery criteria = builder.createQuery( PersonWrapper.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( - builder.construct( - PersonWrapper.class, - personRoot.get( Person_.id ), - personRoot.get( Person_.age ) - ) -); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List people = em.createQuery( criteria ).getResultList(); -for ( PersonWrapper person : people ) { - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_native/Native_SQL.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_native/Native_SQL.xml deleted file mode 100644 index 9d8cdcdc18d8..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_native/Native_SQL.xml +++ /dev/null @@ -1,1127 +0,0 @@ - - - - - - Native SQL Queries - - - You may also express queries in the native SQL dialect of your database. This is useful if you - want to utilize database specific features such as query hints or the CONNECT BY option in Oracle. - It also provides a clean migration path from a direct SQL/JDBC based application to Hibernate/JPA. - Hibernate also allows you to specify handwritten SQL (including stored procedures) for all - create, update, delete, and load operations. - - -
- Using a <literal>SQLQuery</literal> - - Execution of native SQL queries is controlled via the - SQLQuery interface, which is obtained by calling - Session.createSQLQuery(). The following sections - describe how to use this API for querying. - -
- Scalar queries - - The most basic SQL query is to get a list of scalars - (values). - - sess.createSQLQuery("SELECT * FROM CATS").list(); -sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list(); - - - These will return a List of Object arrays (Object[]) with scalar - values for each column in the CATS table. Hibernate will use - ResultSetMetadata to deduce the actual order and types of the returned - scalar values. - - To avoid the overhead of using - ResultSetMetadata, or simply to be more explicit in - what is returned, one can use addScalar(): - - sess.createSQLQuery("SELECT * FROM CATS") - .addScalar("ID", Hibernate.LONG) - .addScalar("NAME", Hibernate.STRING) - .addScalar("BIRTHDATE", Hibernate.DATE) - - - This query specified: - - - - the SQL query string - - - - the columns and types to return - - - - This will return Object arrays, but now it will not use - ResultSetMetadata but will instead explicitly get the - ID, NAME and BIRTHDATE column as respectively a Long, String and a Short - from the underlying resultset. This also means that only these three - columns will be returned, even though the query is using - * and could return more than the three listed - columns. - - It is possible to leave out the type information for all or some - of the scalars. - - sess.createSQLQuery("SELECT * FROM CATS") - .addScalar("ID", Hibernate.LONG) - .addScalar("NAME") - .addScalar("BIRTHDATE") - - - This is essentially the same query as before, but now - ResultSetMetaData is used to determine the type of - NAME and BIRTHDATE, where as the type of ID is explicitly - specified. - - How the java.sql.Types returned from ResultSetMetaData is mapped - to Hibernate types is controlled by the Dialect. If a specific type is - not mapped, or does not result in the expected type, it is possible to - customize it via calls to registerHibernateType in - the Dialect. -
- -
- Entity queries - - The above queries were all about returning scalar values, - basically returning the "raw" values from the resultset. The following - shows how to get entity objects from a native sql query via - addEntity(). - - sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class); -sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class); - - - This query specified: - - - - the SQL query string - - - - the entity returned by the query - - - - Assuming that Cat is mapped as a class with the columns ID, NAME - and BIRTHDATE the above queries will both return a List where each - element is a Cat entity. - - If the entity is mapped with a many-to-one to - another entity it is required to also return this when performing the - native query, otherwise a database specific "column not found" error - will occur. The additional columns will automatically be returned when - using the * notation, but we prefer to be explicit as in the following - example for a many-to-one to a - Dog: - - sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class); - - - This will allow cat.getDog() to function properly. -
- -
- Handling associations and collections - - It is possible to eagerly join in the Dog to - avoid the possible extra roundtrip for initializing the proxy. This is - done via the addJoin() method, which allows you to - join in an association or collection. - - sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID") - .addEntity("cat", Cat.class) - .addJoin("cat.dog"); - - - In this example, the returned Cat's will have - their dog property fully initialized without any - extra roundtrip to the database. Notice that you added an alias name - ("cat") to be able to specify the target property path of the join. It - is possible to do the same eager joining for collections, e.g. if the - Cat had a one-to-many to Dog - instead. - - sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID") - .addEntity("cat", Cat.class) - .addJoin("cat.dogs"); - - - At this stage you are reaching the limits of what is possible with - native queries, without starting to enhance the sql queries to make them - usable in Hibernate. Problems can arise when returning multiple entities - of the same type or when the default alias/column names are not - enough. -
- -
- Returning multiple entities - - Until now, the result set column names are assumed to be the same - as the column names specified in the mapping document. This can be - problematic for SQL queries that join multiple tables, since the same - column names can appear in more than one table. - - Column alias injection is needed in the following query (which - most likely will fail): - - sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID") - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class) - - - The query was intended to return two Cat instances per row: a cat - and its mother. The query will, however, fail because there is a - conflict of names; the instances are mapped to the same column names. - Also, on some databases the returned column aliases will most likely be - on the form "c.ID", "c.NAME", etc. which are not equal to the columns - specified in the mappings ("ID" and "NAME"). - - The following form is not vulnerable to column name - duplication: - - sess.createSQLQuery("SELECT {cat.*}, {m.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = m.ID") - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class) - - - This query specified: - - - - the SQL query string, with placeholders for Hibernate to - inject column aliases - - - - the entities returned by the query - - - - The {cat.*} and {mother.*} notation used above is a shorthand for - "all properties". Alternatively, you can list the columns explicitly, - but even in this case Hibernate injects the SQL column aliases for each - property. The placeholder for a column alias is just the property name - qualified by the table alias. In the following example, you retrieve - Cats and their mothers from a different table (cat_log) to the one - declared in the mapping metadata. You can even use the property aliases - in the where clause. - - String sql = "SELECT ID as {c.id}, NAME as {c.name}, " + - "BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " + - "FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID"; - -List loggedCats = sess.createSQLQuery(sql) - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class).list() - - -
- Alias and property references - - In most cases the above alias injection is needed. For queries - relating to more complex mappings, like composite properties, - inheritance discriminators, collections etc., you can use specific - aliases that allow Hibernate to inject the proper aliases. - - The following table shows the different ways you can use the - alias injection. Please note that the alias names in the result are - simply examples; each alias will have a unique and probably different - name when used. - - - Alias injection names - - - - - - - - - - - Description - - Syntax - - Example - - - - - - A simple property - - {[aliasname].[propertyname] - - A_NAME as {item.name} - - - - A composite property - - {[aliasname].[componentname].[propertyname]} - - CURRENCY as {item.amount.currency}, VALUE as - {item.amount.value} - - - - Discriminator of an entity - - {[aliasname].class} - - DISC as {item.class} - - - - All properties of an entity - - {[aliasname].*} - - {item.*} - - - - A collection key - - {[aliasname].key} - - ORGID as {coll.key} - - - - The id of an collection - - {[aliasname].id} - - EMPID as {coll.id} - - - - The element of an collection - - {[aliasname].element} - - XID as {coll.element} - - - - property of the element in the collection - - {[aliasname].element.[propertyname]} - - NAME as {coll.element.name} - - - - All properties of the element in the collection - - {[aliasname].element.*} - - {coll.element.*} - - - - All properties of the collection - - {[aliasname].*} - - {coll.*} - - - -
-
-
- -
- Returning non-managed entities - - It is possible to apply a ResultTransformer to native SQL queries, - allowing it to return non-managed entities. - - sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") - .setResultTransformer(Transformers.aliasToBean(CatDTO.class)) - - This query specified: - - - - the SQL query string - - - - a result transformer - - - - The above query will return a list of CatDTO - which has been instantiated and injected the values of NAME and - BIRTHNAME into its corresponding properties or fields. -
- -
- Handling inheritance - - Native SQL queries which query for entities that are mapped as - part of an inheritance must include all properties for the baseclass and - all its subclasses. -
- -
- Parameters - - Native SQL queries support positional as well as named - parameters: - - Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class); -List pusList = query.setString(0, "Pus%").list(); - -query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class); -List pusList = query.setString("name", "Pus%").list(); -
-
- -
- Named SQL queries - - Named SQL queries can also be defined in the mapping document and - called in exactly the same way as a named HQL query (see ). In this case, you do - not need to call - addEntity(). - - - Named sql query using the <sql-query> maping - element - - <sql-query name="persons"> - <return alias="person" class="eg.Person"/> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex} - FROM PERSON person - WHERE person.NAME LIKE :namePattern -</sql-query> - - - - Execution of a named query - - List people = sess.getNamedQuery("persons") - .setString("namePattern", namePattern) - .setMaxResults(50) - .list(); - - - The <return-join> element is use to join - associations and the <load-collection> element is - used to define queries which initialize collections, - - - Named sql query with association - - <sql-query name="personsWith"> - <return alias="person" class="eg.Person"/> - <return-join alias="address" property="person.mailingAddress"/> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex}, - address.STREET AS {address.street}, - address.CITY AS {address.city}, - address.STATE AS {address.state}, - address.ZIP AS {address.zip} - FROM PERSON person - JOIN ADDRESS address - ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' - WHERE person.NAME LIKE :namePattern -</sql-query> - - - A named SQL query may return a scalar value. You must declare the - column alias and Hibernate type using the - <return-scalar> element: - - - Named query returning a scalar - - <sql-query name="mySqlQuery"> - <return-scalar column="name" type="string"/> - <return-scalar column="age" type="long"/> - SELECT p.NAME AS name, - p.AGE AS age, - FROM PERSON p WHERE p.NAME LIKE 'Hiber%' -</sql-query> - - - You can externalize the resultset mapping information in a - <resultset> element which will allow you to - either reuse them across several named queries or through the - setResultSetMapping() API. - - - <resultset> mapping used to externalize mapping - information - - <resultset name="personAddress"> - <return alias="person" class="eg.Person"/> - <return-join alias="address" property="person.mailingAddress"/> -</resultset> - -<sql-query name="personsWith" resultset-ref="personAddress"> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex}, - address.STREET AS {address.street}, - address.CITY AS {address.city}, - address.STATE AS {address.state}, - address.ZIP AS {address.zip} - FROM PERSON person - JOIN ADDRESS address - ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' - WHERE person.NAME LIKE :namePattern -</sql-query> - - - You can, alternatively, use the resultset mapping information in - your hbm files directly in java code. - - - Programmatically specifying the result mapping information - - - List cats = sess.createSQLQuery( - "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id" - ) - .setResultSetMapping("catAndKitten") - .list(); - - - So far we have only looked at externalizing SQL queries using - Hibernate mapping files. The same concept is also available with - anntations and is called named native queries. You can use - @NamedNativeQuery - (@NamedNativeQueries) in conjunction with - @SqlResultSetMapping - (@SqlResultSetMappings). Like - @NamedQuery, @NamedNativeQuery - and @SqlResultSetMapping can be defined at class level, - but their scope is global to the application. Lets look at a view - examples. - - - shows how a resultSetMapping parameter is defined in - @NamedNativeQuery. It represents the name of a defined - @SqlResultSetMapping. The resultset mapping declares - the entities retrieved by this native query. Each field of the entity is - bound to an SQL alias (or column name). All fields of the entity including - the ones of subclasses and the foreign key columns of related entities - have to be present in the SQL query. Field definitions are optional - provided that they map to the same column name as the one declared on the - class property. In the example 2 entities, Night and - Area, are returned and each property is declared and - associated to a column name, actually the column name retrieved by the - query. - - In the result - set mapping is implicit. We only describe the entity class of the result - set mapping. The property / column mappings is done using the entity - mapping values. In this case the model property is bound to the model_txt - column. - - Finally, if the association to a related entity involve a composite - primary key, a @FieldResult element should be used for - each foreign key column. The @FieldResult name is - composed of the property name for the relationship, followed by a dot - ("."), followed by the name or the field or property of the primary key. - This can be seen in . - - - Named SQL query using <classname>@NamedNativeQuery</classname> - together with <classname>@SqlResultSetMapping</classname> - - @NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, " - + " night.night_date, area.id aid, night.area_id, area.name " - + "from Night night, Area area where night.area_id = area.id", - resultSetMapping="joinMapping") -@SqlResultSetMapping(name="joinMapping", entities={ - @EntityResult(entityClass=Night.class, fields = { - @FieldResult(name="id", column="nid"), - @FieldResult(name="duration", column="night_duration"), - @FieldResult(name="date", column="night_date"), - @FieldResult(name="area", column="area_id"), - discriminatorColumn="disc" - }), - @EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = { - @FieldResult(name="id", column="aid"), - @FieldResult(name="name", column="name") - }) - } -) - - - - Implicit result set mapping - - @Entity -@SqlResultSetMapping(name="implicit", - entities=@EntityResult(entityClass=SpaceShip.class)) -@NamedNativeQuery(name="implicitSample", - query="select * from SpaceShip", - resultSetMapping="implicit") -public class SpaceShip { - private String name; - private String model; - private double speed; - - @Id - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Column(name="model_txt") - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } -} - - - - Using dot notation in @FieldResult for specifying associations - - - @Entity -@SqlResultSetMapping(name="compositekey", - entities=@EntityResult(entityClass=SpaceShip.class, - fields = { - @FieldResult(name="name", column = "name"), - @FieldResult(name="model", column = "model"), - @FieldResult(name="speed", column = "speed"), - @FieldResult(name="captain.firstname", column = "firstn"), - @FieldResult(name="captain.lastname", column = "lastn"), - @FieldResult(name="dimensions.length", column = "length"), - @FieldResult(name="dimensions.width", column = "width") - }), - columns = { @ColumnResult(name = "surface"), - @ColumnResult(name = "volume") } ) - -@NamedNativeQuery(name="compositekey", - query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip", - resultSetMapping="compositekey") -} ) -public class SpaceShip { - private String name; - private String model; - private double speed; - private Captain captain; - private Dimensions dimensions; - - @Id - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @ManyToOne(fetch= FetchType.LAZY) - @JoinColumns( { - @JoinColumn(name="fname", referencedColumnName = "firstname"), - @JoinColumn(name="lname", referencedColumnName = "lastname") - } ) - public Captain getCaptain() { - return captain; - } - - public void setCaptain(Captain captain) { - this.captain = captain; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } - - public Dimensions getDimensions() { - return dimensions; - } - - public void setDimensions(Dimensions dimensions) { - this.dimensions = dimensions; - } -} - -@Entity -@IdClass(Identity.class) -public class Captain implements Serializable { - private String firstname; - private String lastname; - - @Id - public String getFirstname() { - return firstname; - } - - public void setFirstname(String firstname) { - this.firstname = firstname; - } - - @Id - public String getLastname() { - return lastname; - } - - public void setLastname(String lastname) { - this.lastname = lastname; - } -} - - - - - If you retrieve a single entity using the default mapping, you can - specify the resultClass attribute instead of - resultSetMapping: - - @NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultClass=SpaceShip.class) -public class SpaceShip { - - - In some of your native queries, you'll have to return scalar values, - for example when building report queries. You can map them in the - @SqlResultsetMapping through - @ColumnResult. You actually can even mix, entities and - scalar returns in the same native query (this is probably not that common - though). - - - Scalar values via <classname>@ColumnResult</classname> - - @SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension")) -@NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar") - - - An other query hint specific to native queries has been introduced: - org.hibernate.callable which can be true or false - depending on whether the query is a stored procedure or not. - -
- Using return-property to explicitly specify column/alias - names - - You can explicitly tell Hibernate what column aliases to use with - <return-property>, instead of using the - {}-syntax to let Hibernate inject its own aliases.For - example: - - <sql-query name="mySqlQuery"> - <return alias="person" class="eg.Person"> - <return-property name="name" column="myName"/> - <return-property name="age" column="myAge"/> - <return-property name="sex" column="mySex"/> - </return> - SELECT person.NAME AS myName, - person.AGE AS myAge, - person.SEX AS mySex, - FROM PERSON person WHERE person.NAME LIKE :name -</sql-query> - - - <return-property> also works with - multiple columns. This solves a limitation with the - {}-syntax which cannot allow fine grained control of - multi-column properties. - - <sql-query name="organizationCurrentEmployments"> - <return alias="emp" class="Employment"> - <return-property name="salary"> - <return-column name="VALUE"/> - <return-column name="CURRENCY"/> - </return-property> - <return-property name="endDate" column="myEndDate"/> - </return> - SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer}, - STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate}, - REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY - FROM EMPLOYMENT - WHERE EMPLOYER = :id AND ENDDATE IS NULL - ORDER BY STARTDATE ASC -</sql-query> - - In this example <return-property> was - used in combination with the {}-syntax for injection. - This allows users to choose how they want to refer column and - properties. - - If your mapping has a discriminator you must use - <return-discriminator> to specify the - discriminator column. -
- -
- Using stored procedures for querying - - Hibernate provides support for queries via stored procedures and - functions. Most of the following documentation is equivalent for both. - The stored procedure/function must return a resultset as the first - out-parameter to be able to work with Hibernate. An example of such a - stored function in Oracle 9 and higher is as follows: - - CREATE OR REPLACE FUNCTION selectAllEmployments - RETURN SYS_REFCURSOR -AS - st_cursor SYS_REFCURSOR; -BEGIN - OPEN st_cursor FOR - SELECT EMPLOYEE, EMPLOYER, - STARTDATE, ENDDATE, - REGIONCODE, EID, VALUE, CURRENCY - FROM EMPLOYMENT; - RETURN st_cursor; - END; - - To use this query in Hibernate you need to map it via a named - query. - - <sql-query name="selectAllEmployees_SP" callable="true"> - <return alias="emp" class="Employment"> - <return-property name="employee" column="EMPLOYEE"/> - <return-property name="employer" column="EMPLOYER"/> - <return-property name="startDate" column="STARTDATE"/> - <return-property name="endDate" column="ENDDATE"/> - <return-property name="regionCode" column="REGIONCODE"/> - <return-property name="id" column="EID"/> - <return-property name="salary"> - <return-column name="VALUE"/> - <return-column name="CURRENCY"/> - </return-property> - </return> - { ? = call selectAllEmployments() } -</sql-query> - - Stored procedures currently only return scalars and entities. - <return-join> and - <load-collection> are not supported. - -
- Rules/limitations for using stored procedures - - You cannot use stored procedures with Hibernate unless you - follow some procedure/function rules. If they do not follow those - rules they are not usable with Hibernate. If you still want to use - these procedures you have to execute them via - session.connection(). The rules are different for - each database, since database vendors have different stored procedure - semantics/syntax. - - Stored procedure queries cannot be paged with - setFirstResult()/setMaxResults(). - - The recommended call form is standard SQL92: { ? = call - functionName(<parameters>) } or { ? = call - procedureName(<parameters>}. Native call syntax is not - supported. - - For Oracle the following rules apply: - - - - A function must return a result set. The first parameter of - a procedure must be an OUT that returns a - result set. This is done by using a - SYS_REFCURSOR type in Oracle 9 or 10. In Oracle - you need to define a REF CURSOR type. See - Oracle literature for further information. - - - - For Sybase or MS SQL server the following rules apply: - - - - The procedure must return a result set. Note that since - these servers can return multiple result sets and update counts, - Hibernate will iterate the results and take the first result that - is a result set as its return value. Everything else will be - discarded. - - - - If you can enable SET NOCOUNT ON in your - procedure it will probably be more efficient, but this is not a - requirement. - - -
-
-
- -
- Custom SQL for create, update and delete - - Hibernate can use custom SQL for create, update, and delete - operations. The SQL can be overridden at the statement level or - inidividual column level. This section describes statement overrides. For - columns, see . shows how to define - custom SQL operatons using annotations. - - - Custom CRUD via annotations - - @Entity -@Table(name="CHAOS") -@SQLInsert( sql="INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)") -@SQLUpdate( sql="UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?") -@SQLDelete( sql="DELETE CHAOS WHERE id = ?") -@SQLDeleteAll( sql="DELETE CHAOS") -@Loader(namedQuery = "chaos") -@NamedNativeQuery(name="chaos", query="select id, size, name, lower( nickname ) as nickname from CHAOS where id= ?", resultClass = Chaos.class) -public class Chaos { - @Id - private Long id; - private Long size; - private String name; - private String nickname; - - - @SQLInsert, @SQLUpdate, - @SQLDelete, @SQLDeleteAll - respectively override the INSERT, UPDATE, DELETE, and DELETE all - statement. The same can be achieved using Hibernate mapping files and the - <sql-insert>, - <sql-update> and - <sql-delete> nodes. This can be seen in . - - - Custom CRUD XML - - <class name="Person"> - <id name="id"> - <generator class="increment"/> - </id> - <property name="name" not-null="true"/> - <sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert> - <sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update> - <sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete> -</class> - - - If you expect to call a store procedure, be sure to set the - callable attribute to true. In - annotations as well as in xml. - - To check that the execution happens correctly, Hibernate allows you - to define one of those three strategies: - - - - none: no check is performed: the store procedure is expected to - fail upon issues - - - - count: use of rowcount to check that the update is - successful - - - - param: like COUNT but using an output parameter rather that the - standard mechanism - - - - To define the result check style, use the check - parameter which is again available in annoations as well as in xml. - - You can use the exact same set of annotations respectively xml nodes - to override the collection related statements -see . - - - Overriding SQL statements for collections using - annotations - - @OneToMany -@JoinColumn(name="chaos_fk") -@SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?") -@SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?") -private Set<CasimirParticle> particles = new HashSet<CasimirParticle>(); - - - - The parameter order is important and is defined by the order - Hibernate handles properties. You can see the expected order by enabling - debug logging for the org.hibernate.persister.entity - level. With this level enabled Hibernate will print out the static SQL - that is used to create, update, delete etc. entities. (To see the - expected sequence, remember to not include your custom SQL through - annotations or mapping files as that will override the Hibernate - generated static sql) - - - Overriding SQL statements for secondary tables is also possible - using @org.hibernate.annotations.Table and either (or - all) attributes sqlInsert, - sqlUpdate, sqlDelete: - - - Overriding SQL statements for secondary tables - - @Entity -@SecondaryTables({ - @SecondaryTable(name = "`Cat nbr1`"), - @SecondaryTable(name = "Cat2"}) -@org.hibernate.annotations.Tables( { - @Table(appliesTo = "Cat", comment = "My cat table" ), - @Table(appliesTo = "Cat2", foreignKey = @ForeignKey(name="FK_CAT2_CAT"), fetch = FetchMode.SELECT, - sqlInsert=@SQLInsert(sql="insert into Cat2(storyPart2, id) values(upper(?), ?)") ) -} ) -public class Cat implements Serializable { - - - The previous example also shows that you can give a comment to a - given table (primary or secondary): This comment will be used for DDL - generation. - - - The SQL is directly executed in your database, so you can use any - dialect you like. This will, however, reduce the portability of your - mapping if you use database specific SQL. - - - Last but not least, stored procedures are in most cases required to - return the number of rows inserted, updated and deleted. Hibernate always - registers the first statement parameter as a numeric output parameter for - the CUD operations: - - - Stored procedures and their return value - - CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2) - RETURN NUMBER IS -BEGIN - - update PERSON - set - NAME = uname, - where - ID = uid; - - return SQL%ROWCOUNT; - -END updatePerson; - -
- -
- Custom SQL for loading - - You can also declare your own SQL (or HQL) queries for entity - loading. As with inserts, updates, and deletes, this can be done at the - individual column level as described in or at the statement level. Here - is an example of a statement level override: - - <sql-query name="person"> - <return alias="pers" class="Person" lock-mode="upgrade"/> - SELECT NAME AS {pers.name}, ID AS {pers.id} - FROM PERSON - WHERE ID=? - FOR UPDATE -</sql-query> - - This is just a named query declaration, as discussed earlier. You - can reference this named query in a class mapping: - - <class name="Person"> - <id name="id"> - <generator class="increment"/> - </id> - <property name="name" not-null="true"/> - <loader query-ref="person"/> -</class> - - This even works with stored procedures. - - You can even define a query for collection loading: - - <set name="employments" inverse="true"> - <key/> - <one-to-many class="Employment"/> - <loader query-ref="employments"/> -</set> - - <sql-query name="employments"> - <load-collection alias="emp" role="Person.employments"/> - SELECT {emp.*} - FROM EMPLOYMENT emp - WHERE EMPLOYER = :id - ORDER BY STARTDATE ASC, EMPLOYEE ASC -</sql-query> - - You can also define an entity loader that loads a collection by join - fetching: - - <sql-query name="person"> - <return alias="pers" class="Person"/> - <return-join alias="emp" property="pers.employments"/> - SELECT NAME AS {pers.*}, {emp.*} - FROM PERSON pers - LEFT OUTER JOIN EMPLOYMENT emp - ON pers.ID = emp.PERSON_ID - WHERE ID=? -</sql-query> - - The annotation equivalent <loader> is the - @Loader annotation as seen in . -
- -
diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/HQL_JPQL.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/HQL_JPQL.xml deleted file mode 100644 index 5135a67ab5ef..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/HQL_JPQL.xml +++ /dev/null @@ -1,1449 +0,0 @@ - - - - - - HQL and JPQL - - - The Hibernate Query Language (HQL) and Java Persistence Query Language (JPQL) are both object model - focused query languages similar in nature to SQL. JPQL is a heavily-inspired-by subset of HQL. A JPQL - query is always a valid HQL query, the reverse is not true however. - - - - Both HQL and JPQL are non-type-safe ways to perform query operations. Criteria queries offer a - type-safe approach to querying. See for more information. - - -
- Case Sensitivity - - - With the exception of names of Java classes and properties, queries are case-insensitive. - So SeLeCT is the same as sELEct is the same as - SELECT, but - org.hibernate.eg.FOO and org.hibernate.eg.Foo are different, as are - foo.barSet and foo.BARSET. - - - - - This documentation uses lowercase keywords as convention in examples. - - -
- -
- Statement types - - Both HQL and JPQL allow SELECT, UPDATE and DELETE - statements to be performed. HQL additionally allows INSERT statements, in a form - similar to a SQL INSERT-SELECT. - - - - - Care should be taken as to when a UPDATE or DELETE statement is - executed. - -
- Section 4.10 of the JPA 2.0 Specification - - Caution should be used when executing bulk update or delete operations because they may result in - inconsistencies between the database and the entities in the active persistence context. In general, bulk - update and delete operations should only be performed within a transaction in a new persistence con- - text or before fetching or accessing entities whose state might be affected by such operations. - -
-
- -
- Select statements - - The BNF for SELECT statements in HQL is: - - - - The simplest possible HQL SELECT statement is of the form: - - from com.acme.Cat - - The select statement in JPQL is exactly the same as for HQL except that JPQL requires a - select_clause, whereas HQL does not. Even though HQL does not require the presence - of a select_clause, it is generally good practice to include one. For simple queries - the intent is clear and so the intended result of the select_clause is east to - infer. But on more complex queries that is not always the case. It is usually better to explicitly - specify intent. Hibernate does not actually enforce that a select_clause be present - even when parsing JPQL queries, however applications interested in JPA portability should take heed of - this. - -
- -
- Update statements - - The BNF for UPDATE statements is the same in HQL and JPQL: - - - - UPDATE statements, by default, do not effect the version - or the timestamp attribute values for the affected entities. However, - you can force Hibernate to set the version or timestamp attribute - values through the use of a versioned update. This is achieved by adding the - VERSIONED keyword after the UPDATE keyword. Note, however, that - this is a Hibernate specific feature and will not work in a portable manner. Custom version types, - org.hibernate.usertype.UserVersionType, are not allowed in conjunction - with a update versioned statement. - - - An UPDATE statement is executed using the executeUpdate - of either org.hibernate.Query or - javax.persistence.Query. The method is named for those familiar with - the JDBC executeUpdate on java.sql.PreparedStatement. - The int value returned by the executeUpdate() method - indicates the number of entities effected by the operation. This may or may not correlate to the number - of rows effected in the database. An HQL bulk operation might result in multiple actual SQL statements - being executed (for joined-subclass, for example). The returned number indicates the number of actual - entities affected by the statement. Using a JOINED inheritance hierarchy, a delete against one of the - subclasses may actually result in deletes against not just the table to which that subclass is mapped, - but also the "root" table and tables in between - - - Example UPDATE query statements - - - - -
- - - - Neither UPDATE nor DELETE statements are allowed to - result in what is called an implicit join. Their form already disallows explicit joins. - - - -
- Delete statements - - The BNF for DELETE statements is the same in HQL and JPQL: - - - - A DELETE statement is also executed using the executeUpdate - method of either org.hibernate.Query or - javax.persistence.Query. - -
- -
- Insert statements - - HQL adds the ability to define INSERT statements as well. There is no JPQL - equivalent to this. The BNF for an HQL INSERT statement is: - - - - The attribute_list is analogous to the column specification in the - SQL INSERT statement. For entities involved in mapped inheritance, only attributes - directly defined on the named entity can be used in the attribute_list. Superclass - properties are not allowed and subclass properties do not make sense. In other words, - INSERT statements are inherently non-polymorphic. - - - select_statement can be any valid HQL select query, with the caveat that the return - types must match the types expected by the insert. Currently, this is checked during query - compilation rather than allowing the check to relegate to the database. This may cause problems - between Hibernate Types which are equivalent as opposed to - equal. For example, this might cause lead to issues with mismatches between an - attribute mapped as a org.hibernate.type.DateType and an attribute defined as - a org.hibernate.type.TimestampType, even though the database might not make a - distinction or might be able to handle the conversion. - - - For the id attribute, the insert statement gives you two options. You can either explicitly specify - the id property in the attribute_list, in which case its value is taken from the - corresponding select expression, or omit it from the attribute_list in which case a - generated value is used. This latter option is only available when using id generators that operate - in the database; attempting to use this option with any in memory type - generators will cause an exception during parsing. - - - For optimistic locking attributes, the insert statement again gives you two options. You can either - specify the attribute in the attribute_list in which case its value is taken from - the corresponding select expressions, or omit it from the attribute_list in which - case the seed value defined by the corresponding - org.hibernate.type.VersionType is used. - - - Example INSERT query statements - - -
-
- -
- The <literal>FROM</literal> clause - - The FROM clause is responsible defining the scope of object model types available to - the rest of the query. It also is responsible for defining all the identification variables - available to the rest of the query. - -
- Identification variables - - Identification variables are often referred to as aliases. References to object model classes - in the FROM clause can be associated with an identification variable that can then be used to - refer to that type thoughout the rest of the query. - - - In most cases declaring an identification variable is optional, though it is usually good practice to - declare them. - - - An identification variable must follow the rules for Java identifier validity. - - - According to JPQL, identification variables must be treated as case insensitive. Good practice - says you should use the same case throughout a query to refer to a given identification variable. In - other words, JPQL says they can be case insensitive and so Hibernate must - be able to treat them as such, but this does not make it good practice. - -
-
- Root entity references - - A root entity reference, or what JPA calls a range variable declaration, is - specifically a reference to a mapped entity type from the application. It cannot name component/ - embeddable types. And associations, including collections, are handled in a different manner - discussed later. - - - The BNF for a root entity reference is: - - - - Simple query example - - - - We see that the query is defining a root entity reference to the com.acme.Cat - object model type. Additionally, it declares an alias of c to that - com.acme.Cat reference; this is the identification variable. - - - Usually the root entity reference just names the entity name rather than the - entity class FQN. By default the entity name is the unqualified entity class name, - here Cat - - - Simple query using entity name for root entity reference - - - - Multiple root entity references can also be specified. Even naming the same entity! - - - Simple query using multiple root entity references - - - -
-
- Explicit joins - - The FROM clause can also contain explicit relationship joins using the - join keyword. These joins can be either inner - or left outer style joins. - - - Explicit inner join examples - - - - Explicit left (outer) join examples - - - - An important use case for explicit joins is to define FETCH JOINS which override - the laziness of the joined association. As an example, given an entity named Customer - with a collection-valued association named orders - - - Fetch join example - - - - As you can see from the example, a fetch join is specified by injecting the keyword fetch - after the keyword join. In the example, we used a left outer join because we want - to return customers who have no orders also. Inner joins can also be fetched. But inner joins still - filter. In the example, using an inner join instead would have resulted in customers without any orders - being filtered out of the result. - - - - Fetch joins are not valid in sub-queries. - - - Care should be taken when fetch joining a collection-valued association which is in any way further - restricted; the fetched collection will be restricted too! For this reason it is usually considered - best practice to not assign an identification variable to fetched joins except for the purpose - of specifying nested fetch joins. - - - Fetch joins should not be used in paged queries (aka, setFirstResult/ - setMaxResults). Nor should they be used with the HQL - scroll or iterate features. - - - - HQL also defines a WITH clause to qualify the join conditions. Again, this is - specific to HQL; JPQL does not define this feature. - - - with-clause join example - - - - The important distinction is that in the generated SQL the conditions of the - with clause are made part of the on clause in the generated SQL - as opposed to the other queries in this section where the HQL/JPQL conditions are made part of the - where clause in the generated SQL. The distinction in this specific example is - probably not that significant. The with clause is sometimes necessary in more - complicated queries. - - - Explicit joins may reference association or component/embedded attributes. For further information - about collection-valued association references, see . - In the case of component/embedded attributes, the join is simply logical and does not correlate to a - physical (SQL) join. - -
-
- Implicit joins (path expressions) - - Another means of adding to the scope of object model types available to the query is through the - use of implicit joins, or path expressions. - - - Simple implicit join example - - - - An implicit join always starts from an identification variable, followed by - the navigation operator (.), followed by an attribute for the object model type referenced by the - initial identification variable. In the example, the initial - identification variable is c which refers to the - Customer entity. The c.chiefExecutive reference then refers - to the chiefExecutive attribute of the Customer entity. - chiefExecutive is an association type so we further navigate to its - age attribute. - - - - If the attribute represents an entity association (non-collection) or a component/embedded, that - reference can be further navigated. Basic values and collection-valued associations cannot be - further navigated. - - - - As shown in the example, implicit joins can appear outside the FROM clause. However, - they affect the FROM clause. Implicit joins are always treated as inner joins. - Multiple references to the same implicit join always refer to the same logical and physical (SQL) join. - - - Reused implicit join - - - - Just as with explicit joins, implicit joins may reference association or component/embedded attributes. - For further information about collection-valued association references, see - . In the case of component/embedded attributes, - the join is simply logical and does not correlate to a physical (SQL) join. Unlike explicit joins, - however, implicit joins may also reference basic state fields as long as the path expression ends - there. - -
-
- Collection member references - - References to collection-valued associations actually refer to the values of - that collection. - - - Collection references example - - - - In the example, the identification variable o actually refers to the object model - type Order which is the type of the elements of the - Customer#orders association. - - - The example also shows the alternate syntax for specifying collection association joins using the - IN syntax. Both forms are equivalent. Which form an application chooses to use is - simply a matter of taste. - -
- Special case - qualified path expressions - - We said earlier that collection-valued associations actually refer to the values - of that collection. Based on the type of collection, there are also available a set of - explicit qualification expressions. - - - Qualified collection references example - - - - - VALUE - - - Refers to the collection value. Same as not specifying a qualifier. Useful to - explicitly show intent. Valid for any type of collection-valued reference. - - - - - INDEX - - - According to HQL rules, this is valid for both Maps and Lists which specify a - javax.persistence.OrderColumn annotation to refer to - the Map key or the List position (aka the OrderColumn value). JPQL however, reserves - this for use in the List case and adds KEY for the MAP case. - Applications interested in JPA provider portability should be aware of this - distinction. - - - - - KEY - - - Valid only for Maps. Refers to the map's key. If the key is itself an entity, - can be further navigated. - - - - - ENTRY - - - Only valid only for Maps. Refers to the Map's logical - java.util.Map.Entry tuple (the combination of its key - and value). ENTRY is only valid as a terminal path and only valid - in the select clause. - - - - - - See for additional details on collection related - expressions. - -
-
-
- Polymorphism - - HQL and JPQL queries are inherently polymorphic. - - select p from Payment p - - This query names the Payment entity explicitly. However, all subclasses of - Payment are also available to the query. So if the - CreditCardPayment entity and WireTransferPayment entity - each extend from Payment all three types would be available to the query. And - the query would return instances of all three. - - - The logical extreme - - The HQL query from java.lang.Object is totally valid! It returns every - object of every type defined in your application. - - - - This can be altered by using either the - org.hibernate.annotations.Polymorphism annotation (global, and - Hibernate-specific) or limiting them using in the query itself using an entity type expression. - -
-
- -
- Expressions - - - Essentially expressions are references that resolve to basic or tuple values. - - -
- Identification variable - - See . - -
- -
- Path expressions - - Again, see . - -
- -
- Literals - - String literals are enclosed in single-quotes. To escape a single-quote within a string literal, use - double single-quotes. - - - String literal examples - - - - - Numeric literals are allowed in a few different forms. - - - Numeric literal examples - - - - In the scientific notation form, the E is case insensitive. - - - Specific typing can be achieved through the use of the same suffix approach specified by Java. So, - L denotes a long; D denotes a double; F - denotes a float. The actual suffix is case insensitive. - - - - The boolean literals are TRUE and FALSE, again case-insensitive. - - - - Enums can even be referenced as literals. The fully-qualified enum class name must be used. HQL - can also handle constants in the same manner, though JPQL does not define that as supported. - - - - Entity names can also be used as literal. See . - - - - Date/time literals can be specified using the JDBC escape syntax: {d 'yyyy-mm-dd'} - for dates, {t 'hh:mm:ss'} for times and - {ts 'yyyy-mm-dd hh:mm:ss[.millis]'} (millis optional) for timestamps. These - literals only work if you JDBC drivers supports them. - -
- -
- Parameters - - HQL supports all 3 of the following forms. JPQL does not support the HQL-specific positional - parameters notion. It is good practice to not mix forms in a given query. - -
- Named parameters - - Named parameters are declared using a colon followed by an identifier - - :aNamedParameter. The same named parameter can appear multiple times in a query. - - - Named parameter examples - - -
-
- Positional (JPQL) parameters - - JPQL-style positional parameters are declared using a question mark followed by an ordinal - - ?1, ?2. The ordinals start with 1. Just like with - named parameters, positional parameters can also appear multiple times in a query. - - - Positional (JPQL) parameter examples - - -
-
- Positional (HQL) parameters - - HQL-style positional parameters follow JDBC positional parameter syntax. They are declared using - ? without a following ordinal. There is no way to relate two such - positional parameters as being "the same" aside from binding the same value to each. - - - This form should be considered deprecated and may be removed in the near future. - -
-
- -
- Arithmetic - - Arithmetic operations also represent valid expressions. - - - Numeric arithmetic examples - - - - The following rules apply to the result of arithmetic operations: - - - - - If either of the operands is Double/double, the result is a Double; - - - - - else, if either of the operands is Float/float, the result is a Float; - - - - - else, if either operand is BigDecimal, the result is BigDecimal; - - - - - else, if either operand is BigInteger, the result is BigInteger (except for division, in - which case the result type is not further defined); - - - - - else, if either operand is Long/long, the result is Long (except for division, in - which case the result type is not further defined); - - - - - else, (the assumption being that both operands are of integral type) the result is Integer - (except for division, in which case the result type is not further defined); - - - - - - Date arithmetic is also supported, albeit in a more limited fashion. This is due partially to - differences in database support and partially to the lack of support for INTERVAL - definition in the query language itself. - -
- -
- Concatenation (operation) - - HQL defines a concatenation operator in addition to supporting the concatenation - (CONCAT) function. This is not defined by JPQL, so portable applications - should avoid it use. The concatenation operator is taken from the SQL concatenation operator - - ||. - - - Concatenation operation example - - - - See for details on the concat() function - -
- -
- Aggregate functions - - Aggregate functions are also valid expressions in HQL and JPQL. The semantic is the same as their - SQL counterpart. The supported aggregate functions are: - - - - - COUNT (including distinct/all qualifiers) - The result type is always Long. - - - - - AVG - The result type is always Double. - - - - - MIN - The result type is the same as the argument type. - - - - - MAX - The result type is the same as the argument type. - - - - - SUM - The result type of the avg() function depends on - the type of the values being averaged. For integral values (other than BigInteger), the result - type is Long. For floating point values (other than BigDecimal) the result type is Double. For - BigInteger values, the result type is BigInteger. For BigDecimal values, the result type is - BigDecimal. - - - - - Aggregate function examples - - - - Aggregations often appear with grouping. For information on grouping see - -
- -
- Scalar functions - - Both HQL and JPQL define some standard functions that are available regardless of the underlying - database in use. HQL can also understand additional functions defined by the Dialect as well as the - application. - - -
- Standardized functions - JPQL - - Here are the list of functions defined as supported by JPQL. Applications interested in remaining - portable between JPA providers should stick to these functions. - - - - CONCAT - - - String concatenation function. Variable argument length of 2 or more string values - to be concatenated together. - - - - - SUBSTRING - - - Extracts a portion of a string value. - - - - The second argument denotes the starting position. The third (optional) argument - denotes the length. - - - - - UPPER - - - Upper cases the specified string - - - - - LOWER - - - Lower cases the specified string - - - - - TRIM - - - Follows the semantics of the SQL trim function. - - - - - LENGTH - - - Returns the length of a string. - - - - - LOCATE - - - Locates a string within another string. - - - - The third argument (optional) is used to denote a position from which to start looking. - - - - - ABS - - - Calculates the mathematical absolute value of a numeric value. - - - - - MOD - - - Calculates the remainder of dividing the first argument by the second. - - - - - SQRT - - - Calculates the mathematical square root of a numeric value. - - - - - CURRENT_DATE - - - Returns the database current date. - - - - - CURRENT_TIME - - - Returns the database current time. - - - - - CURRENT_TIMESTAMP - - - Returns the database current timestamp. - - - - -
-
- Standardized functions - HQL - - Beyond the JPQL standardized functions, HQL makes some additional functions available regardless - of the underlying database in use. - - - - BIT_LENGTH - - - Returns the length of binary data. - - - - - CAST - - - Performs a SQL cast. The cast target should name the Hibernate mapping type to use. - See the chapter on data types for more information. - - - - - EXTRACT - - - Performs a SQL extraction on datetime values. An extraction extracts parts of - the datetime (the year, for example). See the abbreviated forms below. - - - - - SECOND - - - Abbreviated extract form for extracting the second. - - - - - MINUTE - - - Abbreviated extract form for extracting the minute. - - - - - HOUR - - - Abbreviated extract form for extracting the hour. - - - - - DAY - - - Abbreviated extract form for extracting the day. - - - - - MONTH - - - Abbreviated extract form for extracting the month. - - - - - YEAR - - - Abbreviated extract form for extracting the year. - - - - - STR - - - Abbreviated form for casting a value as character data. - - - - -
- -
- Non-standardized functions - - Hibernate Dialects can register additional functions known to be available for that particular - database product. These functions are also available in HQL (and JPQL, though only when using - Hibernate as the JPA provider obviously). However, they would only be available when using that - database/Dialect. Applications that aim for database portability should avoid using functions - in this category. - - - Application developers can also supply their own set of functions. This would usually represent - either custom SQL functions or aliases for snippets of SQL. Such function declarations are - made by using the addSqlFunction method of - org.hibernate.cfg.Configuration - -
-
- -
- Collection-related expressions - - There are a few specialized expressions for working with collection-valued associations. Generally - these are just abbreviated forms or other expressions for the sake of conciseness. - - - - SIZE - - - Calculate the size of a collection. Equates to a subquery! - - - - - MAXELEMENT - - - Available for use on collections of basic type. Refers to the maximum value as determined - by applying the max SQL aggregation. - - - - - MAXINDEX - - - Available for use on indexed collections. Refers to the maximum index (key/position) as - determined by applying the max SQL aggregation. - - - - - MINELEMENT - - - Available for use on collections of basic type. Refers to the minimum value as determined - by applying the min SQL aggregation. - - - - - MININDEX - - - Available for use on indexed collections. Refers to the minimum index (key/position) as - determined by applying the min SQL aggregation. - - - - - ELEMENTS - - - Used to refer to the elements of a collection as a whole. Only allowed in the where clause. - Often used in conjunction with ALL, ANY or - SOME restrictions. - - - - - INDICES - - - Similar to elements except that indices refers to - the collections indices (keys/positions) as a whole. - - - - - - Collection-related expressions examples - - - - Elements of indexed collections (arrays, lists, and maps) can be referred to by index operator. - - - Index operator examples - - - - See also as there is a good deal of overlap. - -
- -
- Entity type - - We can also refer to the type of an entity as an expression. This is mainly useful when dealing - with entity inheritance hierarchies. The type can expressed using a TYPE function - used to refer to the type of an identification variable representing an entity. The name of the - entity also serves as a way to refer to an entity type. Additionally the entity type can be - parametrized, in which case the entity's Java Class reference would be bound as the parameter - value. - - - Entity type expression examples - - - - HQL also has a legacy form of referring to an entity type, though that legacy form is considered - deprecated in favor of TYPE. The legacy form would have used p.class - in the examples rather than type(p). It is mentioned only for completeness. - -
- -
- CASE expressions - - Both the simple and searched forms are supported, as well as the 2 SQL defined abbreviated forms - (NULLIF and COALESCE) - -
- Simple CASE expressions - - The simple form has the following syntax: - - - - Simple case expression example - - -
-
- Searched CASE expressions - - The searched form has the following syntax: - - - - Searched case expression example - - -
-
- NULLIF expressions - - NULLIF is an abbreviated CASE expression that returns NULL if its operands are considered equal. - - - NULLIF example - - -
-
- COALESCE expressions - - COALESCE is an abbreviated CASE expression that returns the first non-null operand. We have seen a - number of COALESCE examples above. - -
-
-
- -
- The <literal>SELECT</literal> clause - - The SELECT clause identifies which objects and values to return as the query results. - The expressions discussed in are all valid select expressions, except - where otherwise noted. See the section for information on handling the results - depending on the types of values specified in the SELECT clause. - - - - There is a particular expression type that is only valid in the select clause. Hibernate calls this - dynamic instantiation. JPQL supports some of that feature and calls it - a constructor expression - - - - Dynamic instantiation example - constructor - - - - - So rather than dealing with the Object[] (again, see ) here we are wrapping - the values in a type-safe java object that will be returned as the results of the query. The class - reference must be fully qualified and it must have a matching constructor. - - - The class here need not be mapped. If it does represent an entity, the resulting instances are - returned in the NEW state (not managed!). - - - - That is the part JPQL supports as well. HQL supports additional dynamic instantiation - features. First, the query can specify to return a List rather than an Object[] for scalar results: - - - Dynamic instantiation example - list - - - - The results from this query will be a ]]> as opposed to a ]]> - - - - HQL also supports wrapping the scalar results in a Map. - - - Dynamic instantiation example - map - - - - The results from this query will be a >]]> as opposed to a - ]]>. The keys of the map are defined by the aliases given to the select - expressions. - -
- -
- Predicates - - Predicates form the basis of the where clause, the having clause and searched case expressions. - They are expressions which resolve to a truth value, generally TRUE or - FALSE, although boolean comparisons involving NULLs generally resolve to - UNKNOWN. - - -
- Relational comparisons - - Comparisons involve one of the comparison operators - , >=, <, <=, <>]>. HQL also defines - as a comparison operator synonymous with ]]>. The operands should be - of the same type. - - - Relational comparison examples - - - - Comparisons can also involve subquery qualifiers - ALL, ANY, - SOME. SOME and ANY are synonymous. - - - The ALL qualifier resolves to true if the comparison is true for all of the values in the result of - the subquery. It resolves to false if the subquery result is empty. - - - ALL subquery comparison qualifier example - - - - The ANY/SOME qualifier resolves to true if the comparison is true for some of (at least one of) the - values in the result of the subquery. It resolves to false if the subquery result is empty. - -
- -
- Nullness predicate - - Check a value for nullness. Can be applied to basic attribute references, entity references and - parameters. HQL additionally allows it to be applied to component/embeddable types. - - - Nullness checking examples - - -
- -
- Like predicate - - Performs a like comparison on string values. The syntax is: - - - - The semantics follow that of the SQL like expression. The pattern_value is the - pattern to attempt to match in the string_expression. Just like SQL, - pattern_value can use _ and % as wildcards. The - meanings are the same. _ matches any single character. % matches - any number of characters. - - - The optional escape_character is used to specify an escape character used to - escape the special meaning of _ and % in the - pattern_value. THis is useful when needing to search on patterns including either - _ or % - - - Like predicate examples - - -
- -
- Between predicate - - Analogous to the SQL between expression. Perform a evaluation that a value is within the range - of 2 other values. All the operands should have comparable types. - - - Between predicate examples - - -
- -
- In predicate - - IN predicates performs a check that a particular value is in a list of values. - Its syntax is: - - - - The types of the single_valued_expression and the individual values in the - single_valued_list must be consistent. JPQL limits the valid types here - to string, numeric, date, time, timestamp, and enum types. In JPQL, - single_valued_expression can only refer to: - - - - - state fields, which is its term for simple attributes. Specifically this - excludes association and component/embedded attributes. - - - - - entity type expressions. See - - - - - In HQL, single_valued_expression can refer to a far more broad set of expression - types. Single-valued association are allowed. So are component/embedded attributes, although that - feature depends on the level of support for tuple or row value constructor syntax in - the underlying database. Additionally, HQL does not limit the value type in any way, though - application developers should be aware that different types may incur limited support based on - the underlying database vendor. This is largely the reason for the JPQL limitations. - - - The list of values can come from a number of different sources. In the - constructor_expression and collection_valued_input_parameter, the - list of values must not be empty; it must contain at least one value. - - - In predicate examples - - -
- -
- Exists predicate - - Exists expressions test the existence of results from a subquery. The affirmative form returns true - if the subquery result contains values. The negated form returns true if the subquery - result is empty. - -
- -
- Empty collection predicate - - The IS [NOT] EMPTY expression applies to collection-valued path expressions. It - checks whether the particular collection has any associated values. - - - Empty collection expression examples - - -
- -
- Member-of collection predicate - - The [NOT] MEMBER [OF] expression applies to collection-valued path expressions. It - checks whether a value is a member of the specified collection. - - - Member-of collection expression examples - - -
- -
- NOT predicate operator - - The NOT operator is used to negate the predicate that follows it. If that - following predicate is true, the NOT resolves to false. If the predicate is true, NOT resolves to - false. If the predicate is unknown, the NOT resolves to unknown as well. - -
- -
- AND predicate operator - - The AND operator is used to combine 2 predicate expressions. The result of the - AND expression is true if and only if both predicates resolve to true. If either predicate resolves - to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. - -
- -
- OR predicate operator - - The OR operator is used to combine 2 predicate expressions. The result of the - OR expression is true if either predicate resolves to true. If both predicates resolve to unknown, the - OR expression resolves to unknown. Otherwise, the result is false. - -
-
- -
- The <literal>WHERE</literal> clause - - The WHERE clause of a query is made up of predicates which assert whether values in - each potential row match the predicated checks. Thus, the where clause restricts the results returned - from a select query and limits the scope of update and delete queries. - -
- -
- Grouping - - The GROUP BY clause allows building aggregated results for various value groups. As an - example, consider the following queries: - - - Group-by illustration - - - - The first query retrieves the complete total of all orders. The second retrieves the total for each - customer; grouped by each customer. - - - In a grouped query, the where clause applies to the non aggregated values (essentially it determines whether - rows will make it into the aggregation). The HAVING clause also restricts results, - but it operates on the aggregated values. In the example, - we retrieved order totals for all customers. If that ended up being too much data to deal with, - we might want to restrict the results to focus only on customers with a summed order total of more than - $10,000.00: - - - Having illustration - - - - The HAVING clause follows the same rules as the WHERE clause and is also made up of predicates. HAVING is - applied after the groupings and aggregations have been done; WHERE is applied before. - -
- -
- Ordering - - The results of the query can also be ordered. The ORDER BY clause is used to specify - the selected values to be used to order the result. The types of expressions considered valid as part - of the order-by clause include: - - - - - state fields - - - - - component/embeddable attributes - - - - - scalar expressions such as arithmetic operations, functions, etc. - - - - - identification variable declared in the select clause for any of the previous expression types - - - - - Additionally, JPQL says that all values referenced in the order-by clause must be named in the select - clause. HQL does not mandate that restriction, but applications desiring database portability should be - aware that not all databases support referencing values in the order-by clause that are not referenced - in the select clause. - - - Individual expressions in the order-by can be qualified with either ASC (ascending) or - DESC (descending) to indicated the desired ordering direction. Null values can be placed - in front or at the end of sorted set using NULLS FIRST or NULLS LAST - clause respectively. - - - Order-by examples - - -
- -
- Query API -
-
diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/agg_func_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/agg_func_example.txt deleted file mode 100644 index fffa0f6acccd..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/agg_func_example.txt +++ /dev/null @@ -1,10 +0,0 @@ -select count(*), sum( o.total ), avg( o.total ), min( o.total ), max( o.total ) -from Order o - -select count( distinct c.name ) -from Customer c - -select c.id, c.name, sum( o.total ) -from Customer c - left join c.orders o -group by c.id, c.name \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/arithmetic_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/arithmetic_example.txt deleted file mode 100644 index 19c8b5c2fb0a..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/arithmetic_example.txt +++ /dev/null @@ -1,9 +0,0 @@ -select year( current_date() ) - year( c.dateOfBirth ) -from Customer c - -select c -from Customer c -where year( current_date() ) - year( c.dateOfBirth ) < 30 - -select o.customer, o.total + ( o.total * :salesTax ) -from Order o \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/collection_expression_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/collection_expression_example.txt deleted file mode 100644 index a625cf5066f1..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/collection_expression_example.txt +++ /dev/null @@ -1,36 +0,0 @@ -select cal -from Calendar cal -where maxelement(cal.holidays) > current_date() - -select o -from Order o -where maxindex(o.items) > 100 - -select o -from Order o -where minelement(o.items) > 10000 - -select m -from Cat as m, Cat as kit -where kit in elements(m.kittens) - -// the above query can be re-written in jpql standard way: -select m -from Cat as m, Cat as kit -where kit member of m.kittens - -select p -from NameList l, Person p -where p.name = some elements(l.names) - -select cat -from Cat cat -where exists elements(cat.kittens) - -select p -from Player p -where 3 > all elements(p.scores) - -select show -from Show show -where 'fizard' in indices(show.acts) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/collection_reference_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/collection_reference_example.txt deleted file mode 100644 index d9f1eaceb33f..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/collection_reference_example.txt +++ /dev/null @@ -1,16 +0,0 @@ -select c -from Customer c - join c.orders o - join o.lineItems l - join l.product p -where o.status = 'pending' - and p.status = 'backorder' - -// alternate syntax -select c -from Customer c, - in(c.orders) o, - in(o.lineItems) l - join l.product p -where o.status = 'pending' - and p.status = 'backorder' \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/concat_op_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/concat_op_example.txt deleted file mode 100644 index 645f51dca823..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/concat_op_example.txt +++ /dev/null @@ -1,3 +0,0 @@ -select 'Mr. ' || c.name.first || ' ' || c.name.last -from Customer c -where c.gender = Gender.MALE \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/ctor_dynamic_instantiation_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/ctor_dynamic_instantiation_example.txt deleted file mode 100644 index c96df422fe87..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/ctor_dynamic_instantiation_example.txt +++ /dev/null @@ -1,4 +0,0 @@ -select new Family( mother, mate, offspr ) -from DomesticCat as mother - join mother.mate as mate - left join mother.kittens as offspr \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/empty_collection_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/empty_collection_example.txt deleted file mode 100644 index f6af597a87f3..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/empty_collection_example.txt +++ /dev/null @@ -1,7 +0,0 @@ -select o -from Order o -where o.lineItems is empty - -select c -from Customer c -where c.pastDueBills is not empty \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/entity_type_exp_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/entity_type_exp_example.txt deleted file mode 100644 index 1f6c58e49cda..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/entity_type_exp_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -select p -from Payment p -where type(p) = CreditCardPayment - -select p -from Payment p -where type(p) = :aType - diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/group_by_illustration.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/group_by_illustration.txt deleted file mode 100644 index 598d20c5f8bf..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/group_by_illustration.txt +++ /dev/null @@ -1,10 +0,0 @@ -// retrieve the total for all orders -select sum( o.total ) -from Order o - -// retrieve the total of all orders -// *grouped by* customer -select c.id, sum( o.total ) -from Order o - inner join o.customer c -group by c.id \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/having_illustration.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/having_illustration.txt deleted file mode 100644 index 60f39b3c4a5d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/having_illustration.txt +++ /dev/null @@ -1,5 +0,0 @@ -select c.id, sum( o.total ) -from Order o - inner join o.customer c -group by c.id -having sum( o.total ) > 10000.00 \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/index_operator_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/index_operator_example.txt deleted file mode 100644 index 06f3326c03ed..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/index_operator_example.txt +++ /dev/null @@ -1,22 +0,0 @@ -select o -from Order o -where o.items[0].id = 1234 - -select p -from Person p, Calendar c -where c.holidays['national day'] = p.birthDay - and p.nationality.calendar = c - -select i -from Item i, Order o -where o.items[ o.deliveredItemIndices[0] ] = i - and o.id = 11 - -select i -from Item i, Order o -where o.items[ maxindex(o.items) ] = i - and o.id = 11 - -select i -from Item i, Order o -where o.items[ size(o.items) - 1 ] = i \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_explicit_inner.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_explicit_inner.txt deleted file mode 100644 index a809d3fec960..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_explicit_inner.txt +++ /dev/null @@ -1,10 +0,0 @@ -select c -from Customer c - join c.chiefExecutive ceo -where ceo.age < 25 - -// same query but specifying join type as 'inner' explicitly -select c -from Customer c - inner join c.chiefExecutive ceo -where ceo.age < 25 diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_explicit_outer.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_explicit_outer.txt deleted file mode 100644 index 2e8ed3385451..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_explicit_outer.txt +++ /dev/null @@ -1,15 +0,0 @@ -// get customers who have orders worth more than $5000 -// or who are in "preferred" status -select distinct c -from Customer c - left join c.orders o -where o.value > 5000.00 - or c.status = 'preferred' - -// functionally the same query but using the -// 'left outer' phrase -select distinct c -from Customer c - left outer join c.orders o -where o.value > 5000.00 - or c.status = 'preferred' diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_fetch.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_fetch.txt deleted file mode 100644 index a882f288167b..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_fetch.txt +++ /dev/null @@ -1,3 +0,0 @@ -select c -from Customer c - left join fetch c.orders o \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_implicit.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_implicit.txt deleted file mode 100644 index 4185a64f7271..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_implicit.txt +++ /dev/null @@ -1,9 +0,0 @@ -select c -from Customer c -where c.chiefExecutive.age < 25 - -// same as -select c -from Customer c - inner join c.chiefExecutive ceo -where ceo.age < 25 diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_implicit_reused.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_implicit_reused.txt deleted file mode 100644 index 18db817e5ec1..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_implicit_reused.txt +++ /dev/null @@ -1,19 +0,0 @@ -select c -from Customer c -where c.chiefExecutive.age < 25 - and c.chiefExecutive.address.state = 'TX' - -// same as -select c -from Customer c - inner join c.chiefExecutive ceo -where ceo.age < 25 - and ceo.address.state = 'TX' - -// same as -select c -from Customer c - inner join c.chiefExecutive ceo - inner join ceo.address a -where ceo.age < 25 - and a.state = 'TX' diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_with.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_with.txt deleted file mode 100644 index b0441e621687..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/join_example_with.txt +++ /dev/null @@ -1,4 +0,0 @@ -select distinct c -from Customer c - left join c.orders o - with o.value > 5000.00 \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/jpql_positional_parameter_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/jpql_positional_parameter_example.txt deleted file mode 100644 index ca276e93c575..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/jpql_positional_parameter_example.txt +++ /dev/null @@ -1,16 +0,0 @@ -String queryString = - "select c " + - "from Customer c " + - "where c.name = ?1 " + - " or c.nickName = ?1"; - -// HQL - as you can see, handled just like named parameters -// in terms of API -List customers = session.createQuery( queryString ) - .setParameter( "1", theNameOfInterest ) - .list(); - -// JPQL -List customers = entityManager.createQuery( queryString, Customer.class ) - .setParameter( 1, theNameOfInterest ) - .getResultList(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/list_dynamic_instantiation_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/list_dynamic_instantiation_example.txt deleted file mode 100644 index 1678b5ad2d23..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/list_dynamic_instantiation_example.txt +++ /dev/null @@ -1,4 +0,0 @@ -select new list(mother, offspr, mate.name) -from DomesticCat as mother - inner join mother.mate as mate - left outer join mother.kittens as offspr \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/locate_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/locate_bnf.txt deleted file mode 100644 index c8e0f02d8ffa..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/locate_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -locate( string_expression, string_expression[, numeric_expression] ) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/map_dynamic_instantiation_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/map_dynamic_instantiation_example.txt deleted file mode 100644 index ed9bcfc972b0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/map_dynamic_instantiation_example.txt +++ /dev/null @@ -1,7 +0,0 @@ -select new map( mother as mother, offspr as offspr, mate as mate ) -from DomesticCat as mother - inner join mother.mate as mate - left outer join mother.kittens as offspr - -select new map( max(c.bodyWeight) as max, min(c.bodyWeight) as min, count(*) as n ) -from Cat c \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/member_of_collection_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/member_of_collection_example.txt deleted file mode 100644 index e0969a3d0bd5..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/member_of_collection_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -select p -from Person p -where 'John' member of p.nickNames - -select p -from Person p -where p.name.first = 'Joseph' - and 'Joey' not member of p.nickNames diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/multiple_root_entity_ref_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/multiple_root_entity_ref_example.txt deleted file mode 100644 index 173c8b38791f..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/multiple_root_entity_ref_example.txt +++ /dev/null @@ -1,5 +0,0 @@ -// build a product between customers and active mailing campaigns so we can spam! -select distinct cust, camp -from Customer cust, Campaign camp -where camp.type = 'mail' - and current_timestamp() between camp.activeRange.start and camp.activeRange.end \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/multiple_root_entity_ref_example2.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/multiple_root_entity_ref_example2.txt deleted file mode 100644 index 5e2231e433db..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/multiple_root_entity_ref_example2.txt +++ /dev/null @@ -1,5 +0,0 @@ -// retrieve all customers with headquarters in the same state as Acme's headquarters -select distinct c1 -from Customer c1, Customer c2 -where c1.address.state = c2.address.state - and c2.name = 'Acme' \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/named_parameter_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/named_parameter_example.txt deleted file mode 100644 index 0ca77a503d9c..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/named_parameter_example.txt +++ /dev/null @@ -1,15 +0,0 @@ -String queryString = - "select c " + - "from Customer c " + - "where c.name = :name " + - " or c.nickName = :name"; - -// HQL -List customers = session.createQuery( queryString ) - .setParameter( "name", theNameOfInterest ) - .list(); - -// JPQL -List customers = entityManager.createQuery( queryString, Customer.class ) - .setParameter( "name", theNameOfInterest ) - .getResultList(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/nullif_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/nullif_example.txt deleted file mode 100644 index 6610b5764c55..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/nullif_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -// return customers who have changed their last name -select nullif( c.previousName.last, c.name.last ) -from Customer c - -// equivalent CASE expression -select case when c.previousName.last = c.name.last then null - else c.previousName.last end -from Customer c diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/numeric_literals_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/numeric_literals_example.txt deleted file mode 100644 index 1732edfec295..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/numeric_literals_example.txt +++ /dev/null @@ -1,29 +0,0 @@ -// simple integer literal -select o -from Order o -where o.referenceNumber = 123 - -// simple integer literal, typed as a long -select o -from Order o -where o.referenceNumber = 123L - -// decimal notation -select o -from Order o -where o.total > 5000.00 - -// decimal notation, typed as a float -select o -from Order o -where o.total > 5000.00F - -// scientific notation -select o -from Order o -where o.total > 5e+3 - -// scientific notation, typed as a float -select o -from Order o -where o.total > 5e+3F \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/order_by_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/order_by_example.txt deleted file mode 100644 index 71b6024feeb8..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/order_by_example.txt +++ /dev/null @@ -1,10 +0,0 @@ -// legal because p.name is implicitly part of p -select p -from Person p -order by p.name - -select c.id, sum( o.total ) as t -from Order o - inner join o.customer c -group by c.id -order by t diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_between_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_between_example.txt deleted file mode 100644 index f445b48d4658..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_between_example.txt +++ /dev/null @@ -1,19 +0,0 @@ -select p -from Customer c - join c.paymentHistory p -where c.id = 123 - and index(p) between 0 and 9 - -select c -from Customer c -where c.president.dateOfBirth - between {d '1945-01-01'} - and {d '1965-01-01'} - -select o -from Order o -where o.total between 500 and 5000 - -select p -from Person p -where p.name between 'A' and 'E' \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_comparison_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_comparison_example.txt deleted file mode 100644 index ff52fa793269..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_comparison_example.txt +++ /dev/null @@ -1,34 +0,0 @@ -// numeric comparison -select c -from Customer c -where c.chiefExecutive.age < 30 - -// string comparison -select c -from Customer c -where c.name = 'Acme' - -// datetime comparison -select c -from Customer c -where c.inceptionDate < {d '2000-01-01'} - -// enum comparison -select c -from Customer c -where c.chiefExecutive.gender = com.acme.Gender.MALE - -// boolean comparison -select c -from Customer c -where c.sendEmail = true - -// entity type comparison -select p -from Payment p -where type(p) = WireTransferPayment - -// entity value comparison -select c -from Customer c -where c.chiefExecutive = c.chiefTechnologist \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_comparison_example_using_all.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_comparison_example_using_all.txt deleted file mode 100644 index 2c9aa10ed3ad..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_comparison_example_using_all.txt +++ /dev/null @@ -1,9 +0,0 @@ -// select all players that scored at least 3 points -// in every game. -select p -from Player p -where 3 > all ( - select spg.points - from StatsPerGame spg - where spg.player = p -) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_in_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_in_bnf.txt deleted file mode 100644 index a8f3daec416b..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_in_bnf.txt +++ /dev/null @@ -1,8 +0,0 @@ -in_expression ::= single_valued_expression - [NOT] IN single_valued_list - -single_valued_list ::= constructor_expression | - (subquery) | - collection_valued_input_parameter - -constructor_expression ::= (expression[, expression]*) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_in_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_in_example.txt deleted file mode 100644 index 8e0f3b97f5ff..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_in_example.txt +++ /dev/null @@ -1,36 +0,0 @@ -select p -from Payment p -where type(p) in (CreditCardPayment, WireTransferPayment) - -select c -from Customer c -where c.hqAddress.state in ('TX', 'OK', 'LA', 'NM') - -select c -from Customer c -where c.hqAddress.state in ? - -select c -from Customer c -where c.hqAddress.state in ( - select dm.state - from DeliveryMetadata dm - where dm.salesTax is not null -) - -// Not JPQL compliant! -select c -from Customer c -where c.name in ( - ('John','Doe'), - ('Jane','Doe') -) - -// Not JPQL compliant! -select c -from Customer c -where c.chiefExecutive in ( - select p - from Person p - where ... -) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_like_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_like_bnf.txt deleted file mode 100644 index abac134a49cf..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_like_bnf.txt +++ /dev/null @@ -1,4 +0,0 @@ -like_expression ::= - string_expression - [NOT] LIKE pattern_value - [ESCAPE escape_character] diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_like_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_like_example.txt deleted file mode 100644 index 348c21bae391..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_like_example.txt +++ /dev/null @@ -1,13 +0,0 @@ -select p -from Person p -where p.name like '%Schmidt' - -select p -from Person p -where p.name not like 'Jingleheimmer%' - -// find any with name starting with "sp_" -select sp -from StoredProcedureMetadata sp -where sp.name like 'sp|_%' escape '|' - diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_nullness_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_nullness_example.txt deleted file mode 100644 index bf36fe023035..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/predicate_nullness_example.txt +++ /dev/null @@ -1,9 +0,0 @@ -// select everyone with an associated address -select p -from Person p -where p.address is not null - -// select everyone without an associated address -select p -from Person p -where p.address is null \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/qualified_path_expressions_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/qualified_path_expressions_example.txt deleted file mode 100644 index 5bb7eab416ea..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/qualified_path_expressions_example.txt +++ /dev/null @@ -1,33 +0,0 @@ -// Product.images is a Map : key = a name, value = file path - -// select all the image file paths (the map value) for Product#123 -select i -from Product p - join p.images i -where p.id = 123 - -// same as above -select value(i) -from Product p - join p.images i -where p.id = 123 - -// select all the image names (the map key) for Product#123 -select key(i) -from Product p - join p.images i -where p.id = 123 - -// select all the image names and file paths (the 'Map.Entry') for Product#123 -select entry(i) -from Product p - join p.images i -where p.id = 123 - -// total the value of the initial line items for all orders for a customer -select sum( li.amount ) -from Customer c - join c.orders o - join o.lineItems li -where c.id = 123 - and index(li) = 1 \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/root_entity_ref_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/root_entity_ref_bnf.txt deleted file mode 100644 index 9e5b71cca098..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/root_entity_ref_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -root_entity_reference ::= entity_name [AS] identification_variable \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/searched_case_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/searched_case_bnf.txt deleted file mode 100644 index 211fa3be089d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/searched_case_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -CASE [ WHEN {test_conditional} THEN {match_result} ]* ELSE {miss_result} END \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/searched_case_exp_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/searched_case_exp_example.txt deleted file mode 100644 index 34d80d9cc1fd..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/searched_case_exp_example.txt +++ /dev/null @@ -1,9 +0,0 @@ -select case when c.name.first is not null then c.name.first - when c.nickName is not null then c.nickName - else '' end -from Customer c - -// Again, the abbreviated form coalesce can handle this a -// little more succinctly -select coalesce( c.name.first, c.nickName, '' ) -from Customer c diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simple_case_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simple_case_bnf.txt deleted file mode 100644 index f4b03b45fa73..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simple_case_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -CASE {operand} WHEN {test_value} THEN {match_result} ELSE {miss_result} END \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simple_case_exp_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simple_case_exp_example.txt deleted file mode 100644 index f09820d50618..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simple_case_exp_example.txt +++ /dev/null @@ -1,16 +0,0 @@ -select case c.nickName when null then '' else c.nickName end -from Customer c - -// This NULL checking is such a common case that most dbs -// define an abbreviated CASE form. For example: -select nvl( c.nickName, '' ) -from Customer c - -// or: -select isnull( c.nickName, '' ) -from Customer c - -// the standard coalesce abbreviated form can be used -// to achieve the same result: -select coalesce( c.nickName, '' ) -from Customer c diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simplest_query.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simplest_query.java deleted file mode 100644 index 10b1a0b4bf6c..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simplest_query.java +++ /dev/null @@ -1 +0,0 @@ -select c from com.acme.Cat c \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simplest_query2.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simplest_query2.java deleted file mode 100644 index 4b56392fadb9..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/simplest_query2.java +++ /dev/null @@ -1 +0,0 @@ -select c from Cat c \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_delete_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_delete_bnf.txt deleted file mode 100644 index 087d1ed4da4a..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_delete_bnf.txt +++ /dev/null @@ -1,3 +0,0 @@ -delete_statement ::= delete_clause [where_clause] - -delete_clause ::= DELETE FROM entity_name [[AS] identification_variable] diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_insert_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_insert_bnf.txt deleted file mode 100644 index 95bb029cceb3..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_insert_bnf.txt +++ /dev/null @@ -1,5 +0,0 @@ -insert_statement ::= insert_clause select_statement - -insert_clause ::= INSERT INTO entity_name (attribute_list) - -attribute_list ::= state_field[, state_field ]* \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_insert_example_named_id.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_insert_example_named_id.java deleted file mode 100644 index 1a27846de607..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_insert_example_named_id.java +++ /dev/null @@ -1,2 +0,0 @@ -String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ..."; -int createdEntities = s.createQuery( hqlInsert ).executeUpdate(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_select_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_select_bnf.txt deleted file mode 100644 index e4aac0d1c3bb..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_select_bnf.txt +++ /dev/null @@ -1,7 +0,0 @@ -select_statement :: = - [select_clause] - from_clause - [where_clause] - [groupby_clause] - [having_clause] - [orderby_clause] \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_bnf.txt deleted file mode 100644 index 6ffbb48ed2d3..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_bnf.txt +++ /dev/null @@ -1,11 +0,0 @@ -update_statement ::= update_clause [where_clause] - -update_clause ::= UPDATE entity_name [[AS] identification_variable] - SET update_item {, update_item}* - -update_item ::= [identification_variable.]{state_field | single_valued_object_field} - = new_value - -new_value ::= scalar_expression | - simple_entity_expression | - NULL diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_hql.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_hql.java deleted file mode 100644 index 779ea5121fbc..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_hql.java +++ /dev/null @@ -1,8 +0,0 @@ -String hqlUpdate = - "update Customer c " + - "set c.name = :newName " + - "where c.name = :oldName"; -int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_hql_versioned.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_hql_versioned.java deleted file mode 100644 index aece8f859511..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_hql_versioned.java +++ /dev/null @@ -1,8 +0,0 @@ -String hqlVersionedUpdate = - "update versioned Customer c " + - "set c.name = :newName " + - "where c.name = :oldName"; -int updatedEntities = s.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_jpql.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_jpql.java deleted file mode 100644 index 99a501254853..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/statement_update_example_jpql.java +++ /dev/null @@ -1,8 +0,0 @@ -String jpqlUpdate = - "update Customer c " + - "set c.name = :newName " + - "where c.name = :oldName"; -int updatedEntities = entityManager.createQuery( jpqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/string_literals_example.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/string_literals_example.txt deleted file mode 100644 index d2f95cb50f49..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/string_literals_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -select c -from Customer c -where c.name = 'Acme' - -select c -from Customer c -where c.name = 'Acme''s Pretzel Logic' - diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/substring_bnf.txt b/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/substring_bnf.txt deleted file mode 100644 index 81302e9a4b19..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/query_ql/extras/substring_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -substring( string_expression, numeric_expression [, numeric_expression] ) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/services/Services.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/services/Services.xml deleted file mode 100644 index 3701eb21b660..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/services/Services.xml +++ /dev/null @@ -1,1113 +0,0 @@ - - - - - - Services - -
- What are services? - - Services are classes that provide Hibernate with pluggable implementations of various types of - functionality. Specifically they are implementations of certain service contract interfaces. The interface - is known as the service role; the implementation class is know as the service implementation. Generally - speaking, users can plug in alternate implementations of all standard service roles (overriding); they can - also define additional services beyond the base set of service roles (extending). - -
- -
- Service contracts - - The basic requirement for a service is to implement the marker interface - org.hibernate.service.Service. Hibernate uses this internally for some - basic type safety. - - - Optionally, the service can also implement the - org.hibernate.service.spi.Startable and - org.hibernate.service.spi.Stoppable interfaces to receive notifications - of being started and stopped. Another optional service contract is - org.hibernate.service.spi.Manageable which marks the service as manageable - in JMX provided the JMX integration is enabled. - -
- -
- Service dependencies - - Services are allowed to declare dependencies on other services using either of 2 approaches. - -
- @<interfacename>org.hibernate.service.spi.InjectService</interfacename> - - Any method on the service implementation class accepting a single parameter and annotated with - @InjectService is considered requesting injection of another service. - - - By default the type of the method parameter is expected to be the service role to be injected. If the - parameter type is different than the service role, the serviceRole attribute - of the InjectService should be used to explicitly name the role. - - - By default injected services are considered required, that is the start up will fail if a named - dependent service is missing. If the service to be injected is optional, the - required attribute of the InjectService - should be declared as false (default is true). - -
-
- <interfacename>org.hibernate.service.spi.ServiceRegistryAwareService</interfacename> - - The second approach is a pull approach where the service implements the optional service interface - org.hibernate.service.spi.ServiceRegistryAwareService which declares - a single injectServices method. During startup, Hibernate will inject the - org.hibernate.service.ServiceRegistry itself into services which - implement this interface. The service can then use the ServiceRegistry - reference to locate any additional services it needs. - -
-
- -
- ServiceRegistry - - The central service API, aside from the services themselves, is the - org.hibernate.service.ServiceRegistry interface. The main purpose of - a service registry is to hold, manage and provide access to services. - - - Service registries are hierarchical. Services in one registry can depend on and utilize services in that - same registry as well as any parent registries. - - - Use org.hibernate.boot.registry.StandardServiceRegistryBuilder to build a - org.hibernate.service.ServiceRegistry instance. - -
- - -
- Standard services - -
- <interfacename>org.hibernate.engine.jdbc.batch.spi.BatchBuilder</interfacename> - - - Notes - - - Defines strategy for how Hibernate manages JDBC statement batching - - - - - Initiator - - - org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator - - - - - Implementations - - - org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl - - - - -
- -
- <interfacename>org.hibernate.engine.config.spi.ConfigurationService</interfacename> - - - Notes - - - Provides access to the configuration settings, combining those explicitly provided as well - as those contributed by any registered - org.hibernate.integrator.spi.Integrator implementations - - - - - Initiator - - - org.hibernate.engine.config.internal.ConfigurationServiceInitiator - - - - - Implementations - - - org.hibernate.engine.config.internal.ConfigurationServiceImpl - - - - -
- -
- <interfacename>ConnectionProvider</interfacename> - - - Notes - - - Defines the means in which Hibernate can obtain and release - java.sql.Connection instances for its use. - - - - - Initiator - - - ConnectionProviderInitiator - - - - - Implementations - - - - - org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider - - provides connection pooling based on integration with the C3P0 connection pooling library - - - - - DatasourceConnectionProviderImpl - - provides connection managed delegated to a - javax.sql.DataSource - - - - - DriverManagerConnectionProviderImpl - - provides rudimentary connection pooling based on simple custom pool. Note intended - production use! - - - - - org.hibernate.service.jdbc.connections.internal.ProxoolConnectionProvider - - provides connection pooling based on integration with the proxool connection pooling library - - - - - UserSuppliedConnectionProviderImpl - - Provides no connection support. Indicates the user will supply connections to Hibernate directly. - Not recommended for use. - - - - - - -
- -
- <interfacename>org.hibernate.engine.jdbc.dialect.spi.DialectFactory</interfacename> - - - Notes - - - Contract for Hibernate to obtain org.hibernate.dialect.Dialect - instance to use. This is either explicitly defined by the - hibernate.dialect property or determined by the - service which is a delegate to this service. - - - - - Initiator - - - org.hibernate.engine.jdbc.dialect.internal.DialectFactoryInitiator - - - - - Implementations - - - org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl - - - - -
- -
- <interfacename>org.hibernate.engine.jdbc.dialect.spi.DialectResolver</interfacename> - - - Notes - - - Provides resolution of org.hibernate.dialect.Dialect to use based on - information extracted from JDBC metadata. - - - The standard resolver implementation acts as a chain, delegating to a series of individual - resolvers. The standard Hibernate resolution behavior is contained in - org.hibernate.engine.jdbc.dialect.internal.StandardDatabaseMetaDataDialectResolver. - org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator - also consults with the hibernate.dialect_resolvers setting for any - custom resolvers. - - - - - Initiator - - - org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator - - - - - Implementations - - - org.hibernate.engine.jdbc.dialect.internal.DialectResolverSet - - - - -
- -
- <interfacename>org.hibernate.engine.jdbc.spi.JdbcServices</interfacename> - - - Notes - - - Special type of service that aggregates together a number of other services and provides - a higher-level set of functionality. - - - - - Initiator - - - org.hibernate.engine.jdbc.internal.JdbcServicesInitiator - - - - - Implementations - - - org.hibernate.engine.jdbc.internal.JdbcServicesImpl - - - - -
- -
- <interfacename>org.hibernate.jmx.spi.JmxService</interfacename> - - - Notes - - - Provides simplified access to JMX related features needed by Hibernate. - - - - - Initiator - - - org.hibernate.jmx.internal.JmxServiceInitiator - - - - - Implementations - - - - - org.hibernate.jmx.internal.DisabledJmxServiceImpl - - A no-op implementation when JMX functionality is disabled. - - - - - org.hibernate.jmx.internal.JmxServiceImpl - - Standard implementation of JMX handling - - - - - - -
- -
- <interfacename>org.hibernate.engine.jndi.spi.JndiService</interfacename> - - - Notes - - - Provides simplified access to JNDI related features needed by Hibernate. - - - - - Initiator - - - org.hibernate.engine.jndi.internal.JndiServiceInitiator - - - - - Implementations - - - org.hibernate.engine.jndi.internal.JndiServiceImpl - - - - -
- -
- <interfacename>org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform</interfacename> - - - Notes - - - Provides an abstraction from the underlying JTA platform when JTA features are used. - - - - - Initiator - - - org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - - - - As of 5.0 support has been completely removed for mapping against legacy - org.hibernate.transaction.TransactionManagerLookup - names and custom implementations. Applications implementing - org.hibernate.transaction.TransactionManagerLookup - or using the hibernate.transaction.manager_lookup_class setting - should update to use JtaPlatform. - - - - - - Implementations - - - - - org.hibernate.engine.transaction.jta.platform.internal.BitronixJtaPlatform - - Integration with the Bitronix stand-alone transaction manager. Can also be referenced - using the Bitronix configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.BorlandEnterpriseServerJtaPlatform - - Integration with the transaction manager as deployed within a Borland Enterprise Server. - Can also be referenced using the Borland configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.JBossAppServerJtaPlatform - - Integration with the transaction manager as deployed within a JBoss Application Server. - Can also be referenced using the JBossAS configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.JBossStandAloneJtaPlatform - - Integration with the JBoss Transactions stand-alone transaction manager. - Can also be referenced using the JBossTS configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.JOTMJtaPlatform - - Integration with the JOTM stand-alone transaction manager. Can also be referenced - using the JOTM configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.JOnASJtaPlatform - - Integration with the JOnAS transaction manager. Can also be referenced using the - JOnAS configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.JRun4JtaPlatform - - Integration with the transaction manager as deployed in a JRun 4 application server. - Can also be referenced using the JRun4 configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform - - No-op version when no JTA set up is configured - - - - - org.hibernate.engine.transaction.jta.platform.internal.OC4JJtaPlatform - - Integration with transaction manager as deployed in an OC4J (Oracle) application - Can also be referenced using the OC4J configuration short name - server. - - - - - org.hibernate.engine.transaction.jta.platform.internal.OrionJtaPlatform - - Integration with transaction manager as deployed in an Orion application server. - Can also be referenced using the Orion configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.ResinJtaPlatform - - Integration with transaction manager as deployed in a Resin application server. - Can also be referenced using the Resin configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.SunOneJtaPlatform - - Integration with transaction manager as deployed in a Sun ONE (7 and above) - application server. Can also be referenced using the SunOne - configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform - - Integration with transaction manager as deployed in a WebSphere Application Server - (6 and above). Can also be referenced using the WebSphereExtended - configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.WebSphereJtaPlatform - - Integration with transaction manager as deployed in a WebSphere Application Server - (4, 5.0 and 5.1). Can also be referenced using the WebSphere - configuration short name - - - - - org.hibernate.engine.transaction.jta.platform.internal.WeblogicJtaPlatform - - Integration with transaction manager as deployed in a Weblogic application server. - Can also be referenced using the Weblogic configuration short name - - - - - - -
- -
- <interfacename>MultiTenantConnectionProvider</interfacename> - - - Notes - - - A variation of providing access to JDBC - connections in multi-tenant environments. - - - - - Initiator - - - N/A - - - - - Implementations - - - Intended that users provide appropriate implementation if needed. - - - - -
- -
- <interfacename>org.hibernate.persister.spi.PersisterClassResolver</interfacename> - - - Notes - - - Contract for determining the appropriate - org.hibernate.persister.entity.EntityPersister - or org.hibernate.persister.collection.CollectionPersister - implementation class to use given an entity or collection mapping. - - - - - Initiator - - - org.hibernate.persister.internal.PersisterClassResolverInitiator - - - - - Implementations - - - org.hibernate.persister.internal.StandardPersisterClassResolver - - - - -
- -
- <interfacename>org.hibernate.persister.spi.PersisterFactory</interfacename> - - - Notes - - - Factory for creating - org.hibernate.persister.entity.EntityPersister - and org.hibernate.persister.collection.CollectionPersister - instances. - - - - - Initiator - - - org.hibernate.persister.internal.PersisterFactoryInitiator - - - - - Implementations - - - org.hibernate.persister.internal.PersisterFactoryImpl - - - - -
- -
- <interfacename>org.hibernate.cache.spi.RegionFactory</interfacename> - - - Notes - - - Integration point for Hibernate's second level cache support. - - - - - Initiator - - - org.hibernate.cache.internal.RegionFactoryInitiator - - - - - Implementations - - - - - org.hibernate.cache.ehcache.EhCacheRegionFactory - - - - - org.hibernate.cache.infinispan.InfinispanRegionFactory - - - - - org.hibernate.cache.infinispan.JndiInfinispanRegionFactory - - - - - org.hibernate.cache.internal.NoCachingRegionFactory - - - - - org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory - - - - - - -
- -
- <interfacename>org.hibernate.service.spi.SessionFactoryServiceRegistryFactory</interfacename> - - - Notes - - - Factory for creating - org.hibernate.service.spi.SessionFactoryServiceRegistry - instances which acts as a specialized - org.hibernate.service.ServiceRegistry for - org.hibernate.SessionFactory scoped services. See - for more details. - - - - - Initiator - - - org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator - - - - - Implementations - - - org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryImpl - - - - -
- -
- <interfacename>org.hibernate.stat.Statistics</interfacename> - - - Notes - - - Contract for exposing collected statistics. The statistics are collected through the - org.hibernate.stat.spi.StatisticsImplementor contract. - - - - - Initiator - - - org.hibernate.stat.internal.StatisticsInitiator - - - Defines a hibernate.stats.factory setting to allow - configuring the - org.hibernate.stat.spi.StatisticsFactory to use internally - when building the actual - org.hibernate.stat.Statistics instance. - - - - - Implementations - - - org.hibernate.stat.internal.ConcurrentStatisticsImpl - - - The default org.hibernate.stat.spi.StatisticsFactory - implementation builds a - org.hibernate.stat.internal.ConcurrentStatisticsImpl instance. - - - - -
- -
- <interfacename>org.hibernate.engine.transaction.spi.TransactionFactory</interfacename> - - - Notes - - - Strategy defining how Hibernate's org.hibernate.Transaction - API maps to the underlying transaction approach. - - - - - Initiator - - - org.hibernate.engine.transaction.internal.TransactionFactoryInitiator - - - Defines a hibernate.transaction.factory_class setting to allow - configuring which TransactionFactory to use. - hibernate.transaction.factory_class follows the rules set forth - under . - - - - - Implementations - - - - - org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory - - A non-JTA strategy in which the transactions are managed using the JDBC - java.sql.Connection. This implementation's short - name is jdbc. - - - - - org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory - - A JTA-based strategy in which Hibernate is not controlling the transactions. An - important distinction here is that interaction with the underlying JTA implementation - is done through the - javax.transaction.TransactionManager. This - implementation's short name is cmt. - - - - - org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory - - A JTA-based strategy in which Hibernate *may* be controlling the transactions. An - important distinction here is that interaction with the underlying JTA - implementation is done through the - javax.transaction.UserTransaction. This - implementation's short name is jta. - - - - - - -
- -
- <interfacename>org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor</interfacename> - - - Notes - - - Contract for extracting statements from import.sql scripts. - - - - - Initiator - - - org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractorInitiator - - - - - Implementations - - - - - org.hibernate.tool.hbm2ddl.SingleLineSqlCommandExtractor - treats each line as a complete SQL statement. Comment lines shall start with - --, // or /* character - sequence. - - - - - org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor - supports instructions/comments and quoted strings spread over multiple lines. Each - statement must end with semicolon. - - - - - - -
- -
- - -
- Custom services - - Once a org.hibernate.service.ServiceRegistry is built it is considered - immutable; the services themselves might accept re-configuration, but immutability here means - adding/replacing services. So another role provided by the - org.hibernate.boot.registry.StandardServiceRegistryBuilder is to allow tweaking of the services - that will be contained in the org.hibernate.service.ServiceRegistry - generated from it. - - - There are 2 means to tell a org.hibernate.boot.registry.StandardServiceRegistryBuilder about - custom services. - - - - - Implement a org.hibernate.boot.registry.StandardServiceInitiator class - to control on-demand construction of the service class and add it to the - org.hibernate.boot.registry.StandardServiceRegistryBuilder via its - addInitiator method. - - - - - Just instantiate the service class and add it to the - org.hibernate.boot.registry.StandardServiceRegistryBuilder via its - addService method. - - - - - Either approach the adding a service approach or the adding an initiator approach are valid for extending a - registry (adding new service roles) and overriding services (replacing service implementations). - -
- - -
- Special service registries - -
- Boot-strap registry - - The boot-strap registry holds services that absolutely have to be available for most things to work. - The main service here is the which is a perfect example. - Even resolving configuration files needs access to class loading services (resource look ups). This - is the root registry (no parent) in normal use. - - - - Instances of boot-strap registries are built using the - org.hibernate.boot.registry.BootstrapServiceRegistryBuilder class. - - - - Using BootstrapServiceRegistryBuilder - - - -
- Bootstrap registry services -
- <interfacename>org.hibernate.boot.registry.classloading.spi.ClassLoaderService</interfacename> - - Hibernate needs to interact with ClassLoaders. However, the manner in which Hibernate - (or any library) should interact with ClassLoaders varies based on the runtime environment - which is hosting the application. Application servers, OSGi containers, and other modular - class loading systems impose very specific class-loading requirements. This service is provides - Hibernate an abstraction from this environmental complexity. And just as importantly, it does - so in a single-swappable-component manner. - - - In terms of interacting with a ClassLoader, Hibernate needs the following capabilities: - - - - the ability to locate application classes - - - - - the ability to locate integration classes - - - - - the ability to locate resources (properties files, xml files, etc) - - - - - the ability to load java.util.ServiceLoader - - - - - - - Currently, the ability to load application classes and the ability to load integration - classes are combined into a single "load class" capability on the service. That may - change in a later release. - - -
- -
- <interfacename>org.hibernate.integrator.spi.IntegratorService</interfacename> - - Applications, add-ons and others all need to integrate with Hibernate which used to require - something, usually the application, to coordinate registering the pieces of each integration - needed on behalf of each integrator. The intent of this service is to allow those integrators - to be discovered and to have them integrate themselves with Hibernate. - - - This service focuses on the discovery aspect. It leverages the standard Java - java.util.ServiceLoader capability provided by the - org.hibernate.boot.registry.classloading.spi.ClassLoaderService - in order to discover implementations of the - org.hibernate.integrator.spi.Integrator contract. - Integrators would simply define a file named - /META-INF/services/org.hibernate.integrator.spi.Integrator and make it - available on the classpath. java.util.ServiceLoader covers the - format of this file in detail, but essentially it list classes by FQN that implement the - org.hibernate.integrator.spi.Integrator one per line. - - - See - -
-
-
- -
- SessionFactory registry - - While it is best practice to treat instances of all the registry types as targeting a given - org.hibernate.SessionFactory, the instances of services in this group - explicitly belong to a single org.hibernate.SessionFactory. The - difference is a matter of timing in when they need to be initiated. Generally they need access to the - org.hibernate.SessionFactory to be initiated. This special registry is - org.hibernate.service.spi.SessionFactoryServiceRegistry - - -
- <interfacename>org.hibernate.event.service.spi.EventListenerRegistry</interfacename> - - - Notes - - - Service for managing event listeners. - - - - - Initiator - - - org.hibernate.event.service.internal.EventListenerServiceInitiator - - - - - Implementations - - - org.hibernate.event.service.internal.EventListenerRegistryImpl - - - - -
-
- -
- - -
- Using services and registries - - Coming soon... - -
- -
- Integrators - - The org.hibernate.integrator.spi.Integrator is intended to provide a simple - means for allowing developers to hook into the process of building a functioning SessionFactory. The - The org.hibernate.integrator.spi.Integrator interface defines 2 methods of - interest: integrate allows us to hook into the building process; - disintegrate allows us to hook into a SessionFactory shutting down. - - - - There is a 3rd method defined on org.hibernate.integrator.spi.Integrator, - an overloaded form of integrate accepting a - org.hibernate.metamodel.source.MetadataImplementor instead of - org.hibernate.cfg.Configuration. This form is intended for use with the new - metamodel code scheduled for completion in 5.0 - - - - See - - - In addition to the discovery approach provided by the IntegratorService, applications can manually - register Integrator implementations when building the BootstrapServiceRegistry. - See - - -
- Integrator use-cases - - The main use cases for an org.hibernate.integrator.spi.Integrator right - now are registering event listeners and providing services (see - org.hibernate.integrator.spi.ServiceContributingIntegrator). With 5.0 - we plan on expanding that to allow altering the metamodel describing the mapping between object and - relational models. - - - - Registering event listeners - - -
-
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/services/extras/BootstrapServiceRegistryBuilder-example.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/services/extras/BootstrapServiceRegistryBuilder-example.java deleted file mode 100644 index 5e2638fd0ae7..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/services/extras/BootstrapServiceRegistryBuilder-example.java +++ /dev/null @@ -1,12 +0,0 @@ -BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder() - // pass in org.hibernate.integrator.spi.Integrator instances which are not - // auto-discovered (for whatever reason) but which should be included - .with( anExplicitIntegrator ) - // pass in a class-loader Hibernate should use to load application classes - .withApplicationClassLoader( anExplicitClassLoaderForApplicationClasses ) - // pass in a class-loader Hibernate should use to load resources - .withResourceClassLoader( anExplicitClassLoaderForResources ) - // see BootstrapServiceRegistryBuilder for rest of available methods - ... - // finally, build the bootstrap registry with all the above options - .build(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/services/extras/register-event-listeners-example.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/services/extras/register-event-listeners-example.java deleted file mode 100644 index 7231d09939e3..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/services/extras/register-event-listeners-example.java +++ /dev/null @@ -1,23 +0,0 @@ -public class MyIntegrator implements org.hibernate.integrator.spi.Integrator { - - public void integrate( - Configuration configuration, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - // As you might expect, an EventListenerRegistry is the thing with which event listeners are registered It is a - // service so we look it up using the service registry - final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); - - // If you wish to have custom determination and handling of "duplicate" listeners, you would have to add an - // implementation of the org.hibernate.event.service.spi.DuplicationStrategy contract like this - eventListenerRegistry.addDuplicationStrategy( myDuplicationStrategy ); - - // EventListenerRegistry defines 3 ways to register listeners: - // 1) This form overrides any existing registrations with - eventListenerRegistry.setListeners( EventType.AUTO_FLUSH, myCompleteSetOfListeners ); - // 2) This form adds the specified listener(s) to the beginning of the listener chain - eventListenerRegistry.prependListeners( EventType.AUTO_FLUSH, myListenersToBeCalledFirst ); - // 3) This form adds the specified listener(s) to the end of the listener chain - eventListenerRegistry.appendListeners( EventType.AUTO_FLUSH, myListenersToBeCalledLast ); - } -} diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/Transactions.xml b/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/Transactions.xml deleted file mode 100644 index e12e40dd91ee..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/Transactions.xml +++ /dev/null @@ -1,540 +0,0 @@ - - - - - - Transactions and concurrency control - -
- Defining Transaction - - It is important to understand that the term transaction has many different yet related meanings in regards - to persistence and Object/Relational Mapping. In most use-cases these definitions align, but that is not - always the case. - - - - - Might refer to the physical transaction with the database. - - - - - Might refer to the logical notion of a transaction as related to a persistence context. - - - - - Might refer to the application notion of a Unit-of-Work, as defined by the archetypal pattern. - - - - - - This documentation largely treats the physical and logic notions of transaction as one-in-the-same. - - -
- -
- Physical Transactions - - Hibernate uses the JDBC API for persistence. In the world of Java there are 2 well defined mechanism - for dealing with transactions in JDBC: JDBC itself and JTA. Hibernate supports both mechanisms for - integrating with transactions and allowing applications to manage physical transactions. - - - The first concept in understanding Hibernate transaction support is the - org.hibernate.engine.transaction.spi.TransactionFactory interface which - serves 2 main functions: - - - - - It allows Hibernate to understand the transaction semantics of the environment. Are we operating - in a JTA environment? Is a physical transaction already currently active? etc. - - - - - It acts as a factory for org.hibernate.Transaction instances which - are used to allow applications to manage and check the state of transactions. - org.hibernate.Transaction is Hibernate's notion of a logical - transaction. JPA has a similar notion in the - javax.persistence.EntityTransaction interface. - - - - - - - javax.persistence.EntityTransaction is only available when using - resource-local transactions. Hibernate allows access to - org.hibernate.Transaction regardless of environment. - - - - - org.hibernate.engine.transaction.spi.TransactionFactory is a standard - Hibernate service. See for details. - - -
- Physical Transactions - JDBC - - JDBC-based transaction management leverages the JDBC defined methods - java.sql.Connection.commit() and - java.sql.Connection.rollback() (JDBC does not define an explicit - method of beginning a transaction). In Hibernate, this approach is represented by the - org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory class. - -
- -
- Physical Transactions - JTA - - JTA-based transaction approach which leverages the - javax.transaction.UserTransaction interface as obtained from - org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform API. This approach - is represented by the - org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory class. - - - See for information on integration with the underlying JTA - system. - -
- - -
- Physical Transactions - CMT - - Another JTA-based transaction approach which leverages the JTA - javax.transaction.TransactionManager interface as obtained from - org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform API. This approach - is represented by the - org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory class. In - an actual JEE CMT environment, access to the - javax.transaction.UserTransaction is restricted. - - - - The term CMT is potentially misleading here. The important point simply being that the physical JTA - transactions are being managed by something other than the Hibernate transaction API. - - - - See for information on integration with the underlying JTA - system. - -
- -
- Physical Transactions - Custom - - Its is also possible to plug in a custom transaction approach by implementing the - org.hibernate.engine.transaction.spi.TransactionFactory contract. - The default service initiator has built-in support for understanding custom transaction approaches - via the hibernate.transaction.factory_class which can name either: - - - - - The instance of org.hibernate.engine.transaction.spi.TransactionFactory - to use. - - - - - The name of a class implementing - org.hibernate.engine.transaction.spi.TransactionFactory - to use. The expectation is that the implementation class have a no-argument constructor. - - - -
- -
- Physical Transactions - Legacy - - During development of 4.0, most of these classes named here were moved to new packages. To help - facilitate upgrading, Hibernate will also recognize the legacy names here for a short period of time. - - - - - org.hibernate.transaction.JDBCTransactionFactory is mapped to - org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory - - - - - org.hibernate.transaction.JTATransactionFactory is mapped to - org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory - - - - - org.hibernate.transaction.CMTTransactionFactory is mapped to - org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory - - - -
- -
- - -
- Hibernate Transaction Usage - - Hibernate uses JDBC connections and JTA resources directly, without adding any additional locking behavior. - It is important for you to become familiar with the JDBC, ANSI SQL, and transaction isolation specifics - of your database management system. - - - Hibernate does not lock objects in memory. The behavior defined by the isolation level of your database - transactions does not change when you use Hibernate. The Hibernate - org.hibernate.Session acts as a transaction-scoped cache providing - repeatable reads for lookup by identifier and queries that result in loading entities. - - - - - To reduce lock contention in the database, the physical database transaction needs to be as short as - possible. Long database transactions prevent your application from scaling to a highly-concurrent load. - Do not hold a database transaction open during end-user-level work, but open it after the end-user-level - work is finished. This is concept is referred to as transactional write-behind. - - -
- -
- Transactional patterns (and anti-patterns) - -
- Session-per-operation anti-pattern - - This is an anti-pattern of opening and closing a Session for each database call - in a single thread. It is also an anti-pattern in terms of database transactions. Group your database - calls into a planned sequence. In the same way, do not auto-commit after every SQL statement in your - application. Hibernate disables, or expects the application server to disable, auto-commit mode - immediately. Database transactions are never optional. All communication with a database must - be encapsulated by a transaction. Avoid auto-commit behavior for reading data, because many small - transactions are unlikely to perform better than one clearly-defined unit of work, and are more - difficult to maintain and extend. - - - - Using auto-commit does not circumvent database transactions. Instead, when in auto-commit mode, - JDBC drivers simply perform each call in an implicit transaction call. It is as if your application - called commit after each and every JDBC call. - - -
- -
- Session-per-request pattern - - This is the most common transaction pattern. The term request here relates to the concept of a system - that reacts to a series of requests from a client/user. Web applications are a prime example of this - type of system, though certainly not the only one. At the beginning of handling such a request, the - application opens a Hibernate Session, starts a transaction, performs - all data related work, ends the transaction and closes the Session. - The crux of the pattern is the one-to-one relationship between the transaction and the - Session. - - - - Within this pattern there is a common technique of defining a current session to - simplify the need of passing this Session around to all the application - components that may need access to it. Hibernate provides support for this technique through the - getCurrentSession method of the SessionFactory. - The concept of a "current" session has to have a scope that defines the bounds in which the notion - of "current" is valid. This is purpose of the - org.hibernate.context.spi.CurrentSessionContext contract. There are 2 - reliable defining scopes: - - - - - First is a JTA transaction because it allows a callback hook to know when it is ending which - gives Hibernate a chance to close the Session and clean up. - This is represented by the - org.hibernate.context.internal.JTASessionContext implementation of - the org.hibernate.context.spi.CurrentSessionContext contract. - Using this implementation, a Session will be opened the first - time getCurrentSession is called within that transaction. - - - - - Secondly is this application request cycle itself. This is best represented with the - org.hibernate.context.internal.ManagedSessionContext implementation of - the org.hibernate.context.spi.CurrentSessionContext contract. - Here an external component is responsible for managing the lifecycle and scoping of a "current" - session. At the start of such a scope, ManagedSessionContext's - bind method is called passing in the - Session. At the end, its unbind - method is called. - - - Some common examples of such "external components" include: - - - - - javax.servlet.Filter implementation - - - - - AOP interceptor with a pointcut on the service methods - - - - - A proxy/interception container - - - - - - - - The getCurrentSession() method has one downside in a JTA environment. If - you use it, after_statement connection release mode is also used by default. Due to a limitation of - the JTA specification, Hibernate cannot automatically clean up any unclosed - ScrollableResults or Iterator - instances returned by scroll() or iterate(). - Release the underlying database cursor by calling ScrollableResults.close() - or Hibernate.close(Iterator) explicitly from a - finally block. - - -
- -
- Conversations - - The session-per-request pattern is not the only valid way of designing units of work. - Many business processes require a whole series of interactions with the user that are interleaved with - database accesses. In web and enterprise applications, it is not acceptable for a database transaction - to span a user interaction. Consider the following example: - - - An example of a long-running conversation - - - The first screen of a dialog opens. The data seen by the user is loaded in a particular - Session and database transaction. The user is free to modify the objects. - - - - - The user uses a UI element to save their work after five minutes of editing. The modifications - are made persistent. The user also expects to have exclusive access to the data during the edit - session. - - - - - - Even though we have multiple databases access here, from the point of view of the user, this series of - steps represents a single unit of work. There are many ways to implement this in your application. - - - - A first naive implementation might keep the Session and database transaction open - while the user is editing, using database-level locks to prevent other users from modifying the same - data and to guarantee isolation and atomicity. This is an anti-pattern, because lock contention is a - bottleneck which will prevent scalability in the future. - - - Several database transactions are used to implement the conversation. In this case, maintaining - isolation of business processes becomes the partial responsibility of the application tier. A single - conversation usually spans several database transactions. These multiple database accesses can only - be atomic as a whole if only one of these database transactions (typically the last one) stores the - updated data. All others only read data. A common way to receive this data is through a wizard-style - dialog spanning several request/response cycles. Hibernate includes some features which make this easy - to implement. - - - - - - - - - Automatic Versioning - - - - - Hibernate can perform automatic optimistic concurrency control for you. It can - automatically detect if a concurrent modification occurred during user think time. - Check for this at the end of the conversation. - - - - - - - Detached Objects - - - - - If you decide to use the session-per-request pattern, all loaded instances will be - in the detached state during user think time. Hibernate allows you to reattach the - objects and persist the modifications. The pattern is called - session-per-request-with-detached-objects. Automatic versioning is used to isolate - concurrent modifications. - - - - - - - Extended Session - - - - - The Hibernate Session can be disconnected from the - underlying JDBC connection after the database transaction has been committed and - reconnected when a new client request occurs. This pattern is known as - session-per-conversation and makes even reattachment unnecessary. Automatic - versioning is used to isolate concurrent modifications and the - Session will not be allowed to flush automatically, - only explicitly. - - - - - - - - - Session-per-request-with-detached-objects and session-per-conversation - each have advantages and disadvantages. - -
- -
- Session-per-application - - Discussion coming soon.. - -
-
- -
- Object identity - - An application can concurrently access the same persistent state (database row) in two different Sessions. - However, an instance of a persistent class is never shared between two - Session instances. Two different notions of identity exist and come into - play here: Database identity and JVM identity. - - - Database identity - - - - JVM identity - - - - For objects attached to a particular Session, the two notions are - equivalent, and JVM identity for database identity is guaranteed by Hibernate. The application might - concurrently access a business object with the same identity in two different sessions, the two - instances are actually different, in terms of JVM identity. Conflicts are resolved using an optimistic - approach and automatic versioning at flush/commit time. - - - This approach places responsibility for concurrency on Hibernate and the database. It also provides the - best scalability, since expensive locking is not needed to guarantee identity in single-threaded units - of work. The application does not need to synchronize on any business object, as long as it maintains - a single thread per anti-patterns. While not recommended, within a - Session the application could safely use the == - operator to compare objects. - - - - However, an application that uses the == operator outside of a - Session - may introduce problems.. If you put two detached instances into the same Set, they might - use the same database identity, which means they represent the same row in the database. They would not be - guaranteed to have the same JVM identity if they are in a detached state. Override the - equals and hashCode methods in persistent classes, so that - they have their own notion of object equality. Never use the database identifier to implement equality. Instead, - use a business key that is a combination of unique, typically immutable, attributes. The database identifier - changes if a transient object is made persistent. If the transient instance, together with detached instances, - is held in a Set, changing the hash-code breaks the contract of the - Set. Attributes for business keys can be less stable than database primary keys. You only - need to guarantee stability as long as the objects are in the same Set.This is not a - Hibernate issue, but relates to Java's implementation of object identity and equality. - - -
- - -
- Common issues - - - Both the session-per-user-session and session-per-application - anti-patterns are susceptible to the following issues. Some of the issues might also arise within the - recommended patterns, so ensure that you understand the implications before making a design decision: - - - - - - A Session is not thread-safe. Things that work concurrently, like - HTTP requests, session beans, or Swing workers, will cause race conditions if a - Session instance is shared. If you keep your Hibernate - Session in your - javax.servlet.http.HttpSession (this is discussed later in the - chapter), you should consider synchronizing access to your - HttpSession; otherwise, a user that clicks reload fast enough can use - the same Session in two concurrently running threads. - - - - - An exception thrown by Hibernate means you have to rollback your database transaction - and close the Session immediately (this is discussed in more detail - later in the chapter). If your Session is bound to the application, - you have to stop the application. Rolling back the database transaction does not put your business - objects back into the state they were at the start of the transaction. This means that the - database state and the business objects will be out of sync. Usually this is not a - problem, because exceptions are not recoverable and you will have to start over after - rollback anyway. - - - - - The Session caches every object that is in a persistent state - (watched and checked for changes by Hibernate). If you keep it open for a long time or simply load - too much data, it will grow endlessly until you get an OutOfMemoryException. One solution is to - call clear() and evict() to manage the - Session cache, but you should consider an alternate means of dealing - with large amounts of data such as a Stored Procedure. Java is simply not the right tool for these - kind of operations. Some solutions are shown in . Keeping a - Session open for the duration of a user session also means a higher - probability of stale data. - - - - -
- -
diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/extras/database-identity.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/extras/database-identity.java deleted file mode 100644 index 79e273346ba1..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/extras/database-identity.java +++ /dev/null @@ -1 +0,0 @@ -foo.getId().equals( bar.getId() ) \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/extras/jvm-identity.java b/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/extras/jvm-identity.java deleted file mode 100644 index 5e48a72052c1..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/chapters/transactions/extras/jvm-identity.java +++ /dev/null @@ -1 +0,0 @@ -foo==bar \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/batch_insert.java b/documentation/src/main/docbook/devguide-old/en-US/extras/batch_insert.java deleted file mode 100644 index 6890847fbe19..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/batch_insert.java +++ /dev/null @@ -1,8 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); -for ( int i=0; i<100000; i++ ) { - Customer customer = new Customer(.....); - session.save(customer); -} -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/bmt-idiom.java b/documentation/src/main/docbook/devguide-old/en-US/extras/bmt-idiom.java deleted file mode 100644 index 8957dd8d4423..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/bmt-idiom.java +++ /dev/null @@ -1,20 +0,0 @@ -// BMT idiom -Session sess = factory.openSession(); -Transaction tx = null; -try { - tx = sess.beginTransaction(); - - // do some work - ... - - tx.commit(); -} - -catch (RuntimeException e) { - if (tx != null) tx.rollback(); - throw e; // or display error message -} - -finally { - sess.close(); -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/browsing_cache.java b/documentation/src/main/docbook/devguide-old/en-US/extras/browsing_cache.java deleted file mode 100644 index 4302770ccd45..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/browsing_cache.java +++ /dev/null @@ -1,3 +0,0 @@ -Map cacheEntries = sessionFactory.getStatistics() - .getSecondLevelCacheStatistics(regionName) - .getEntries(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/cache_providers.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/cache_providers.xml deleted file mode 100644 index 66597aa84db3..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/cache_providers.xml +++ /dev/null @@ -1,11 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/cache_providers_mapping.java b/documentation/src/main/docbook/devguide-old/en-US/extras/cache_providers_mapping.java deleted file mode 100644 index 5467dbef65f7..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/cache_providers_mapping.java +++ /dev/null @@ -1,4 +0,0 @@ -@Entity -@Cacheable -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public class Forest { ... } \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/check.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/check.xml deleted file mode 100644 index 7569492e77c0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/check.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - ... - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/child-column-elements.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/child-column-elements.xml deleted file mode 100644 index 21700d2ba468..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/child-column-elements.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/cmt-idiom.java b/documentation/src/main/docbook/devguide-old/en-US/extras/cmt-idiom.java deleted file mode 100644 index c868e96e91fd..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/cmt-idiom.java +++ /dev/null @@ -1,4 +0,0 @@ -// CMT idiom - Session sess = factory.getCurrentSession(); - // do some work - ... diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/comments.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/comments.xml deleted file mode 100644 index a4b5918a4dd4..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/comments.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - Current customers only - ... - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/default-attribute.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/default-attribute.xml deleted file mode 100644 index 0aabcf7a9b77..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/default-attribute.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/embedding_SchemaExport.java b/documentation/src/main/docbook/devguide-old/en-US/extras/embedding_SchemaExport.java deleted file mode 100644 index 6d1b06db704d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/embedding_SchemaExport.java +++ /dev/null @@ -1,2 +0,0 @@ -Configuration cfg = ....; -new SchemaExport(cfg).create(false, true); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/evicting_from_second_level_cache.java b/documentation/src/main/docbook/devguide-old/en-US/extras/evicting_from_second_level_cache.java deleted file mode 100644 index d7c4d32921c5..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/evicting_from_second_level_cache.java +++ /dev/null @@ -1,15 +0,0 @@ -sessionFactory.getCache().containsEntity(Cat.class, catId); // is this particular Cat currently in the cache - -sessionFactory.getCache().evictEntity(Cat.class, catId); // evict a particular Cat - -sessionFactory.getCache().evictEntityRegion(Cat.class); // evict all Cats - -sessionFactory.getCache().evictEntityRegions(); // evict all entity data - -sessionFactory.getCache().containsCollection("Cat.kittens", catId); // is this particular collection currently in the cache - -sessionFactory.getCache().evictCollection("Cat.kittens", catId); // evict a particular collection of kittens - -sessionFactory.getCache().evictCollectionRegion("Cat.kittens"); // evict all kitten collections - -sessionFactory.getCache().evictCollectionRegions(); // evict all collection data \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/evicting_item.java b/documentation/src/main/docbook/devguide-old/en-US/extras/evicting_item.java deleted file mode 100644 index bea3702e1d29..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/evicting_item.java +++ /dev/null @@ -1,6 +0,0 @@ -ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set -while ( cats.next() ) { - Cat cat = (Cat) cats.get(0); - doSomethingWithACat(cat); - sess.evict(cat); -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/executeUpdate.java b/documentation/src/main/docbook/devguide-old/en-US/extras/executeUpdate.java deleted file mode 100644 index ce05fc09a2d2..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/executeUpdate.java +++ /dev/null @@ -1,11 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName"; -// or String hqlUpdate = "update Customer set name = :newName where name = :oldName"; -int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/flush_and_clear_session.java b/documentation/src/main/docbook/devguide-old/en-US/extras/flush_and_clear_session.java deleted file mode 100644 index 957ab2d5fd13..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/flush_and_clear_session.java +++ /dev/null @@ -1,15 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -for ( int i=0; i<100000; i++ ) { - Customer customer = new Customer(.....); - session.save(customer); - if ( i % 20 == 0 ) { //20, same as the JDBC batch size - //flush a batch of inserts and release memory: - session.flush(); - session.clear(); - } -} - -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/foreign-key.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/foreign-key.xml deleted file mode 100644 index 2a7b86b781ab..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/foreign-key.xml +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/hibernate.cfg.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/hibernate.cfg.xml deleted file mode 100644 index 52a66e540f47..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/hibernate.cfg.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - org.hsqldb.jdbcDriver - jdbc:hsqldb:hsql://localhost - sa - - - - 1 - - - org.hibernate.dialect.HSQLDialect - - - thread - - - org.hibernate.cache.internal.NoCacheProvider - - - true - - - update - - - diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/hibernate.properties b/documentation/src/main/docbook/devguide-old/en-US/extras/hibernate.properties deleted file mode 100644 index 93ccd4089148..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/hibernate.properties +++ /dev/null @@ -1,15 +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 . -# -hibernate.connection.driver_class = org.postgresql.Driver -hibernate.connection.url = jdbc:postgresql://localhost/mydatabase -hibernate.connection.username = myuser -hibernate.connection.password = secret -hibernate.c3p0.min_size=5 -hibernate.c3p0.max_size=20 -hibernate.c3p0.timeout=1800 -hibernate.c3p0.max_statements=50 -hibernate.dialect = org.hibernate.dialect.PostgreSQL82Dialect \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/hql_delete.java b/documentation/src/main/docbook/devguide-old/en-US/extras/hql_delete.java deleted file mode 100644 index 87c2971706be..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/hql_delete.java +++ /dev/null @@ -1,10 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -String hqlDelete = "delete Customer c where c.name = :oldName"; -// or String hqlDelete = "delete Customer where name = :oldName"; -int deletedEntities = session.createQuery( hqlDelete ) - .setString( "oldName", oldName ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/hql_insert.java b/documentation/src/main/docbook/devguide-old/en-US/extras/hql_insert.java deleted file mode 100644 index 9505919c3a06..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/hql_insert.java +++ /dev/null @@ -1,8 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ..."; -int createdEntities = session.createQuery( hqlInsert ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/length-precision-scale.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/length-precision-scale.xml deleted file mode 100644 index 1dfb3a5e4725..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/length-precision-scale.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/letting_hibernate_find_mapping_files.java b/documentation/src/main/docbook/devguide-old/en-US/extras/letting_hibernate_find_mapping_files.java deleted file mode 100644 index 1a6746038d99..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/letting_hibernate_find_mapping_files.java +++ /dev/null @@ -1,3 +0,0 @@ -Configuration cfg = new Configuration() - .addClass(org.hibernate.auction.Item.class) - .addClass(org.hibernate.auction.Bid.class); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/notnull-unique.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/notnull-unique.xml deleted file mode 100644 index 6b9b86bd4519..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/notnull-unique.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/opening_a_session.java b/documentation/src/main/docbook/devguide-old/en-US/extras/opening_a_session.java deleted file mode 100644 index 7d71677a8084..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/opening_a_session.java +++ /dev/null @@ -1 +0,0 @@ -Session session = sessions.openSession(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/setCacheRegion.java b/documentation/src/main/docbook/devguide-old/en-US/extras/setCacheRegion.java deleted file mode 100644 index 6a3f3b57f872..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/setCacheRegion.java +++ /dev/null @@ -1,6 +0,0 @@ -List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger") - .setEntity("blogger", blogger) - .setMaxResults(15) - .setCacheable(true) - .setCacheRegion("frontpages") - .list(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/specify_mapping_files_directly.java b/documentation/src/main/docbook/devguide-old/en-US/extras/specify_mapping_files_directly.java deleted file mode 100644 index b7b559a2bce4..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/specify_mapping_files_directly.java +++ /dev/null @@ -1,3 +0,0 @@ -Configuration cfg = new Configuration() - .addResource("Item.hbm.xml") - .addResource("Bid.hbm.xml"); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/specifying_configuration_properties_programmatically.java b/documentation/src/main/docbook/devguide-old/en-US/extras/specifying_configuration_properties_programmatically.java deleted file mode 100644 index 6ed68a3f8888..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/specifying_configuration_properties_programmatically.java +++ /dev/null @@ -1,6 +0,0 @@ -Configuration cfg = new Configuration() - .addClass(org.hibernate.auction.Item.class) - .addClass(org.hibernate.auction.Bid.class) - .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect") - .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test") - .setProperty("hibernate.order_updates", "true"); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/sql-type.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/sql-type.xml deleted file mode 100644 index 52fee4649401..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/sql-type.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/timestamp_version.java b/documentation/src/main/docbook/devguide-old/en-US/extras/timestamp_version.java deleted file mode 100644 index 20724336cfe4..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/timestamp_version.java +++ /dev/null @@ -1,6 +0,0 @@ -@Entity -public class Flight implements Serializable { -... - @Version - public Date getLastUpdate() { ... } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/timestamp_version.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/timestamp_version.xml deleted file mode 100644 index 51ed52a0550d..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/timestamp_version.xml +++ /dev/null @@ -1,15 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/transaction-bound-Session.java b/documentation/src/main/docbook/devguide-old/en-US/extras/transaction-bound-Session.java deleted file mode 100644 index 7485ac752d13..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/transaction-bound-Session.java +++ /dev/null @@ -1,18 +0,0 @@ -// BMT idiom with getCurrentSession() -try { - UserTransaction tx = (UserTransaction)new InitialContext() - .lookup("java:comp/UserTransaction"); - - tx.begin(); - - // Do some work on Session bound to transaction - factory.getCurrentSession().load(...); - factory.getCurrentSession().persist(...); - - tx.commit(); -} - -catch (RuntimeException e) { - tx.rollback(); - throw e; // or display error message -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/updating_version.java b/documentation/src/main/docbook/devguide-old/en-US/extras/updating_version.java deleted file mode 100644 index b68c41c370ba..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/updating_version.java +++ /dev/null @@ -1,9 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); -String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName"; -int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/using_a_StatelessSession.java b/documentation/src/main/docbook/devguide-old/en-US/extras/using_a_StatelessSession.java deleted file mode 100644 index fbfd3e313c84..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/using_a_StatelessSession.java +++ /dev/null @@ -1,13 +0,0 @@ -StatelessSession session = sessionFactory.openStatelessSession(); -Transaction tx = session.beginTransaction(); - -ScrollableResults customers = session.getNamedQuery("GetCustomers") - .scroll(ScrollMode.FORWARD_ONLY); -while ( customers.next() ) { - Customer customer = (Customer) customers.get(0); - customer.updateStuff(...); - session.update(customer); -} - -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/using_scroll.java b/documentation/src/main/docbook/devguide-old/en-US/extras/using_scroll.java deleted file mode 100644 index 80c807fe2f7f..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/using_scroll.java +++ /dev/null @@ -1,19 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -ScrollableResults customers = session.getNamedQuery("GetCustomers") - .setCacheMode(CacheMode.IGNORE) - .scroll(ScrollMode.FORWARD_ONLY); -int count=0; -while ( customers.next() ) { - Customer customer = (Customer) customers.get(0); - customer.updateStuff(...); - if ( ++count % 20 == 0 ) { - //flush a batch of updates and release memory: - session.flush(); - session.clear(); - } -} - -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/version_annotation.java b/documentation/src/main/docbook/devguide-old/en-US/extras/version_annotation.java deleted file mode 100644 index 416ab76e4cfb..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/version_annotation.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class Flight implements Serializable { -... - @Version - @Column(name="OPTLOCK") - public Integer getVersion() { ... } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/version_property.xml b/documentation/src/main/docbook/devguide-old/en-US/extras/version_property.xml deleted file mode 100644 index 517dc7e7670e..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/extras/version_property.xml +++ /dev/null @@ -1,16 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/devguide-old/en-US/images/icon.svg b/documentation/src/main/docbook/devguide-old/en-US/images/icon.svg deleted file mode 100644 index b2f16d0f61d0..000000000000 --- a/documentation/src/main/docbook/devguide-old/en-US/images/icon.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/devguide-old/publican.cfg b/documentation/src/main/docbook/devguide-old/publican.cfg deleted file mode 100644 index e94c3679986c..000000000000 --- a/documentation/src/main/docbook/devguide-old/publican.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# Config::Simple 4.59 -# Mon Jan 17 13:52:44 2011 - -xml_lang: en-US -type: Book -brand: jboss-community-hibernate - diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/Hibernate_Integrations_Guide.ent b/documentation/src/main/docbook/integrationsGuide/en-US/Hibernate_Integrations_Guide.ent deleted file mode 100644 index aff71044189b..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/Hibernate_Integrations_Guide.ent +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/Hibernate_Integrations_Guide.xml b/documentation/src/main/docbook/integrationsGuide/en-US/Hibernate_Integrations_Guide.xml deleted file mode 100644 index b7558e683c1a..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/Hibernate_Integrations_Guide.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - -%BOOK_ENTITIES; -]> - - - - Hibernate Integrations Guide - Hibernate - Relational Persistence for Idiomatic Java - &version; - Hibernate ORM - &version; - &today; - - - - - - - - - - ©rightYear; - ©rightHolder; - - - - - The Hibernate Team - - - The JBoss Visual Design Team - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/Preface.xml b/documentation/src/main/docbook/integrationsGuide/en-US/Preface.xml deleted file mode 100644 index 032432abcee0..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/Preface.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - Preface - - Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. - Development costs are significantly higher due to a paradigm mismatch between how data is represented in - objects versus relational databases. Hibernate is an Object/Relational Mapping solution for Java environments. - The term Object/Relational Mapping refers to the technique of mapping data from an object model representation - to a relational data model representation (and visa versa). See - for a good high-level discussion. - - - - - While having a strong background in SQL is not required to use Hibernate, having a basic understanding of - the concepts can greatly help you understand Hibernate more fully and quickly. Probably the single - best background is an understanding of data modeling principles. You might want to consider these resources - as a good starting point: - - - - - - - - - - - - - - - - - Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to - SQL data types), but also provides data query and retrieval facilities. It can significantly reduce - development time otherwise spent with manual data handling in SQL and JDBC. Hibernate’s design goal is to - relieve the developer from 95% of common data persistence-related programming tasks by eliminating the need for - manual, hand-crafted data processing using SQL and JDBC. However, unlike many other persistence solutions, - Hibernate does not hide the power of SQL from you and guarantees that your investment in relational technology - and knowledge is as valid as always. - - - - Hibernate may not be the best solution for data-centric applications that only use stored-procedures to - implement the business logic in the database, it is most useful with object-oriented domain models and business - logic in the Java-based middle-tier. However, Hibernate can certainly help you to remove or encapsulate - vendor-specific SQL code and will help with the common task of result set translation from a tabular - representation to a graph of objects. - - -
- Get Involved - - - - Use Hibernate and report any bugs or issues you find. See - for details. - - - - - Try your hand at fixing some bugs or implementing enhancements. Again, see - . - - - - - Engage with the community using mailing lists, forums, IRC, or other ways listed at - . - - - - - Help improve or translate this documentation. Contact us on - the developer mailing list if you have interest. - - - - - Spread the word. Let the rest of your organization know about the benefits of - Hibernate. - - - -
- -
- Getting Started Guide - - New users may want to first look through the - Hibernate Getting Started Guide for basic information as well as - tutorials. Even seasoned veterans may want to considering perusing the sections pertaining to - build artifacts for any changes. - -
- -
- diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/Services.xml b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/Services.xml deleted file mode 100644 index cbd950fb373f..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/Services.xml +++ /dev/null @@ -1,683 +0,0 @@ - - - - - - Services and Registries - - - Services and registries are new *as a formalized concept* starting in 4.0. But the functionality provided by - the different Services have actually been around in Hibernate much, much longer. What is new is managing them, - their lifecycles and dependencies through a lightweight, dedicated container we call a ServiceRegistry. The - goal of this guide is to describe the design and purpose of these Services and Registries, as well as to look at - details of their implementations where appropriate. It will also delve into the ways third-party integrators and - applications can leverage and customize Services and Registries. - - -
- What is a Service? - - - A services provides a certain types of functionality, in a pluggable manner. Specifically they are - interfaces defining certain functionality and then implementations of those service contract interfaces. - The interface is known as the service role; the implementation class is known as the service implementation. - The pluggability comes from the fact that the service implementation adheres to contract defined by the - interface of the service role and that consumers of the service program to the service role, not the - implementation. - - - - Generally speaking, users can plug in alternate implementations of all standard service roles (overriding); - they can also define additional services beyond the base set of service roles (extending). - - - - Let's look at an example to better define what a Service is. Hibernate needs to be able to access - JDBC Connections to the database. The way it obtains and releases these Connections is through the - ConnectionProvider service. The service is defined by the interface (service role) - org.hibernate.engine.jdbc.connections.spi.ConnectionProvider which declares - methods for obtaining and releasing the Connections. There are then multiple implementations of that - service contract, varying in how they actually manage the Connections. - - - - Internally Hibernate always references org.hibernate.engine.jdbc.connections.spi.ConnectionProvider - rather than specific implementations in consuming the service (we will get to producing the service later - when we talk about registries). Because of that fact, other ConnectionProvider service implementations - could easily be plugged in. - - - - There is nothing revolutionary here; programming to interfaces is generally accepted as good programming - practice. What's interesting is the ServiceRegistry and the pluggable swapping of the different implementors. - - -
- Service contracts - - - The basic requirement for a service is to implement the marker interface - org.hibernate.service.Service. Hibernate uses this internally for some - basic type safety. - - - - The service can also implement a number of optional life-cycle related contracts: - - - - org.hibernate.service.spi.Startable - allows the service - impl to be notified that it is being started and about to be put into use. - - - - - org.hibernate.service.spi.Stoppable - allows the service - impl to be notified that it is being stopped and will be removed from use. - - - - - org.hibernate.service.spi.ServiceRegistryAwareService - allows - the service to be injected with a reference to the registry that is managing it. See - . - - - - - org.hibernate.service.spi.Manageable - marks the service - as manageable in JMX provided the JMX integration is enabled. This feature is still incomplete. - - - - - The different registry implementations also understand additional optional contracts specific - to that registry. For details, see the details for each registry under - - - - - -
- -
- Service dependencies - - Services are allowed to declare dependencies on other services using either of 2 approaches. - -
- @<interfacename>org.hibernate.service.spi.InjectService</interfacename> - - Any method on the service implementation class accepting a single parameter and annotated with - @InjectService is considered requesting injection of another service. - - - By default the type of the method parameter is expected to be the service role to be injected. If the - parameter type is different than the service role, the serviceRole attribute - of the InjectService annotation should be used to explicitly name the role. - - - By default injected services are considered required, that is the start up will fail if a named - dependent service is missing. If the service to be injected is optional, the - required attribute of the InjectService - annotation should be declared as false (default is true). - -
-
- <interfacename>org.hibernate.service.spi.ServiceRegistryAwareService</interfacename> - - The second approach is a pull approach where the service implements the optional service interface - org.hibernate.service.spi.ServiceRegistryAwareService which declares - a single injectServices method. During startup, Hibernate will inject the - org.hibernate.service.ServiceRegistry itself into services which - implement this interface. The service can then use the ServiceRegistry - reference to locate any additional services it needs. - -
-
-
- - -
- What is a ServiceRegistry? - - - A ServiceRegistry, at its most basic, hosts and manages Services. Its contract is defined by the - org.hibernate.service.ServiceRegistry interface. - - - - We already gave a basic overview and definition of services. But services have other interesting - characteristics as well. Services have a lifecycle. They have a scope. Services might depend on other - services. And they need to be produced (choose using one implementation over another). The ServiceRegistry - fulfills all these needs. - - - - In a concise definition, the ServiceRegistry acts as a inversion-of-control (IoC) container. - - - - - Despite some recent revisionist history, Spring did not invent IoC nor dependency injection nor were - they even the first to bring it into Java. Projects like JBoss MicroContainer and Apache Avalon - pre-date Spring by many years and each did IoC and dependency injection. The concepts in ServiceRegistry - are actually very similar to Apache Avalon. - - - - - Why not just use an existing IoC framework? The main reason was that this had to be as light-weight and as - small of a footprint as possible. The initial design also had called for Services to be swappable at runtime, - which unfortunately had to be removed due to performance problems in the proxy-based swapping-solution; the - plan is to investigate alternate ways to achieve swap-ability with better performance at a later date. - - - - A Service is associated with a ServiceRegistry. The ServiceRegistry scopes the Service. The - ServiceRegistry manages the lifecycle of the Service. The ServiceRegistry handles injecting dependencies - into the Service (actually both a pull and a push/injection approach are supported). ServiceRegistries are - also hierarchical, meaning a ServiceRegistry can have a parent ServiceRegistry. Services in one registry - can depend on and utilize services in that same registry as well as any parent registries. - - -
- - -
- ServiceBinding - - - The association of a given Service to a given ServiceRegistry is called a binding and is represented by the - org.hibernate.service.spi.ServiceBinding interface. Furthermore, the specific - contract between a ServiceBinding and the ServiceRegistry is represented by the - org.hibernate.service.spi.ServiceBinding.ServiceLifecycleOwner interface. - - - - There are 2 ways a Service becomes associated (bound) to a ServiceRegistry. - - - - the Service can be directly instantiated and then handed to the ServiceRegistry - - - - - a ServiceInitiator can be given to the ServiceRegistry (which the ServiceRegistry will use if and when the Service is needed) - - - - ServiceRegistry implementations register bindings through calls to the overloaded - org.hibernate.service.internal.AbstractServiceRegistryImpl#createServiceBinding - method accepting either a Service instance or a ServiceInitiator instance. - - - - Each specific type of registry defines its own ServiceInitiator specialization. - -
- - -
- Types of ServiceRegistries - - - Currently Hibernate utilizes 3 different ServiceRegistry implementations forming a hierarchy. Each - type is a specialization for the purpose of type-safety, but they add no new functionality. - - -
- BootstrapServiceRegistry - - - The org.hibernate.boot.registry.BootstrapServiceRegistry - holds 3 service and is normally built by means of the - org.hibernate.boot.registry.BootstrapServiceRegistryBuilder factory class. - The builder gives type safe access to customizing these 3 Services. - - - - - This registry holds services that absolutely have to be available for most things in Hibernate to work. - - - - - In normal usage, the BootstrapServiceRegistry has no parent. - - - - The services of the BootstrapServiceRegistry cannot be extended (added to) nor overridden (replaced). - - -
- ClassLoaderService - - - The service role for this service is org.hibernate.boot.registry.classloading.spi.ClassLoaderService. - - - - This service defines Hibernate's ability to interact with ClassLoaders. The manner in which - Hibernate (or any library) should interact with ClassLoaders varies based on the runtime environment - which is hosting the application. Application servers, OSGi containers, and other modular class - loading systems impose very specific class-loading requirements. This service is provides - Hibernate an abstraction from this environmental complexity. And just as importantly, it does so - in a centralized, swappable manner. - - - - The specific capabilities exposed on this service include: - - - - Locating Class references by name. This includes application classes as well as "integration" classes. - - - - - Locating resources (properties files, xml files, etc) as "classpath resources" - - - - - Interacting with java.util.ServiceLoader, Java's own service - provider discovery mechanism - - - - -
- -
- IntegratorService - - - The service role for this service is org.hibernate.integrator.spi.IntegratorService. - - - - Applications, third-party integrators and others all need to integrate with Hibernate. Historically - this used to require something (usually the application) to coordinate registering the pieces of each - integration needed on behalf of each integration. The - org.hibernate.integrator.spi.Integrator contract formalized this - "integration SPI". The IntegratorService manages all known integrators. - - - - - The concept of "Integrator" is still being actively defined and developed. Expect changes in - these SPIs. - - - - - There are 2 ways an integrator becomes known. - - - - The integrator may be manually registered by calling - BootstrapServiceRegistryBuilder#with(Integrator) - - - - - The integrator may be discovered, leveraging the standard Java ServiceLoader - capability provided by the ClassLoaderService. Integrators would simply define a file - named /META-INF/services/org.hibernate.integrator.spi.Integrator - and make it available on the classpath. ServiceLoader covers the format of this file - in detail, but essentially it lists classes by FQN that implement Integrator one - per line. - - - - -
- -
- StrategySelector - - - The service role for this service is org.hibernate.boot.registry.selector.spi.StrategySelector. - - - - Think of this as the "short naming" service. Historically to configure Hibernate users would - often need to give FQN references to internal Hibernate classes. Of course this has caused lots - of problems as we refactor internal code and move these classes around into different package - structures. Enter the concept of short-naming, using a well defined and well known "short name" - for the strategy/implementation class. - - - - The short name mappings in this service can be managed, even by applications and integrators - which can be very powerful. For more information on this aspect, see: - - - - BootstrapServiceRegistryBuilder#applyStrategySelector - - - - - BootstrapServiceRegistryBuilder#applyStrategySelectors - - - - - org.hibernate.boot.registry.selector.StrategyRegistrationProvider - via ServiceLoader discovery - - - - - StrategySelector#registerStrategyImplementor` / - StrategySelector#unRegisterStrategyImplementor - - - - -
-
- - -
- StandardServiceRegistry - - - The org.hibernate.boot.registry.StandardServiceRegistry defines the - main Hibernate ServiceRegistry, building on the BootstrapServiceRegistry (BootstrapServiceRegistry is - its parent). This registry is generally built using the - org.hibernate.boot.registry.StandardServiceRegistryBuilder class. By default - it holds most of the Services used by Hibernate. For the full list of Services typically held in the - StandardServiceRegistry, see the source code of org.hibernate.service.StandardServiceInitiators. - Some particular StandardServiceRegistry Services of note include: - - - - In normal usage, the parent of the StandardServiceRegistry is the BootstrapServiceRegistry. - - - - The services of the StandardServiceRegistry can be extended (added to) and overridden (replaced). - - -
- ConnectionProvider/MultiTenantConnectionProvider - - The Service providing Hibernate with Connections as needed. Comes in 2 distinct (and mutually - exclusive) roles: - - - - org.hibernate.engine.jdbc.connections.spi.ConnectionProvider - - provides Connections in normal environments - - - - - org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider - - provides (tenant-specific) Connections in multi-tenant environments - - - - -
- -
- JdbcServices - - org.hibernate.engine.jdbc.spi.JdbcServices is an aggregator - Service (a Service that aggregates other Services) exposing unified functionality around JDBC - accessibility. - -
- -
- TransactionCoordinatorBuilder - - org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder - is used by Hibernate to integrate with and underlying transaction system. It is responsible for - building org.hibernate.resource.transaction.spi.TransactionCoordinator - instances for use by each Hibernate Session. - -
- -
- JtaPlatform - - When using a JTA-based TransactionCoordinatorBuilder, the - org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform Service - provides Hibernate access to the JTA TransactionManager and UserTransaction, as well handling - Synchronization registration. - -
- -
- JndiService - - The org.hibernate.engine.jndi.spi.JndiService service is used - by Hibernate to interact with JNDI contexts. Hibernate's default JndiService assumes just a single - InitialContext. - -
- -
- RegionFactory - - The org.hibernate.cache.spi.RegionFactory service defines the - integration with third party cache implementors as second-level caching providers. - -
- -
- SessionFactoryServiceRegistryFactory - - org.hibernate.service.spi.SessionFactoryServiceRegistryFactory is a - service that acts as a factory for building the third type of ServiceRegistry - (the SessionFactoryServiceRegistry) which we will discuss next. I opted for the "factory as - service" approach because in the current design there is really not a good exposed hook-in spot - for when the SessionFactoryServiceRegistry needs to be built. - -
-
- - -
- SessionFactoryServiceRegistry - - - org.hibernate.service.spi.SessionFactoryServiceRegistry is the 3rd - standard Hibernate ServiceRegistry. SessionFactoryServiceRegistry is designed to hold Services which - need access to the SessionFactory. - - - - Typically its parent registry is the StandardServiceRegistry. - - - - - Integrators, as it stands in 4.x, operate on the SessionFactoryServiceRegistry... - - - - - Currently SessionFactoryServiceRegistry holds just 4 Services: - - -
- EventListenerRegistry - - org.hibernate.event.service.spi.EventListenerRegistry is the main - service managed in the SessionFactoryServiceRegistry. The is the Service that manages all of - Hibernate's event listeners. A major use-case for Integrators is to alter the listener registry. - - - If doing custom listener registration, it is important to understand the - org.hibernate.event.service.spi.DuplicationStrategy and its effect on - registration. The basic idea is to tell Hibernate: - - what makes a listener a duplicate - how to handle duplicate registrations (error, first wins, last wins) - - -
- -
- StatisticsImplementor - - org.hibernate.stat.spi.StatisticsImplementor is the SPI portion of - the Statistics API; the collector portion, if you will. - -
- -
- NativeQueryInterpreter - - org.hibernate.engine.query.spi.NativeQueryInterpreter is the - service Hibernate uses for interpreting native queries. Exists as a service mainly so that - integrations such as OGM can override it. - -
- -
- CacheImplementor - - To be honest, I have no idea why this is a service... :) - -
-
-
- -
- Custom Services - - So far we have focused on the Hibernate provided services. But applications and integrations - can provide their own services as well, either - - - providing a new implementation of a standard service (overriding) - - - providing a whole new service role (extending) - - - - -
- Custom Service Implementations (overriding) - - We discussed swappability of service implementations above. Lets look at an example in practice. - For the sake of illustration, lets say that we have developed a new ConnectionProvider integrating - with the wonderful new latest-and-greatest connection pooling library. Let's look at the steps - necessary to make that happen. - - - The first step is to develop the actual integration by implementing the ConnectionProvider contract. - - - Custom ConnectionProvider implementation - - - - At this point we have a decision about how to integrate this new ConnectionProvider into Hibernate. - As you might guess, there are multiple ways. - - - As a first option, we might just require that the code bootstrapping the StandardServiceRegistry do - the integration. - - - Overriding service implementation via StandardServiceRegistryBuilder - - - - A second option, if our LatestAndGreatestConnectionProviderImpl should always be used, would be to - provide a org.hibernate.service.spi.ServiceContributor implementation - as well to handle the integration on the users behalf. - - - LatestAndGreatestConnectionProviderImplContributor - - - - We still need to be able to tell Hibernate to perform this integration for us. To do that we leverage - Java's ServiceLoader. When building the StandardServiceRegistry, Hibernate will look for JDK - service providers of type org.hibernate.service.spi.ServiceContributor - and automatically integrate them. We discussed this behavior above. Here we'd define a classpath - resource named META-INF/services/org.hibernate.service.spi.ServiceContributor. - This file will have just a single line naming our impl. - - - META-INF/services/org.hibernate.service.spi.ServiceContributor - - - - A third option, if we simply want to make our LatestAndGreatestConnectionProviderImpl available - as a configuration choice, we would again use a ServiceContributor but in a slightly - different way. - - - LatestAndGreatestConnectionProviderImplContributor - - - - That all allows the appication to pick our LatestAndGreatestConnectionProviderImpl by a short-name. - - - Custom service short-name - - -
- -
- Custom Service Roles (extending) - - We can also have the ServiceRegistry host custom services (completely new Service roles). As an example, - let's say our application publishes Hibernate events to a JMS Topic and that we want to leverage the - Hibernate ServiceRegistry to host a Service representing our publishing of events. So we will expand the - ServiceRegistry to host this completely new Service role for us and manage its lifecycle. - - - - The EventPublishingService service role - - - - - The EventPublishingService implementation - - - - - An alternative EventPublishingService implementation - - - - - Because we have alternative implementations, it is a good idea to develop an initiator as well - that can choose between them at runtime. - - - - The EventPublishingServiceInitiator - - - - - We could have the application register the EventPublishingServiceInitiator with the - StandardServiceRegistryBuilder, but it is much nicer to write a ServiceContributor to handle this - for the application. - -
-
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/BootstrapServiceRegistryBuilder-example.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/BootstrapServiceRegistryBuilder-example.java deleted file mode 100644 index 5e2638fd0ae7..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/BootstrapServiceRegistryBuilder-example.java +++ /dev/null @@ -1,12 +0,0 @@ -BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder() - // pass in org.hibernate.integrator.spi.Integrator instances which are not - // auto-discovered (for whatever reason) but which should be included - .with( anExplicitIntegrator ) - // pass in a class-loader Hibernate should use to load application classes - .withApplicationClassLoader( anExplicitClassLoaderForApplicationClasses ) - // pass in a class-loader Hibernate should use to load resources - .withResourceClassLoader( anExplicitClassLoaderForResources ) - // see BootstrapServiceRegistryBuilder for rest of available methods - ... - // finally, build the bootstrap registry with all the above options - .build(); diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/DisabledEventPublishingServiceImpl.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/DisabledEventPublishingServiceImpl.java deleted file mode 100644 index f11b81d3bdda..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/DisabledEventPublishingServiceImpl.java +++ /dev/null @@ -1,11 +0,0 @@ -public class DisabledEventPublishingServiceImpl implements EventPublishingService { - public static DisabledEventPublishingServiceImpl INSTANCE = new DisabledEventPublishingServiceImpl(); - - private DisabledEventPublishingServiceImpl() { - } - - @Override - public void publish(Event theEvent) { - // nothing to do... - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingService.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingService.java deleted file mode 100644 index b371271fce19..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingService.java +++ /dev/null @@ -1,3 +0,0 @@ -public interface EventPublishingService extends Service { - public void publish(Event theEvent); -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceContributor.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceContributor.java deleted file mode 100644 index a20507156d53..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceContributor.java +++ /dev/null @@ -1,12 +0,0 @@ -public class EventPublishingServiceContributor - implements ServiceContributor { - @Override - public void contribute(StandardServiceRegistryBuilder builder) { - builder.addInitiator( EventPublishingServiceInitiator.INSTANCE ); - - // if we wanted to allow other strategies (e.g. a JMS - // Queue publisher) we might also register short names - // here with the StrategySelector. The initiator would - // then need to accept the strategy as a config setting - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceImpl.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceImpl.java deleted file mode 100644 index 10368dac087f..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -public class EventPublishingServiceImpl - implements EventPublishingService, - Configurable, - Startable, - Stoppable, - ServiceRegistryAwareService { - - private ServiceRegistryImplementor serviceRegistry; - private String jmsConnectionFactoryName; - private String destinationName; - - private Connection jmsConnection; - private Session jmsSession; - private MessageProducer publisher; - - @Override - public void injectServices(ServiceRegistryImplementor serviceRegistry) { - this.serviceRegistry = serviceRegistry; - } - - public void configure(Map configurationValues) { - this.jmsConnectionFactoryName = configurationValues.get( JMS_CONNECTION_FACTORY_NAME_SETTING ); - this.destinationName = configurationValues.get( JMS_DESTINATION_NAME_SETTING ); - } - - @Override - public void start() { - final JndiService jndiService = serviceRegistry.getService( JndiService.class ); - final ConnectionFactory jmsConnectionFactory = jndiService.locate( jmsConnectionFactoryName ); - - this.jmsConnection = jmsConnectionFactory.createConnection(); - this.jmsSession = jmsConnection.createSession( true, Session.AUTO_ACKNOWLEDGE ); - - final Destination destination = jndiService.locate( destinationName ); - - this.publisher = jmsSession.createProducer( destination ); - } - - @Override - public void publish(Event theEvent) { - publisher.send( theEvent ); - } - - @Override - public void stop() { - publisher.close(); - jmsSession.close(); - jmsConnection.close(); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceInitiator.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceInitiator.java deleted file mode 100644 index 70cd53278e59..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/extend/EventPublishingServiceInitiator.java +++ /dev/null @@ -1,22 +0,0 @@ -public class EventPublishingServiceInitiator implements StandardServiceInitiator { - public static EventPublishingServiceInitiator INSTANCE = new EventPublishingServiceInitiator(); - public static final String ENABLE_PUBLISHING_SETTING = "com.acme.EventPublishingService.enabled"; - - @Override - public Class getServiceInitiated() { - return EventPublishingService.class; - } - - @Override - public R initiateService(Map configurationValues, ServiceRegistryImplementor registry) { - final boolean enabled = extractBoolean( configurationValues, ENABLE_PUBLISHING_SETTING ); - if ( enabled ) { - return new EventPublishingServiceImpl(); - } - else { - return DisabledEventPublishingServiceImpl.INSTANCE; - } - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/LatestAndGreatestConnectionProviderImpl.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/LatestAndGreatestConnectionProviderImpl.java deleted file mode 100644 index 6ce565500327..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/LatestAndGreatestConnectionProviderImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -import java.lang.Override; - -public class LatestAndGreatestConnectionProviderImpl - implements ConnectionProvider, Startable, Stoppable, Configurable { - - private LatestAndGreatestPoolBuilder lagPoolBuilder; - private LatestAndGreatestPool lagPool; - private boolean available = false; - - @Override - public void configure(Map configurationValues) { - // extract our config from the settings map - lagPoolBuilder = buildBuilder( configurationValues ); - } - - @Override - public void start() { - // start the underlying pool - lagPool = lagPoolBuilder.buildPool(); - - available = true; - } - - @Override - public void stop() { - available = false; - - // stop the underlying pool - lagPool.shutdown(); - } - - @Override - public Connection getConnection() throws SQLException { - if ( !available ) { - throwException( "LatestAndGreatest ConnectionProvider not available for use" ) - } - - return lagPool.borrowConnection(); - } - - @Override - public void closeConnection(Connection conn) throws SQLException { - if ( !available ) { - warn( "LatestAndGreatest ConnectionProvider not available for use" ) - } - - if ( conn == null ) { - return; - } - - lagPool.releaseConnection( conn ); - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex1-direct.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex1-direct.java deleted file mode 100644 index ce48995a0041..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex1-direct.java +++ /dev/null @@ -1,7 +0,0 @@ -StandardServiceRegistryBuilder builder = ...; -... -builder.addService( - ConnectionProvider.class, - new LatestAndGreatestConnectionProviderImpl() -); -... \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex2-contributor.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex2-contributor.java deleted file mode 100644 index 546b67ac262a..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex2-contributor.java +++ /dev/null @@ -1,10 +0,0 @@ -public class LatestAndGreatestConnectionProviderImplContributor1 - implements ServiceContributor { - @Override - public void contribute(StandardServiceRegistryBuilder serviceRegistryBuilder) { - serviceRegistryBuilder.addService( - ConnectionProvider.class, - new LatestAndGreatestConnectionProviderImpl() - ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex2-meta-inf b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex2-meta-inf deleted file mode 100644 index daeaf7cf3343..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex2-meta-inf +++ /dev/null @@ -1 +0,0 @@ -fully.qualified.package.LatestAndGreatestConnectionProviderImplContributor1 \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-app.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-app.java deleted file mode 100644 index d7c9a7a28532..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-app.java +++ /dev/null @@ -1,4 +0,0 @@ -StandardServiceRegistryBuilder builder = ...; -... -builder.applySetting( "hibernate.connection.provider_class", "lag" ); -... \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-contributor.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-contributor.java deleted file mode 100644 index 555c15a45ddc..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-contributor.java +++ /dev/null @@ -1,14 +0,0 @@ -public class LatestAndGreatestConnectionProviderImplContributor1 - implements ServiceContributor { - @Override - public void contribute(StandardServiceRegistryBuilder serviceRegistryBuilder) { - // here we will register a short-name for our service strategy - StrategySelector selector = serviceRegistryBuilder.getBootstrapServiceRegistry(). - .getService( StrategySelector.class ); - selector.registerStrategyImplementor( - ConnectionProvider.class, - "lag" - LatestAndGreatestConnectionProviderImpl.class - ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-meta-inf b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-meta-inf deleted file mode 100644 index 554711e5c750..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/override/ex3-meta-inf +++ /dev/null @@ -1 +0,0 @@ -fully.qualified.package.LatestAndGreatestConnectionProviderImplContributor2 \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/register-event-listeners-example.java b/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/register-event-listeners-example.java deleted file mode 100644 index 7231d09939e3..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/chapters/services/extras/register-event-listeners-example.java +++ /dev/null @@ -1,23 +0,0 @@ -public class MyIntegrator implements org.hibernate.integrator.spi.Integrator { - - public void integrate( - Configuration configuration, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - // As you might expect, an EventListenerRegistry is the thing with which event listeners are registered It is a - // service so we look it up using the service registry - final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); - - // If you wish to have custom determination and handling of "duplicate" listeners, you would have to add an - // implementation of the org.hibernate.event.service.spi.DuplicationStrategy contract like this - eventListenerRegistry.addDuplicationStrategy( myDuplicationStrategy ); - - // EventListenerRegistry defines 3 ways to register listeners: - // 1) This form overrides any existing registrations with - eventListenerRegistry.setListeners( EventType.AUTO_FLUSH, myCompleteSetOfListeners ); - // 2) This form adds the specified listener(s) to the beginning of the listener chain - eventListenerRegistry.prependListeners( EventType.AUTO_FLUSH, myListenersToBeCalledFirst ); - // 3) This form adds the specified listener(s) to the end of the listener chain - eventListenerRegistry.appendListeners( EventType.AUTO_FLUSH, myListenersToBeCalledLast ); - } -} diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/batch_insert.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/batch_insert.java deleted file mode 100644 index 6890847fbe19..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/batch_insert.java +++ /dev/null @@ -1,8 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); -for ( int i=0; i<100000; i++ ) { - Customer customer = new Customer(.....); - session.save(customer); -} -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/bmt-idiom.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/bmt-idiom.java deleted file mode 100644 index 8957dd8d4423..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/bmt-idiom.java +++ /dev/null @@ -1,20 +0,0 @@ -// BMT idiom -Session sess = factory.openSession(); -Transaction tx = null; -try { - tx = sess.beginTransaction(); - - // do some work - ... - - tx.commit(); -} - -catch (RuntimeException e) { - if (tx != null) tx.rollback(); - throw e; // or display error message -} - -finally { - sess.close(); -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/browsing_cache.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/browsing_cache.java deleted file mode 100644 index 4302770ccd45..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/browsing_cache.java +++ /dev/null @@ -1,3 +0,0 @@ -Map cacheEntries = sessionFactory.getStatistics() - .getSecondLevelCacheStatistics(regionName) - .getEntries(); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/cache_providers.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/cache_providers.xml deleted file mode 100644 index 66597aa84db3..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/cache_providers.xml +++ /dev/null @@ -1,11 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/cache_providers_mapping.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/cache_providers_mapping.java deleted file mode 100644 index 5467dbef65f7..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/cache_providers_mapping.java +++ /dev/null @@ -1,4 +0,0 @@ -@Entity -@Cacheable -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public class Forest { ... } \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/check.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/check.xml deleted file mode 100644 index 7569492e77c0..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/check.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - ... - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/child-column-elements.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/child-column-elements.xml deleted file mode 100644 index 21700d2ba468..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/child-column-elements.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/cmt-idiom.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/cmt-idiom.java deleted file mode 100644 index c868e96e91fd..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/cmt-idiom.java +++ /dev/null @@ -1,4 +0,0 @@ -// CMT idiom - Session sess = factory.getCurrentSession(); - // do some work - ... diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/comments.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/comments.xml deleted file mode 100644 index a4b5918a4dd4..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/comments.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - Current customers only - ... - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/default-attribute.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/default-attribute.xml deleted file mode 100644 index 0aabcf7a9b77..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/default-attribute.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/embedding_SchemaExport.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/embedding_SchemaExport.java deleted file mode 100644 index 6d1b06db704d..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/embedding_SchemaExport.java +++ /dev/null @@ -1,2 +0,0 @@ -Configuration cfg = ....; -new SchemaExport(cfg).create(false, true); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/evicting_from_second_level_cache.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/evicting_from_second_level_cache.java deleted file mode 100644 index d7c4d32921c5..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/evicting_from_second_level_cache.java +++ /dev/null @@ -1,15 +0,0 @@ -sessionFactory.getCache().containsEntity(Cat.class, catId); // is this particular Cat currently in the cache - -sessionFactory.getCache().evictEntity(Cat.class, catId); // evict a particular Cat - -sessionFactory.getCache().evictEntityRegion(Cat.class); // evict all Cats - -sessionFactory.getCache().evictEntityRegions(); // evict all entity data - -sessionFactory.getCache().containsCollection("Cat.kittens", catId); // is this particular collection currently in the cache - -sessionFactory.getCache().evictCollection("Cat.kittens", catId); // evict a particular collection of kittens - -sessionFactory.getCache().evictCollectionRegion("Cat.kittens"); // evict all kitten collections - -sessionFactory.getCache().evictCollectionRegions(); // evict all collection data \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/evicting_item.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/evicting_item.java deleted file mode 100644 index bea3702e1d29..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/evicting_item.java +++ /dev/null @@ -1,6 +0,0 @@ -ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set -while ( cats.next() ) { - Cat cat = (Cat) cats.get(0); - doSomethingWithACat(cat); - sess.evict(cat); -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/executeUpdate.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/executeUpdate.java deleted file mode 100644 index ce05fc09a2d2..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/executeUpdate.java +++ /dev/null @@ -1,11 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName"; -// or String hqlUpdate = "update Customer set name = :newName where name = :oldName"; -int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/flush_and_clear_session.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/flush_and_clear_session.java deleted file mode 100644 index 957ab2d5fd13..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/flush_and_clear_session.java +++ /dev/null @@ -1,15 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -for ( int i=0; i<100000; i++ ) { - Customer customer = new Customer(.....); - session.save(customer); - if ( i % 20 == 0 ) { //20, same as the JDBC batch size - //flush a batch of inserts and release memory: - session.flush(); - session.clear(); - } -} - -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/foreign-key.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/foreign-key.xml deleted file mode 100644 index 2a7b86b781ab..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/foreign-key.xml +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hibernate.cfg.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/hibernate.cfg.xml deleted file mode 100644 index 52a66e540f47..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hibernate.cfg.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - org.hsqldb.jdbcDriver - jdbc:hsqldb:hsql://localhost - sa - - - - 1 - - - org.hibernate.dialect.HSQLDialect - - - thread - - - org.hibernate.cache.internal.NoCacheProvider - - - true - - - update - - - diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hibernate.properties b/documentation/src/main/docbook/integrationsGuide/en-US/extras/hibernate.properties deleted file mode 100644 index 93ccd4089148..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hibernate.properties +++ /dev/null @@ -1,15 +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 . -# -hibernate.connection.driver_class = org.postgresql.Driver -hibernate.connection.url = jdbc:postgresql://localhost/mydatabase -hibernate.connection.username = myuser -hibernate.connection.password = secret -hibernate.c3p0.min_size=5 -hibernate.c3p0.max_size=20 -hibernate.c3p0.timeout=1800 -hibernate.c3p0.max_statements=50 -hibernate.dialect = org.hibernate.dialect.PostgreSQL82Dialect \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hql-insert.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/hql-insert.java deleted file mode 100644 index 9505919c3a06..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hql-insert.java +++ /dev/null @@ -1,8 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ..."; -int createdEntities = session.createQuery( hqlInsert ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hql_delete.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/hql_delete.java deleted file mode 100644 index 87c2971706be..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/hql_delete.java +++ /dev/null @@ -1,10 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -String hqlDelete = "delete Customer c where c.name = :oldName"; -// or String hqlDelete = "delete Customer where name = :oldName"; -int deletedEntities = session.createQuery( hqlDelete ) - .setString( "oldName", oldName ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/length-precision-scale.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/length-precision-scale.xml deleted file mode 100644 index 1dfb3a5e4725..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/length-precision-scale.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/letting_hibernate_find_mapping_files.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/letting_hibernate_find_mapping_files.java deleted file mode 100644 index 1a6746038d99..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/letting_hibernate_find_mapping_files.java +++ /dev/null @@ -1,3 +0,0 @@ -Configuration cfg = new Configuration() - .addClass(org.hibernate.auction.Item.class) - .addClass(org.hibernate.auction.Bid.class); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/notnull-unique.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/notnull-unique.xml deleted file mode 100644 index 6b9b86bd4519..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/notnull-unique.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/opening_a_session.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/opening_a_session.java deleted file mode 100644 index 7d71677a8084..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/opening_a_session.java +++ /dev/null @@ -1 +0,0 @@ -Session session = sessions.openSession(); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/setCacheRegion.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/setCacheRegion.java deleted file mode 100644 index 6a3f3b57f872..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/setCacheRegion.java +++ /dev/null @@ -1,6 +0,0 @@ -List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger") - .setEntity("blogger", blogger) - .setMaxResults(15) - .setCacheable(true) - .setCacheRegion("frontpages") - .list(); diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/specify_mapping_files_directly.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/specify_mapping_files_directly.java deleted file mode 100644 index b7b559a2bce4..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/specify_mapping_files_directly.java +++ /dev/null @@ -1,3 +0,0 @@ -Configuration cfg = new Configuration() - .addResource("Item.hbm.xml") - .addResource("Bid.hbm.xml"); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/specifying_configuration_properties_programmatically.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/specifying_configuration_properties_programmatically.java deleted file mode 100644 index 6ed68a3f8888..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/specifying_configuration_properties_programmatically.java +++ /dev/null @@ -1,6 +0,0 @@ -Configuration cfg = new Configuration() - .addClass(org.hibernate.auction.Item.class) - .addClass(org.hibernate.auction.Bid.class) - .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect") - .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test") - .setProperty("hibernate.order_updates", "true"); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/sql-type.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/sql-type.xml deleted file mode 100644 index 52fee4649401..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/sql-type.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/timestamp_version.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/timestamp_version.java deleted file mode 100644 index 20724336cfe4..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/timestamp_version.java +++ /dev/null @@ -1,6 +0,0 @@ -@Entity -public class Flight implements Serializable { -... - @Version - public Date getLastUpdate() { ... } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/timestamp_version.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/timestamp_version.xml deleted file mode 100644 index 51ed52a0550d..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/timestamp_version.xml +++ /dev/null @@ -1,15 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/transaction-bound-Session.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/transaction-bound-Session.java deleted file mode 100644 index 7485ac752d13..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/transaction-bound-Session.java +++ /dev/null @@ -1,18 +0,0 @@ -// BMT idiom with getCurrentSession() -try { - UserTransaction tx = (UserTransaction)new InitialContext() - .lookup("java:comp/UserTransaction"); - - tx.begin(); - - // Do some work on Session bound to transaction - factory.getCurrentSession().load(...); - factory.getCurrentSession().persist(...); - - tx.commit(); -} - -catch (RuntimeException e) { - tx.rollback(); - throw e; // or display error message -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/updating_version.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/updating_version.java deleted file mode 100644 index b68c41c370ba..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/updating_version.java +++ /dev/null @@ -1,9 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); -String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName"; -int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/using_a_StatelessSession.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/using_a_StatelessSession.java deleted file mode 100644 index fbfd3e313c84..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/using_a_StatelessSession.java +++ /dev/null @@ -1,13 +0,0 @@ -StatelessSession session = sessionFactory.openStatelessSession(); -Transaction tx = session.beginTransaction(); - -ScrollableResults customers = session.getNamedQuery("GetCustomers") - .scroll(ScrollMode.FORWARD_ONLY); -while ( customers.next() ) { - Customer customer = (Customer) customers.get(0); - customer.updateStuff(...); - session.update(customer); -} - -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/using_scroll.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/using_scroll.java deleted file mode 100644 index 80c807fe2f7f..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/using_scroll.java +++ /dev/null @@ -1,19 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); - -ScrollableResults customers = session.getNamedQuery("GetCustomers") - .setCacheMode(CacheMode.IGNORE) - .scroll(ScrollMode.FORWARD_ONLY); -int count=0; -while ( customers.next() ) { - Customer customer = (Customer) customers.get(0); - customer.updateStuff(...); - if ( ++count % 20 == 0 ) { - //flush a batch of updates and release memory: - session.flush(); - session.clear(); - } -} - -tx.commit(); -session.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/version_annotation.java b/documentation/src/main/docbook/integrationsGuide/en-US/extras/version_annotation.java deleted file mode 100644 index 416ab76e4cfb..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/version_annotation.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class Flight implements Serializable { -... - @Version - @Column(name="OPTLOCK") - public Integer getVersion() { ... } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/extras/version_property.xml b/documentation/src/main/docbook/integrationsGuide/en-US/extras/version_property.xml deleted file mode 100644 index 517dc7e7670e..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/extras/version_property.xml +++ /dev/null @@ -1,16 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/images/hibernate_logo_a.png b/documentation/src/main/docbook/integrationsGuide/en-US/images/hibernate_logo_a.png deleted file mode 100644 index 0a343c4bca60..000000000000 Binary files a/documentation/src/main/docbook/integrationsGuide/en-US/images/hibernate_logo_a.png and /dev/null differ diff --git a/documentation/src/main/docbook/integrationsGuide/en-US/images/icon.svg b/documentation/src/main/docbook/integrationsGuide/en-US/images/icon.svg deleted file mode 100644 index b2f16d0f61d0..000000000000 --- a/documentation/src/main/docbook/integrationsGuide/en-US/images/icon.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/manual-old/README b/documentation/src/main/docbook/manual-old/README deleted file mode 100644 index b0fe3aefce4c..000000000000 --- a/documentation/src/main/docbook/manual-old/README +++ /dev/null @@ -1,176 +0,0 @@ -THE HIBERNATE DOCUMENTATION -christian@hibernate.org - -COPYRIGHT NOTICE: This documentation system and all its source files are -licensed under the GNU Lesser Public License (LGPL). Authors and translators -retain the copyright of their work. All font and other build files (the DocBook -system) are property of their respective copyright holders. Some of the files -(especially font files) might require a license from the respective vendor; you -are responsible to check and obtain these licenses as necessary before you use -and/or distribute these files. - - -Preface - -The Hibernate documentation is a modular documentation, it uses -various XML files (written with the DocBook DTD) and a Java-based -build process to generate HTML and PDF output. Use a simple text -editor with XML support, such as JEdit, to edit the source files. You -will need Java and Ant installed for the output generation. The toolset -is Java only and should work on any operating system. - -Note: Always use 4 spaces to indent, no tabstops (code examples will -be broken otherwise). - - -1. How to get it - -Check out a copy of Hibernate from the repository. A regular -Hibernate download will not contain the build process for the -documentation, only the PDF/HTML output, use the repository! -See http://www.hibernate.org/Download/DownloadOverview - - -2. Working on the original language - -The original and master language is English, hence the "en" subdirectory -in /doc/reference/ is authorative. We use "id" and "revision" attributes on -XML elements to track changes. Here are the rules, they are mandatory: - -2a. Changing existing content involves an update of the "revision" of the XML -element you are working on (e.g. a , or even a ). - -If a has a revision="1", you update it to "2" after updating the -content in that section. - -You can also add a revision attribute to an element if there is none, -start with revision="1". You should not add a revision attribute to each -paragraph, try to only add/use revision attributes to sections. You can' -t add a revision attribute to elements without an "id" attribute! - -2b. Adding new content involves adding new elements (even new files), such -as , and so on. Any new element (or its new parent element) -needs an "id" attribute if the new content is to be included in the change -tracking. If you add a section, give it a unique short text -identifer, look at the parent element's identifier for the common prefix. - -2c. Deleting content involves removing old elements. Just remove them and -make sure that the parent elements revision is updated, if the removed -element did not itself have an identifer and a revision. If you remove an -element with its own identifier, everything is fine and no other changes are -necessary. - - -3. Starting a new language - -If you start a translation for a new language, you have to copy -the default language (English) and start an initial translation. - -3a. First, duplicate the default language "en" by duplicating the directory -/doc/reference/en. For example, a new German translation -will be a copy of that directory in /doc/reference/de. We use the ISO -codes to name the language subdirectories. - -3b. You also have to add your new language to the language build file, -/doc/reference/build.xml. Look for the lines that have a "TRANSLATOR" -comment and duplicate them. Change the default "en" to your language -code, every language listed here will be included in both the PDF/HTML -generation and the revision diff change tracking reports (discussed later). - - -4. The initial translation - -If you just copied the default language, start translating the DocBook -XML modules and illustrations in the new language subdirectory. For -example, all modules for German would be in /doc/reference/de/modules -and all illustrations in /doc/reference/de/images, note that you also have -to translate the master.xml in your language subdirectory. - -The initial translation is straightforward: Translate all modules and -all illustrations, but don't add any files, don't add any new XML elements -(like a section or a chapter, not even a paragraph). Simply translate -sentence by sentence. This is very important. - -Note that every DocBook XML file needs an encoding, specific to a -language. Add a line like this at the top of every file, if it doesn't exist: - - -You can use UTF-8 or any other character set, please experiment with -the builds to see what works for you. - -If you need a new section or paragraph, because your translation requires -more explanation, you can add it if you also add an "id" and a "revision" -to that new section or paragraph. - -For example, if you add a new element to the existing document, -give it an identifier, a short unique string that extends the identifier -string of the parent element: -would be a special paragraph in the -section in the chapter . - -Never add a new element in a translated version without also adding a new -unique identifier value! Also, you have to mark this new element as "only -relevant in the translated version". Simply set the "revision" attribute of -your new element to "-1". For example, set the previously created -paragraph to "only relevant in the translation" by declaring -. -Changes to that paragraph will not be tracked, it is your responsibility to -watch out for neccessary updates. Any element with revision="-1" will not be -tracked. - - -5. Updating translated documentation - -Translators get updates by updating their working directory from the -repository. As a translator you will get an e-mail from us when translation -is required, you can then update your copy. Or, subscribe to the commit -mailing list to get all updates automatically. - -The documentation tools can generate a report after you updated -from the repository and show you what needs to be translated and/or removed -in your local translation copy. To generate that report, run "ant all.revdiff" -in the doc/reference/ subdirectory. Click on the generated HTML report -file for your language and you will see what has to be updated and/or -removed. - -If the report indicates that content in the original has been removed, -simply remove the identified XML element from your language modules. - -If the report detects a new revision, open the file that has been updated -in your translation, find the identified XML element and update/translate -its contents. Important: Make sure you also update the "revision" -attribute of that XML element by setting it to the same version as in -the original file, hence both the original XML file and your translated -file should have the same revision number for all elements. If an -XML element in your translation doesn't have a revision, but the original -file has, add a new "revision" attribute to your XML element. -The HTML report shows the identifiers and revisions for both the original -and the translated files, use it to compare. - -Rerun the "ant all.revdiff" report generation as often as you like until -no more differences are detected. You should always try to get your -copy clean, with all updated revisions and all identified elements -synchronzied. - - -6. Committing a translation - -All translators will be asked to submit their translated versions from -time to time. This will be a manual process, you will get an e-mail from -the Hibernate team and simply send your language subdirectory as -a ZIP file to us. It will then be integrated in the main Hibernate -distribution and on the website. Or, you can contact us for commit access -to the repository, where you can maintain a translation directly. - - -7. Generating PDF and HTML output - -The documentation is generated with the target 'ant all.doc'. - -To build the reference docs for a particular language only, use -"ant -Dlang=en", for example, and call either lang.all, lang.docpdf, -lang.dochtml, or lang.dochtmlsingle for the target of your choice. - -You can also call lang.section-check to track down missing identifiers in -a particular language, or you can call lang.revdiff to get a difference -report for a particular language, compared with the English reference. diff --git a/documentation/src/main/docbook/manual-old/en-US/HIBERNATE_-_Relational_Persistence_for_Idiomatic_Java.ent b/documentation/src/main/docbook/manual-old/en-US/HIBERNATE_-_Relational_Persistence_for_Idiomatic_Java.ent deleted file mode 100644 index db53b9bea55a..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/HIBERNATE_-_Relational_Persistence_for_Idiomatic_Java.ent +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/HIBERNATE_-_Relational_Persistence_for_Idiomatic_Java.xml b/documentation/src/main/docbook/manual-old/en-US/HIBERNATE_-_Relational_Persistence_for_Idiomatic_Java.xml deleted file mode 100644 index 6a134f29cff8..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/HIBERNATE_-_Relational_Persistence_for_Idiomatic_Java.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - -%BOOK_ENTITIES; -]> - - - - HIBERNATE - Relational Persistence for Idiomatic Java - Hibernate Reference Documentation - &version; - 1.0 - JBoss Hibernate Core - &version; - &today; - 1 - - - - - - - - - - ©rightYear; - ©rightHolder; - - - - - The Hibernate Team - - - The JBoss Visual Design Team - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/author_group.xml b/documentation/src/main/docbook/manual-old/en-US/author_group.xml deleted file mode 100644 index f33e2dcbc899..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/author_group.xml +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - Gavin - King - - - Christian - Bauer - - - Max - Rydahl - Andersen - - - Emmanuel - Bernard - - - Steve - Ebersole - - - Hardy - Ferentschik - - - - James - Cobb - - Graphic Design - - - - Cheyenne - Weaver - - Graphic Design - - - - - - - kreimer@bbs.frc.utn.edu.ar - - - - - Vincent - Ricard - - - Sebastien - Cesbron - - - Michael - Courcy - - - Vincent - Giguère - - - Baptiste - Mathus - - - Emmanuel - Bernard - - - Anthony - Patricio - - - - - Alvaro - Netto - alvaronetto@cetip.com.br - - - Anderson - Braulio - andersonbraulio@gmail.com - - - Daniel Vieira - Costa - danielvc@gmail.com - - - Francisco - gamarra - francisco.gamarra@gmail.com - - - Gamarra - mauricio.gamarra@gmail.com - - - Luiz Carlos - Rodrigues - luizcarlos_rodrigues@yahoo.com.br - - - Marcel - Castelo - marcel.castelo@gmail.com - - - Paulo - César - paulocol@gmail.com - - - Pablo L. - de Miranda - pablolmiranda@gmail.com - - - Renato - Deggau - rdeggau@gmail.com - - - Rogério - Araújo - rgildoaraujo@yahoo.com.br - - - Wanderson - Siqueira - wandersonxs@gmail.com - - - - - Cao - Xiaogang - - RedSaga - - Translation Lead - caoxg@yahoo.com - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/content/additionalmodules.xml b/documentation/src/main/docbook/manual-old/en-US/content/additionalmodules.xml deleted file mode 100644 index 55e5ea55f4d1..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/additionalmodules.xml +++ /dev/null @@ -1,277 +0,0 @@ - - - - - Additional modules - - Hibernate Core also offers integration with some external - modules/projects. This includes Hibernate Validator the reference - implementation of Bean Validation (JSR 303) and Hibernate Search. - -
- Bean Validation - - Bean Validation standardizes how to define and declare domain model - level constraints. You can, for example, express that a property should - never be null, that the account balance should be strictly positive, etc. - These domain model constraints are declared in the bean itself by - annotating its properties. Bean Validation can then read them and check - for constraint violations. The validation mechanism can be executed in - different layers in your application without having to duplicate any of - these rules (presentation layer, data access layer). Following the DRY - principle, Bean Validation and its reference implementation Hibernate - Validator has been designed for that purpose. - - The integration between Hibernate and Bean Validation works at two - levels. First, it is able to check in-memory instances of a class for - constraint violations. Second, it can apply the constraints to the - Hibernate metamodel and incorporate them into the generated database - schema. - - Each constraint annotation is associated to a validator - implementation responsible for checking the constraint on the entity - instance. A validator can also (optionally) apply the constraint to the - Hibernate metamodel, allowing Hibernate to generate DDL that expresses the - constraint. With the appropriate event listener, you can execute the - checking operation on inserts, updates and deletes done by - Hibernate. - - When checking instances at runtime, Hibernate Validator returns - information about constraint violations in a set of - ConstraintViolations. Among other information, the - ConstraintViolation contains an error description - message that can embed the parameter values bundle with the annotation - (eg. size limit), and message strings that may be externalized to a - ResourceBundle. - -
- Adding Bean Validation - - To enable Hibernate's Bean Validation integration, simply add a - Bean Validation provider (preferably Hibernate Validation 4) on your - classpath. -
- -
- Configuration - - By default, no configuration is necessary. - - The Default group is validated on entity - insert and update and the database model is updated accordingly based on - the Default group as well. - - You can customize the Bean Validation integration by setting the - validation mode. Use the - javax.persistence.validation.mode property and set it - up for example in your persistence.xml file or your - hibernate.cfg.xml file. Several options are - possible: - - - - auto (default): enable integration between - Bean Validation and Hibernate (callback and ddl generation) only if - Bean Validation is present in the classpath. - - - - none: disable all integration between Bean - Validation and Hibernate - - - - callback: only validate entities when they - are either inserted, updated or deleted. An exception is raised if - no Bean Validation provider is present in the classpath. - - - - ddl: only apply constraints to the database - schema when generated by Hibernate. An exception is raised if no - Bean Validation provider is present in the classpath. This value is - not defined by the Java Persistence spec and is specific to - Hibernate. - - - - - You can use both callback and - ddl together by setting the property to - callback, dll - - <persistence ...> - <persistence-unit ...> - ... - <properties> - <property name="javax.persistence.validation.mode" - value="callback, ddl"/> - </properties> - </persistence-unit> -</persistence> - - This is equivalent to auto except that if no - Bean Validation provider is present, an exception is raised. - - - If you want to validate different groups during insertion, update - and deletion, use: - - - - javax.persistence.validation.group.pre-persist: - groups validated when an entity is about to be persisted (default to - Default) - - - - javax.persistence.validation.group.pre-update: - groups validated when an entity is about to be updated (default to - Default) - - - - javax.persistence.validation.group.pre-remove: - groups validated when an entity is about to be deleted (default to - no group) - - - - org.hibernate.validator.group.ddl: groups - considered when applying constraints on the database schema (default - to Default) - - - - Each property accepts the fully qualified class names of the - groups validated separated by a comma (,) - - - Using custom groups for validation - - <persistence ...> - <persistence-unit ...> - ... - <properties> - <property name="javax.persistence.validation.group.pre-update" - value="javax.validation.group.Default, com.acme.group.Strict"/> - <property name="javax.persistence.validation.group.pre-remove" - value="com.acme.group.OnDelete"/> - <property name="org.hibernate.validator.group.ddl" - value="com.acme.group.DDL"/> - </properties> - </persistence-unit> -</persistence> - - - - You can set these properties in - hibernate.cfg.xml, - hibernate.properties or programmatically. - -
- -
- Catching violations - - If an entity is found to be invalid, the list of constraint - violations is propagated by the - ConstraintViolationException which exposes the - set of ConstraintViolations. - - This exception is wrapped in a - RollbackException when the violation happens at - commit time. Otherwise the - ConstraintViolationException is returned (for - example when calling flush(). Note that - generally, catchable violations are validated at a higher level (for - example in Seam / JSF 2 via the JSF - Bean Validation integration or in - your business layer by explicitly calling Bean Validation). - - An application code will rarely be looking for a - ConstraintViolationException raised by Hibernate. - This exception should be treated as fatal and the persistence context - should be discarded (EntityManager or - Session). -
- -
- Database schema - - Hibernate uses Bean Validation constraints to generate an accurate - database schema: - - - - @NotNull leads to a not null column - (unless it conflicts with components or table inheritance) - - - - @Size.max leads to a - varchar(max) definition for Strings - - - - @Min, @Max lead - to column checks (like value <= max) - - - - @Digits leads to the definition of - precision and scale (ever wondered which is which? It's easy now - with @Digits :) ) - - - - These constraints can be declared directly on the entity - properties or indirectly by using constraint composition. - - - For more information check the Hibernate Validator reference documentation at - - -
-
- -
- Hibernate Search - -
- Description - - - Full text search engines like Apache Lucene are a very powerful technology to - bring free text/efficient queries to applications. If suffers several mismatches when dealing with a - object domain model (keeping the index up to date, mismatch between the index structure and the domain - model, querying mismatch...) Hibernate Search indexes your domain model thanks to a few annotations, - takes care of the database / index synchronization and brings you back regular managed objects from - free text queries. Hibernate Search is using - Apache Lucene - under the covers. - -
- -
- Integration with Hibernate Annotations - - Hibernate Search integrates with Hibernate Core transparently - provided that the Hibernate Search jar is present on the classpath. If - you do not wish to automatically register Hibernate Search event - listeners, you can set - hibernate.search.autoregister_listeners to false. - Such a need is very uncommon and not recommended. - - - Check the Hibernate Search reference documentation ( - - ) for more information. - -
-
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/architecture.xml b/documentation/src/main/docbook/manual-old/en-US/content/architecture.xml deleted file mode 100644 index a8a348f6ced8..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/architecture.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - Architecture - -
- Overview - - - - - - - - - - - - Hibernate, as an ORM solution, effectively "sits between" the Java application and the Relational - Database, as can be seen in the diagram above. The Java application makes use of the Hibernate APIs - to load, store, query, etc its domain data. Here we will introduce the essential Hibernate APIs. - This will be a brief introduction; we will discuss these contracts in detail later. - - - - SessionFactory (org.hibernate.SessionFactory) - - - A thread-safe (and immutable) representation of the mapping of the application - domain model to a database. Acts as a factory for - org.hibernate.Session instances. - - - A SessionFactory is very expensive to create; there should be only - one SessionFactory for an application for a given database. Maintains - services that Hibernate uses across all Sessions such as second level caches, - connection pools, transaction system integrations, etc. - - - - - Session (org.hibernate.Session) - - - A single-threaded, short-lived object conceptually modeling a - "Unit of Work"PoEAA. - - - Wraps a JDBC java.sql.Connection. Acts as a factory for - org.hibernate.Transaction instances. Maintains a - generally "repeatable read" persistence context (first level cache) of the application's - domain model. - - - - - Transaction (org.hibernate.Transaction) - - - A single-threaded, short-lived object used by the application to demarcate individual - physical transaction boundaries. It acts as an abstraction API to isolate the application - from the underling transaction system in use (JDBC, JTA, CORBA, etc). - - - - - -
- -
- Contextual sessions - - Most applications using Hibernate need some form of "contextual" session, where a given - session is in effect throughout the scope of a given context. However, across applications - the definition of what constitutes a context is typically different; different contexts - define different scopes to the notion of current. Applications using Hibernate prior - to version 3.0 tended to utilize either home-grown ThreadLocal-based - contextual sessions, helper classes such as HibernateUtil, or utilized - third-party frameworks, such as Spring or Pico, which provided proxy/interception-based contextual sessions. - - - Starting with version 3.0.1, Hibernate added the SessionFactory.getCurrentSession() - method. Initially, this assumed usage of JTA transactions, where the - JTA transaction defined both the scope and context of a current session. - Given the maturity of the numerous stand-alone - JTA TransactionManager implementations, most, if not all, - applications should be using JTA transaction management, whether or not - they are deployed into a J2EE container. Based on that, the - JTA-based contextual sessions are all you need to use. - - - However, as of version 3.1, the processing behind - SessionFactory.getCurrentSession() is now pluggable. To that - end, a new extension interface, org.hibernate.context.spi.CurrentSessionContext, - and a new configuration parameter, hibernate.current_session_context_class, - have been added to allow pluggability of the scope and context of defining current sessions. - - - See the Javadocs for the org.hibernate.context.spi.CurrentSessionContext - interface for a detailed discussion of its contract. It defines a single method, - currentSession(), by which the implementation is responsible for - tracking the current contextual session. Out-of-the-box, Hibernate comes with three - implementations of this interface: - - - - - - org.hibernate.context.internal.JTASessionContext: current sessions - are tracked and scoped by a JTA transaction. The processing - here is exactly the same as in the older JTA-only approach. See the Javadocs - for details. - - - - - org.hibernate.context.internal.ThreadLocalSessionContext:current - sessions are tracked by thread of execution. See the Javadocs for details. - - - - - org.hibernate.context.internal.ManagedSessionContext: current - sessions are tracked by thread of execution. However, you are responsible to - bind and unbind a Session instance with static methods - on this class: it does not open, flush, or close a Session. - - - - - - Typically, the value of this parameter would just name the implementation class to - use. For the three out-of-the-box implementations, however, there are three corresponding - short names: "jta", "thread", and "managed". - - - - The first two implementations provide a "one session - one database transaction" programming - model. This is also known and used as session-per-request. The beginning - and end of a Hibernate session is defined by the duration of a database transaction. - If you use programmatic transaction demarcation in plain JSE without JTA, you are advised to - use the Hibernate Transaction API to hide the underlying transaction system - from your code. If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you - execute in an EJB container that supports CMT, transaction boundaries are defined declaratively - and you do not need any transaction or session demarcation operations in your code. - Refer to for more information and code examples. - - - - The hibernate.current_session_context_class configuration parameter - defines which org.hibernate.context.spi.CurrentSessionContext implementation - should be used. For backwards compatibility, if this configuration parameter is not set - but a org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform is configured, - Hibernate will use the org.hibernate.context.internal.JTASessionContext. - - -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/association_mapping.xml b/documentation/src/main/docbook/manual-old/en-US/content/association_mapping.xml deleted file mode 100755 index aca06e64dca4..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/association_mapping.xml +++ /dev/null @@ -1,631 +0,0 @@ - - - - - - Association Mappings - -
- Introduction - - - Association mappings are often the most difficult thing to implement correctly. In - this section we examine some canonical cases one by one, starting - with unidirectional mappings and then bidirectional cases. - We will use Person and Address in all - the examples. - - - - Associations will be classified by multiplicity and whether or not they map to an intervening - join table. - - - - Nullable foreign keys are not considered to be good practice in traditional data - modelling, so our examples do not use nullable foreign keys. This is not a - requirement of Hibernate, and the mappings will work if you drop the - nullability constraints. - - -
- -
- Unidirectional associations - -
- Many-to-one - - - A unidirectional many-to-one association is the most - common kind of unidirectional association. - - - - - - - - - - - - - -]]> - - -
- -
- One-to-one - - - A unidirectional one-to-one association on a foreign key - is almost identical. The only difference is the column unique constraint. - - - - - - - - - - - - - -]]> - - - - A unidirectional one-to-one association on a primary key - usually uses a special id generator In this example, however, we have reversed the direction - of the association: - - - - - - - - - - - - person - - - -]]> - - -
- -
- One-to-many - - - A unidirectional one-to-many association on a foreign key - is an unusual case, and is not recommended. - - - - - - - - - - - - - - - - -]]> - - - - You should instead use a join table for this kind of association. - - -
- -
- -
- Unidirectional associations with join tables - -
- One-to-many - - - A unidirectional one-to-many association on a join table - is the preferred option. Specifying unique="true", - changes the multiplicity from many-to-many to one-to-many. - - - - - - - - - - - - - - - - -]]> - - -
- -
- Many-to-one - - - A unidirectional many-to-one association on a join table - is common when the association is optional. For example: - - - - - - - - - - - - - - - - -]]> - - -
- -
- One-to-one - - - A unidirectional one-to-one association on a join table is possible, - but extremely unusual. - - - - - - - - - - - - - - - - -]]> - - -
- -
- Many-to-many - - - Finally, here is an example of a unidirectional many-to-many association. - - - - - - - - - - - - - - - - -]]> - - -
- -
- -
- Bidirectional associations - -
- one-to-many / many-to-one - - - A bidirectional many-to-one association is the - most common kind of association. The following example illustrates the standard parent/child - relationship. - - - - - - - - - - - - - - - - - -]]> - - - - - If you use a List, or other indexed collection, - set the key column of the foreign key to not null. - Hibernate will manage the association from the collections side to maintain the index - of each element, making the other side virtually inverse by setting - update="false" and insert="false": - - - - - ... - - - - - - ... - - - - - -]]> - - - If the underlying foreign key column is NOT NULL, it - is important that you define not-null="true" on the - <key> element of the collection mapping. - Do not only - declare not-null="true" on a possible nested - <column> element, but on the <key> - element. - - -
- -
- One-to-one - - - A bidirectional one-to-one association on a foreign key - is common: - - - - - - - - - - - - - - -]]> - - - - A bidirectional one-to-one association on a primary key - uses the special id generator: - - - - - - - - - - - - - person - - - -]]> - - -
- -
- -
- Bidirectional associations with join tables - -
- one-to-many / many-to-one - - - The following is an example of a bidirectional one-to-many association on a join table. - The inverse="true" can go on either end of the - association, on the collection, or on the join. - - - - - - - - - - - - - - - - - - - - -]]> - - -
- -
- one to one - - - A bidirectional one-to-one association on a join table is possible, - but extremely unusual. - - - - - - - - - - - - - - - - - - - - -]]> - - -
- -
- Many-to-many - - - Here is an example of a bidirectional many-to-many association. - - - - - - - - - - - - - - - - - - - - -]]> - - - -
- -
- -
- More complex association mappings - - - More complex association joins are extremely rare. - Hibernate handles more complex situations by using - SQL fragments embedded in the mapping document. For example, if a table - with historical account information data defines - accountNumber, effectiveEndDate - and effectiveStartDatecolumns, it would be mapped as follows: - - - - - - case when effectiveEndDate is null then 1 else 0 end - - - -]]> - - - You can then map an association to the current instance, - the one with null effectiveEndDate, by using: - - - - - '1' -]]> - - - In a more complex example, imagine that the association between - Employee and Organization is maintained - in an Employment table full of historical employment data. - An association to the employee's most recent employer, - the one with the most recent startDate, could be mapped in the following way: - - - - - - select employeeId, orgId - from Employments - group by orgId - having startDate = max(startDate) - - -]]> - - - This functionality allows a degree of creativity and flexibility, but it is more practical - to handle these kinds of cases using HQL or a criteria query. - - -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/basic_mapping.xml b/documentation/src/main/docbook/manual-old/en-US/content/basic_mapping.xml deleted file mode 100644 index b0cb27de7157..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/basic_mapping.xml +++ /dev/null @@ -1,5966 +0,0 @@ - - - - - Basic O/R Mapping - -
- Mapping declaration - - Object/relational mappings can be defined in three - approaches: - - - - using Java 5 annotations (via the Java Persistence 2 - annotations) - - - - using JPA 2 XML deployment descriptors (described in chapter - XXX) - - - - using the Hibernate legacy XML files approach known as - hbm.xml - - - - Annotations are split in two categories, the logical mapping - annotations (describing the object model, the association between two - entities etc.) and the physical mapping annotations (describing the - physical schema, tables, columns, indexes, etc). We will mix annotations - from both categories in the following code examples. - - JPA annotations are in the javax.persistence.* - package. Hibernate specific extensions are in - org.hibernate.annotations.*. You favorite IDE can - auto-complete annotations and their attributes for you (even without a - specific "JPA" plugin, since JPA annotations are plain Java 5 - annotations). - - Here is an example of mapping - - package eg; - -@Entity -@Table(name="cats") @Inheritance(strategy=SINGLE_TABLE) -@DiscriminatorValue("C") @DiscriminatorColumn(name="subclass", discriminatorType=CHAR) -public class Cat { - - @Id @GeneratedValue - public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - public BigDecimal getWeight() { return weight; } - public void setWeight(BigDecimal weight) { this.weight = weight; } - private BigDecimal weight; - - @Temporal(DATE) @NotNull @Column(updatable=false) - public Date getBirthdate() { return birthdate; } - public void setBirthdate(Date birthdate) { this.birthdate = birthdate; } - private Date birthdate; - - @org.hibernate.annotations.Type(type="eg.types.ColorUserType") - @NotNull @Column(updatable=false) - public ColorType getColor() { return color; } - public void setColor(ColorType color) { this.color = color; } - private ColorType color; - - @NotNull @Column(updatable=false) - public String getSex() { return sex; } - public void setSex(String sex) { this.sex = sex; } - private String sex; - - @NotNull @Column(updatable=false) - public Integer getLitterId() { return litterId; } - public void setLitterId(Integer litterId) { this.litterId = litterId; } - private Integer litterId; - - @ManyToOne @JoinColumn(name="mother_id", updatable=false) - public Cat getMother() { return mother; } - public void setMother(Cat mother) { this.mother = mother; } - private Cat mother; - - @OneToMany(mappedBy="mother") @OrderBy("litterId") - public Set<Cat> getKittens() { return kittens; } - public void setKittens(Set<Cat> kittens) { this.kittens = kittens; } - private Set<Cat> kittens = new HashSet<Cat>(); -} - -@Entity @DiscriminatorValue("D") -public class DomesticCat extends Cat { - - public String getName() { return name; } - public void setName(String name) { this.name = name } - private String name; -} - -@Entity -public class Dog { ... } - - The legacy hbm.xml approach uses an XML schema designed to be - readable and hand-editable. The mapping language is Java-centric, meaning - that mappings are constructed around persistent class declarations and not - table declarations. - - Please note that even though many Hibernate users choose to write - the XML by hand, a number of tools exist to generate the mapping document. - These include XDoclet, Middlegen and AndroMDA. - - Here is an example mapping: - - <?xml version="1.0"?> -<!DOCTYPE hibernate-mapping PUBLIC - "-//Hibernate/Hibernate Mapping DTD 3.0//EN" - "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - -<hibernate-mapping package="eg"> - - <class name="Cat" - table="cats" - discriminator-value="C"> - - <id name="id"> - <generator class="native"/> - </id> - - <discriminator column="subclass" - type="character"/> - - <property name="weight"/> - - <property name="birthdate" - type="date" - not-null="true" - update="false"/> - - <property name="color" - type="eg.types.ColorUserType" - not-null="true" - update="false"/> - - <property name="sex" - not-null="true" - update="false"/> - - <property name="litterId" - column="litterId" - update="false"/> - - <many-to-one name="mother" - column="mother_id" - update="false"/> - - <set name="kittens" - inverse="true" - order-by="litter_id"> - <key column="mother_id"/> - <one-to-many class="Cat"/> - </set> - - <subclass name="DomesticCat" - discriminator-value="D"> - - <property name="name" - type="string"/> - - </subclass> - - </class> - - <class name="Dog"> - <!-- mapping for Dog could go here --> - </class> - -</hibernate-mapping> - - We will now discuss the concepts of the mapping documents (both - annotations and XML). We will only describe, however, the document - elements and attributes that are used by Hibernate at runtime. The mapping - document also contains some extra optional attributes and elements that - affect the database schemas exported by the schema export tool (for - example, the not-null attribute). - -
- Entity - - An entity is a regular Java object (aka POJO) which will be - persisted by Hibernate. - - To mark an object as an entity in annotations, use the - @Entity annotation. - - @Entity -public class Flight implements Serializable { - Long id; - - @Id - public Long getId() { return id; } - - public void setId(Long id) { this.id = id; } -} - - That's pretty much it, the rest is optional. There are however any - options to tweak your entity mapping, let's explore them. - - @Table lets you define the table the entity - will be persisted into. If undefined, the table name is the unqualified - class name of the entity. You can also optionally define the catalog, - the schema as well as unique constraints on the table. - - @Entity -@Table(name="TBL_FLIGHT", - schema="AIR_COMMAND", - uniqueConstraints= - @UniqueConstraint( - name="flight_number", - columnNames={"comp_prefix", "flight_number"} ) ) -public class Flight implements Serializable { - @Column(name="comp_prefix") - public String getCompagnyPrefix() { return companyPrefix; } - - @Column(name="flight_number") - public String getNumber() { return number; } -} - - The constraint name is optional (generated if left undefined). The - column names composing the constraint correspond to the column names as - defined before the Hibernate NamingStrategy is - applied. - - - Be sure to use the database-level column names for the columnNames - property of a @UniqueConstraint. For example, whilst for simple types the - database-level column name may be the same as the entity-level property name, this is often - not the case for relational properties. - - - - @Entity.name lets you define the shortcut name - of the entity you can use in JP-QL and HQL queries. It defaults to the - unqualified class name of the class. - - Hibernate goes beyond the JPA specification and provide additional - configurations. Some of them are hosted on - @org.hibernate.annotations.Entity: - - - - dynamicInsert / - dynamicUpdate (defaults to false): specifies that - INSERT / UPDATE SQL should be - generated at runtime and contain only the columns whose values are - not null. The dynamic-update and - dynamic-insert settings are not inherited by - subclasses. Although these settings can increase performance in some - cases, they can actually decrease performance in others. - - - - selectBeforeUpdate (defaults to false): - specifies that Hibernate should never perform - an SQL UPDATE unless it is certain that an object - is actually modified. Only when a transient object has been - associated with a new session using update(), - will Hibernate perform an extra SQL SELECT to - determine if an UPDATE is actually required. Use - of select-before-update will usually decrease - performance. It is useful to prevent a database update trigger being - called unnecessarily if you reattach a graph of detached instances - to a Session. - - - - polymorphisms (defaults to - IMPLICIT): determines whether implicit or - explicit query polymorphisms is used. Implicit - polymorphisms means that instances of the class will be returned by - a query that names any superclass or implemented interface or class, - and that instances of any subclass of the class will be returned by - a query that names the class itself. Explicit - polymorphisms means that class instances will be returned only by - queries that explicitly name that class. Queries that name the class - will return only instances of subclasses mapped. For most purposes, - the default polymorphisms=IMPLICIT is - appropriate. Explicit polymorphisms is useful when two different - classes are mapped to the same table This allows a "lightweight" - class that contains a subset of the table columns. - - - - persister: specifies a custom - ClassPersister. The persister - attribute lets you customize the persistence strategy used for the - class. You can, for example, specify your own subclass of - org.hibernate.persister.EntityPersister, or you - can even provide a completely new implementation of the interface - org.hibernate.persister.ClassPersister that - implements, for example, persistence via stored procedure calls, - serialization to flat files or LDAP. See - org.hibernate.test.CustomPersister for a simple - example of "persistence" to a Hashtable. - - - - optimisticLock (defaults to - VERSION): determines the optimistic locking - strategy. If you enable dynamicUpdate, you will - have a choice of optimistic locking strategies: - - - - version: check the version/timestamp - columns - - - - all: check all columns - - - - dirty: check the changed columns, - allowing some concurrent updates - - - - none: do not use optimistic - locking - - - - It is strongly recommended that you use - version/timestamp columns for optimistic locking with Hibernate. - This strategy optimizes performance and correctly handles - modifications made to detached instances (i.e. when - Session.merge() is used). - - - - - Be sure to import - @javax.persistence.Entity to mark a class as an - entity. It's a common mistake to import - @org.hibernate.annotations.Entity by - accident. - - - Some entities are not mutable. They cannot be updated - by the application. This allows Hibernate to make some minor performance - optimizations.. Use the @Immutable - annotation. - - You can also alter how Hibernate deals with lazy initialization - for this class. On @Proxy, use - lazy=false to disable lazy fetching (not - recommended). You can also specify an interface to use for lazy - initializing proxies (defaults to the class itself): use - proxyClass on @Proxy. - Hibernate will initially return proxies ( using bytecode provider defined by hibernate.bytecode.provider) that - implement the named interface. The persistent object will load when a - method of the proxy is invoked. See "Initializing collections and - proxies" below. - - @BatchSize specifies a "batch size" for - fetching instances of this class by identifier. Not yet loaded instances - are loaded batch-size at a time (default 1). - - You can specific an arbitrary SQL WHERE condition to be used when - retrieving objects of this class. Use @Where for - that. - - In the same vein, @Check lets you define an - SQL expression used to generate a multi-row check - constraint for automatic schema generation. - - There is no difference between a view and a base table for a - Hibernate mapping. This is transparent at the database level, although - some DBMS do not support views properly, especially with updates. - Sometimes you want to use a view, but you cannot create one in the - database (i.e. with a legacy schema). In this case, you can map an - immutable and read-only entity to a given SQL subselect expression using - @org.hibernate.annotations.Subselect: - - @Entity -@Subselect("select item.name, max(bid.amount), count(*) " - + "from item " - + "join bid on bid.item_id = item.id " - + "group by item.name") -@Synchronize( {"item", "bid"} ) //tables impacted -public class Summary { - @Id - public String getId() { return id; } - ... -} - - Declare the tables to synchronize this entity with, ensuring that - auto-flush happens correctly and that queries against the derived entity - do not return stale data. The <subselect> is - available both as an attribute and a nested mapping element. - - We will now explore the same options using the hbm.xml structure. - You can declare a persistent class using the class - element. For example: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <class - name="ClassName" - table="tableName" - discriminator-value="discriminator_value" - mutable="true|false" - schema="owner" - catalog="catalog" - proxy="ProxyInterface" - dynamic-update="true|false" - dynamic-insert="true|false" - select-before-update="true|false" - polymorphism="implicit|explicit" - where="arbitrary sql where condition" - persister="PersisterClass" - batch-size="N" - optimistic-lock="none|version|dirty|all" - lazy="true|false" - entity-name="EntityName" - check="arbitrary sql check condition" - rowxml:id="rowid" - subselect="SQL expression" - abstract="true|false" - node="element-name" -/> - - - - name (optional): the fully qualified Java - class name of the persistent class or interface. If this attribute - is missing, it is assumed that the mapping is for a non-POJO - entity. - - - - table (optional - defaults to the - unqualified class name): the name of its database table. - - - - discriminator-value (optional - defaults - to the class name): a value that distinguishes individual - subclasses that is used for polymorphic behavior. Acceptable - values include null and not - null. - - - - mutable (optional - defaults to - true): specifies that instances of the class - are (not) mutable. - - - - schema (optional): overrides the schema - name specified by the root - <hibernate-mapping> element. - - - - catalog (optional): overrides the catalog - name specified by the root - <hibernate-mapping> element. - - - - proxy (optional): specifies an interface - to use for lazy initializing proxies. You can specify the name of - the class itself. - - - - dynamic-update (optional - defaults to - false): specifies that - UPDATE SQL should be generated at runtime and - can contain only those columns whose values have changed. - - - - dynamic-insert (optional - defaults to - false): specifies that - INSERT SQL should be generated at runtime and - contain only the columns whose values are not null. - - - - select-before-update (optional - defaults - to false): specifies that Hibernate should - never perform an SQL - UPDATE unless it is certain that an object is - actually modified. Only when a transient object has been - associated with a new session using update(), - will Hibernate perform an extra SQL SELECT to - determine if an UPDATE is actually - required. - - - - polymorphisms (optional - defaults to - implicit): determines whether implicit or - explicit query polymorphisms is used. - - - - where (optional): specifies an arbitrary - SQL WHERE condition to be used when retrieving - objects of this class. - - - - persister (optional): specifies a custom - ClassPersister. - - - - batch-size (optional - defaults to - 1): specifies a "batch size" for fetching - instances of this class by identifier. - - - - optimistic-lock (optional - defaults to - version): determines the optimistic locking - strategy. - - - - lazy (optional): lazy fetching can be - disabled by setting lazy="false". - - - - entity-name (optional - defaults to the - class name): Hibernate allows a class to be mapped multiple - times, potentially to different tables. It also allows entity - mappings that are represented by Maps or XML at the Java level. In - these cases, you should provide an explicit arbitrary name for the - entity. See - and for more information. - - - - check (optional): an SQL expression used - to generate a multi-row check constraint for - automatic schema generation. - - - - rowid (optional): Hibernate can use - ROWIDs on databases. On Oracle, for example, Hibernate can use the - rowid extra column for fast updates once this - option has been set to rowid. A ROWID is an - implementation detail and represents the physical location of a - stored tuple. - - - - subselect (optional): maps an immutable - and read-only entity to a database subselect. This is useful if - you want to have a view instead of a base table. See below for - more information. - - - - abstract (optional): is used to mark - abstract superclasses in <union-subclass> - hierarchies. - - - - - It is acceptable for the named persistent class to be an - interface. You can declare implementing classes of that interface using - the <subclass> element. You can persist any - static inner class. Specify the class name using - the standard form i.e. e.g.Foo$Bar. - - Here is how to do a virtual view (subselect) in XML: - - <class name="Summary"> - <subselect> - select item.name, max(bid.amount), count(*) - from item - join bid on bid.item_id = item.id - group by item.name - </subselect> - <synchronize table="item"/> - <synchronize table="bid"/> - <id name="name"/> - ... -</class> - - The <subselect> is available both as an - attribute and a nested mapping element. -
- -
- Identifiers - - Mapped classes must declare the primary key - column of the database table. Most classes will also have a - JavaBeans-style property holding the unique identifier of an - instance. - - Mark the identifier property with - @Id. - - @Entity -public class Person { - @Id Integer getId() { ... } - ... -} - - In hbm.xml, use the <id> element which - defines the mapping from that property to the primary key column. - - - - - - - - - - - - - - - <id - name="propertyName" - type="typename" - column="column_name" - unsaved-value="null|any|none|undefined|id_value" - access="field|property|ClassName"> - node="element-name|@attribute-name|element/@attribute|." - - <generator class="generatorClass"/> -</id> - - - - name (optional): the name of the - identifier property. - - - - type (optional): a name that indicates - the Hibernate type. - - - - column (optional - defaults to the - property name): the name of the primary key column. - - - - unsaved-value (optional - defaults to a - "sensible" value): an identifier property value that indicates an - instance is newly instantiated (unsaved), distinguishing it from - detached instances that were saved or loaded in a previous - session. - - - - access (optional - defaults to - property): the strategy Hibernate should use - for accessing the property value. - - - - - If the name attribute is missing, it is assumed - that the class has no identifier property. - - The unsaved-value attribute is almost never - needed in Hibernate and indeed has no corresponding element in - annotations. - - You can also declare the identifier as a composite identifier. - This allows access to legacy data with composite keys. Its use is - strongly discouraged for anything else. - -
- Composite identifier - - You can define a composite primary key through several - syntaxes: - - - - use a component type to represent the identifier and map it - as a property in the entity: you then annotated the property as - @EmbeddedId. The component type has to be - Serializable. - - - - map multiple properties as @Id - properties: the identifier type is then the entity class itself - and needs to be Serializable. This approach - is unfortunately not standard and only supported by - Hibernate. - - - - map multiple properties as @Id - properties and declare an external class to be the identifier - type. This class, which needs to be - Serializable, is declared on the entity via - the @IdClass annotation. The identifier - type must contain the same properties as the identifier properties - of the entity: each property name must be the same, its type must - be the same as well if the entity property is of a basic type, its - type must be the type of the primary key of the associated entity - if the entity property is an association (either a - @OneToOne or a - @ManyToOne). - - - - As you can see the last case is far from obvious. It has been - inherited from the dark ages of EJB 2 for backward compatibilities and - we recommend you not to use it (for simplicity sake). - - Let's explore all three cases using examples. - -
- id as a property using a component type - - Here is a simple example of - @EmbeddedId. - - @Entity -class User { - @EmbeddedId - @AttributeOverride(name="firstName", column=@Column(name="fld_firstname") - UserId id; - - Integer age; -} - -@Embeddable -class UserId implements Serializable { - String firstName; - String lastName; -} - - You can notice that the UserId class is - serializable. To override the column mapping, use - @AttributeOverride. - - An embedded id can itself contains the primary key of an - associated entity. - - @Entity -class Customer { - @EmbeddedId CustomerId id; - boolean preferredCustomer; - - @MapsId("userId") - @JoinColumns({ - @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"), - @JoinColumn(name="userlastname_fk", referencedColumnName="lastName") - }) - @OneToOne User user; -} - -@Embeddable -class CustomerId implements Serializable { - UserId userId; - String customerNumber; - - //implements equals and hashCode -} - -@Entity -class User { - @EmbeddedId UserId id; - Integer age; -} - -@Embeddable -class UserId implements Serializable { - String firstName; - String lastName; - - //implements equals and hashCode -} - - In the embedded id object, the association is represented as - the identifier of the associated entity. But you can link its value - to a regular association in the entity via the - @MapsId annotation. The - @MapsId value correspond to the property name - of the embedded id object containing the associated entity's - identifier. In the database, it means that the - Customer.user and the - CustomerId.userId properties share the same - underlying column (user_fk in this case). - - - The component type used as identifier must implement - equals() and - hashCode(). - - - In practice, your code only sets the - Customer.user property and the user id value is - copied by Hibernate into the CustomerId.userId - property. - - - The id value can be copied as late as flush time, don't rely - on it until after flush time. - - - While not supported in JPA, Hibernate lets you place your - association directly in the embedded id component (instead of having - to use the @MapsId annotation). - - @Entity -class Customer { - @EmbeddedId CustomerId id; - boolean preferredCustomer; -} - -@Embeddable -class CustomerId implements Serializable { - @OneToOne - @JoinColumns({ - @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"), - @JoinColumn(name="userlastname_fk", referencedColumnName="lastName") - }) - User user; - String customerNumber; - - //implements equals and hashCode -} - -@Entity -class User { - @EmbeddedId UserId id; - Integer age; -} - -@Embeddable -class UserId implements Serializable { - String firstName; - String lastName; - - - //implements equals and hashCode -} - - Let's now rewrite these examples using the hbm.xml - syntax. - - <composite-id - name="propertyName" - class="ClassName" - mapped="true|false" - access="field|property|ClassName" - node="element-name|."> - - <key-property name="propertyName" type="typename" column="column_name"/> - <key-many-to-one name="propertyName" class="ClassName" column="column_name"/> - ...... -</composite-id> - - First a simple example: - - <class name="User"> - <composite-id name="id" class="UserId"> - <key-property name="firstName" column="fld_firstname"/> - <key-property name="lastName"/> - </composite-id> -</class> - - Then an example showing how an association can be - mapped. - - <class name="Customer"> - <composite-id name="id" class="CustomerId"> - <key-property name="firstName" column="userfirstname_fk"/> - <key-property name="lastName" column="userlastname_fk"/> - <key-property name="customerNumber"/> - </composite-id> - - <property name="preferredCustomer"/> - - <many-to-one name="user"> - <column name="userfirstname_fk" updatable="false" insertable="false"/> - <column name="userlastname_fk" updatable="false" insertable="false"/> - </many-to-one> -</class> - -<class name="User"> - <composite-id name="id" class="UserId"> - <key-property name="firstName"/> - <key-property name="lastName"/> - </composite-id> - - <property name="age"/> -</class> - - Notice a few things in the previous example: - - - - the order of the properties (and column) matters. It must - be the same between the association and the primary key of the - associated entity - - - - the many to one uses the same columns as the primary key - and thus must be marked as read only - (insertable and updatable - to false). - - - - unlike with @MapsId, the id value - of the associated entity is not transparently copied, check the - foreign id generator for more - information. - - - - The last example shows how to map association directly in the - embedded id component. - - <class name="Customer"> - <composite-id name="id" class="CustomerId"> - <key-many-to-one name="user"> - <column name="userfirstname_fk"/> - <column name="userlastname_fk"/> - </key-many-to-one> - <key-property name="customerNumber"/> - </composite-id> - - <property name="preferredCustomer"/> -</class> - -<class name="User"> - <composite-id name="id" class="UserId"> - <key-property name="firstName"/> - <key-property name="lastName"/> - </composite-id> - - <property name="age"/> -</class> - - This is the recommended approach to map composite identifier. - The following options should not be considered unless some - constraint are present. -
- -
- Multiple id properties without identifier type - - Another, arguably more natural, approach is to place - @Id on multiple properties of your entity. - This approach is only supported by Hibernate (not JPA compliant) but - does not require an extra embeddable component. - - @Entity -class Customer implements Serializable { - @Id @OneToOne - @JoinColumns({ - @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"), - @JoinColumn(name="userlastname_fk", referencedColumnName="lastName") - }) - User user; - - @Id String customerNumber; - - boolean preferredCustomer; - - //implements equals and hashCode -} - -@Entity -class User { - @EmbeddedId UserId id; - Integer age; -} - -@Embeddable -class UserId implements Serializable { - String firstName; - String lastName; - - //implements equals and hashCode -} - - In this case Customer is its own - identifier representation: it must implement - Serializable and must implement - equals() and - hashCode(). - - In hbm.xml, the same mapping is: - - <class name="Customer"> - <composite-id> - <key-many-to-one name="user"> - <column name="userfirstname_fk"/> - <column name="userlastname_fk"/> - </key-many-to-one> - <key-property name="customerNumber"/> - </composite-id> - - <property name="preferredCustomer"/> -</class> - -<class name="User"> - <composite-id name="id" class="UserId"> - <key-property name="firstName"/> - <key-property name="lastName"/> - </composite-id> - - <property name="age"/> -</class> -
- -
- Multiple id properties with a dedicated identifier - type - - @IdClass on an entity points to the - class (component) representing the identifier of the class. The - properties marked @Id on the entity must have - their corresponding property on the @IdClass. - The return type of search twin property must be either identical for - basic properties or must correspond to the identifier class of the - associated entity for an association. - - - This approach is inherited from the EJB 2 days and we - recommend against its use. But, after all it's your application - and Hibernate supports it. - - - @Entity -@IdClass(CustomerId.class) -class Customer implements Serializable { - @Id @OneToOne - @JoinColumns({ - @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"), - @JoinColumn(name="userlastname_fk", referencedColumnName="lastName") - }) - User user; - - @Id String customerNumber; - - boolean preferredCustomer; -} - -class CustomerId implements Serializable { - UserId user; - String customerNumber; - - //implements equals and hashCode -} - -@Entity -class User { - @EmbeddedId UserId id; - Integer age; - - //implements equals and hashCode -} - -@Embeddable -class UserId implements Serializable { - String firstName; - String lastName; - - //implements equals and hashCode -} - - Customer and - CustomerId do have the same properties - customerNumber as well as - user. CustomerId must be - Serializable and implement - equals() and - hashCode(). - - While not JPA standard, Hibernate let's you declare the - vanilla associated property in the - @IdClass. - - @Entity -@IdClass(CustomerId.class) -class Customer implements Serializable { - @Id @OneToOne - @JoinColumns({ - @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"), - @JoinColumn(name="userlastname_fk", referencedColumnName="lastName") - }) - User user; - - @Id String customerNumber; - - boolean preferredCustomer; -} - -class CustomerId implements Serializable { - @OneToOne User user; - String customerNumber; - - //implements equals and hashCode -} - -@Entity -class User { - @EmbeddedId UserId id; - Integer age; - - //implements equals and hashCode -} - -@Embeddable -class UserId implements Serializable { - String firstName; - String lastName; -} - - This feature is of limited interest though as you are likely - to have chosen the @IdClass approach to stay - JPA compliant or you have a quite twisted mind. - - Here are the equivalent on hbm.xml files: - - <class name="Customer"> - <composite-id class="CustomerId" mapped="true"> - <key-many-to-one name="user"> - <column name="userfirstname_fk"/> - <column name="userlastname_fk"/> - </key-many-to-one> - <key-property name="customerNumber"/> - </composite-id> - - <property name="preferredCustomer"/> -</class> - -<class name="User"> - <composite-id name="id" class="UserId"> - <key-property name="firstName"/> - <key-property name="lastName"/> - </composite-id> - - <property name="age"/> -</class> -
-
- -
- Identifier generator - - Hibernate can generate and populate identifier values for you - automatically. This is the recommended approach over "business" or - "natural" id (especially composite ids). - - Hibernate offers various generation strategies, let's explore - the most common ones first that happens to be standardized by - JPA: - - - - IDENTITY: supports identity columns in DB2, MySQL, MS SQL - Server, Sybase and HypersonicSQL. The returned identifier is of - type long, short or - int. - - - - SEQUENCE (called seqhilo in Hibernate): - uses a hi/lo algorithm to efficiently generate identifiers of type - long, short or - int, given a named database sequence. - - - - TABLE (called - MultipleHiLoPerTableGenerator in Hibernate) - : uses a hi/lo algorithm to efficiently generate identifiers of - type long, short or - int, given a table and column as a source of hi - values. The hi/lo algorithm generates identifiers that are unique - only for a particular database. - - - - AUTO: selects IDENTITY, - SEQUENCE or TABLE depending - upon the capabilities of the underlying database. - - - - - We recommend all new projects to use the new enhanced - identifier generators. They are deactivated by default for entities - using annotations but can be activated using - hibernate.id.new_generator_mappings=true. These new - generators are more efficient and closer to the JPA 2 specification - semantic. - - However they are not backward compatible with existing - Hibernate based application (if a sequence or a table is used for id - generation). See XXXXXXX for - more information on how to activate them. - - - To mark an id property as generated, use the - @GeneratedValue annotation. You can specify the - strategy used (default to AUTO) by setting - strategy. - - @Entity -public class Customer { - @Id @GeneratedValue - Integer getId() { ... }; -} - -@Entity -public class Invoice { - @Id @GeneratedValue(strategy=GenerationType.IDENTITY) - Integer getId() { ... }; -} - - SEQUENCE and TABLE require - additional configurations that you can set using - @SequenceGenerator and - @TableGenerator: - - - - name: name of the generator - - - - table / sequenceName: - name of the table or the sequence (defaulting respectively to - hibernate_sequences and - hibernate_sequence) - - - - catalog / - schema: - - - - initialValue: the value from which the id - is to start generating - - - - allocationSize: the amount to increment - by when allocating id numbers from the generator - - - - In addition, the TABLE strategy also let - you customize: - - - - pkColumnName: the column name containing - the entity identifier - - - - valueColumnName: the column name - containing the identifier value - - - - pkColumnValue: the entity - identifier - - - - uniqueConstraints: any potential column - constraint on the table containing the ids - - - - To link a table or sequence generator definition with an actual - generated property, use the same name in both the definition - name and the generator value - generator as shown below. - - @Id -@GeneratedValue( - strategy=GenerationType.SEQUENCE, - generator="SEQ_GEN") -@javax.persistence.SequenceGenerator( - name="SEQ_GEN", - sequenceName="my_sequence", - allocationSize=20 -) -public Integer getId() { ... } - - The scope of a generator definition can be the application or - the class. Class-defined generators are not visible outside the class - and can override application level generators. Application level - generators are defined in JPA's XML deployment descriptors (see XXXXXX - ): - - <table-generator name="EMP_GEN" - table="GENERATOR_TABLE" - pk-column-name="key" - value-column-name="hi" - pk-column-value="EMP" - allocation-size="20"/> - -//and the annotation equivalent - -@javax.persistence.TableGenerator( - name="EMP_GEN", - table="GENERATOR_TABLE", - pkColumnName = "key", - valueColumnName = "hi" - pkColumnValue="EMP", - allocationSize=20 -) - -<sequence-generator name="SEQ_GEN" - sequence-name="my_sequence" - allocation-size="20"/> - -//and the annotation equivalent - -@javax.persistence.SequenceGenerator( - name="SEQ_GEN", - sequenceName="my_sequence", - allocationSize=20 -) - - - If a JPA XML descriptor (like - META-INF/orm.xml) is used to define the - generators, EMP_GEN and SEQ_GEN - are application level generators. - - - Package level definition is not supported by the JPA - specification. However, you can use the - @GenericGenerator at the package level (see ). - - - These are the four standard JPA generators. Hibernate goes - beyond that and provide additional generators or additional options as - we will see below. You can also write your own custom identifier - generator by implementing - org.hibernate.id.IdentifierGenerator. - - To define a custom generator, use the - @GenericGenerator annotation (and its plural - counter part @GenericGenerators) that describes - the class of the identifier generator or its short cut name (as - described below) and a list of key/value parameters. When using - @GenericGenerator and assigning it via - @GeneratedValue.generator, the - @GeneratedValue.strategy is ignored: leave it - blank. - - @Id @GeneratedValue(generator="system-uuid") -@GenericGenerator(name="system-uuid", strategy = "uuid") -public String getId() { - -@Id @GeneratedValue(generator="trigger-generated") -@GenericGenerator( - name="trigger-generated", - strategy = "select", - parameters = @Parameter(name="key", value = "socialSecurityNumber") -) -public String getId() { - - The hbm.xml approach uses the optional - <generator> child element inside - <id>. If any parameters are required to - configure or initialize the generator instance, they are passed using - the <param> element. - - <id name="id" type="long" column="cat_id"> - <generator class="org.hibernate.id.TableHiLoGenerator"> - <param name="table">uid_table</param> - <param name="column">next_hi_value_column</param> - </generator> -</id> - -
- Various additional generators - - All generators implement the interface - org.hibernate.id.IdentifierGenerator. This is a - very simple interface. Some applications can choose to provide their - own specialized implementations, however, Hibernate provides a range - of built-in implementations. The shortcut names for the built-in - generators are as follows: - - increment - - - generates identifiers of type long, - short or int that are - unique only when no other process is inserting data into the - same table. Do not use in a - cluster. - - - - - identity - - - supports identity columns in DB2, MySQL, MS SQL - Server, Sybase and HypersonicSQL. The returned identifier is - of type long, short or - int. - - - - - sequence - - - uses a sequence in DB2, PostgreSQL, Oracle, SAP DB, - McKoi or a generator in Interbase. The returned identifier - is of type long, short - or int - - - - - hilo - - - uses a hi/lo algorithm to efficiently generate - identifiers of type long, - short or int, given a - table and column (by default - hibernate_unique_key and - next_hi respectively) as a source of hi - values. The hi/lo algorithm generates identifiers that are - unique only for a particular database. - - - - - seqhilo - - - uses a hi/lo algorithm to efficiently generate - identifiers of type long, - short or int, given a - named database sequence. - - - - - uuid - - - Generates a 128-bit UUID based on a custom algorithm. - The value generated is represented as a string of 32 - hexidecimal digits. Users can also configure it to use a - separator (config parameter "separator") which separates the - hexidecimal digits into 8{sep}8{sep}4{sep}8{sep}4. Note - specifically that this is different than the IETF RFC 4122 - representation of 8-4-4-4-12. If you need RFC 4122 compliant - UUIDs, consider using "uuid2" generator discussed - below. - - - - - uuid2 - - - Generates a IETF RFC 4122 compliant (variant 2) - 128-bit UUID. The exact "version" (the RFC term) generated - depends on the pluggable "generation strategy" used (see - below). Capable of generating values as - java.util.UUID, - java.lang.String or as a byte array - of length 16 (byte[16]). The "generation - strategy" is defined by the interface - org.hibernate.id.UUIDGenerationStrategy. - The generator defines 2 configuration parameters for - defining which generation strategy to use: - - uuid_gen_strategy_class - - - Names the UUIDGenerationStrategy class to - use - - - - - uuid_gen_strategy - - - Names the UUIDGenerationStrategy instance to - use - - - - - Out of the box, comes with the following strategies: - - - org.hibernate.id.uuid.StandardRandomStrategy - (the default) - generates "version 3" (aka, "random") - UUID values via the - randomUUID method of - java.util.UUID - - - - org.hibernate.id.uuid.CustomVersionOneStrategy - - generates "version 1" UUID values, using IP address - since mac address not available. If you need mac - address to be used, consider leveraging one of the - existing third party UUID generators which sniff out - mac address and integrating it via the - org.hibernate.id.UUIDGenerationStrategy - contract. Two such libraries known at time of this - writing include - and - - - - - - - - guid - - - uses a database-generated GUID string on MS SQL Server - and MySQL. - - - - - native - - - selects identity, - sequence or hilo - depending upon the capabilities of the underlying - database. - - - - - assigned - - - lets the application assign an identifier to the - object before save() is called. This is - the default strategy if no - <generator> element is - specified. - - - - - select - - - retrieves a primary key, assigned by a database - trigger, by selecting the row by some unique key and - retrieving the primary key value. - - - - - foreign - - - uses the identifier of another associated object. It - is usually used in conjunction with a - <one-to-one> primary key - association. - - - - - sequence-identity - - - a specialized sequence generation strategy that - utilizes a database sequence for the actual value - generation, but combines this with JDBC3 getGeneratedKeys to - return the generated identifier value as part of the insert - statement execution. This strategy is only supported on - Oracle 10g drivers targeted for JDK 1.4. Comments on these - insert statements are disabled due to a bug in the Oracle - drivers. - - - -
- -
- Hi/lo algorithm - - The hilo and seqhilo - generators provide two alternate implementations of the hi/lo - algorithm. The first implementation requires a "special" database - table to hold the next available "hi" value. Where supported, the - second uses an Oracle-style sequence. - - <id name="id" type="long" column="cat_id"> - <generator class="hilo"> - <param name="table">hi_value</param> - <param name="column">next_value</param> - <param name="max_lo">100</param> - </generator> -</id> - - <id name="id" type="long" column="cat_id"> - <generator class="seqhilo"> - <param name="sequence">hi_value</param> - <param name="max_lo">100</param> - </generator> -</id> - - Unfortunately, you cannot use hilo when - supplying your own Connection to Hibernate. When - Hibernate uses an application server datasource to obtain - connections enlisted with JTA, you must configure the - hibernate.transaction.manager_lookup_class. -
- -
- UUID algorithm - - The UUID contains: IP address, startup time of the JVM that is - accurate to a quarter second, system time and a counter value that - is unique within the JVM. It is not possible to obtain a MAC address - or memory address from Java code, so this is the best option without - using JNI. -
- -
- Identity columns and sequences - - For databases that support identity columns (DB2, MySQL, - Sybase, MS SQL), you can use identity key - generation. For databases that support sequences (DB2, Oracle, - PostgreSQL, Interbase, McKoi, SAP DB) you can use - sequence style key generation. Both of these - strategies require two SQL queries to insert a new object. For - example: - - <id name="id" type="long" column="person_id"> - <generator class="sequence"> - <param name="sequence">person_id_sequence</param> - </generator> -</id> - - <id name="id" type="long" column="person_id" unsaved-value="0"> - <generator class="identity"/> -</id> - - For cross-platform development, the native - strategy will, depending on the capabilities of the underlying - database, choose from the identity, - sequence and hilo - strategies. -
- -
- Assigned identifiers - - If you want the application to assign identifiers, as opposed - to having Hibernate generate them, you can use the - assigned generator. This special generator uses - the identifier value already assigned to the object's identifier - property. The generator is used when the primary key is a natural - key instead of a surrogate key. This is the default behavior if you - do not specify @GeneratedValue nor - <generator> elements. - - The assigned generator makes Hibernate use - unsaved-value="undefined". This forces Hibernate - to go to the database to determine if an instance is transient or - detached, unless there is a version or timestamp property, or you - define Interceptor.isUnsaved(). -
- -
- Primary keys assigned by triggers - - Hibernate does not generate DDL with triggers. It is for - legacy schemas only. - - <id name="id" type="long" column="person_id"> - <generator class="select"> - <param name="key">socialSecurityNumber</param> - </generator> -</id> - - In the above example, there is a unique valued property named - socialSecurityNumber. It is defined by the class, - as a natural key and a surrogate key named - person_id, whose value is generated by a - trigger. -
- -
- Identity copy (foreign generator) - - Finally, you can ask Hibernate to copy the identifier from - another associated entity. In the Hibernate jargon, it is known as a - foreign generator but the JPA mapping reads better and is - encouraged. - - @Entity -class MedicalHistory implements Serializable { - @Id @OneToOne - @JoinColumn(name = "person_id") - Person patient; -} - -@Entity -public class Person implements Serializable { - @Id @GeneratedValue Integer id; -} - - Or alternatively - - @Entity -class MedicalHistory implements Serializable { - @Id Integer id; - - @MapsId @OneToOne - @JoinColumn(name = "patient_id") - Person patient; -} - -@Entity -class Person { - @Id @GeneratedValue Integer id; -} - - In hbm.xml use the following approach: - - <class name="MedicalHistory"> - <id name="id"> - <generator class="foreign"> - <param name="property">patient</param> - </generator> - </id> - <one-to-one name="patient" class="Person" constrained="true"/> -</class> -
-
- -
- Enhanced identifier generators - - Starting with release 3.2.3, there are 2 new generators which - represent a re-thinking of 2 different aspects of identifier - generation. The first aspect is database portability; the second is - optimization Optimization means that you do not have to query the - database for every request for a new identifier value. These two new - generators are intended to take the place of some of the named - generators described above, starting in 3.3.x. However, they are - included in the current releases and can be referenced by FQN. - - The first of these new generators is - org.hibernate.id.enhanced.SequenceStyleGenerator - which is intended, firstly, as a replacement for the - sequence generator and, secondly, as a better - portability generator than native. This is because - native generally chooses between - identity and sequence which have - largely different semantics that can cause subtle issues in - applications eyeing portability. - org.hibernate.id.enhanced.SequenceStyleGenerator, - however, achieves portability in a different manner. It chooses - between a table or a sequence in the database to store its - incrementing values, depending on the capabilities of the dialect - being used. The difference between this and native - is that table-based and sequence-based storage have the same exact - semantic. In fact, sequences are exactly what Hibernate tries to - emulate with its table-based generators. This generator has a number - of configuration parameters: - - sequence_name (optional, defaults to - hibernate_sequence): the name of the sequence - or table to be used. - - - - initial_value (optional, defaults to - 1): the initial value to be retrieved from - the sequence/table. In sequence creation terms, this is - analogous to the clause typically named "STARTS WITH". - - - - increment_size (optional - defaults to - 1): the value by which subsequent calls to - the sequence/table should differ. In sequence creation terms, - this is analogous to the clause typically named "INCREMENT - BY". - - - - force_table_use (optional - defaults to - false): should we force the use of a table as - the backing structure even though the dialect might support - sequence? - - - - value_column (optional - defaults to - next_val): only relevant for table - structures, it is the name of the column on the table which is - used to hold the value. - - - - prefer_sequence_per_entity (optional - - defaults to false): should we create - separate sequence for each entity that share current generator - based on its name? - - - - sequence_per_entity_suffix (optional - - defaults to _SEQ): suffix added to the name - of a dedicated sequence. - - - - optimizer (optional - defaults to - none): See - - - - The second of these new generators is - org.hibernate.id.enhanced.TableGenerator, which is - intended, firstly, as a replacement for the table - generator, even though it actually functions much more like - org.hibernate.id.MultipleHiLoPerTableGenerator, and - secondly, as a re-implementation of - org.hibernate.id.MultipleHiLoPerTableGenerator that - utilizes the notion of pluggable optimizers. Essentially this - generator defines a table capable of holding a number of different - increment values simultaneously by using multiple distinctly keyed - rows. This generator has a number of configuration parameters: - - - table_name (optional - defaults to - hibernate_sequences): the name of the table - to be used. - - - - value_column_name (optional - defaults - to next_val): the name of the column on the - table that is used to hold the value. - - - - segment_column_name (optional - - defaults to sequence_name): the name of the - column on the table that is used to hold the "segment key". This - is the value which identifies which increment value to - use. - - - - segment_value (optional - defaults to - default): The "segment key" value for the - segment from which we want to pull increment values for this - generator. - - - - segment_value_length (optional - - defaults to 255): Used for schema generation; - the column size to create this segment key column. - - - - initial_value (optional - defaults to - 1): The initial value to be retrieved from - the table. - - - - increment_size (optional - defaults to - 1): The value by which subsequent calls to - the table should differ. - - - - optimizer (optional - defaults to - ??): See . - - - -
- Identifier generator optimization - - For identifier generators that store values in the database, - it is inefficient for them to hit the database on each and every - call to generate a new identifier value. Instead, you can group a - bunch of them in memory and only hit the database when you have - exhausted your in-memory value group. This is the role of the - pluggable optimizers. Currently only the two enhanced generators - ( support this - operation. - - - - none (generally this is the default if - no optimizer was specified): this will not perform any - optimizations and hit the database for each and every - request. - - - - hilo: applies a hi/lo algorithm around - the database retrieved values. The values from the database for - this optimizer are expected to be sequential. The values - retrieved from the database structure for this optimizer - indicates the "group number". The - increment_size is multiplied by that value in - memory to define a group "hi value". - - - - pooled: as with the case of - hilo, this optimizer attempts to minimize the - number of hits to the database. Here, however, we simply store - the starting value for the "next group" into the database - structure rather than a sequential value in combination with an - in-memory grouping algorithm. Here, - increment_size refers to the values coming - from the database. - - -
-
- -
- Partial identifier generation - - Hibernate supports the automatic generation of some of the - identifier properties. Simply use the - @GeneratedValue annotation on one or several id - properties. - - - The Hibernate team has always felt such a construct as - fundamentally wrong. Try hard to fix your data model before using - this feature. - - - @Entity -public class CustomerInventory implements Serializable { - @Id - @TableGenerator(name = "inventory", - table = "U_SEQUENCES", - pkColumnName = "S_ID", - valueColumnName = "S_NEXTNUM", - pkColumnValue = "inventory", - allocationSize = 1000) - @GeneratedValue(strategy = GenerationType.TABLE, generator = "inventory") - Integer id; - - - @Id @ManyToOne(cascade = CascadeType.MERGE) - Customer customer; -} - -@Entity -public class Customer implements Serializable { - @Id - private int id; -} - - You can also generate properties inside an - @EmbeddedId class. -
-
- -
- Optimistic locking properties (optional) - - When using long transactions or conversations that span several - database transactions, it is useful to store versioning data to ensure - that if the same entity is updated by two conversations, the last to - commit changes will be informed and not override the other - conversation's work. It guarantees some isolation while still allowing - for good scalability and works particularly well in read-often - write-sometimes situations. - - You can use two approaches: a dedicated version number or a - timestamp. - - A version or timestamp property should never be null for a - detached instance. Hibernate will detect any instance with a null - version or timestamp as transient, irrespective of what other - unsaved-value strategies are specified. - Declaring a nullable version or timestamp property is an easy - way to avoid problems with transitive reattachment in Hibernate. It is - especially useful for people using assigned identifiers or composite - keys. - -
- Version number - - You can add optimistic locking capability to an entity using the - @Version annotation: - - @Entity -public class Flight implements Serializable { -... - @Version - @Column(name="OPTLOCK") - public Integer getVersion() { ... } -} - - The version property will be mapped to the - OPTLOCK column, and the entity manager will use it - to detect conflicting updates (preventing lost updates you might - otherwise see with the last-commit-wins strategy). - - The version column may be a numeric. Hibernate supports any kind - of type provided that you define and implement the appropriate - UserVersionType. - - The application must not alter the version number set up by - Hibernate in any way. To artificially increase the version number, - check in Hibernate Entity Manager's reference documentation - LockModeType.OPTIMISTIC_FORCE_INCREMENT or - LockModeType.PESSIMISTIC_FORCE_INCREMENT. - - If the version number is generated by the database (via a - trigger for example), make sure to use - @org.hibernate.annotations.Generated(GenerationTime.ALWAYS). - - To declare a version property in hbm.xml, use: - - - - - - - - - - - - - - - - - - - <version - column="version_column" - name="propertyName" - type="typename" - access="field|property|ClassName" - unsaved-value="null|negative|undefined" - generated="never|always" - insert="true|false" - node="element-name|@attribute-name|element/@attribute|." -/> - - - - column (optional - defaults to the - property name): the name of the column holding the version - number. - - - - name: the name of a property of the - persistent class. - - - - type (optional - defaults to - integer): the type of the version - number. - - - - access (optional - defaults to - property): the strategy Hibernate uses to - access the property value. - - - - unsaved-value (optional - defaults to - undefined): a version property value that - indicates that an instance is newly instantiated (unsaved), - distinguishing it from detached instances that were saved or - loaded in a previous session. Undefined - specifies that the identifier property value should be - used. - - - - generated (optional - defaults to - never): specifies that this version property - value is generated by the database. See the discussion of generated properties for more - information. - - - - insert (optional - defaults to - true): specifies whether the version column - should be included in SQL insert statements. It can be set to - false if the database column is defined with - a default value of 0. - - - -
- -
- Timestamp - - Alternatively, you can use a timestamp. Timestamps are a less - safe implementation of optimistic locking. However, sometimes an - application might use the timestamps in other ways as well. - - Simply mark a property of type Date or - Calendar as - @Version. - - @Entity -public class Flight implements Serializable { -... - @Version - public Date getLastUpdate() { ... } -} - - When using timestamp versioning you can tell Hibernate where to - retrieve the timestamp value from - database or JVM - by optionally - adding the @org.hibernate.annotations.Source - annotation to the property. Possible values for the value attribute of - the annotation are - org.hibernate.annotations.SourceType.VM and - org.hibernate.annotations.SourceType.DB. The - default is SourceType.DB which is also used in - case there is no @Source annotation at - all. - - Like in the case of version numbers, the timestamp can also be - generated by the database instead of Hibernate. To do that, use - @org.hibernate.annotations.Generated(GenerationTime.ALWAYS). - - In hbm.xml, use the <timestamp> - element: - - - - - - - - - - - - - - - - - <timestamp - column="timestamp_column" - name="propertyName" - access="field|property|ClassName" - unsaved-value="null|undefined" - source="vm|db" - generated="never|always" - node="element-name|@attribute-name|element/@attribute|." -/> - - - - column (optional - defaults to the - property name): the name of a column holding the - timestamp. - - - - name: the name of a JavaBeans style - property of Java type Date or - Timestamp of the persistent class. - - - - access (optional - defaults to - property): the strategy Hibernate uses for - accessing the property value. - - - - unsaved-value (optional - defaults to - null): a version property value that - indicates that an instance is newly instantiated (unsaved), - distinguishing it from detached instances that were saved or - loaded in a previous session. Undefined - specifies that the identifier property value should be - used. - - - - source (optional - defaults to - vm): Where should Hibernate retrieve the - timestamp value from? From the database, or from the current - JVM? Database-based timestamps incur an overhead because - Hibernate must hit the database in order to determine the "next - value". It is safer to use in clustered environments. Not all - Dialects are known to support the retrieval - of the database's current timestamp. Others may also be unsafe - for usage in locking due to lack of precision (Oracle 8, for - example). - - - - generated (optional - defaults to - never): specifies that this timestamp - property value is actually generated by the database. See the - discussion of generated - properties for more information. - - - - - - Note - - <Timestamp> is equivalent to - <version type="timestamp">. And - <timestamp source="db"> is equivalent to - <version type="dbtimestamp"> - -
-
- -
- Property - - You need to decide which property needs to be made persistent in a - given entity. This differs slightly between the annotation driven - metadata and the hbm.xml files. - -
- Property mapping with annotations - - In the annotations world, every non static non transient - property (field or method depending on the access type) of an entity - is considered persistent, unless you annotate it as - @Transient. Not having an annotation for your - property is equivalent to the appropriate @Basic - annotation. - - The @Basic annotation allows you to declare - the fetching strategy for a property. If set to - LAZY, specifies that this property should be - fetched lazily when the instance variable is first accessed. It - requires build-time bytecode instrumentation, if your classes are not - instrumented, property level lazy loading is silently ignored. The - default is EAGER. You can also mark a property as - not optional thanks to the @Basic.optional - attribute. This will ensure that the underlying column are not - nullable (if possible). Note that a better approach is to use the - @NotNull annotation of the Bean Validation - specification. - - Let's look at a few examples: - - public transient int counter; //transient property - -private String firstname; //persistent property - -@Transient -String getLengthInMeter() { ... } //transient property - -String getName() {... } // persistent property - -@Basic -int getLength() { ... } // persistent property - -@Basic(fetch = FetchType.LAZY) -String getDetailedComment() { ... } // persistent property - -@Temporal(TemporalType.TIME) -java.util.Date getDepartureTime() { ... } // persistent property - -@Enumerated(EnumType.STRING) -Starred getNote() { ... } //enum persisted as String in database - - counter, a transient field, and - lengthInMeter, a method annotated as - @Transient, and will be ignored by the Hibernate. - name, length, and - firstname properties are mapped persistent and - eagerly fetched (the default for simple properties). The - detailedComment property value will be lazily - fetched from the database once a lazy property of the entity is - accessed for the first time. Usually you don't need to lazy simple - properties (not to be confused with lazy association fetching). The - recommended alternative is to use the projection capability of JP-QL - (Java Persistence Query Language) or Criteria queries. - - JPA support property mapping of all basic types supported by - Hibernate (all basic Java types , their respective wrappers and - serializable classes). Hibernate Annotations supports out of the box - enum type mapping either into a ordinal column (saving the enum - ordinal) or a string based column (saving the enum string - representation): the persistence representation, defaulted to ordinal, - can be overridden through the @Enumerated - annotation as shown in the note property - example. - - In plain Java APIs, the temporal precision of time is not - defined. When dealing with temporal data you might want to describe - the expected precision in database. Temporal data can have - DATE, TIME, or - TIMESTAMP precision (ie the actual date, only the - time, or both). Use the @Temporal annotation to - fine tune that. - - @Lob indicates that the property should be - persisted in a Blob or a Clob depending on the property type: - java.sql.Clob, - Character[], char[] and - java.lang.String will be persisted in a Clob. - java.sql.Blob, Byte[], - byte[] and Serializable - type will be persisted in a Blob. - - @Lob -public String getFullText() { - return fullText; -} - -@Lob -public byte[] getFullCode() { - return fullCode; -} - - If the property type implements - java.io.Serializable and is not a basic type, - and if the property is not annotated with @Lob, - then the Hibernate serializable type is - used. - -
- Type - - You can also manually specify a type using the - @org.hibernate.annotations.Type and some - parameters if needed. @Type.type could - be: - - - - The name of a Hibernate basic type: integer, - string, character, date, timestamp, float, binary, serializable, - object, blob etc. - - - - The name of a Java class with a default basic type: - int, float, char, java.lang.String, java.util.Date, - java.lang.Integer, java.sql.Clob etc. - - - - The name of a serializable Java class. - - - - The class name of a custom type: - com.illflow.type.MyCustomType etc. - - - - If you do not specify a type, Hibernate will use reflection - upon the named property and guess the correct Hibernate type. - Hibernate will attempt to interpret the name of the return class of - the property getter using, in order, rules 2, 3, and 4. - - @org.hibernate.annotations.TypeDef and - @org.hibernate.annotations.TypeDefs allows you to - declare type definitions. These annotations can be placed at the - class or package level. Note that these definitions are global for - the session factory (even when defined at the class level). If the - type is used on a single entity, you can place the definition on the - entity itself. Otherwise, it is recommended to place the definition - at the package level. In the example below, when Hibernate - encounters a property of class PhoneNumer, it - delegates the persistence strategy to the custom mapping type - PhoneNumberType. However, properties belonging to - other classes, too, can delegate their persistence strategy to - PhoneNumberType, by explicitly using the - @Type annotation. - - - Package level annotations are placed in a file named - package-info.java in the appropriate package. - Place your annotations before the package declaration. - - - @TypeDef( - name = "phoneNumber", - defaultForType = PhoneNumber.class, - typeClass = PhoneNumberType.class -) - -@Entity -public class ContactDetails { - [...] - private PhoneNumber localPhoneNumber; - @Type(type="phoneNumber") - private OverseasPhoneNumber overseasPhoneNumber; - [...] -} - - The following example shows the usage of the - parameters attribute to customize the - TypeDef. - - //in org/hibernate/test/annotations/entity/package-info.java -@TypeDefs( - { - @TypeDef( - name="caster", - typeClass = CasterStringType.class, - parameters = { - @Parameter(name="cast", value="lower") - } - ) - } -) -package org.hibernate.test.annotations.entity; - -//in org/hibernate/test/annotations/entity/Forest.java -public class Forest { - @Type(type="caster") - public String getSmallText() { - ... -} - - When using composite user type, you will have to express - column definitions. The @Columns has been - introduced for that purpose. - - @Type(type="org.hibernate.test.annotations.entity.MonetaryAmountUserType") -@Columns(columns = { - @Column(name="r_amount"), - @Column(name="r_currency") -}) -public MonetaryAmount getAmount() { - return amount; -} - - -public class MonetaryAmount implements Serializable { - private BigDecimal amount; - private Currency currency; - ... -} -
- -
- Access type - - By default the access type of a class hierarchy is defined by - the position of the @Id or - @EmbeddedId annotations. If these annotations - are on a field, then only fields are considered for persistence and - the state is accessed via the field. If these annotations are on a - getter, then only the getters are considered for persistence and the - state is accessed via the getter/setter. That works well in practice - and is the recommended approach. - The placement of annotations within a class hierarchy has - to be consistent (either field or on property) to be able to - determine the default access type. It is recommended to stick to - one single annotation placement strategy throughout your whole - application. - - - However in some situations, you need to: - - - - force the access type of the entity hierarchy - - - - override the access type of a specific entity in the class - hierarchy - - - - override the access type of an embeddable type - - - - The best use case is an embeddable class used by several - entities that might not use the same access type. In this case it is - better to force the access type at the embeddable class - level. - - To force the access type on a given class, use the - @Access annotation as showed below: - - @Entity -public class Order { - @Id private Long id; - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - - @Embedded private Address address; - public Address getAddress() { return address; } - public void setAddress(Address address) { this.address = address; } -} - -@Entity -public class User { - private Long id; - @Id public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - - private Address address; - @Embedded public Address getAddress() { return address; } - public void setAddress(Address address) { this.address = address; } -} - -@Embeddable -@Access(AcessType.PROPERTY) -public class Address { - private String street1; - public String getStreet1() { return street1; } - public void setStreet1(String street1) { this.street1 = street1; } - - private hashCode; //not persistent -} - - You can also override the access type of a single property - while keeping the other properties standard. - - @Entity -public class Order { - @Id private Long id; - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - @Transient private String userId; - @Transient private String orderId; - - @Access(AccessType.PROPERTY) - public String getOrderNumber() { return userId + ":" + orderId; } - public void setOrderNumber(String userId, String orderId) { this.userId = userId; this.orderId = orderId; } -} - - In this example, the default access type is - FIELD except for the - orderNumber property. Note that the corresponding - field, if any must be marked as @Transient or - transient. - - - @org.hibernate.annotations.AccessType - - The annotation - @org.hibernate.annotations.AccessType - should be considered deprecated for FIELD and PROPERTY access. It - is still useful however if you need to use a custom access - type. - -
- -
- Optimistic lock - - It is sometimes useful to avoid increasing the version number - even if a given property is dirty (particularly collections). You - can do that by annotating the property (or collection) with - @OptimisticLock(excluded=true). - - More formally, specifies that updates to this property do not - require acquisition of the optimistic lock. -
- -
- Declaring column attributes - - The column(s) used for a property mapping can be defined using - the @Column annotation. Use it to override - default values (see the JPA specification for more information on - the defaults). You can use this annotation at the property level for - properties that are: - - - - not annotated at all - - - - annotated with @Basic - - - - annotated with @Version - - - - annotated with @Lob - - - - annotated with @Temporal - - - - -@Entity -public class Flight implements Serializable { -... -@Column(updatable = false, name = "flight_name", nullable = false, length=50) -public String getName() { ... } - - - The name property is mapped to the - flight_name column, which is not nullable, has a - length of 50 and is not updatable (making the property - immutable). - - This annotation can be applied to regular properties as well - as @Id or @Version - properties. - - - - - - - - - - - - - - - - - - - - - - - - - @Column( - name="columnName"; - boolean unique() default false; - boolean nullable() default true; - boolean insertable() default true; - boolean updatable() default true; - String columnDefinition() default ""; - String table() default ""; - int length() default 255; - int precision() default 0; // decimal precision - int scale() default 0; // decimal scale - - - - name (optional): the column name - (default to the property name) - - - - unique (optional): set a unique - constraint on this column or not (default false) - - - - nullable (optional): set the column - as nullable (default true). - - - - insertable (optional): whether or not - the column will be part of the insert statement (default - true) - - - - updatable (optional): whether or not - the column will be part of the update statement (default - true) - - - - columnDefinition (optional): override - the sql DDL fragment for this particular column (non - portable) - - - - table (optional): define the targeted - table (default primary table) - - - - length (optional): - column length (default 255) - - - - precision - (optional): column decimal precision (default 0) - - - - scale (optional): - column decimal scale if useful (default 0) - - - -
- -
- Formula - - Sometimes, you want the Database to do some computation for - you rather than in the JVM, you might also create some kind of - virtual column. You can use a SQL fragment (aka formula) instead of - mapping a property into a column. This kind of property is read only - (its value is calculated by your formula fragment). - - @Formula("obj_length * obj_height * obj_width") -public long getObjectVolume() - - The SQL fragment can be as complex as you want and even - include subselects. -
- -
- Non-annotated property defaults - - If a property is not annotated, the following rules - apply: - - If the property is of a single type, it is mapped as - @Basic - - - - Otherwise, if the type of the property is annotated as - @Embeddable, it is mapped as @Embedded - - - - Otherwise, if the type of the property is - Serializable, it is mapped as - @Basic in a column holding the object - in its serialized version - - - - Otherwise, if the type of the property is - java.sql.Clob or - java.sql.Blob, it is mapped as - @Lob with the appropriate - LobType - - -
-
- -
- Property mapping with hbm.xml - - The <property> element declares a - persistent JavaBean style property of the class. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <property - name="propertyName" - column="column_name" - type="typename" - update="true|false" - insert="true|false" - formula="arbitrary SQL expression" - access="field|property|ClassName" - lazy="true|false" - unique="true|false" - not-null="true|false" - optimistic-lock="true|false" - generated="never|insert|always" - node="element-name|@attribute-name|element/@attribute|." - index="index_name" - unique_key="unique_key_id" - length="L" - precision="P" - scale="S" -/> - - - - name: the name of the property, with an - initial lowercase letter. - - - - column (optional - defaults to the - property name): the name of the mapped database table column. - This can also be specified by nested - <column> element(s). - - - - type (optional): a name that indicates - the Hibernate type. - - - - update, insert (optional - defaults to - true): specifies that the mapped columns - should be included in SQL UPDATE and/or - INSERT statements. Setting both to - false allows a pure "derived" property whose - value is initialized from some other property that maps to the - same column(s), or by a trigger or other application. - - - - formula (optional): an SQL expression - that defines the value for a computed - property. Computed properties do not have a column mapping of - their own. - - - - access (optional - defaults to - property): the strategy Hibernate uses for - accessing the property value. - - - - lazy (optional - defaults to - false): specifies that this property should - be fetched lazily when the instance variable is first accessed. - It requires build-time bytecode instrumentation. - - - - unique (optional): enables the DDL - generation of a unique constraint for the columns. Also, allow - this to be the target of a - property-ref. - - - - not-null (optional): enables the DDL - generation of a nullability constraint for the columns. - - - - optimistic-lock (optional - defaults to - true): specifies that updates to this - property do or do not require acquisition of the optimistic - lock. In other words, it determines if a version increment - should occur when this property is dirty. - - - - generated (optional - defaults to - never): specifies that this property value is - actually generated by the database. See the discussion of generated properties for more - information. - - - - - typename could be: - - - - The name of a Hibernate basic type: integer, - string, character, date, timestamp, float, binary, serializable, - object, blob etc. - - - - The name of a Java class with a default basic type: - int, float, char, java.lang.String, java.util.Date, - java.lang.Integer, java.sql.Clob etc. - - - - The name of a serializable Java class. - - - - The class name of a custom type: - com.illflow.type.MyCustomType etc. - - - - If you do not specify a type, Hibernate will use reflection upon - the named property and guess the correct Hibernate type. Hibernate - will attempt to interpret the name of the return class of the property - getter using, in order, rules 2, 3, and 4. In certain cases you will - need the type attribute. For example, to - distinguish between Hibernate.DATE and - Hibernate.TIMESTAMP, or to specify a custom - type. - - The access attribute allows you to control - how Hibernate accesses the property at runtime. By default, Hibernate - will call the property get/set pair. If you specify - access="field", Hibernate will bypass the get/set - pair and access the field directly using reflection. You can specify - your own strategy for property access by naming a class that - implements the interface - org.hibernate.property.access.spi.PropertyAccessStrategy. - - A powerful feature is derived properties. These properties are - by definition read-only. The property value is computed at load time. - You declare the computation as an SQL expression. This then translates - to a SELECT clause subquery in the SQL query that - loads an instance: - - -<property name="totalPrice" - formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p - WHERE li.productId = p.productId - AND li.customerId = customerId - AND li.orderNumber = orderNumber )"/> - - You can reference the entity table by not declaring an alias on - a particular column. This would be customerId in - the given example. You can also use the nested - <formula> mapping element if you do not want - to use the attribute. -
-
- -
- Embedded objects (aka components) - - Embeddable objects (or components) are objects whose properties - are mapped to the same table as the owning entity's table. Components - can, in turn, declare their own properties, components or - collections - - It is possible to declare an embedded component inside an entity - and even override its column mapping. Component classes have to be - annotated at the class level with the @Embeddable - annotation. It is possible to override the column mapping of an embedded - object for a particular entity using the @Embedded - and @AttributeOverride annotation in the associated - property: - - @Entity -public class Person implements Serializable { - - // Persistent component using defaults - Address homeAddress; - - @Embedded - @AttributeOverrides( { - @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ), - @AttributeOverride(name="name", column = @Column(name="bornCountryName") ) - } ) - Country bornIn; - ... -} - - @Embeddable -public class Address implements Serializable { - String city; - Country nationality; //no overriding here -} - - @Embeddable -public class Country implements Serializable { - private String iso2; - @Column(name="countryName") private String name; - - public String getIso2() { return iso2; } - public void setIso2(String iso2) { this.iso2 = iso2; } - - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - ... -} - - An embeddable object inherits the access type of its owning entity - (note that you can override that using the @Access - annotation). - - The Person entity has two component properties, - homeAddress and bornIn. - homeAddress property has not been annotated, but - Hibernate will guess that it is a persistent component by looking for - the @Embeddable annotation in the Address class. We - also override the mapping of a column name (to - bornCountryName) with the - @Embedded and @AttributeOverride - annotations for each mapped attribute of - Country. As you can see, Country - is also a nested component of Address, - again using auto-detection by Hibernate and JPA defaults. Overriding - columns of embedded objects of embedded objects is through dotted - expressions. - - @Embedded - @AttributeOverrides( { - @AttributeOverride(name="city", column = @Column(name="fld_city") ), - @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ), - @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") ) - //nationality columns in homeAddress are overridden - } ) - Address homeAddress; - - Hibernate Annotations supports something that is not explicitly - supported by the JPA specification. You can annotate an embedded object - with the @MappedSuperclass annotation to make the - superclass properties persistent (see - @MappedSuperclass for more information). - - You can also use association annotations in an embeddable object - (ie @OneToOne, @ManyToOne, - @OneToMany or @ManyToMany). To - override the association columns you can use - @AssociationOverride. - - If you want to have the same embeddable object type twice in the - same entity, the column name defaulting will not work as several - embedded objects would share the same set of columns. In plain JPA, you - need to override at least one set of columns. Hibernate, however, allows - you to enhance the default naming mechanism through the - NamingStrategy interface. You can write a - strategy that prevent name clashing in such a situation. - DefaultComponentSafeNamingStrategy is an example - of this. - - If a property of the embedded object points back to the owning - entity, annotate it with the @Parent annotation. - Hibernate will make sure this property is properly loaded with the - entity reference. - - In XML, use the <component> - element. - - - - - - - - - - - - - - - - - - - - - <component - name="propertyName" - class="className" - insert="true|false" - update="true|false" - access="field|property|ClassName" - lazy="true|false" - optimistic-lock="true|false" - unique="true|false" - node="element-name|." -> - - <property ...../> - <many-to-one .... /> - ........ -</component> - - - - name: the name of the property. - - - - class (optional - defaults to the - property type determined by reflection): the name of the component - (child) class. - - - - insert: do the mapped columns appear in - SQL INSERTs? - - - - update: do the mapped columns appear in - SQL UPDATEs? - - - - access (optional - defaults to - property): the strategy Hibernate uses for - accessing the property value. - - - - lazy (optional - defaults to - false): specifies that this component should be - fetched lazily when the instance variable is first accessed. It - requires build-time bytecode instrumentation. - - - - optimistic-lock (optional - defaults to - true): specifies that updates to this component - either do or do not require acquisition of the optimistic lock. It - determines if a version increment should occur when this property - is dirty. - - - - unique (optional - defaults to - false): specifies that a unique constraint - exists upon all mapped columns of the component. - - - - - The child <property> tags map properties - of the child class to table columns. - - The <component> element allows a - <parent> subelement that maps a property of the - component class as a reference back to the containing entity. - - The <dynamic-component> element allows a - Map to be mapped as a component, where the property - names refer to keys of the map. See for more information. This feature is - not supported in annotations. -
- -
- Inheritance strategy - - Java is a language supporting polymorphism: a class can inherit - from another. Several strategies are possible to persist a class - hierarchy: - - - - Single table per class hierarchy strategy: a single table - hosts all the instances of a class hierarchy - - - - Joined subclass strategy: one table per class and subclass is - present and each table persist the properties specific to a given - subclass. The state of the entity is then stored in its - corresponding class table and all its superclasses - - - - Table per class strategy: one table per concrete class and - subclass is present and each table persist the properties of the - class and its superclasses. The state of the entity is then stored - entirely in the dedicated table for its class. - - - -
- Single table per class hierarchy strategy - - With this approach the properties of all the subclasses in a - given mapped class hierarchy are stored in a single table. - - Each subclass declares its own persistent properties and - subclasses. Version and id properties are assumed to be inherited from - the root class. Each subclass in a hierarchy must define a unique - discriminator value. If this is not specified, the fully qualified - Java class name is used. - - @Entity -@Inheritance(strategy=InheritanceType.SINGLE_TABLE) -@DiscriminatorColumn( - name="planetype", - discriminatorType=DiscriminatorType.STRING -) -@DiscriminatorValue("Plane") -public class Plane { ... } - -@Entity -@DiscriminatorValue("A320") -public class A320 extends Plane { ... } - - In hbm.xml, for the table-per-class-hierarchy mapping strategy, - the <subclass> declaration is used. For - example: - - - - - - - - - - - - - <subclass - name="ClassName" - discriminator-value="discriminator_value" - proxy="ProxyInterface" - lazy="true|false" - dynamic-update="true|false" - dynamic-insert="true|false" - entity-name="EntityName" - node="element-name" - extends="SuperclassName"> - - <property .... /> - ..... -</subclass> - - - - name: the fully qualified class name of - the subclass. - - - - discriminator-value (optional - - defaults to the class name): a value that distinguishes - individual subclasses. - - - - proxy (optional): specifies a class or - interface used for lazy initializing proxies. - - - - lazy (optional - defaults to - true): setting - lazy="false" disables the use of lazy - fetching. - - - - - For information about inheritance mappings see . - -
- Discriminator - - Discriminators are required for polymorphic persistence using - the table-per-class-hierarchy mapping strategy. It declares a - discriminator column of the table. The discriminator column contains - marker values that tell the persistence layer what subclass to - instantiate for a particular row. Hibernate Core supports the - follwoing restricted set of types as discriminator column: - string, character, - integer, byte, - short, boolean, - yes_no, true_false. - - Use the @DiscriminatorColumn to define - the discriminator column as well as the discriminator type. - The enum DiscriminatorType used in - javax.persitence.DiscriminatorColumn only - contains the values STRING, - CHAR and INTEGER which - means that not all Hibernate supported types are available via - the @DiscriminatorColumn - annotation. - You can also use - @DiscriminatorFormula to express in SQL a - virtual discriminator column. This is particularly useful when the - discriminator value can be extracted from one or more columns of the - table. Both @DiscriminatorColumn and - @DiscriminatorFormula are to be set on the - root entity (once per persisted hierarchy). - - @org.hibernate.annotations.DiscriminatorOptions - allows to optionally specify Hibernate specific discriminator - options which are not standardized in JPA. The available options are - force and insert. The - force attribute is useful if the table contains - rows with "extra" discriminator values that are not mapped to a - persistent class. This could for example occur when working with a - legacy database. If force is set to - true Hibernate will specify the allowed - discriminator values in the SELECT query, even - when retrieving all instances of the root class. The second option - - insert - tells Hibernate whether or not to - include the discriminator column in SQL INSERTs. - Usually the column should be part of the INSERT - statement, but if your discriminator column is also part of a mapped - composite identifier you have to set this option to - false. - There is also a - @org.hibernate.annotations.ForceDiscriminator - annotation which is deprecated since version 3.6. Use - @DiscriminatorOptions instead. - - - Finally, use @DiscriminatorValue on - each class of the hierarchy to specify the value stored in the - discriminator column for a given entity. If you do not set - @DiscriminatorValue on a class, the fully - qualified class name is used. - - @Entity -@Inheritance(strategy=InheritanceType.SINGLE_TABLE) -@DiscriminatorColumn( - name="planetype", - discriminatorType=DiscriminatorType.STRING -) -@DiscriminatorValue("Plane") -public class Plane { ... } - -@Entity -@DiscriminatorValue("A320") -public class A320 extends Plane { ... } - - In hbm.xml, the <discriminator> - element is used to define the discriminator column or - formula: - - - - - - - - - - - - - - - <discriminator - column="discriminator_column" - type="discriminator_type" - force="true|false" - insert="true|false" - formula="arbitrary sql expression" -/> - - - - column (optional - defaults to - class): the name of the discriminator - column. - - - - type (optional - defaults to - string): a name that indicates the - Hibernate type - - - - force (optional - defaults to - false): "forces" Hibernate to specify the - allowed discriminator values, even when retrieving all - instances of the root class. - - - - insert (optional - defaults to - true): set this to false - if your discriminator column is also part of a mapped - composite identifier. It tells Hibernate not to include the - column in SQL INSERTs. - - - - formula (optional): an arbitrary SQL - expression that is executed when a type has to be evaluated. - It allows content-based discrimination. - - - - - Actual values of the discriminator column are specified by the - discriminator-value attribute of the - <class> and - <subclass> elements. - - The formula attribute allows you to declare - an arbitrary SQL expression that will be used to evaluate the type - of a row. For example: - - <discriminator - formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end" - type="integer"/> -
-
- -
- Joined subclass strategy - - Each subclass can also be mapped to its own table. This is - called the table-per-subclass mapping strategy. An inherited state is - retrieved by joining with the table of the superclass. A discriminator - column is not required for this mapping strategy. Each subclass must, - however, declare a table column holding the object identifier. The - primary key of this table is also a foreign key to the superclass - table and described by the - @PrimaryKeyJoinColumns or the - <key> element. - - @Entity @Table(name="CATS") -@Inheritance(strategy=InheritanceType.JOINED) -public class Cat implements Serializable { - @Id @GeneratedValue(generator="cat-uuid") - @GenericGenerator(name="cat-uuid", strategy="uuid") - String getId() { return id; } - - ... -} - -@Entity @Table(name="DOMESTIC_CATS") -@PrimaryKeyJoinColumn(name="CAT") -public class DomesticCat extends Cat { - public String getName() { return name; } -} - - - The table name still defaults to the non qualified class name. - Also if @PrimaryKeyJoinColumn is not set, the - primary key / foreign key columns are assumed to have the same names - as the primary key columns of the primary table of the - superclass. - - - In hbm.xml, use the <joined-subclass> - element. For example: - - - - - - - - - - - - - <joined-subclass - name="ClassName" - table="tablename" - proxy="ProxyInterface" - lazy="true|false" - dynamic-update="true|false" - dynamic-insert="true|false" - schema="schema" - catalog="catalog" - extends="SuperclassName" - persister="ClassName" - subselect="SQL expression" - entity-name="EntityName" - node="element-name"> - - <key .... > - - <property .... /> - ..... -</joined-subclass> - - - - name: the fully qualified class name of - the subclass. - - - - table: the name of the subclass - table. - - - - proxy (optional): specifies a class or - interface to use for lazy initializing proxies. - - - - lazy (optional, defaults to - true): setting - lazy="false" disables the use of lazy - fetching. - - - - - Use the <key> element to declare the - primary key / foreign key column. The mapping at the start of the - chapter would then be re-written as: - - <?xml version="1.0"?> -<!DOCTYPE hibernate-mapping PUBLIC - "-//Hibernate/Hibernate Mapping DTD//EN" - "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - -<hibernate-mapping package="eg"> - - <class name="Cat" table="CATS"> - <id name="id" column="uid" type="long"> - <generator class="hilo"/> - </id> - <property name="birthdate" type="date"/> - <property name="color" not-null="true"/> - <property name="sex" not-null="true"/> - <property name="weight"/> - <many-to-one name="mate"/> - <set name="kittens"> - <key column="MOTHER"/> - <one-to-many class="Cat"/> - </set> - <joined-subclass name="DomesticCat" table="DOMESTIC_CATS"> - <key column="CAT"/> - <property name="name" type="string"/> - </joined-subclass> - </class> - - <class name="eg.Dog"> - <!-- mapping for Dog could go here --> - </class> - -</hibernate-mapping> - - For information about inheritance mappings see . -
- -
- Table per class strategy - - A third option is to map only the concrete classes of an - inheritance hierarchy to tables. This is called the - table-per-concrete-class strategy. Each table defines all persistent - states of the class, including the inherited state. In Hibernate, it - is not necessary to explicitly map such inheritance hierarchies. You - can map each class as a separate entity root. However, if you wish use - polymorphic associations (e.g. an association to the superclass of - your hierarchy), you need to use the union subclass mapping. - - @Entity -@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -public class Flight implements Serializable { ... } - - Or in hbm.xml: - - - - - - - - - - - - - <union-subclass - name="ClassName" - table="tablename" - proxy="ProxyInterface" - lazy="true|false" - dynamic-update="true|false" - dynamic-insert="true|false" - schema="schema" - catalog="catalog" - extends="SuperclassName" - abstract="true|false" - persister="ClassName" - subselect="SQL expression" - entity-name="EntityName" - node="element-name"> - - <property .... /> - ..... -</union-subclass> - - - - name: the fully qualified class name of - the subclass. - - - - table: the name of the subclass - table. - - - - proxy (optional): specifies a class or - interface to use for lazy initializing proxies. - - - - lazy (optional, defaults to - true): setting - lazy="false" disables the use of lazy - fetching. - - - - - No discriminator column or key column is required for this - mapping strategy. - - For information about inheritance mappings see . -
- -
- Inherit properties from superclasses - - This is sometimes useful to share common properties through a - technical or a business superclass without including it as a regular - mapped entity (ie no specific table for this entity). For that purpose - you can map them as @MappedSuperclass. - - @MappedSuperclass -public class BaseEntity { - @Basic - @Temporal(TemporalType.TIMESTAMP) - public Date getLastUpdate() { ... } - public String getLastUpdater() { ... } - ... -} - -@Entity class Order extends BaseEntity { - @Id public Integer getId() { ... } - ... -} - - In database, this hierarchy will be represented as an - Order table having the id, - lastUpdate and lastUpdater - columns. The embedded superclass property mappings are copied into - their entity subclasses. Remember that the embeddable superclass is - not the root of the hierarchy though. - - - Properties from superclasses not mapped as - @MappedSuperclass are ignored. - - - - The default access type (field or methods) is used, unless you - use the @Access annotation. - - - - The same notion can be applied to - @Embeddable objects to persist properties from - their superclasses. You also need to use - @MappedSuperclass to do that (this should not be - considered as a standard EJB3 feature though) - - - - It is allowed to mark a class as - @MappedSuperclass in the middle of the mapped - inheritance hierarchy. - - - - Any class in the hierarchy non annotated with - @MappedSuperclass nor @Entity - will be ignored. - - - You can override columns defined in entity superclasses at the - root entity level using the @AttributeOverride - annotation. - - @MappedSuperclass -public class FlyingObject implements Serializable { - - public int getAltitude() { - return altitude; - } - - @Transient - public int getMetricAltitude() { - return metricAltitude; - } - - @ManyToOne - public PropulsionType getPropulsion() { - return metricAltitude; - } - ... -} - -@Entity -@AttributeOverride( name="altitude", column = @Column(name="fld_altitude") ) -@AssociationOverride( - name="propulsion", - joinColumns = @JoinColumn(name="fld_propulsion_fk") -) -public class Plane extends FlyingObject { - ... -} - - The altitude property will be persisted in an - fld_altitude column of table - Plane and the propulsion association will be - materialized in a fld_propulsion_fk foreign key - column. - - You can define @AttributeOverride(s) and - @AssociationOverride(s) on - @Entity classes, - @MappedSuperclass classes and properties pointing - to an @Embeddable object. - - In hbm.xml, simply map the properties of the superclass in the - <class> element of the entity that needs to - inherit them. -
- -
- Mapping one entity to several tables - - While not recommended for a fresh schema, some legacy databases - force you to map a single entity on several tables. - - Using the @SecondaryTable or - @SecondaryTables class level annotations. To - express that a column is in a particular table, use the - table parameter of @Column or - @JoinColumn. - - @Entity -@Table(name="MainCat") -@SecondaryTables({ - @SecondaryTable(name="Cat1", pkJoinColumns={ - @PrimaryKeyJoinColumn(name="cat_id", referencedColumnName="id") - ), - @SecondaryTable(name="Cat2", uniqueConstraints={@UniqueConstraint(columnNames={"storyPart2"})}) -}) -public class Cat implements Serializable { - - private Integer id; - private String name; - private String storyPart1; - private String storyPart2; - - @Id @GeneratedValue - public Integer getId() { - return id; - } - - public String getName() { - return name; - } - - @Column(table="Cat1") - public String getStoryPart1() { - return storyPart1; - } - - @Column(table="Cat2") - public String getStoryPart2() { - return storyPart2; - } -} - - In this example, name will be in - MainCat. storyPart1 will be in - Cat1 and storyPart2 will be in - Cat2. Cat1 will be joined to - MainCat using the cat_id as a - foreign key, and Cat2 using id - (ie the same column name, the MainCat id column - has). Plus a unique constraint on storyPart2 has - been set. - - There is also additional tuning accessible via the - @org.hibernate.annotations.Table - annotation: - - - - fetch: If set to JOIN, the default, - Hibernate will use an inner join to retrieve a secondary table - defined by a class or its superclasses and an outer join for a - secondary table defined by a subclass. If set to - SELECT then Hibernate will use a sequential - select for a secondary table defined on a subclass, which will be - issued only if a row turns out to represent an instance of the - subclass. Inner joins will still be used to retrieve a secondary - defined by the class and its superclasses. - - - - inverse: If true, Hibernate will not try - to insert or update the properties defined by this join. Default - to false. - - - - optional: If enabled (the default), - Hibernate will insert a row only if the properties defined by this - join are non-null and will always use an outer join to retrieve - the properties. - - - - foreignKey: defines the Foreign Key name - of a secondary table pointing back to the primary table. - - - - Make sure to use the secondary table name in the - appliesto property - - @Entity -@Table(name="MainCat") -@SecondaryTable(name="Cat1") -@org.hibernate.annotations.Table( - appliesTo="Cat1", - fetch=FetchMode.SELECT, - optional=true) -public class Cat implements Serializable { - - private Integer id; - private String name; - private String storyPart1; - private String storyPart2; - - @Id @GeneratedValue - public Integer getId() { - return id; - } - - public String getName() { - return name; - } - - @Column(table="Cat1") - public String getStoryPart1() { - return storyPart1; - } - - @Column(table="Cat2") - public String getStoryPart2() { - return storyPart2; - } -} - - In hbm.xml, use the <join> - element. - - - - - - - - - - - - - - - - - <join - table="tablename" - schema="owner" - catalog="catalog" - fetch="join|select" - inverse="true|false" - optional="true|false"> - - <key ... /> - - <property ... /> - ... -</join> - - - - table: the name of the joined - table. - - - - schema (optional): overrides the schema - name specified by the root - <hibernate-mapping> element. - - - - catalog (optional): overrides the - catalog name specified by the root - <hibernate-mapping> element. - - - - fetch (optional - defaults to - join): if set to join, the - default, Hibernate will use an inner join to retrieve a - <join> defined by a class or its - superclasses. It will use an outer join for a - <join> defined by a subclass. If set to - select then Hibernate will use a sequential - select for a <join> defined on a - subclass. This will be issued only if a row represents an - instance of the subclass. Inner joins will still be used to - retrieve a <join> defined by the class - and its superclasses. - - - - inverse (optional - defaults to - false): if enabled, Hibernate will not insert - or update the properties defined by this join. - - - - optional (optional - defaults to - false): if enabled, Hibernate will insert a - row only if the properties defined by this join are non-null. It - will always use an outer join to retrieve the properties. - - - - - For example, address information for a person can be mapped to a - separate table while preserving value type semantics for all - properties: - - <class name="Person" - table="PERSON"> - - <id name="id" column="PERSON_ID">...</id> - - <join table="ADDRESS"> - <key column="ADDRESS_ID"/> - <property name="address"/> - <property name="zip"/> - <property name="country"/> - </join> - ... - - This feature is often only useful for legacy data models. We - recommend fewer tables than classes and a fine-grained domain model. - However, it is useful for switching between inheritance mapping - strategies in a single hierarchy, as explained later. -
-
- -
- Mapping one to one and many to one associations - - To link one entity to another, you need to map the association - property as a to one association. In the relational model, you can - either use a foreign key or an association table, or (a bit less common) - share the same primary key value between the two entities. - - To mark an association, use either - @ManyToOne or - @OnetoOne. - - @ManyToOne and @OneToOne - have a parameter named targetEntity which describes - the target entity name. You usually don't need this parameter since the - default value (the type of the property that stores the association) is - good in almost all cases. However this is useful when you want to use - interfaces as the return type instead of the regular entity. - - Setting a value of the cascade attribute to any - meaningful value other than nothing will propagate certain operations to - the associated object. The meaningful values are divided into three - categories. - - - - basic operations, which include: persist, merge, - delete, save-update, evict, replicate, lock and - refresh; - - - - special values: delete-orphan or - all ; - - - - comma-separated combinations of operation names: - cascade="persist,merge,evict" or - cascade="all,delete-orphan". See for a full explanation. Note - that single valued many-to-one associations do not support orphan - delete. - - - - By default, single point associations are eagerly fetched in JPA - 2. You can mark it as lazily fetched by using - @ManyToOne(fetch=FetchType.LAZY) in which case - Hibernate will proxy the association and load it when the state of the - associated entity is reached. You can force Hibernate not to use a proxy - by using @LazyToOne(NO_PROXY). In this case, the - property is fetched lazily when the instance variable is first accessed. - This requires build-time bytecode instrumentation. lazy="false" - specifies that the association will always be eagerly fetched. - - With the default JPA options, single-ended associations are loaded - with a subsequent select if set to LAZY, or a SQL - JOIN is used for EAGER associations. You can however - adjust the fetching strategy, ie how data is fetched by using - @Fetch. FetchMode can be - SELECT (a select is triggered when the association - needs to be loaded) or JOIN (use a SQL JOIN to load - the association while loading the owner entity). JOIN - overrides any lazy attribute (an association loaded through a - JOIN strategy cannot be lazy). - -
- Using a foreign key or an association table - - An ordinary association to another persistent class is declared - using a - - - - @ManyToOne if several entities can - point to the the target entity - - - - @OneToOne if only a single entity can - point to the the target entity - - - - and a foreign key in one table is referencing the primary key - column(s) of the target table. - - @Entity -public class Flight implements Serializable { - @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} ) - @JoinColumn(name="COMP_ID") - public Company getCompany() { - return company; - } - ... -} - - The @JoinColumn attribute is optional, the - default value(s) is the concatenation of the name of the relationship - in the owner side, _ (underscore), and the name of - the primary key column in the owned side. In this example - company_id because the property name is - company and the column id of Company is - id. - - @Entity -public class Flight implements Serializable { - @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class ) - @JoinColumn(name="COMP_ID") - public Company getCompany() { - return company; - } - ... -} - -public interface Company { - ... -} - - You can also map a to one association through an association - table. This association table described by the - @JoinTable annotation will contains a foreign key - referencing back the entity table (through - @JoinTable.joinColumns) and a a foreign key - referencing the target entity table (through - @JoinTable.inverseJoinColumns). - - @Entity -public class Flight implements Serializable { - @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} ) - @JoinTable(name="Flight_Company", - joinColumns = @JoinColumn(name="FLIGHT_ID"), - inverseJoinColumns = @JoinColumn(name="COMP_ID") - ) - public Company getCompany() { - return company; - } - ... -} - - - You can use a SQL fragment to simulate a physical join column - using the @JoinColumnOrFormula / - @JoinColumnOrformulas annotations (just like - you can use a SQL fragment to simulate a property column via the - @Formula annotation). - - @Entity -public class Ticket implements Serializable { - @ManyToOne - @JoinColumnOrFormula(formula="(firstname + ' ' + lastname)") - public Person getOwner() { - return person; - } - ... -} - - - You can mark an association as mandatory by using the - optional=false attribute. We recommend to use Bean - Validation's @NotNull annotation as a better - alternative however. As a consequence, the foreign key column(s) will - be marked as not nullable (if possible). - - When Hibernate cannot resolve the association because the - expected associated element is not in database (wrong id on the - association column), an exception is raised. This might be - inconvenient for legacy and badly maintained schemas. You can ask - Hibernate to ignore such elements instead of raising an exception - using the @NotFound annotation. - - - @NotFound annotation - - @Entity -public class Child { - ... - @ManyToOne - @NotFound(action=NotFoundAction.IGNORE) - public Parent getParent() { ... } - ... -} - - - Sometimes you want to delegate to your database the deletion of - cascade when a given entity is deleted. In this case Hibernate - generates a cascade delete constraint at the database level. - - - @OnDelete annotation - - @Entity -public class Child { - ... - @ManyToOne - @OnDelete(action=OnDeleteAction.CASCADE) - public Parent getParent() { ... } - ... -} - - - Foreign key constraints, while generated by Hibernate, have a - fairly unreadable name. You can override the constraint name using - @ForeignKey. - - - @ForeignKey annotation - - @Entity -public class Child { - ... - @ManyToOne - @ForeignKey(name="FK_PARENT") - public Parent getParent() { ... } - ... -} - -alter table Child add constraint FK_PARENT foreign key (parent_id) references Parent - - - Sometimes, you want to link one entity to another not by the - target entity primary key but by a different unique key. You can - achieve that by referencing the unique key column(s) in - @JoinColumn.referenceColumnName. - - @Entity -class Person { - @Id Integer personNumber; - String firstName; - @Column(name="I") - String initial; - String lastName; -} - -@Entity -class Home { - @ManyToOne - @JoinColumns({ - @JoinColumn(name="first_name", referencedColumnName="firstName"), - @JoinColumn(name="init", referencedColumnName="I"), - @JoinColumn(name="last_name", referencedColumnName="lastName"), - }) - Person owner -} - - This is not encouraged however and should be reserved to legacy - mappings. - - In hbm.xml, mapping an association is similar. The main - difference is that a @OneToOne is mapped as - <many-to-one unique="true"/>, let's dive into - the subject. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <many-to-one - name="propertyName" - column="column_name" - class="ClassName" - cascade="cascade_style" - fetch="join|select" - update="true|false" - insert="true|false" - property-ref="propertyNameFromAssociatedClass" - access="field|property|ClassName" - unique="true|false" - not-null="true|false" - optimistic-lock="true|false" - lazy="proxy|no-proxy|false" - not-found="ignore|exception" - entity-name="EntityName" - formula="arbitrary SQL expression" - node="element-name|@attribute-name|element/@attribute|." - embed-xml="true|false" - index="index_name" - unique_key="unique_key_id" - foreign-key="foreign_key_name" -/> - - - - name: the name of the property. - - - - column (optional): the name of the - foreign key column. This can also be specified by nested - <column> element(s). - - - - class (optional - defaults to the - property type determined by reflection): the name of the - associated class. - - - - cascade (optional): specifies which - operations should be cascaded from the parent object to the - associated object. - - - - fetch (optional - defaults to - select): chooses between outer-join fetching - or sequential select fetching. - - - - update, insert (optional - defaults to - true): specifies that the mapped columns - should be included in SQL UPDATE and/or - INSERT statements. Setting both to - false allows a pure "derived" association - whose value is initialized from another property that maps to - the same column(s), or by a trigger or other application. - - - - property-ref (optional): the name of a - property of the associated class that is joined to this foreign - key. If not specified, the primary key of the associated class - is used. - - - - access (optional - defaults to - property): the strategy Hibernate uses for - accessing the property value. - - - - unique (optional): enables the DDL - generation of a unique constraint for the foreign-key column. By - allowing this to be the target of a - property-ref, you can make the association - multiplicity one-to-one. - - - - not-null (optional): enables the DDL - generation of a nullability constraint for the foreign key - columns. - - - - optimistic-lock (optional - defaults to - true): specifies that updates to this - property do or do not require acquisition of the optimistic - lock. In other words, it determines if a version increment - should occur when this property is dirty. - - - - lazy (optional - defaults to - proxy): by default, single point associations - are proxied. lazy="no-proxy" specifies that - the property should be fetched lazily when the instance variable - is first accessed. This requires build-time bytecode - instrumentation. lazy="false" specifies that - the association will always be eagerly fetched. - - - - not-found (optional - defaults to - exception): specifies how foreign keys that - reference missing rows will be handled. - ignore will treat a missing row as a null - association. - - - - entity-name (optional): the entity name - of the associated class. - - - - formula (optional): an SQL expression - that defines the value for a computed - foreign key. - - - - - Setting a value of the cascade attribute to - any meaningful value other than none will propagate - certain operations to the associated object. The meaningful values are - divided into three categories. First, basic operations, which include: - persist, merge, delete, save-update, evict, replicate, lock - and refresh; second, special values: - delete-orphan; and third,all - comma-separated combinations of operation names: - cascade="persist,merge,evict" or - cascade="all,delete-orphan". See for a full explanation. Note that - single valued, many-to-one and one-to-one, associations do not support - orphan delete. - - Here is an example of a typical many-to-one - declaration: - - <many-to-one name="product" class="Product" column="PRODUCT_ID"/> - - The property-ref attribute should only be - used for mapping legacy data where a foreign key refers to a unique - key of the associated table other than the primary key. This is a - complicated and confusing relational model. For example, if the - Product class had a unique serial number that is - not the primary key. The unique attribute controls - Hibernate's DDL generation with the SchemaExport tool. - - <property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/> - - Then the mapping for OrderItem might - use: - - <many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/> - - This is not encouraged, however. - - If the referenced unique key comprises multiple properties of - the associated entity, you should map the referenced properties inside - a named <properties> element. - - If the referenced unique key is the property of a component, you - can specify a property path: - - <many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/> -
- -
- Sharing the primary key with the associated entity - - The second approach is to ensure an entity and its associated - entity share the same primary key. In this case the primary key column - is also a foreign key and there is no extra column. These associations - are always one to one. - - - One to One association - - @Entity -public class Body { - @Id - public Long getId() { return id; } - - @OneToOne(cascade = CascadeType.ALL) - @MapsId - public Heart getHeart() { - return heart; - } - ... -} - -@Entity -public class Heart { - @Id - public Long getId() { ...} -} - - - - Many people got confused by these primary key based one to one - associations. They can only be lazily loaded if Hibernate knows that - the other side of the association is always present. To indicate to - Hibernate that it is the case, use - @OneToOne(optional=false). - - - In hbm.xml, use the following mapping. - - - - - - - - - - - - - - - - - - - - - - - - - <one-to-one - name="propertyName" - class="ClassName" - cascade="cascade_style" - constrained="true|false" - fetch="join|select" - property-ref="propertyNameFromAssociatedClass" - access="field|property|ClassName" - formula="any SQL expression" - lazy="proxy|no-proxy|false" - entity-name="EntityName" - node="element-name|@attribute-name|element/@attribute|." - embed-xml="true|false" - foreign-key="foreign_key_name" -/> - - - - name: the name of the property. - - - - class (optional - defaults to the - property type determined by reflection): the name of the - associated class. - - - - cascade (optional): specifies which - operations should be cascaded from the parent object to the - associated object. - - - - constrained (optional): specifies that - a foreign key constraint on the primary key of the mapped table - and references the table of the associated class. This option - affects the order in which save() and - delete() are cascaded, and determines whether - the association can be proxied. It is also used by the schema - export tool. - - - - fetch (optional - defaults to - select): chooses between outer-join fetching - or sequential select fetching. - - - - property-ref (optional): the name of a - property of the associated class that is joined to the primary - key of this class. If not specified, the primary key of the - associated class is used. - - - - access (optional - defaults to - property): the strategy Hibernate uses for - accessing the property value. - - - - formula (optional): almost all - one-to-one associations map to the primary key of the owning - entity. If this is not the case, you can specify another column, - columns or expression to join on using an SQL formula. See - org.hibernate.test.onetooneformula for an - example. - - - - lazy (optional - defaults to - proxy): by default, single point associations - are proxied. lazy="no-proxy" specifies that - the property should be fetched lazily when the instance variable - is first accessed. It requires build-time bytecode - instrumentation. lazy="false" specifies that - the association will always be eagerly fetched. Note - that if constrained="false", proxying is - impossible and Hibernate will eagerly fetch the - association. - - - - entity-name (optional): the entity name - of the associated class. - - - - - Primary key associations do not need an extra table column. If - two rows are related by the association, then the two table rows share - the same primary key value. To relate two objects by a primary key - association, ensure that they are assigned the same identifier - value. - - For a primary key association, add the following mappings to - Employee and Person - respectively: - - <one-to-one name="person" class="Person"/> - - <one-to-one name="employee" class="Employee" constrained="true"/> - - Ensure that the primary keys of the related rows in the PERSON - and EMPLOYEE tables are equal. You use a special Hibernate identifier - generation strategy called foreign: - - <class name="person" table="PERSON"> - <id name="id" column="PERSON_ID"> - <generator class="foreign"> - <param name="property">employee</param> - </generator> - </id> - ... - <one-to-one name="employee" - class="Employee" - constrained="true"/> -</class> - - A newly saved instance of Person is assigned - the same primary key value as the Employee instance - referred with the employee property of that - Person. -
-
- -
- Natural-id - - Although we recommend the use of surrogate keys as primary keys, - you should try to identify natural keys for all entities. A natural key - is a property or combination of properties that is unique and non-null. - It is also immutable. Map the properties of the natural key as - @NaturalId or map them inside the - <natural-id> element. Hibernate will generate - the necessary unique key and nullability constraints and, as a result, - your mapping will be more self-documenting. - - @Entity -public class Citizen { - @Id - @GeneratedValue - private Integer id; - private String firstname; - private String lastname; - - @NaturalId - @ManyToOne - private State state; - - @NaturalId - private String ssn; - ... -} - - - -//and later on query -List results = s.createCriteria( Citizen.class ) - .add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", ste ) ) - .list(); - - Or in XML, - - <natural-id mutable="true|false"/> - <property ... /> - <many-to-one ... /> - ...... -</natural-id> - - It is recommended that you implement equals() - and hashCode() to compare the natural key properties - of the entity. - - This mapping is not intended for use with entities that have - natural primary keys. - - - - mutable (optional - defaults to - false): by default, natural identifier properties - are assumed to be immutable (constant). - - -
- -
- Any - - There is one more type of property mapping. The - @Any mapping defines a polymorphic association to - classes from multiple tables. This type of mapping requires more than - one column. The first column contains the type of the associated entity. - The remaining columns contain the identifier. It is impossible to - specify a foreign key constraint for this kind of association. This is - not the usual way of mapping polymorphic associations and you should use - this only in special cases. For example, for audit logs, user session - data, etc. - - The @Any annotation describes the column - holding the metadata information. To link the value of the metadata - information and an actual entity type, The - @AnyDef and @AnyDefs - annotations are used. The metaType attribute allows - the application to specify a custom type that maps database column - values to persistent classes that have identifier properties of the type - specified by idType. You must specify the mapping - from values of the metaType to class names. - - @Any( metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER ) -@AnyMetaDef( - idType = "integer", - metaType = "string", - metaValues = { - @MetaValue( value = "S", targetEntity = StringProperty.class ), - @MetaValue( value = "I", targetEntity = IntegerProperty.class ) - } ) -@JoinColumn( name = "property_id" ) -public Property getMainProperty() { - return mainProperty; -} - - Note that @AnyDef can be mutualized and - reused. It is recommended to place it as a package metadata in this - case. - - //on a package -@AnyMetaDef( name="property" - idType = "integer", - metaType = "string", - metaValues = { - @MetaValue( value = "S", targetEntity = StringProperty.class ), - @MetaValue( value = "I", targetEntity = IntegerProperty.class ) - } ) -package org.hibernate.test.annotations.any; - - -//in a class - @Any( metaDef="property", metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER ) - @JoinColumn( name = "property_id" ) - public Property getMainProperty() { - return mainProperty; - } - - The hbm.xml equivalent is: - - <any name="being" id-type="long" meta-type="string"> - <meta-value value="TBL_ANIMAL" class="Animal"/> - <meta-value value="TBL_HUMAN" class="Human"/> - <meta-value value="TBL_ALIEN" class="Alien"/> - <column name="table_name"/> - <column name="id"/> -</any> - - - You cannot mutualize the metadata in hbm.xml as you can in - annotations. - - - - - - - - - - - - - - - - - - <any - name="propertyName" - id-type="idtypename" - meta-type="metatypename" - cascade="cascade_style" - access="field|property|ClassName" - optimistic-lock="true|false" -> - <meta-value ... /> - <meta-value ... /> - ..... - <column .... /> - <column .... /> - ..... -</any> - - - - name: the property name. - - - - id-type: the identifier type. - - - - meta-type (optional - defaults to - string): any type that is allowed for a - discriminator mapping. - - - - cascade (optional- defaults to - none): the cascade style. - - - - access (optional - defaults to - property): the strategy Hibernate uses for - accessing the property value. - - - - optimistic-lock (optional - defaults to - true): specifies that updates to this property - either do or do not require acquisition of the optimistic lock. It - defines whether a version increment should occur if this property - is dirty. - - - -
- -
- Properties - - The <properties> element allows the - definition of a named, logical grouping of the properties of a class. - The most important use of the construct is that it allows a combination - of properties to be the target of a property-ref. It - is also a convenient way to define a multi-column unique constraint. For - example: - - - - - - - - - - - - - - - <properties - name="logicalName" - insert="true|false" - update="true|false" - optimistic-lock="true|false" - unique="true|false" -> - - <property ...../> - <many-to-one .... /> - ........ -</properties> - - - - name: the logical name of the grouping. - It is not an actual property name. - - - - insert: do the mapped columns appear in - SQL INSERTs? - - - - update: do the mapped columns appear in - SQL UPDATEs? - - - - optimistic-lock (optional - defaults to - true): specifies that updates to these - properties either do or do not require acquisition of the - optimistic lock. It determines if a version increment should occur - when these properties are dirty. - - - - unique (optional - defaults to - false): specifies that a unique constraint - exists upon all mapped columns of the component. - - - - - For example, if we have the following - <properties> mapping: - - <class name="Person"> - <id name="personNumber"/> - - ... - <properties name="name" - unique="true" update="false"> - <property name="firstName"/> - <property name="initial"/> - <property name="lastName"/> - </properties> -</class> - - You might have some legacy data association that refers to this - unique key of the Person table, instead of to the - primary key: - - <many-to-one name="owner" - class="Person" property-ref="name"> - <column name="firstName"/> - <column name="initial"/> - <column name="lastName"/> -</many-to-one> - - - When using annotations as a mapping strategy, such construct is - not necessary as the binding between a column and its related column - on the associated table is done directly - - @Entity -class Person { - @Id Integer personNumber; - String firstName; - @Column(name="I") - String initial; - String lastName; -} - -@Entity -class Home { - @ManyToOne - @JoinColumns({ - @JoinColumn(name="first_name", referencedColumnName="firstName"), - @JoinColumn(name="init", referencedColumnName="I"), - @JoinColumn(name="last_name", referencedColumnName="lastName"), - }) - Person owner -} - - - The use of this outside the context of mapping legacy data is not - recommended. -
- -
- Some hbm.xml specificities - - The hbm.xml structure has some specificities naturally not present - when using annotations, let's describe them briefly. - -
- Doctype - - All XML mappings should declare the doctype shown. The actual - DTD can be found at the URL above, in the directory - hibernate-x.x.x/src/org/hibernate , or in - hibernate3.jar. Hibernate will always look for the - DTD in its classpath first. If you experience lookups of the DTD using - an Internet connection, check the DTD declaration against the contents - of your classpath. - -
- EntityResolver - - Hibernate will first attempt to resolve DTDs in its classpath. - It does this is by registering a custom - org.xml.sax.EntityResolver implementation with - the SAXReader it uses to read in the xml files. This custom - EntityResolver recognizes two different systemId - namespaces: - - - - a hibernate namespace is recognized - whenever the resolver encounters a systemId starting with - http://www.hibernate.org/dtd/. The resolver - attempts to resolve these entities via the classloader which - loaded the Hibernate classes. - - - - a user namespace is recognized whenever - the resolver encounters a systemId using a - classpath:// URL protocol. The resolver will - attempt to resolve these entities via (1) the current thread - context classloader and (2) the classloader which loaded the - Hibernate classes. - - - - The following is an example of utilizing user - namespacing: - - - - - - Where types.xml is a resource in the - your.domain package and contains a custom typedef. -
-
- -
- Hibernate-mapping - - This element has several optional attributes. The - schema and catalog attributes - specify that tables referred to in this mapping belong to the named - schema and/or catalog. If they are specified, tablenames will be - qualified by the given schema and catalog names. If they are missing, - tablenames will be unqualified. The default-cascade - attribute specifies what cascade style should be assumed for - properties and collections that do not specify a - cascade attribute. By default, the - auto-import attribute allows you to use unqualified - class names in the query language. - - - - - - - - - - - - - - - - - - - <hibernate-mapping - schema="schemaName" - catalog="catalogName" - default-cascade="cascade_style" - default-access="field|property|ClassName" - default-lazy="true|false" - auto-import="true|false" - package="package.name" - /> - - - - schema (optional): the name of a - database schema. - - - - catalog (optional): the name of a - database catalog. - - - - default-cascade (optional - defaults to - none): a default cascade style. - - - - default-access (optional - defaults to - property): the strategy Hibernate should use - for accessing all properties. It can be a custom implementation - of PropertyAccessor. - - - - default-lazy (optional - defaults to - true): the default value for unspecified - lazy attributes of class and collection - mappings. - - - - auto-import (optional - defaults to - true): specifies whether we can use - unqualified class names of classes in this mapping in the query - language. - - - - package (optional): specifies a package - prefix to use for unqualified class names in the mapping - document. - - - - - If you have two persistent classes with the same unqualified - name, you should set auto-import="false". An - exception will result if you attempt to assign two classes to the same - "imported" name. - - The hibernate-mapping element allows you to - nest several persistent <class> mappings, as - shown above. It is, however, good practice (and expected by some - tools) to map only a single persistent class, or a single class - hierarchy, in one mapping file and name it after the persistent - superclass. For example, Cat.hbm.xml, - Dog.hbm.xml, or if using inheritance, - Animal.hbm.xml. -
- -
- Key - - The <key> element is featured a few - times within this guide. It appears anywhere the parent mapping - element defines a join to a new table that references the primary key - of the original table. It also defines the foreign key in the joined - table: - - - - - - - - - - - - - - - - - <key - column="columnname" - on-delete="noaction|cascade" - property-ref="propertyName" - not-null="true|false" - update="true|false" - unique="true|false" -/> - - - - column (optional): the name of the - foreign key column. This can also be specified by nested - <column> element(s). - - - - on-delete (optional - defaults to - noaction): specifies whether the foreign key - constraint has database-level cascade delete enabled. - - - - property-ref (optional): specifies that - the foreign key refers to columns that are not the primary key - of the original table. It is provided for legacy data. - - - - not-null (optional): specifies that the - foreign key columns are not nullable. This is implied whenever - the foreign key is also part of the primary key. - - - - update (optional): specifies that the - foreign key should never be updated. This is implied whenever - the foreign key is also part of the primary key. - - - - unique (optional): specifies that the - foreign key should have a unique constraint. This is implied - whenever the foreign key is also the primary key. - - - - - For systems where delete performance is important, we recommend - that all keys should be defined - on-delete="cascade". Hibernate uses a - database-level ON CASCADE DELETE constraint, - instead of many individual DELETE statements. Be - aware that this feature bypasses Hibernate's usual optimistic locking - strategy for versioned data. - - The not-null and update - attributes are useful when mapping a unidirectional one-to-many - association. If you map a unidirectional one-to-many association to a - non-nullable foreign key, you must declare the - key column using <key - not-null="true">. -
- -
- Import - - If your application has two persistent classes with the same - name, and you do not want to specify the fully qualified package name - in Hibernate queries, classes can be "imported" explicitly, rather - than relying upon auto-import="true". You can also - import classes and interfaces that are not explicitly mapped: - - <import class="java.lang.Object" rename="Universe"/> - - - - - - - - - <import - class="ClassName" - rename="ShortName" -/> - - - - class: the fully qualified class name - of any Java class. - - - - rename (optional - defaults to the - unqualified class name): a name that can be used in the query - language. - - - - - - This feature is unique to hbm.xml and is not supported in - annotations. - -
- -
- Column and formula elements - - Mapping elements which accept a column - attribute will alternatively accept a - <column> subelement. Likewise, - <formula> is an alternative to the - formula attribute. For example: - - <column - name="column_name" - length="N" - precision="N" - scale="N" - not-null="true|false" - unique="true|false" - unique-key="multicolumn_unique_key_name" - index="index_name" - sql-type="sql_type_name" - check="SQL expression" - default="SQL expression" - read="SQL expression" - write="SQL expression"/> - - <formula>SQL expression</formula> - - Most of the attributes on column provide a - means of tailoring the DDL during automatic schema generation. The - read and write attributes allow - you to specify custom SQL that Hibernate will use to access the - column's value. For more on this, see the discussion of column read and write - expressions. - - The column and formula - elements can even be combined within the same property or association - mapping to express, for example, exotic join conditions. - - <many-to-one name="homeAddress" class="Address" - insert="false" update="false"> - <column name="person_id" not-null="true" length="10"/> - <formula>'MAILING'</formula> -</many-to-one> -
-
-
- -
- Hibernate types - -
- Entities and values - - In relation to the persistence service, Java language-level - objects are classified into two groups: - - An entity exists independently of any other - objects holding references to the entity. Contrast this with the usual - Java model, where an unreferenced object is garbage collected. Entities - must be explicitly saved and deleted. Saves and deletions, however, can - be cascaded from a parent entity to its children. - This is different from the ODMG model of object persistence by - reachability and corresponds more closely to how application objects are - usually used in large systems. Entities support circular and shared - references. They can also be versioned. - - An entity's persistent state consists of references to other - entities and instances of value types. Values are - primitives: collections (not what is inside a collection), components - and certain immutable objects. Unlike entities, values in particular - collections and components, are persisted and - deleted by reachability. Since value objects and primitives are - persisted and deleted along with their containing entity, they cannot be - independently versioned. Values have no independent identity, so they - cannot be shared by two entities or collections. - - Until now, we have been using the term "persistent class" to refer - to entities. We will continue to do that. Not all user-defined classes - with a persistent state, however, are entities. A - component is a user-defined class with value - semantics. A Java property of type java.lang.String - also has value semantics. Given this definition, all types (classes) - provided by the JDK have value type semantics in Java, while - user-defined types can be mapped with entity or value type semantics. - This decision is up to the application developer. An entity class in a - domain model will normally have shared references to a single instance - of that class, while composition or aggregation usually translates to a - value type. - - We will revisit both concepts throughout this reference - guide. - - The challenge is to map the Java type system, and the developers' - definition of entities and value types, to the SQL/database type system. - The bridge between both systems is provided by Hibernate. For entities, - <class>, <subclass> - and so on are used. For value types we use - <property>, - <component>etc., that usually have a - type attribute. The value of this attribute is the - name of a Hibernate mapping type. Hibernate - provides a range of mappings for standard JDK value types out of the - box. You can write your own mapping types and implement your own custom - conversion strategies. - - With the exception of collections, all built-in Hibernate types - support null semantics. -
- -
- Basic value types - - The built-in basic mapping types can be - roughly categorized into the following: - - integer, long, short, float, double, character, - byte, boolean, yes_no, true_false - - - Type mappings from Java primitives or wrapper classes to - appropriate (vendor-specific) SQL column types. - boolean, yes_no and - true_false are all alternative encodings for - a Java boolean or - java.lang.Boolean. - - - - - string - - - A type mapping from java.lang.String to - VARCHAR (or Oracle - VARCHAR2). - - - - - date, time, timestamp - - - Type mappings from java.util.Date and - its subclasses to SQL types DATE, - TIME and TIMESTAMP (or - equivalent). - - - - - calendar, calendar_date - - - Type mappings from java.util.Calendar - to SQL types TIMESTAMP and - DATE (or equivalent). - - - - - big_decimal, big_integer - - - Type mappings from java.math.BigDecimal - and java.math.BigInteger to - NUMERIC (or Oracle - NUMBER). - - - - - locale, timezone, currency - - - Type mappings from java.util.Locale, - java.util.TimeZone and - java.util.Currency to - VARCHAR (or Oracle - VARCHAR2). Instances of - Locale and Currency are - mapped to their ISO codes. Instances of - TimeZone are mapped to their - ID. - - - - - class - - - A type mapping from java.lang.Class to - VARCHAR (or Oracle - VARCHAR2). A Class is - mapped to its fully qualified name. - - - - - binary - - - Maps byte arrays to an appropriate SQL binary type. - - - - - text - - - Maps long Java strings to a SQL LONGVARCHAR or - TEXT type. - - - - - image - - - Maps long byte arrays to a SQL LONGVARBINARY. - - - - - serializable - - - Maps serializable Java types to an appropriate SQL binary - type. You can also indicate the Hibernate type - serializable with the name of a serializable - Java class or interface that does not default to a basic - type. - - - - - clob, blob - - - Type mappings for the JDBC classes - java.sql.Clob and - java.sql.Blob. These types can be - inconvenient for some applications, since the blob or clob - object cannot be reused outside of a transaction. Driver support - is patchy and inconsistent. - - - - - materialized_clob - - - Maps long Java strings to a SQL CLOB - type. When read, the CLOB value is - immediately materialized into a Java string. Some drivers - require the CLOB value to be read within - a transaction. Once materialized, the Java string is - available outside of the transaction. - - - - - materialized_blob - - - Maps long Java byte arrays to a SQL BLOB - type. When read, the BLOB value is - immediately materialized into a byte array. Some drivers - require the BLOB value to be read within - a transaction. Once materialized, the byte array is - available outside of the transaction. - - - - - imm_date, imm_time, imm_timestamp, imm_calendar, - imm_calendar_date, imm_serializable, imm_binary - - - Type mappings for what are considered mutable Java types. - This is where Hibernate makes certain optimizations appropriate - only for immutable Java types, and the application treats the - object as immutable. For example, you should not call - Date.setTime() for an instance mapped as - imm_timestamp. To change the value of the - property, and have that change made persistent, the application - must assign a new, nonidentical, object to the property. - - - - - Unique identifiers of entities and collections can be of any basic - type except binary, blob and - clob. Composite identifiers are also allowed. See - below for more information. - - The basic value types have corresponding Type - constants defined on org.hibernate.Hibernate. For - example, Hibernate.STRING represents the - string type. -
- -
- Custom value types - - It is relatively easy for developers to create their own value - types. For example, you might want to persist properties of type - java.lang.BigInteger to VARCHAR - columns. Hibernate does not provide a built-in type for this. Custom - types are not limited to mapping a property, or collection element, to a - single table column. So, for example, you might have a Java property - getName()/setName() of type - java.lang.String that is persisted to the columns - FIRST_NAME, INITIAL, - SURNAME. - - To implement a custom type, implement either - org.hibernate.UserType or - org.hibernate.CompositeUserType and declare - properties using the fully qualified classname of the type. View - org.hibernate.test.DoubleStringType to see the kind - of things that are possible. - - <property name="twoStrings" type="org.hibernate.test.DoubleStringType"> - <column name="first_string"/> - <column name="second_string"/> -</property> - - Notice the use of <column> tags to map a - property to multiple columns. - - The CompositeUserType, - EnhancedUserType, - UserCollectionType, and - UserVersionType interfaces provide support for more - specialized uses. - - You can even supply parameters to a UserType in - the mapping file. To do this, your UserType must - implement the - org.hibernate.usertype.ParameterizedType interface. - To supply parameters to your custom type, you can use the - <type> element in your mapping files. - - <property name="priority"> - <type name="com.mycompany.usertypes.DefaultValueIntegerType"> - <param name="default">0</param> - </type> -</property> - - The UserType can now retrieve the value for the - parameter named default from the - Properties object passed to it. - - If you regularly use a certain UserType, it is - useful to define a shorter name for it. You can do this using the - <typedef> element. Typedefs assign a name to a - custom type, and can also contain a list of default parameter values if - the type is parameterized. - - <typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero"> - <param name="default">0</param> -</typedef> - - <property name="priority" type="default_zero"/> - - It is also possible to override the parameters supplied in a - typedef on a case-by-case basis by using type parameters on the property - mapping. - - Even though Hibernate's rich range of built-in types and support - for components means you will rarely need to use a custom type, it is - considered good practice to use custom types for non-entity classes that - occur frequently in your application. For example, a - MonetaryAmount class is a good candidate for a - CompositeUserType, even though it could be mapped as - a component. One reason for this is abstraction. With a custom type, - your mapping documents would be protected against changes to the way - monetary values are represented. -
-
- -
- Mapping a class more than once - - It is possible to provide more than one mapping for a particular - persistent class. In this case, you must specify an entity - name to disambiguate between instances of the two mapped - entities. By default, the entity name is the same as the class name. - Hibernate lets you specify the entity name when working with persistent - objects, when writing queries, or when mapping associations to the named - entity. - - <class name="Contract" table="Contracts" - entity-name="CurrentContract"> - ... - <set name="history" inverse="true" - order-by="effectiveEndDate desc"> - <key column="currentContractId"/> - <one-to-many entity-name="HistoricalContract"/> - </set> -</class> - -<class name="Contract" table="ContractHistory" - entity-name="HistoricalContract"> - ... - <many-to-one name="currentContract" - column="currentContractId" - entity-name="CurrentContract"/> -</class> - - Associations are now specified using entity-name - instead of class. - - - This feature is not supported in Annotations - -
- -
- SQL quoted identifiers - - You can force Hibernate to quote an identifier in the generated SQL - by enclosing the table or column name in backticks in the mapping - document. Hibernate will use the correct quotation style for the SQL - Dialect. This is usually double quotes, but the SQL - Server uses brackets and MySQL uses backticks. - - @Entity @Table(name="`Line Item`") -class LineItem { - @id @Column(name="`Item Id`") Integer id; - @Column(name="`Item #`") int itemNumber -} - -<class name="LineItem" table="`Line Item`"> - <id name="id" column="`Item Id`"/><generator class="assigned"/></id> - <property name="itemNumber" column="`Item #`"/> - ... -</class> -
- -
- Generated properties - - Generated properties are properties that have their values generated - by the database. Typically, Hibernate applications needed to - refresh objects that contain any properties for which - the database was generating values. Marking properties as generated, - however, lets the application delegate this responsibility to Hibernate. - When Hibernate issues an SQL INSERT or UPDATE for an entity that has - defined generated properties, it immediately issues a select afterwards to - retrieve the generated values. - - Properties marked as generated must additionally be non-insertable - and non-updateable. Only versions, timestamps, and simple properties, can be - marked as generated. - - never (the default): the given property value is - not generated within the database. - - insert: the given property value is generated on - insert, but is not regenerated on subsequent updates. Properties like - created-date fall into this category. Even though version and timestamp properties can be - marked as generated, this option is not available. - - always: the property value is generated both on - insert and on update. - - To mark a property as generated, use - @Generated. -
- -
- Column transformers: read and write expressions - - Hibernate allows you to customize the SQL it uses to read and write - the values of columns mapped to simple properties. For - example, if your database provides a set of data encryption functions, you - can invoke them for individual columns like this: - - @Entity -class CreditCard { - @Column(name="credit_card_num") - @ColumnTransformer( - read="decrypt(credit_card_num)", - write="encrypt(?)") - public String getCreditCardNumber() { return creditCardNumber; } - public void setCreditCardNumber(String number) { this.creditCardNumber = number; } - private String creditCardNumber; -} - - or in XML - - <property name="creditCardNumber"> - <column - name="credit_card_num" - read="decrypt(credit_card_num)" - write="encrypt(?)"/> -</property> - - - You can use the plural form - @ColumnTransformers if more than one columns need - to define either of these rules. - - - If a property uses more than one column, you must use the - forColumn attribute to specify which column, the - expressions are targeting. - - @Entity -class User { - @Type(type="com.acme.type.CreditCardType") - @Columns( { - @Column(name="credit_card_num"), - @Column(name="exp_date") } ) - @ColumnTransformer( - forColumn="credit_card_num", - read="decrypt(credit_card_num)", - write="encrypt(?)") - public CreditCard getCreditCard() { return creditCard; } - public void setCreditCard(CreditCard card) { this.creditCard = card; } - private CreditCard creditCard; -} - - Hibernate applies the custom expressions automatically whenever the - property is referenced in a query. This functionality is similar to a - derived-property formula with two differences: - - - The property is backed by one or more columns that are - exported as part of automatic schema generation. - - - - The property is read-write, not read-only. - - - - The write expression, if specified, must contain - exactly one '?' placeholder for the value. -
- -
- Auxiliary database objects - - Auxiliary database objects allow for the CREATE and DROP of - arbitrary database objects. In conjunction with Hibernate's schema - evolution tools, they have the ability to fully define a user schema - within the Hibernate mapping files. Although designed specifically for - creating and dropping things like triggers or stored procedures, any SQL - command that can be run via a - java.sql.Statement.execute() method is valid (for - example, ALTERs, INSERTS, etc.). There are essentially two modes for - defining auxiliary database objects: - - The first mode is to explicitly list the CREATE and DROP commands in - the mapping file: - - <hibernate-mapping> - ... - <database-object> - <create>CREATE TRIGGER my_trigger ...</create> - <drop>DROP TRIGGER my_trigger</drop> - </database-object> -</hibernate-mapping> - - The second mode is to supply a custom class that constructs the - CREATE and DROP commands. This custom class must implement the - org.hibernate.mapping.AuxiliaryDatabaseObject - interface. - - <hibernate-mapping> - ... - <database-object> - <definition class="MyTriggerDefinition"/> - </database-object> -</hibernate-mapping> - - Additionally, these database objects can be optionally scoped so - that they only apply when certain dialects are used. - - <hibernate-mapping> - ... - <database-object> - <definition class="MyTriggerDefinition"/> - <dialect-scope name="org.hibernate.dialect.Oracle9iDialect"/> - <dialect-scope name="org.hibernate.dialect.Oracle10gDialect"/> - </database-object> -</hibernate-mapping> - - - This feature is not supported in Annotations - -
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/batch.xml b/documentation/src/main/docbook/manual-old/en-US/content/batch.xml deleted file mode 100755 index 4c93601cd0b1..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/batch.xml +++ /dev/null @@ -1,358 +0,0 @@ - - - - - Batch processing - - - A naive approach to inserting 100,000 rows in the database using Hibernate might - look like this: - - - - - - This would fall over with an OutOfMemoryException somewhere - around the 50,000th row. That is because Hibernate caches all the newly inserted - Customer instances in the session-level cache. In this chapter - we will show you how to avoid this problem. - - - - - If you are undertaking batch processing you will need to enable the use of - JDBC batching. This is absolutely essential if you want to achieve optimal performance. - Set the JDBC batch size to a reasonable number (10-50, for example): - - - - - - Hibernate disables insert batching at the JDBC level transparently if you - use an identity identifier generator. - - - - You can also do this kind of work in a process where interaction with - the second-level cache is completely disabled: - - - - - - However, this is not absolutely necessary, since we can explicitly set the - CacheMode to disable interaction with the second-level cache. - - -
- Batch inserts - - - When making new objects persistent flush() and - then clear() the session regularly in order to control the size of - the first-level cache. - - - - -
- -
- Batch updates - - - For retrieving and updating data, the same ideas apply. In addition, you need to - use scroll() to take advantage of server-side cursors for - queries that return many rows of data. - - - - -
- -
- The StatelessSession interface - - Alternatively, Hibernate provides a command-oriented API that can be used for - streaming data to and from the database in the form of detached objects. A - StatelessSession has no persistence context associated - with it and does not provide many of the higher-level life cycle semantics. - In particular, a stateless session does not implement a first-level cache nor - interact with any second-level or query cache. It does not implement - transactional write-behind or automatic dirty checking. Operations performed - using a stateless session never cascade to associated instances. Collections - are ignored by a stateless session. Operations performed via a stateless session - bypass Hibernate's event model and interceptors. Due to the lack of a first-level cache, - Stateless sessions are vulnerable to data aliasing effects. A stateless - session is a lower-level abstraction that is much closer to the underlying JDBC. - - - - - - In this code example, the Customer instances returned - by the query are immediately detached. They are never associated with any persistence - context. - - - - The insert(), update() and delete() operations - defined by the StatelessSession interface are considered to be - direct database row-level operations. They result in the immediate execution of a SQL - INSERT, UPDATE or DELETE respectively. - They have different semantics to the save(), saveOrUpdate() - and delete() operations defined by the Session - interface. - - -
- -
- DML-style operations - - - As already discussed, automatic and transparent object/relational mapping is concerned - with the management of the object state. The object state is available in memory. This means that manipulating data directly in the database (using the SQL Data Manipulation Language - (DML) the statements: INSERT, UPDATE, DELETE) - will not affect in-memory state. However, Hibernate provides methods - for bulk SQL-style DML statement execution that is performed through the - Hibernate Query Language (HQL). - - - - The pseudo-syntax for UPDATE and DELETE statements - is: ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?. - - - - Some points to note: - - - - - - In the from-clause, the FROM keyword is optional - - - - - There can only be a single entity named in the from-clause. It can, however, be - aliased. If the entity name is aliased, then any property references must - be qualified using that alias. If the entity name is not aliased, then it is - illegal for any property references to be qualified. - - - - - No joins, either implicit or explicit, - can be specified in a bulk HQL query. Sub-queries can be used in the where-clause, where - the subqueries themselves may contain joins. - - - - - The where-clause is also optional. - - - - - - As an example, to execute an HQL UPDATE, use the - Query.executeUpdate() method. The method is named for - those familiar with JDBC's PreparedStatement.executeUpdate(): - - - - - - In keeping with the EJB3 specification, HQL UPDATE statements, by default, do not effect the - version - or the timestamp property values - for the affected entities. However, - you can force Hibernate to reset the version or - timestamp property values through the use of a versioned update. - This is achieved by adding the VERSIONED keyword after the UPDATE - keyword. - - - - - Custom version types, org.hibernate.usertype.UserVersionType, - are not allowed in conjunction with a update versioned statement. - - - - To execute an HQL DELETE, use the same Query.executeUpdate() - method: - - - - - - The int value returned by the Query.executeUpdate() - method indicates the number of entities effected by the operation. This may or may not - correlate to the number of rows effected in the database. An HQL bulk operation might result in - multiple actual SQL statements being executed (for joined-subclass, for example). The returned - number indicates the number of actual entities affected by the statement. Going back to the - example of joined-subclass, a delete against one of the subclasses may actually result - in deletes against not just the table to which that subclass is mapped, but also the "root" - table and potentially joined-subclass tables further down the inheritance hierarchy. - - - - The pseudo-syntax for INSERT statements is: - INSERT INTO EntityName properties_list select_statement. Some - points to note: - - - - - - Only the INSERT INTO ... SELECT ... form is supported; not the INSERT INTO ... VALUES ... form. - - - The properties_list is analogous to the column specification - in the SQL INSERT statement. For entities involved in mapped - inheritance, only properties directly defined on that given class-level can be - used in the properties_list. Superclass properties are not allowed and subclass - properties do not make sense. In other words, INSERT - statements are inherently non-polymorphic. - - - - - select_statement can be any valid HQL select query, with the caveat that the return types - must match the types expected by the insert. Currently, this is checked during query - compilation rather than allowing the check to relegate to the database. - This might, however, cause problems between Hibernate Types which are - equivalent as opposed to equal. This might cause - issues with mismatches between a property defined as a org.hibernate.type.DateType - and a property defined as a org.hibernate.type.TimestampType, even though the - database might not make a distinction or might be able to handle the conversion. - - - - - For the id property, the insert statement gives you two options. You can either - explicitly specify the id property in the properties_list, in which case its value - is taken from the corresponding select expression, or omit it from the properties_list, - in which case a generated value is used. This latter option is only available when - using id generators that operate in the database; attempting to use this option with - any "in memory" type generators will cause an exception during parsing. - For the purposes of this discussion, in-database generators are considered to be - org.hibernate.id.SequenceGenerator (and its subclasses) and - any implementers of org.hibernate.id.PostInsertIdentifierGenerator. - The most notable exception here is org.hibernate.id.TableHiLoGenerator, - which cannot be used because it does not expose a selectable way to get its values. - - - - - For properties mapped as either version or timestamp, - the insert statement gives you two options. You can either specify the property in the - properties_list, in which case its value is taken from the corresponding select expressions, - or omit it from the properties_list, in which case the seed value defined - by the org.hibernate.type.VersionType is used. - - - - - - The following is an example of an HQL INSERT statement execution: - - - - -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/best_practices.xml b/documentation/src/main/docbook/manual-old/en-US/content/best_practices.xml deleted file mode 100644 index bcd2d848bbf6..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/best_practices.xml +++ /dev/null @@ -1,231 +0,0 @@ - - - - - Best Practices - - - - Write fine-grained classes and map them using <component>: - - - Use an Address class to encapsulate street, - suburb, state, postcode. - This encourages code reuse and simplifies refactoring. - - - - - Declare identifier properties on persistent classes: - - - Hibernate makes identifier properties optional. There are a range of reasons why - you should use them. We recommend that identifiers be 'synthetic', that is, generated with - no business meaning. - - - - - Identify natural keys: - - - Identify natural keys for all entities, and map them using - <natural-id>. Implement equals() and - hashCode() to compare the properties that make up the natural key. - - - - - Place each class mapping in its own file: - - - Do not use a single monolithic mapping document. Map com.eg.Foo in - the file com/eg/Foo.hbm.xml. This makes sense, particularly in - a team environment. - - - - - Load mappings as resources: - - - Deploy the mappings along with the classes they map. - - - - - Consider externalizing query strings: - - - This is recommended if your queries call non-ANSI-standard SQL functions. - Externalizing the query strings to mapping files will make the application more - portable. - - - - - Use bind variables. - - - As in JDBC, always replace non-constant values by "?". Do not use string manipulation to - bind a non-constant value in a query. You should also consider using named parameters in - queries. - - - - - Do not manage your own JDBC connections: - - - Hibernate allows the application to manage JDBC connections, but his approach should be considered - a last-resort. If you cannot use the built-in connection providers, consider providing your - own implementation of org.hibernate.connection.ConnectionProvider. - - - - - Consider using a custom type: - - - Suppose you have a Java type from a library that needs to be persisted but does not - provide the accessors needed to map it as a component. You should consider implementing - org.hibernate.UserType. This approach frees the application - code from implementing transformations to/from a Hibernate type. - - - - - Use hand-coded JDBC in bottlenecks: - - - In performance-critical areas of the system, some kinds of operations might benefit from - direct JDBC. Do not assume, however, that JDBC is necessarily faster. Please wait until you know something is a bottleneck. - If you need to use direct JDBC, - you can open a Hibernate Session, wrap your JDBC operation as a org.hibernate.jdbc.Work object and using that JDBC connection. This - way you can still use the same transaction strategy and underlying connection provider. - - - - - Understand Session flushing: - - - Sometimes the Session synchronizes its persistent state with the database. Performance will - be affected if this process occurs too often. You can sometimes minimize unnecessary flushing by - disabling automatic flushing, or even by changing the order of queries and other operations within a - particular transaction. - - - - - In a three tiered architecture, consider using detached objects: - - - When using a servlet/session bean architecture, you can pass persistent objects loaded in - the session bean to and from the servlet/JSP layer. Use a new session to service each request. - Use Session.merge() or Session.saveOrUpdate() to - synchronize objects with the database. - - - - - In a two tiered architecture, consider using long persistence contexts: - - - Database Transactions have to be as short as possible for best scalability. However, it is often - necessary to implement long running application transactions, a single - unit-of-work from the point of view of a user. An application transaction might span several - client request/response cycles. It is common to use detached objects to implement application - transactions. An appropriate alternative in a two tiered architecture, is to maintain - a single open persistence contact session for the whole life cycle of the application transaction. Then - simply disconnect from the JDBC connection at the end of each request and reconnect at the - beginning of the subsequent request. Never share a single session across more than one application - transaction or you will be working with stale data. - - - - - Do not treat exceptions as recoverable: - - - This is more of a necessary practice than a "best" practice. When an exception occurs, roll back - the Transaction and close the Session. If you do not do this, Hibernate - cannot guarantee that in-memory state accurately represents the persistent state. For example, - do not use Session.load() to determine if an instance with the given identifier - exists on the database; use Session.get() or a query instead. - - - - - Prefer lazy fetching for associations: - - - Use eager fetching sparingly. Use proxies and lazy collections for most associations to classes that - are not likely to be completely held in the second-level cache. For associations to cached classes, - where there is an a extremely high probability of a cache hit, explicitly disable eager fetching using - lazy="false". When join fetching is appropriate to a particular use - case, use a query with a left join fetch. - - - - - - Use the open session in view pattern, or a disciplined - assembly phase to avoid problems with unfetched data: - - - - Hibernate frees the developer from writing tedious Data Transfer Objects (DTO). - In a traditional EJB architecture, DTOs serve dual purposes: first, they work around the problem - that entity beans are not serializable; second, they implicitly define an assembly phase where - all data to be used by the view is fetched and marshalled into the DTOs before returning control - to the presentation tier. Hibernate eliminates the first purpose. Unless you are prepared to hold the - persistence context (the session) open across the view rendering process, you will still need - an assembly phase. Think of your business methods as having a strict contract with the presentation - tier about what data is available in the detached objects. This is not a limitation - of Hibernate. It is a fundamental requirement of safe transactional data access. - - - - - Consider abstracting your business logic from Hibernate: - - - Hide Hibernate data-access code behind an interface. Combine the DAO and - Thread Local Session patterns. You can even have some classes persisted by - handcoded JDBC associated to Hibernate via a UserType. This advice is, however, - intended for "sufficiently large" applications. It is not appropriate for an application with - five tables. - - - - - Do not use exotic association mappings: - - - Practical test cases for real many-to-many associations are rare. Most of the time you need - additional information stored in the "link table". In this case, it is much better to - use two one-to-many associations to an intermediate link class. In fact, - most associations are one-to-many and many-to-one. For this reason, you should proceed cautiously when using any - other association style. - - - - - Prefer bidirectional associations: - - - Unidirectional associations are more difficult to query. In a large application, almost - all associations must be navigable in both directions in queries. - - - - - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/content/bibliography.xml b/documentation/src/main/docbook/manual-old/en-US/content/bibliography.xml deleted file mode 100644 index ae2891abb473..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/bibliography.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - References - - - PoEAA - Patterns of Enterprise Application Architecture - 0-321-12742-0 - - - - Martin - Fowler - - - - - 2003 - Pearson Education, Inc. - - - Addison-Wesley Publishing Company - - - - - JPwH - Java Persistence with Hibernate - Second Edition of Hibernate in Action - 1-932394-88-5 - - - - - - - Christian - Bauer - - - - - Gavin - King - - - - - 2007 - Manning Publications Co. - - - Manning Publications Co. - - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/content/bootstrap.xml b/documentation/src/main/docbook/manual-old/en-US/content/bootstrap.xml deleted file mode 100644 index 8304654b5b82..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/bootstrap.xml +++ /dev/null @@ -1,1375 +0,0 @@ - - - - - Bootstrapping - - - This chapter discusses the methods and options for bootstrapping Hibernate. - - - - - The discussion of "bootstrapping Hibernate" is really 2 different discussions depending on - whether the application wants to bootstrap a JPA EntityManagerFactory or a native Hibernate - SessionFactory. Each topic is discussed in detail in its own topical guide: - Bootstrapping Hibernate JPA for bootrapping a JPA EntityManagerFactory, - and Native Bootstrapping for bootstrapping a native Hibernate - SessionFactory. These topical guides discuss the various API calls involved in detail; this - discussion will focus on the various settings and their effect. - - - - Most of the available settings for native bootstrapping are exposed as constants on the - org.hibernate.cfg.AvailableSettings interface. Most of the available - settings for JPA bootstrapping (beyond the native ones) are exposed as constants on the - org.hibernate.jpa.AvailableSettings interface. Each setting - is explained individually in detail in the javadoc. Those javadocs are the authoritative source for the - discussion of each individual setting. The focus of this chapter is more about groupings of settings and - values to achieve certain goals. - - - - - The Hibernate team have made a concerted effort in recent releases to begin removing the need for users - to name Hibernate classes by FQN in configurations. The reason being that referencing Hibernate classes - by FQN can cause troubles when upgrading between releases where those class have undergone refactoring. - We call this improvement "short naming". Whenever possible, users should prefer to use short names for - configuration values over Hibernate class FQNs. - - - -
- ConnectionProvider - - As an ORM tool, probably the single most important thing you need to tell Hibernate is how to connect to - your database. This is ultimately the function of the org.hibernate.engine.jdbc.connections.spi.ConnectionProvider - interface. Every SessionFactory or EntityManagerFactory an application ever builds will contain - a ConnectionProvider (unless the application is using Hibernate's multi-tenancy support, which is currently - beyond the scope of this documentation). ConnectionProvider is an interface; Hibernate provides some - out of the box implementations of this interface. ConnectionProvider is also an extension point, so - you can also use custom implementations from third parties or written yourself. The ConnectionProvider - to use is defined by the hibernate.connection.provider_class setting. See - the org.hibernate.cfg.AvailableSettings#CONNECTION_PROVIDER - - - - Generally speaking applications should not have to configure a ConnectionProvider explicitly if using - one of the Hibernate-provided implementations. Hibernate will internally determine which ConnectionProvider - to use based on the following algorithm: - - - - - If hibernate.connection.provider_class is set, it takes precedence - - - - - else if hibernate.connection.datasource is set -> - - - - - else if any setting prefixed by hibernate.c3p0. is set -> - - - - - else if any setting prefixed by hibernate.proxool. is set -> - - - - - else if any setting prefixed by hibernate.hikari. is set -> - - - - - else if hibernate.connection.url is set -> - - - - - else -> - - - - - - - -
- Using DataSources - - - Hibernate can integrate with a javax.sql.DataSource for obtaining - JDBC Connections. Applications would tell Hibernate about the DataSource via the (required) - hibernate.connection.datasource setting which can either specify a JNDI name - or would reference the actual DataSource instance. For cases where a JNDI namespace is specified, be - sure to read . - - - - - For JPA applications, note that hibernate.connection.datasource corresponds to - either javax.persistence.jtaDataSource or javax.persistence.nonJtaDataSource. - - - - - The DataSource ConnectionProvider also (optionally) accepts the hibernate.connection.username - and hibernate.connection.password. If specified, the form of DataSource#getConnection - accepting username and password will be used. Otherwise the no-arg form is used. - -
- -
- Using c3p0 - - - - To use this integration, the application must include the hibernate-c3p0 - module jar (as well as its dependencies) on the classpath. - - - - - Hibernate also provides support for applications to use c3p0 - connection pooling. When using this c3p0 support, a number of additional configuration settings - are recognized. - - - - Additional settings - - hibernate.connection.driver_class - - - The name of the JDBC Driver class to use - - - - - hibernate.connection.url - - - The JDBC connection url. - - - - - Any settings prefixed with hibernate.connection. (other than the "special ones") - - - These all have the hibernate.connection. prefix stripped and the - rest will be passed as JDBC connection properties - - - - - hibernate.c3p0.min_size or c3p0.minPoolSize - - - The minimum size of the c3p0 pool. See - - - - - hibernate.c3p0.max_size or c3p0.maxPoolSize - - - The maximum size of the c3p0 pool. See - - - - - hibernate.c3p0.timeout or c3p0.maxIdleTime - - - The Connection idle time. See - - - - - hibernate.c3p0.max_statements or c3p0.maxStatements - - - Controls the c3p0 PreparedStatement cache size (if using). See - - - - - hibernate.c3p0.acquire_increment or c3p0.acquireIncrement - - - Number of connections c3p0 should acquire at a time when pool is exhauted. See - - - - - hibernate.c3p0.idle_test_period or c3p0.idleConnectionTestPeriod - - - Idle time before a c3p0 pooled connection is validated. See - - - - - c3p0.initialPoolSize - - - The initial c3p0 pool size. If not specified, default is to use the min pool size. - See - - - - - Any other settings prefixed with hibernate.c3p0. - - - Will have the hibernate. portion stripped and be passed to c3p0. - - - - - Any other settings prefixed with c3p0. - - - Get passed to c3p0 as is. See - - - - -
- -
- Using Proxool - - - - To use this integration, the application must include the hibernate-proxool - module jar (as well as its dependencies) on the classpath. - - - - - Hibernate also provides support for applications to use Proxool - connection pooling. - - -
- Using existing Proxool pools - - - Controlled by the hibernate.proxool.existing_pool setting. If set to true, - this ConnectionProvider will use an already existing Proxool pool by alias as indicated by - the hibernate.proxool.pool_alias setting - -
- -
- Configuring Proxool via XML - - - The hibernate.proxool.xml setting names a Proxool configuration XML - file to be loaded as a classpath resource and loaded by Proxool's JAXPConfigurator. - See . - hibernate.proxool.pool_alias must be set to indicate which pool to use. - -
- -
- Configuring Proxool via Properties - - - The hibernate.proxool.properties setting names a Proxool configuration Properties - file to be loaded as a classpath resource and loaded by Proxool's PropertyConfigurator. - See . - hibernate.proxool.pool_alias must be set to indicate which pool to use. - - -
-
- -
- Using Hikari - - - - To use this integration, the application must include the hibernate-hikari - module jar (as well as its dependencies) on the classpath. - - - - - Hibernate also provides support for applications to use - Hikari connection pool. - - - - Set all of your Hikari settings in Hibernate prefixed by hibernate.hikari. - and this ConnectionProvider will pick them up and pass them along to Hikari. Additionally, this - ConnectionProvider will pick up the following Hibernate-specific properties and map - them to the corresponding Hikari ones (any hibernate.hikari. prefixed ones have precedence): - - - - Hibernate-specific properties recognized by Hikari ConnectionProvider - - hibernate.connection.driver_class - - Mapped to Hikari's driverClassName setting - - - - hibernate.connection.url - - Mapped to Hikari's jdbcUrl setting - - - - hibernate.connection.username - - Mapped to Hikari's username setting - - - - hibernate.connection.password - - Mapped to Hikari's password setting - - - - hibernate.connection.isolation - - Mapped to Hikari's transactionIsolation setting - - - - hibernate.connection.autocommit - - Mapped to Hikari's autoCommit setting - - - -
- -
- Using Hibernate's built-in (and unsupported) pooling - - - - The built-in connection pool is not supported supported for use. - - - - - This section is here just for completeness. - -
- -
- User-provided Connections - - - It is possible to use Hibernate by simply passing a Connection to use to the Session when the Session - is opened. This usage is discouraged and not discussed here. - -
-
- -
- Database Dialect - - - All databases vary from the ANSI standard to some degree. Each database's variance makes up its dialect. - Hibernate needs to understand how to talk to your specific database. That is the function of - the org.hibernate.dialect.Dialect (in conjunction with information from - java.sql.DatabaseMetaData). - - - - In most cases Hibernate will be able to determine the proper Dialect to use by asking some questions - of the JDBC Connection during bootstrap. If for some reason it is not able to determine the proper one - or you want to use a custom Dialect, you will need to set the hibernate.dialect setting. - - - - - TODO : document short names - -
- -
- Transaction configuration - - - Transaction handling per Session is handled by the org.hibernate.resource.transaction.spi.TransactionCoordinator - contract, which are built by the org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder - service. TransactionCoordinatorBuilder represents a strategy for dealing with transactions whereas - TransactionCoordinator represents one instance of that strategy related to a Session. Which - TransactionCoordinatorBuilder implementation to use is defined by the hibernate.transaction.coordinator_class - setting. - - - - Hibernate-provided TransactionCoordinatorBuilder implementations - - - jdbc (the default) - Manages transactions via calls to java.sql.Connection - - - - - jta - Manages transactions via JTA. See - - - -
- -
- JTA configuration - - - Interaction with a JTA system is consolidated behind a single contract named - org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform which - exposes access to the javax.transaction.TransactionManager and - javax.transaction.UserTransaction for that system as well as exposing - the ability to register javax.transaction.Synchronization instances, - check transaction status, etc. - - - - Hibernate tries to discover the JtaPlatform it should use through the use of another service named - org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformResolver. - If that resolution does not work, or if you wish to provide a custom implementation you will need to - specify the hibernate.transaction.jta.platform setting. Hibernate provides many - implementations of the JtaPlatform contract, all with short-names: - - - - Built-in JtaPlatform implementations by short name - - - Borland - JtaPlatform for the Borland Enterprise Server. - - - - - Bitronix - JtaPlatform for Bitronix. - - - - - JBossAS - JtaPlatform for Arjuna/JBossTransactions/Narnya when used within the JBoss/WildFly Application Server. - - - - - JBossTS - JtaPlatform for Arjuna/JBossTransactions/Narnya when used standalone. - - - - - JOnAS - JtaPlatform for JOTM when used within JOnAS. - - - - - JOTM - JtaPlatform for JOTM when used standalone. - - - - - JRun4 - JtaPlatform for the JRun 4 Application Server. - - - - - OC4J - JtaPlatform for Oracle's OC4J container. - - - - - Orion - JtaPlatform for the Orion Application Server. - - - - - Resin - JtaPlatform for the Resin Application Server. - - - - - SunOne - JtaPlatform for the SunOne Application Server. - - - - - Weblogic - JtaPlatform for the Weblogic Application Server. - - - - - WebSphere - JtaPlatform for older versions of the WebSphere Application Server. - - - - - WebSphereExtended - JtaPlatform for newer versions of the WebSphere Application Server. - - - -
- -
- JNDI configuration - - - Hibernate's JNDI capabilities are represented by the org.hibernate.engine.jndi.spi.JndiService - contract. The default implementation assumes that anything it is asked to retrieve from JNDI comes from - the same JNDI root context. If that is not the case in your set up you will need to supply a custom - JndiService implementation. - - - - Settings recognized by the default JndiService implementation - - hibernate.jndi.class - - - Names the JNDI javax.naming.spi.InitialContextFactory - implementation class to use. Leave null to pick up the system default, if one. - - - - - hibernate.jndi.url - - - Names the JNDI javax.naming.InitialContext url. - Leave null to pick up the system default, if one. - - - - - Any other settings prefixed with hibernate.jndi. - - - The hibernate.jndi. prefix will be stripped off and the value - passed in while obtaining the javax.naming.InitialContext - - - - -
- - -
- Second level caching - - - Hibernate defines the ability to integrate with pluggable providers for the purpose of - caching data outside the context of a particular Session. This section defines - the settings which control that behavior. - - -
- RegionFactory - - org.hibernate.cache.spi.RegionFactory defines the integration - between Hibernate and a pluggable caching provider. hibernate.cache.region.factory_class - is used to declare the provider to use. Hibernate comes with support for 2 popular caching - libraries: Ehcache and Infinispan. - - -
- Ehcache - - - Use of the build-in integration for Ehcache requires that the hibernate-ehcache module - jar (and all of its dependencies) are on the classpath. - - - - The hibernate-ehcache module defines 2 specific providers: - - - - - ehcache - todo : document - - - - - - ehcache-singleton - todo : document - - - - -
- -
- Infinispan - - - Use of the build-in integration for Infinispan requires that the hibernate-infinispan module - jar (and all of its dependencies) are on the classpath. - - - - The hibernate-infinispan module defines 2 specific providers: - - - - - infinispan - todo : document - - - - - - infinispan-jndi - todo : document - - - - -
-
- -
- Caching behavior - - - Besides specific provider configuration, there are a number of configurations options on the - Hibernate side of the integration that control various caching behavior: - - - hibernate.cache.use_second_level_cache - Enable or disable - second level caching overall. Default is true. - - - hibernate.cache.use_query_cache - Enable or disable second level - caching of query results. Default is false. - - - hibernate.cache.query_cache_factory - Query result caching is - handled by a special contract that deals with staleness-based invalidation of the results. - The default implementation does not allow stale results at all. Use this for applications - that would like to relax that. Names an implementation of - org.hibernate.cache.spi.QueryCacheFactory - - - hibernate.cache.use_minimal_puts - Optimizes second-level cache - operations to minimize writes, at the cost of more frequent reads. Providers typically - set this appropriately. - - - hibernate.cache.region_prefix - Defines a name to be used as a prefix to - all second-level cache region names. - - - hibernate.cache.default_cache_concurrency_strategy - In Hibernate - second-level caching, all regions can be configured differently including the concurrency - strategy to use when accessing the region. This setting allows to define a default strategy to - be used. This setting is very rarely required as the pluggable providers do specify the - default strategy to use. Valid values include: read-only, - read-write, nonstrict-read-write, - transactional. - - - hibernate.cache.use_structured_entries - If true, - forces Hibernate to store data in the second-level cache in a more human-friendly format. - Can be useful if you'd like to be able to "browse" the data directly in your cache, but does - have a performance impact. - - - hibernate.cache.auto_evict_collection_cache - Enables or disables the - automatic eviction of a bi-directional association's collection cache entry when the association - is changed just from the owning side. This is disabled by default, as it has a performance - impact to track this state. However if your application does not manage both sides - of bi-directional association where the collection side is cached, the alternative is to - have stale data in that collection cache. - - - -
-
- - -
- Statistics and monitoring - - - At the SessionFactory-level Hibernate provides a set of comprehensive statistics that - it can collect for your application to help troubleshoot problems via its - org.hibernate.stat.Statistics interface. You can enable or disable - Hibernate collecting this information by setting hibernate.generate_statistics to - true or false (disabled is the default). - - - - At runtime these Statistics can be obtained by calling - SessionFactory.getStatistics(). Collecting these statistics can have a - performance hit (we collect timings, etc). To help alleviate that, the collection of statistics - can be toggled at runtime by calling Statistics.setStatisticsEnabled - with true or false. - - - - Hibernate also allows performance related monitoring at the Session level through the - org.hibernate.SessionEventListener contract. Multiple - SessionEventListener may be associated with a given Session. Hibernate provides one - implementation out of the box that simply logs some useful statistics at the end of each Session. - This implementation can be enabled by setting hibernate.session.events.log to - true (it is false by default). - - - - An application can also provide its own SessionEventListener implementation. To define a - SessionEventListener that should apply to all Sessions, use the - hibernate.session.events.auto setting. The SessionEventListeners for a given Session - can be further controlled when opening the Session via the clearEventListeners - and eventListeners methods of - org.hibernate.SessionBuilder - - - - For details, see . - -
- - -
- JDBC batching - - - JDBC offers support for batching together SQL statements that can be represented - as a single PreparedStatement. Implementation wise this generally means that drivers - will send the batched operation to the server in one call, which can save on network calls - to the database. Hibernate can leverage JDBC batching. The following settings control this - behavior. - - - - - - hibernate.jdbc.batch_size - Controls the maximum number of - statements Hibernate will batch together before asking the driver to execute - the batch. Zero or a negative number disables this feature. - - - - - hibernate.jdbc.batch_versioned_data - Some JDBC drivers - return incorrect row counts when a batch is executed. If your JDBC driver - falls into this category this setting should be set to false. - Otherwise it is safe to enable this which will allow Hibernate to still - batch the DML for versioned entities and still use the returned row counts for - optimitic lock checks. Currently defaults to false to be safe. - - - - - hibernate.jdbc.batch.builder - Names the implementation class - used to manage batching capabilities. It is almost never a good idea to switch from - Hibernate's default implementation. But if you wish to, this setting would name the - org.hibernate.engine.jdbc.batch.spi.BatchBuilder - implementation to use. - - - - - hibernate.order_updates - Forces Hibernate to order SQL updates by the - entity type and the primary key value of the items being updated. This allows for more batching - to be used. It will also result in fewer transaction deadlocks in highly concurrent systems. - Comes with a performance hit, so benchmark before and after to see if this actually helps or - hurts your application. - - - - - hibernate.order_inserts - Forces Hibernate to order inserts to allow for - more batching to be used. Comes with a performance hit, so benchmark before and after to see - if this actually helps or hurts your application. - - - -
- - - -
- Schema tooling - - - Hibernate offers the ability to perform some schema management tasks for you including creating - and dropping schemas, validating schemas and limited support for migrating schemas. - The following settings control this behavior. - - - - - - hibernate.hbm2ddl.auto - Tells Hibernate to automatically - perform the indicated schema management tasks when the SessionFactory is bootstrapped. - - - - - create - Hibernate will drop and then create the necessary database - objects when the SessionFactory starts. - - - - - create-drop - Hibernate will drop and then create the necessary database - objects when the SessionFactory starts. It will also drop them when the SessionFactory ends. - - - - - validate - Hibernate will validate that the database is compatible with - what you have told it about the database. - - - - - update - Hibernate will alter an existing database making sure it looks - like what you said it should look like. - - - - - - - hibernate.hbm2dll.create_namespaces - Should Hibernate create/drop catalogs and - schemas as part of its create and drop processes? - - - - - hibernate.hbm2ddl.import_files - Comma-separated names of the optional files - containing additional SQL DML statements to be executed during the creation of the database. - The files are resolved by classpath resource lookup. - - - - - hibernate.hbm2ddl.import_files_sql_extractor - The classname of a - custom org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor - implementation to be used to extract the commands from the indicated import files. The default - implementation Hibernate uses assumes that each line is a command. Hibernate also provides - an alternative implementation (org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor) - you can tell it to use which can read multi-line commands delimited by semicolon. You could also - develop a custom ImportSqlCommandExtractor implementation and specify it here. - - - -
- - -
- Query settings - - - Multiple settings affect how Hibernate handles HQL, JPQL and native queries. - - - - - - hibernate.query.factory_class - Names the - org.hibernate.hql.spi.QueryTranslatorFactory implementation to use. - QueryTranslatorFactory is responsible for parsing HQL. It is generally best to accept Hibernate's - default implementation. - - - - - hibernate.query.jpaql_strict_compliance - HQL is superset of JPQL. This setting - tells the default QueryTranslatorFactory to strictly comply with the JPQL restrictions from the - JPA specification. The default is to accept the HQL superset. - - - - - hibernate.query.startup_check - By default, when the SessionFactory starts it - will cycle through all the named queries and validate them. Setting this to false - tells Hibernate to skip that. Useful mainly for some testing/development environment. - - - - - hibernate.query.plan_cache_max_size - Controls the maximum number of "plans" - Hibernate will cache in its org.hibernate.engine.query.spi.QueryPlanCache. - The default is 2048 - - - - - hibernate.query.plan_parameter_metadata_max_size - Controls the maximum number of - org.hibernate.query.internal.ParameterMetadata instances Hibernate - will maintain in its QueryPlanCache. The default is 128. - - - -
- - -
- Miscellaneous settings - - - There are a number of other properties that control the behavior of Hibernate at runtime. - - - - Miscellaneous settings - - hibernate.show_sql - - - Write all SQL statements to stdout. This is an alternative to setting the log - category org.hibernate.SQL to debug - - - - - hibernate.format_sql - - - Pretty print the SQL in the log and console. - - - - - hibernate.default_catalog - - - Defines an implicit catalog for any database objects (tables, sequences, etc) that - did not define one. - - - - - hibernate.default_schema - - - Defines an implicit schema for any database objects (tables, sequences, etc) that - did not define one. - - - - - hibernate.session_factory_name - - - Names a SessionFactory. This is often important if using Hibernate in a cluster; the - "related" SessionFactory instances should share the same name on all nodes. If - hibernate.session_factory_name_is_jndi is set to true - (the default) then the SessionFactory name is also taken as a JNDI name and Hibernate - will try to bind the SessionFactory into JNDI for you when the SessionFactory starts (and unbind - it when the SessionFactory closes). - - - - - hibernate.connection.release_mode - - - Specifies when Hibernate should release JDBC connections. By default, a JDBC connection is - held until the session is explicitly closed or disconnected. Occasionally this can lead to - problems in Java EE environments; this setting allows to control this behavior. Allowed values - include: - - - - on_close (the default) - release the Connection when the Session is closed. - - - - - auto - chooses an appropriate release mode per the - transaction strategy via the getDefaultConnectionReleaseMode - method on TransactionCoordinatorBuilder. Generally that means - after_statement for JTA-based transaction strategies and - after_transaction for JDBC-based strategies. - See . - - - - - after_transaction - release the Connection at the end of each - transaction. This is often useful for applications which hold a Session open - during user think time. This would allow the Connection to be released back into - the pool prior to returning back to the user for their think time. - Generally speaking this setting is not compatible with JTA DataSources. - - - - - after_statement - aggressively release the Connection - after the execution of each JDBC call. This is sometimes useful with JTA DataSources - in containers. If you see errors or warnings from your container regarding leaked - Connections (and you are sure your code is closing Sessions) you may need this setting. - - - - - - - - This setting only affects Sessions opened manually from the SessionFactory. For "current Sessions" - the CurrentSessionContext controls the release mode. See - - - - - - - - - - - - - - - - - - - - - -
- - - - - hibernate.max_fetch_depth - - Sets a maximum "depth" for the outer join fetch tree for - single-ended associations (one-to-one, many-to-one). A - 0 disables default outer join fetching. - e.g. recommended values between - 0 and 3 - - - - hibernate.default_batch_fetch_size - - Sets a default size for Hibernate batch fetching of - associations. e.g. - recommended values 4, 8, - 16 - - - - hibernate.default_entity_mode - - Sets a default mode for entity representation for all - sessions opened from this SessionFactory, - defaults to pojo. - e.g. dynamic-map | - pojo - - - - - hibernate.use_identifier_rollback - - If enabled, generated identifier properties will be reset - to default values when objects are deleted. e.g. true | - false - - - - hibernate.use_sql_comments - - If turned on, Hibernate will generate comments inside the - SQL, for easier debugging, defaults to false. - e.g. - true | false - - - - hibernate.id.new_generator_mappings - - Setting is relevant when using - @GeneratedValue. It indicates whether or - not the new IdentifierGenerator - implementations are used for - javax.persistence.GenerationType.AUTO, - javax.persistence.GenerationType.TABLE and - javax.persistence.GenerationType.SEQUENCE. - Default to false to keep backward - compatibility. e.g. - true | false - - - - We recommend all new projects which make use of to use - @GeneratedValue to also set - hibernate.id.new_generator_mappings=true as the new - generators are more efficient and closer to the JPA 2 specification - semantic. However they are not backward compatible with existing - databases (if a sequence or a table is used for id generation). - - - - hibernate.jdbc.fetch_size - - A non-zero value determines the JDBC fetch size (calls - Statement.setFetchSize()). - - - - hibernate.jdbc.use_scrollable_resultset - - Enables use of JDBC2 scrollable resultsets by Hibernate. - This property is only necessary when using user-supplied JDBC - connections. Hibernate uses connection metadata otherwise. - e.g. true | - false - - - - hibernate.jdbc.use_streams_for_binary - - Use streams when writing/reading binary - or serializable types to/from JDBC. - *system-level property* e.g. true | - false - - - - hibernate.jdbc.use_get_generated_keys - - Enables use of JDBC3 - PreparedStatement.getGeneratedKeys() to - retrieve natively generated keys after insert. Requires JDBC3+ - driver and JRE1.4+, set to false if your driver has problems with - the Hibernate identifier generators. By default, it tries to - determine the driver capabilities using connection metadata. - e.g. - true|false - - - - - hibernate.current_session_context_class - - Supply a custom strategy for the scoping of the "current" - Session. See for more information - about the built-in strategies. e.g. jta | - thread | managed | - custom.Class - - - - - hibernate.bytecode.use_reflection_optimizer - - Enables the use of bytecode manipulation instead of - runtime reflection. This is a System-level property and cannot be - set in hibernate.cfg.xml. Reflection can - sometimes be useful when troubleshooting. Hibernate always - requires javassist even if you turn off the - optimizer. e.g. - true | false - - - - hibernate.bytecode.provider - - At the moment, javassist is the only supported bytecode provider. e.g. javassist - - - - -
- Outer Join Fetching - - If your database supports ANSI, Oracle or Sybase style outer - joins, outer join fetching will often increase - performance by limiting the number of round trips to and from the - database. This is, however, at the cost of possibly more work performed - by the database itself. Outer join fetching allows a whole graph of - objects connected by many-to-one, one-to-many, many-to-many and - one-to-one associations to be retrieved in a single SQL - SELECT. - - Outer join fetching can be disabled globally - by setting the property hibernate.max_fetch_depth to - 0. A setting of 1 or higher - enables outer join fetching for one-to-one and many-to-one associations - that have been mapped with fetch="join". - - See for more - information. -
- -
- Binary Streams - - Oracle limits the size of byte arrays that can - be passed to and/or from its JDBC driver. If you wish to use large - instances of binary or - serializable type, you should enable - hibernate.jdbc.use_streams_for_binary. This - is a system-level setting only. -
- - - - -
- Implementing a <literal>NamingStrategy</literal> - - The interface org.hibernate.cfg.NamingStrategy - allows you to specify a "naming standard" for database objects and schema - elements. - - You can provide rules for automatically generating database - identifiers from Java identifiers or for processing "logical" column and - table names given in the mapping file into "physical" table and column - names. This feature helps reduce the verbosity of the mapping document, - eliminating repetitive noise (TBL_ prefixes, for - example). The default strategy used by Hibernate is quite minimal. - - You can specify a different strategy by calling - Configuration.setNamingStrategy() before adding - mappings: - - SessionFactory sf = new Configuration() - .setNamingStrategy(ImprovedNamingStrategy.INSTANCE) - .addFile("Item.hbm.xml") - .addFile("Bid.hbm.xml") - .buildSessionFactory(); - - org.hibernate.cfg.ImprovedNamingStrategy is a - built-in strategy that might be a useful starting point for some - applications. -
- -
- Implementing a PersisterClassProvider - - You can configure the persister implementation used to persist your - entities and collections: - - - - by default, Hibernate uses persisters that make sense in a - relational model and follow Java Persistence's specification - - - - you can define a PersisterClassProvider - implementation that provides the persister class used of a given - entity or collection - - - - finally, you can override them on a per entity and collection - basis in the mapping using @Persister or its - XML equivalent - - - - The latter in the list the higher in priority. - - You can pass the PersisterClassProvider - instance to the Configuration object. - - SessionFactory sf = new Configuration() - .setPersisterClassProvider(customPersisterClassProvider) - .addAnnotatedClass(Order.class) - .buildSessionFactory(); - - The persister class provider methods, when returning a non null - persister class, override the default Hibernate persisters. The entity - name or the collection role are passed to the methods. It is a nice way to - centralize the overriding logic of the persisters instead of spreading - them on each entity or collection mapping. -
- -
- XML configuration file - - An alternative approach to configuration is to specify a full - configuration in a file named hibernate.cfg.xml. This - file can be used as a replacement for the - hibernate.properties file or, if both are present, to - override properties. - - The XML configuration file is by default expected to be in the root - of your CLASSPATH. Here is an example: - - <?xml version='1.0' encoding='utf-8'?> -<!DOCTYPE hibernate-configuration PUBLIC - "-//Hibernate/Hibernate Configuration DTD//EN" - "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> - -<hibernate-configuration> - - <!-- a SessionFactory instance listed as /jndi/name --> - <session-factory - name="java:hibernate/SessionFactory"> - - <!-- properties --> - <property name="connection.datasource">java:/comp/env/jdbc/MyDB</property> - <property name="dialect">org.hibernate.dialect.MySQLDialect</property> - <property name="show_sql">false</property> - <property name="transaction.factory_class"> - org.hibernate.transaction.JTATransactionFactory - </property> - <property name="jta.UserTransaction">java:comp/UserTransaction</property> - - <!-- mapping files --> - <mapping resource="org/hibernate/auction/Item.hbm.xml"/> - <mapping resource="org/hibernate/auction/Bid.hbm.xml"/> - - <!-- cache settings --> - <class-cache class="org.hibernate.auction.Item" usage="read-write"/> - <class-cache class="org.hibernate.auction.Bid" usage="read-only"/> - <collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/> - - </session-factory> - -</hibernate-configuration> - - The advantage of this approach is the externalization of the mapping - file names to configuration. The hibernate.cfg.xml is - also more convenient once you have to tune the Hibernate cache. It is your - choice to use either hibernate.properties or - hibernate.cfg.xml. Both are equivalent, except for the - above mentioned benefits of using the XML syntax. - - With the XML configuration, starting Hibernate is then as simple - as: - - SessionFactory sf = new Configuration().configure().buildSessionFactory(); - - You can select a different XML configuration file using: - - SessionFactory sf = new Configuration() - .configure("catdb.cfg.xml") - .buildSessionFactory(); -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/collection_mapping.xml b/documentation/src/main/docbook/manual-old/en-US/content/collection_mapping.xml deleted file mode 100644 index 877ddcc92457..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/collection_mapping.xml +++ /dev/null @@ -1,1887 +0,0 @@ - - - - - Collection mapping - -
- Persistent collections - - Naturally Hibernate also allows to persist collections. These - persistent collections can contain almost any other Hibernate type, - including: basic types, custom types, components and references to other - entities. The distinction between value and reference semantics is in this - context very important. An object in a collection might be handled with - "value" semantics (its life cycle fully depends on the collection owner), - or it might be a reference to another entity with its own life cycle. In - the latter case, only the "link" between the two objects is considered to - be a state held by the collection. - - As a requirement persistent collection-valued fields must be - declared as an interface type (see ). The actual interface - might be java.util.Set, - java.util.Collection, - java.util.List, java.util.Map, - java.util.SortedSet, - java.util.SortedMap or anything you like ("anything you - like" means you will have to write an implementation of - org.hibernate.usertype.UserCollectionType). - - Notice how in the instance variable - parts was initialized with an instance of - HashSet. This is the best way to initialize collection - valued properties of newly instantiated (non-persistent) instances. When - you make the instance persistent, by calling persist(), - Hibernate will actually replace the HashSet with an - instance of Hibernate's own implementation of Set. Be - aware of the following error: - - - Hibernate uses its own collection implementations - - Cat cat = new DomesticCat(); -Cat kitten = new DomesticCat(); -.... -Set kittens = new HashSet(); -kittens.add(kitten); -cat.setKittens(kittens); -session.persist(cat); - -kittens = cat.getKittens(); // Okay, kittens collection is a Set -(HashSet) cat.getKittens(); // Error! - - - The persistent collections injected by Hibernate behave like - HashMap, HashSet, - TreeMap, TreeSet or - ArrayList, depending on the interface type. - - Collections instances have the usual behavior of value types. They - are automatically persisted when referenced by a persistent object and are - automatically deleted when unreferenced. If a collection is passed from - one persistent object to another, its elements might be moved from one - table to another. Two entities cannot share a reference to the same - collection instance. Due to the underlying relational model, - collection-valued properties do not support null value semantics. - Hibernate does not distinguish between a null collection reference and an - empty collection. - - - Use persistent collections the same way you use ordinary Java - collections. However, ensure you understand the semantics of - bidirectional associations (see ). - -
- -
- How to map collections - - Using annotations you can map Collections, - Lists, Maps and - Sets of associated entities using @OneToMany and - @ManyToMany. For collections of a basic or embeddable type use - @ElementCollection. In the simplest case a collection mapping looks like - this: - - - Collection mapping using @OneToMany and @JoinColumn - - @Entity -public class Product { - - private String serialNumber; - private Set<Part> parts = new HashSet<Part>(); - - @Id - public String getSerialNumber() { return serialNumber; } - void setSerialNumber(String sn) { serialNumber = sn; } - - @OneToMany - @JoinColumn(name="PART_ID") - public Set<Part> getParts() { return parts; } - void setParts(Set parts) { this.parts = parts; } -} - - -@Entity -public class Part { - ... -} - - - Product describes a unidirectional relationship with Part using the - join column PART_ID. In this unidirectional one to many scenario you can - also use a join table as seen in . - - - Collection mapping using @OneToMany and @JoinTable - - @Entity -public class Product { - - private String serialNumber; - private Set<Part> parts = new HashSet<Part>(); - - @Id - public String getSerialNumber() { return serialNumber; } - void setSerialNumber(String sn) { serialNumber = sn; } - - @OneToMany - @JoinTable( - name="PRODUCT_PARTS", - joinColumns = @JoinColumn( name="PRODUCT_ID"), - inverseJoinColumns = @JoinColumn( name="PART_ID") - ) - public Set<Part> getParts() { return parts; } - void setParts(Set parts) { this.parts = parts; } -} - - -@Entity -public class Part { - ... -} - - - Without describing any physical mapping (no - @JoinColumn or @JoinTable), - a unidirectional one to many with join table is used. The table name is - the concatenation of the owner table name, _, and the other side table - name. The foreign key name(s) referencing the owner table is the - concatenation of the owner table, _, and the owner primary key column(s) - name. The foreign key name(s) referencing the other side is the - concatenation of the owner property name, _, and the other side primary - key column(s) name. A unique constraint is added to the foreign key - referencing the other side table to reflect the one to many. - - Lets have a look now how collections are mapped using Hibernate - mapping files. In this case the first step is to chose the right mapping - element. It depends on the type of interface. For example, a - <set> element is used for mapping properties of - type Set. - - - Mapping a Set using <set> - - <class name="Product"> - <id name="serialNumber" column="productSerialNumber"/> - <set name="parts"> - <key column="productSerialNumber" not-null="true"/> - <one-to-many class="Part"/> - </set> -</class> - - - In a - one-to-many association links the - Product and Part entities. This - association requires the existence of a foreign key column and possibly an - index column to the Part table. This mapping loses - certain semantics of normal Java collections: - - - - An instance of the contained entity class cannot belong to more - than one instance of the collection. - - - - An instance of the contained entity class cannot appear at more - than one value of the collection index. - - - - Looking closer at the used <one-to-many> - tag we see that it has the following options. - - - options of <one-to-many> element - - - - - - - - - - - <one-to-many - class="ClassName" - not-found="ignore|exception" - entity-name="EntityName" - node="element-name" - embed-xml="true|false" - /> - - - - class (required): the name of the - associated class. - - - - not-found (optional - defaults to - exception): specifies how cached identifiers - that reference missing rows will be handled. - ignore will treat a missing row as a null - association. - - - - entity-name (optional): the entity name - of the associated class, as an alternative to - class. - - - - - - The <one-to-many> element does not need to - declare any columns. Nor is it necessary to specify the - table name anywhere. - - - If the foreign key column of a - <one-to-many> association is declared - NOT NULL, you must declare the - <key> mapping - not-null="true" or use a bidirectional - association with the collection mapping marked - inverse="true". See . - - - Apart from the <set> tag as shown in , there is also - <list>, <map>, - <bag>, <array> and - <primitive-array> mapping elements. The - <map> element is representative: - - - Elements of the <map> mapping - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <map - name="propertyName" - table="table_name" - schema="schema_name" - lazy="true|extra|false" - inverse="true|false" - cascade="all|none|save-update|delete|all-delete-orphan|delete-orphan" - sort="unsorted|natural|comparatorClass" - order-by="column_name asc|desc" - where="arbitrary sql where condition" - fetch="join|select|subselect" - batch-size="N" - access="field|property|ClassName" - optimistic-lock="true|false" - mutable="true|false" - node="element-name|." - embed-xml="true|false" -> - - <key .... /> - <map-key .... /> - <element .... /> -</map> - - - - name: the collection property name - - - - table (optional - defaults to property - name): the name of the collection table. It is not used for - one-to-many associations. - - - - schema (optional): the name of a table - schema to override the schema declared on the root element - - - - lazy (optional - defaults to - true): disables lazy fetching and specifies - that the association is always eagerly fetched. It can also be - used to enable "extra-lazy" fetching where most operations do not - initialize the collection. This is suitable for large - collections. - - - - inverse (optional - defaults to - false): marks this collection as the "inverse" - end of a bidirectional association. - - - - cascade (optional - defaults to - none): enables operations to cascade to child - entities. - - - - sort (optional): specifies a sorted - collection with natural sort order or a given - comparator class. - - - - order-by (optional): specifies a table - column or columns that define the iteration order of the - Map, Set or bag, together - with an optional asc or - desc. - - - - where (optional): specifies an arbitrary - SQL WHERE condition that is used when - retrieving or removing the collection. This is useful if the - collection needs to contain only a subset of the available - data. - - - - fetch (optional, defaults to - select): chooses between outer-join fetching, - fetching by sequential select, and fetching by sequential - subselect. - - - - batch-size (optional, defaults to - 1): specifies a "batch size" for lazily - fetching instances of this collection. - - - - access (optional - defaults to - property): the strategy Hibernate uses for - accessing the collection property value. - - - - optimistic-lock (optional - defaults to - true): specifies that changes to the state of - the collection results in increments of the owning entity's - version. For one-to-many associations you may want to disable this - setting. - - - - mutable (optional - defaults to - true): a value of false - specifies that the elements of the collection never change. This - allows for minor performance optimization in some cases. - - - - - - After exploring the basic mapping of collections in the preceding - paragraphs we will now focus details like physical mapping considerations, - indexed collections and collections of value types. - -
- Collection foreign keys - - On the database level collection instances are distinguished by - the foreign key of the entity that owns the collection. This foreign key - is referred to as the collection key column, or - columns, of the collection table. The collection key column is mapped by - the @JoinColumn annotation respectively the - <key> XML element. - - There can be a nullability constraint on the foreign key column. - For most collections, this is implied. For unidirectional one-to-many - associations, the foreign key column is nullable by default, so you may - need to specify - - @JoinColumn(nullable=false) - - or - - <key column="productSerialNumber" not-null="true"/> - - The foreign key constraint can use ON DELETE - CASCADE. In XML this can be expressed via: - - <key column="productSerialNumber" on-delete="cascade"/> - - In annotations the Hibernate specific annotation @OnDelete has to - be used. - - @OnDelete(action=OnDeleteAction.CASCADE) - - See for more information - about the <key> element. -
- -
- Indexed collections - - In the following paragraphs we have a closer look at the indexed - collections List and Map - how the their index can be mapped in Hibernate. - -
- Lists - - Lists can be mapped in two different ways: - - - - as ordered lists, where the order is not materialized in the - database - - - - as indexed lists, where the order is materialized in the - database - - - - To order lists in memory, add - @javax.persistence.OrderBy to your property. This - annotation takes as parameter a list of comma separated properties (of - the target entity) and orders the collection accordingly (eg - firstname asc, age desc, weight asc nulls last), if the string - is empty, the collection will be ordered by the primary key of the target - entity. - - - Ordered lists using <classname>@OrderBy</classname> - - @Entity -public class Customer { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - @OneToMany(mappedBy="customer") - @OrderBy("number") - public List<Order> getOrders() { return orders; } - public void setOrders(List<Order> orders) { this.orders = orders; } - private List<Order> orders; -} - -@Entity -public class Order { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - public String getNumber() { return number; } - public void setNumber(String number) { this.number = number; } - private String number; - - @ManyToOne - public Customer getCustomer() { return customer; } - public void setCustomer(Customer customer) { this.customer = customer; } - private Customer number; -} - --- Table schema -|-------------| |----------| -| Order | | Customer | -|-------------| |----------| -| id | | id | -| number | |----------| -| customer_id | -|-------------| - - - To store the index value in a dedicated column, use the - @javax.persistence.OrderColumn annotation on - your property. This annotations describes the column name and - attributes of the column keeping the index value. This column is - hosted on the table containing the association foreign key. If the - column name is not specified, the default is the name of the - referencing property, followed by underscore, followed by - ORDER (in the following example, it would be - orders_ORDER). - - - Explicit index column using - <classname>@OrderColumn</classname> - - @Entity -public class Customer { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - @OneToMany(mappedBy="customer") - @OrderColumn(name="orders_index") - public List<Order> getOrders() { return orders; } - public void setOrders(List<Order> orders) { this.orders = orders; } - private List<Order> orders; -} - -@Entity -public class Order { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - public String getNumber() { return number; } - public void setNumber(String number) { this.number = number; } - private String number; - - @ManyToOne - public Customer getCustomer() { return customer; } - public void setCustomer(Customer customer) { this.customer = customer; } - private Customer number; -} - --- Table schema -|--------------| |----------| -| Order | | Customer | -|--------------| |----------| -| id | | id | -| number | |----------| -| customer_id | -| orders_index | -|--------------| - - - - - We recommend you to convert the legacy @org.hibernate.annotations.IndexColumn - usages to the JPA standard @javax.persistence.OrderColumn. - - - If you are leveraging a custom list index base (maybe currently using the - org.hibernate.annotations.IndexColumn.literal attribute), you can - specify this using the @org.hibernate.annotations.ListIndexBase in conjunction - with @javax.persistence.OrderColumn. The default base is 0 like in Java. - - - - Looking again at the Hibernate mapping file equivalent, the - index of an array or list is always of type integer - and is mapped using the <list-index> element. - The mapped column contains sequential integers that are numbered from - zero by default. - - - index-list element for indexed collections in xml - mapping - - - - - - - - - <list-index - column="column_name" - base="0|1|..."/> - - - - column_name (required): the name of - the column holding the collection index values. - - - - base (optional - defaults to - 0): the value of the index column that - corresponds to the first element of the list or array. - - - - - - If your table does not have an index column, and you still wish - to use List as the property type, you can map the - property as a Hibernate <bag>. A bag does - not retain its order when it is retrieved from the database, but it - can be optionally sorted or ordered. -
- -
- Maps - - The question with Maps is where the key - value is stored. There are several options. Maps can borrow their keys - from one of the associated entity properties or have dedicated columns - to store an explicit key. - - To use one of the target entity property as a key of the map, - use @MapKey(name="myProperty"), where - myProperty is a property name in the target entity. - When using @MapKey without the name attribute, the - target entity primary key is used. The map key uses the same column as - the property pointed out. There is no additional column defined to - hold the map key, because the map key represent a target property. Be - aware that once loaded, the key is no longer kept in sync with the - property. In other words, if you change the property value, the key - will not change automatically in your Java model. - - - Use of target entity property as map key via - <classname>@MapKey</classname> - - @Entity -public class Customer { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - @OneToMany(mappedBy="customer") - @MapKey(name="number") - public Map<String,Order> getOrders() { return orders; } - public void setOrders(Map<String,Order> order) { this.orders = orders; } - private Map<String,Order> orders; -} - -@Entity -public class Order { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - public String getNumber() { return number; } - public void setNumber(String number) { this.number = number; } - private String number; - - @ManyToOne - public Customer getCustomer() { return customer; } - public void setCustomer(Customer customer) { this.customer = customer; } - private Customer number; -} - --- Table schema -|-------------| |----------| -| Order | | Customer | -|-------------| |----------| -| id | | id | -| number | |----------| -| customer_id | -|-------------| - - - Alternatively the map key is mapped to a dedicated column or - columns. In order to customize the mapping use one of the following - annotations: - - - - @MapKeyColumn if the map key is a - basic type. If you don't specify the column name, the name of the - property followed by underscore followed by KEY - is used (for example orders_KEY). - - - - @MapKeyEnumerated / - @MapKeyTemporal if the map key type is - respectively an enum or a Date. - - - - @MapKeyJoinColumn/@MapKeyJoinColumns - if the map key type is another entity. - - - - @AttributeOverride/@AttributeOverrides - when the map key is a embeddable object. Use - key. as a prefix for your embeddable object - property names. - - - - You can also use @MapKeyClass to define - the type of the key if you don't use generics. - - - Map key as basic type using - <classname>@MapKeyColumn</classname> - - @Entity -public class Customer { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - @OneToMany @JoinTable(name="Cust_Order") - @MapKeyColumn(name="orders_number") - public Map<String,Order> getOrders() { return orders; } - public void setOrders(Map<String,Order> orders) { this.orders = orders; } - private Map<String,Order> orders; -} - -@Entity -public class Order { - @Id @GeneratedValue public Integer getId() { return id; } - public void setId(Integer id) { this.id = id; } - private Integer id; - - public String getNumber() { return number; } - public void setNumber(String number) { this.number = number; } - private String number; - - @ManyToOne - public Customer getCustomer() { return customer; } - public void setCustomer(Customer customer) { this.customer = customer; } - private Customer number; -} - --- Table schema -|-------------| |----------| |---------------| -| Order | | Customer | | Cust_Order | -|-------------| |----------| |---------------| -| id | | id | | customer_id | -| number | |----------| | order_id | -| customer_id | | orders_number | -|-------------| |---------------| - - - - We recommend you to migrate from - @org.hibernate.annotations.MapKey / - @org.hibernate.annotation.MapKeyManyToMany to - the new standard approach described above - - - Using Hibernate mapping files there exists equivalent concepts - to the descibed annotations. You have to use - <map-key>, - <map-key-many-to-many> and - <composite-map-key>. - <map-key> is used for any basic type, - <map-key-many-to-many> for an entity - reference and <composite-map-key> for a - composite type. - - - map-key xml mapping element - - - - - - - - - - - <map-key - column="column_name" - formula="any SQL expression" - type="type_name" - node="@attribute-name" - length="N"/> - - - - column (optional): the name of the - column holding the collection index values. - - - - formula (optional): a SQL formula - used to evaluate the key of the map. - - - - type (required): the type of the map - keys. - - - - - - - map-key-many-to-many - - - - - - - - - - - <map-key-many-to-many - column="column_name" - formula="any SQL expression" - class="ClassName" -/> - - - - column (optional): the name of the - foreign key column for the collection index values. - - - - formula (optional): a SQ formula used - to evaluate the foreign key of the map key. - - - - class (required): the entity class - used as the map key. - - - - -
-
- -
- Collections of basic types and embeddable objects - - In some situations you don't need to associate two entities but - simply create a collection of basic types or embeddable objects. Use the - @ElementCollection for this case. - - - Collection of basic types mapped via - <classname>@ElementCollection</classname> - - @Entity -public class User { - [...] - public String getLastname() { ...} - - @ElementCollection - @CollectionTable(name="Nicknames", joinColumns=@JoinColumn(name="user_id")) - @Column(name="nickname") - public Set<String> getNicknames() { ... } -} - - - The collection table holding the collection data is set using the - @CollectionTable annotation. If omitted the - collection table name defaults to the concatenation of the name of the - containing entity and the name of the collection attribute, separated by - an underscore. In our example, it would be - User_nicknames. - - The column holding the basic type is set using the - @Column annotation. If omitted, the column name - defaults to the property name: in our example, it would be - nicknames. - - But you are not limited to basic types, the collection type can be - any embeddable object. To override the columns of the embeddable object - in the collection table, use the - @AttributeOverride annotation. - - - @ElementCollection for embeddable objects - - @Entity -public class User { - [...] - public String getLastname() { ...} - - @ElementCollection - @CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id")) - @AttributeOverrides({ - @AttributeOverride(name="street1", column=@Column(name="fld_street")) - }) - public Set<Address> getAddresses() { ... } -} - -@Embeddable -public class Address { - public String getStreet1() {...} - [...] -} - - - Such an embeddable object cannot contains a collection - itself. - - - in @AttributeOverride, you must use the - value. prefix to override properties of the - embeddable object used in the map value and the - key. prefix to override properties of the - embeddable object used in the map key. - - @Entity -public class User { - @ElementCollection - @AttributeOverrides({ - @AttributeOverride(name="key.street1", column=@Column(name="fld_street")), - @AttributeOverride(name="value.stars", column=@Column(name="fld_note")) - }) - public Map<Address,Rating> getFavHomes() { ... } - - - - We recommend you to migrate from - @org.hibernate.annotations.CollectionOfElements - to the new @ElementCollection - annotation. - - - Using the mapping file approach a collection of values is mapped - using the <element> tag. For example: - - - <element> tag for collection values using mapping - files - - - - - - - - - - - <element - column="column_name" - formula="any SQL expression" - type="typename" - length="L" - precision="P" - scale="S" - not-null="true|false" - unique="true|false" - node="element-name" -/> - - - - column (optional): the name of the - column holding the collection element values. - - - - formula (optional): an SQL formula used - to evaluate the element. - - - - type (required): the type of the - collection element. - - - - -
-
- -
- Advanced collection mappings - -
- Sorted collections - - Hibernate supports collections implementing - java.util.SortedMap and - java.util.SortedSet. With annotations you declare a - sort comparator using @Sort. You chose between the - comparator types unsorted, natural or custom. If you want to use your - own comparator implementation, you'll also have to specify the - implementation class using the comparator attribute. - Note that you need to use either a SortedSet or a - SortedMap interface. - - - Sorted collection with @Sort - - @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) -@JoinColumn(name="CUST_ID") -@Sort(type = SortType.COMPARATOR, comparator = TicketComparator.class) -public SortedSet<Ticket> getTickets() { - return tickets; -} - - - Using Hibernate mapping files you specify a comparator in the - mapping file with <sort>: - - - Sorted collection using xml mapping - - <set name="aliases" - table="person_aliases" - sort="natural"> - <key column="person"/> - <element column="name" type="string"/> -</set> - -<map name="holidays" sort="my.custom.HolidayComparator"> - <key column="year_id"/> - <map-key column="hol_name" type="string"/> - <element column="hol_date" type="date"/> -</map> - - - Allowed values of the sort attribute are - unsorted, natural and the name of - a class implementing java.util.Comparator. - - - Sorted collections actually behave like - java.util.TreeSet or - java.util.TreeMap. - - - If you want the database itself to order the collection elements, - use the order-by attribute of set, - bag or map mappings. This solution - is implemented using LinkedHashSet or - LinkedHashMap and performs the ordering in the SQL - query and not in the memory. - - - Sorting in database using order-by - - <set name="aliases" table="person_aliases" order-by="lower(name) asc"> - <key column="person"/> - <element column="name" type="string"/> -</set> - -<map name="holidays" order-by="hol_date, hol_name"> - <key column="year_id"/> - <map-key column="hol_name" type="string"/> - <element column="hol_date type="date"/> -</map> - - - - Note - - The value of the order-by attribute is an SQL - ordering, not an HQL ordering. - - - Associations can even be sorted by arbitrary criteria at runtime - using a collection filter(): - - - Sorting via a query filter - - sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list(); - -
- -
- Bidirectional associations - - A bidirectional association allows navigation - from both "ends" of the association. Two kinds of bidirectional - association are supported: - - one-to-many - - - set or bag valued at one end and single-valued at the - other - - - - - many-to-many - - - set or bag valued at both ends - - - - - Often there exists a many to one association which is the owner - side of a bidirectional relationship. The corresponding one to many - association is in this case annotated by - @OneToMany(mappedBy=...) - - - Bidirectional one to many with many to one side as association - owner - - @Entity -public class Troop { - @OneToMany(mappedBy="troop") - public Set<Soldier> getSoldiers() { - ... -} - -@Entity -public class Soldier { - @ManyToOne - @JoinColumn(name="troop_fk") - public Troop getTroop() { - ... -} - - - Troop has a bidirectional one to many - relationship with Soldier through the - troop property. You don't have to (must not) define - any physical mapping in the mappedBy side. - - To map a bidirectional one to many, with the one-to-many side as - the owning side, you have to remove the mappedBy - element and set the many to one @JoinColumn as - insertable and updatable to false. This solution is not optimized and - will produce additional UPDATE statements. - - - Bidirectional association with one to many side as - owner - - @Entity -public class Troop { - @OneToMany - @JoinColumn(name="troop_fk") //we need to duplicate the physical information - public Set<Soldier> getSoldiers() { - ... -} - -@Entity -public class Soldier { - @ManyToOne - @JoinColumn(name="troop_fk", insertable=false, updatable=false) - public Troop getTroop() { - ... -} - - - How does the mappping of a bidirectional mapping look like in - Hibernate mapping xml? There you define a bidirectional one-to-many - association by mapping a one-to-many association to the same table - column(s) as a many-to-one association and declaring the many-valued end - inverse="true". - - - Bidirectional one to many via Hibernate mapping files - - <class name="Parent"> - <id name="id" column="parent_id"/> - .... - <set name="children" inverse="true"> - <key column="parent_id"/> - <one-to-many class="Child"/> - </set> -</class> - -<class name="Child"> - <id name="id" column="child_id"/> - .... - <many-to-one name="parent" - class="Parent" - column="parent_id" - not-null="true"/> -</class> - - - Mapping one end of an association with - inverse="true" does not affect the operation of - cascades as these are orthogonal concepts. - - A many-to-many association is defined logically using the - @ManyToMany annotation. You also have to describe the - association table and the join conditions using the - @JoinTable annotation. If the association is - bidirectional, one side has to be the owner and one side has to be the - inverse end (ie. it will be ignored when updating the relationship - values in the association table): - - - Many to many association via @ManyToMany - - @Entity -public class Employer implements Serializable { - @ManyToMany( - targetEntity=org.hibernate.test.metadata.manytomany.Employee.class, - cascade={CascadeType.PERSIST, CascadeType.MERGE} - ) - @JoinTable( - name="EMPLOYER_EMPLOYEE", - joinColumns=@JoinColumn(name="EMPER_ID"), - inverseJoinColumns=@JoinColumn(name="EMPEE_ID") - ) - public Collection getEmployees() { - return employees; - } - ... -} - - @Entity -public class Employee implements Serializable { - @ManyToMany( - cascade = {CascadeType.PERSIST, CascadeType.MERGE}, - mappedBy = "employees", - targetEntity = Employer.class - ) - public Collection getEmployers() { - return employers; - } -} - - - In this example @JoinTable defines a - name, an array of join columns, and an array of - inverse join columns. The latter ones are the columns of the association - table which refer to the Employee primary key - (the "other side"). As seen previously, the other side don't have to - (must not) describe the physical mapping: a simple - mappedBy argument containing the owner side property - name bind the two. - - As any other annotations, most values are guessed in a many to - many relationship. Without describing any physical mapping in a - unidirectional many to many the following rules applied. The table name - is the concatenation of the owner table name, _ and the - other side table name. The foreign key name(s) referencing the owner - table is the concatenation of the owner table name, _ - and the owner primary key column(s). The foreign key name(s) referencing - the other side is the concatenation of the owner property name, - _, and the other side primary key column(s). These are - the same rules used for a unidirectional one to many - relationship. - - - Default values for <classname>@ManyToMany</classname> - (uni-directional) - - @Entity -public class Store { - @ManyToMany(cascade = CascadeType.PERSIST) - public Set<City> getImplantedIn() { - ... - } -} - -@Entity -public class City { - ... //no bidirectional relationship -} - - - A Store_City is used as the join table. The - Store_id column is a foreign key to the - Store table. The implantedIn_id - column is a foreign key to the City table. - - Without describing any physical mapping in a bidirectional many to - many the following rules applied. The table name is the concatenation of - the owner table name, _ and the other side table name. - The foreign key name(s) referencing the owner table is the concatenation - of the other side property name, _, and the owner - primary key column(s). The foreign key name(s) referencing the other - side is the concatenation of the owner property name, - _, and the other side primary key column(s). These are - the same rules used for a unidirectional one to many - relationship. - - - Default values for <classname>@ManyToMany</classname> - (bi-directional) - - @Entity -public class Store { - @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) - public Set<Customer> getCustomers() { - ... - } -} - -@Entity -public class Customer { - @ManyToMany(mappedBy="customers") - public Set<Store> getStores() { - ... - } -} - - - A Store_Customer is used as the join table. The - stores_id column is a foreign key to the - Store table. The customers_id - column is a foreign key to the Customer table. - - Using Hibernate mapping files you can map a bidirectional - many-to-many association by mapping two many-to-many associations to the - same database table and declaring one end as - inverse. - You cannot select an indexed collection. - - - shows a - bidirectional many-to-many association that illustrates how each - category can have many items and each item can be in many - categories: - - - Many to many association using Hibernate mapping files - - <class name="Category"> - <id name="id" column="CATEGORY_ID"/> - ... - <bag name="items" table="CATEGORY_ITEM"> - <key column="CATEGORY_ID"/> - <many-to-many class="Item" column="ITEM_ID"/> - </bag> -</class> - -<class name="Item"> - <id name="id" column="ITEM_ID"/> - ... - - <!-- inverse end --> - <bag name="categories" table="CATEGORY_ITEM" inverse="true"> - <key column="ITEM_ID"/> - <many-to-many class="Category" column="CATEGORY_ID"/> - </bag> -</class> - - - Changes made only to the inverse end of the association are - not persisted. This means that Hibernate has two - representations in memory for every bidirectional association: one link - from A to B and another link from B to A. This is easier to understand - if you think about the Java object model and how a many-to-many - relationship in Javais created: - - - Effect of inverse vs. non-inverse side of many to many - associations - - category.getItems().add(item); // The category now "knows" about the relationship -item.getCategories().add(category); // The item now "knows" about the relationship - -session.persist(item); // The relationship won't be saved! -session.persist(category); // The relationship will be saved - - - The non-inverse side is used to save the in-memory representation - to the database. -
- -
- Bidirectional associations with indexed collections - - There are some additional considerations for bidirectional - mappings with indexed collections (where one end is represented as a - <list> or <map>) when - using Hibernate mapping files. If there is a property of the child class - that maps to the index column you can use - inverse="true" on the collection mapping: - - - Bidirectional association with indexed collection - - <class name="Parent"> - <id name="id" column="parent_id"/> - .... - <map name="children" inverse="true"> - <key column="parent_id"/> - <map-key column="name" - type="string"/> - <one-to-many class="Child"/> - </map> -</class> - -<class name="Child"> - <id name="id" column="child_id"/> - .... - <property name="name" - not-null="true"/> - <many-to-one name="parent" - class="Parent" - column="parent_id" - not-null="true"/> -</class> - - - If there is no such property on the child class, the association - cannot be considered truly bidirectional. That is, there is information - available at one end of the association that is not available at the - other end. In this case, you cannot map the collection - inverse="true". Instead, you could use the following - mapping: - - - Bidirectional association with indexed collection, but no index - column - - <class name="Parent"> - <id name="id" column="parent_id"/> - .... - <map name="children"> - <key column="parent_id" - not-null="true"/> - <map-key column="name" - type="string"/> - <one-to-many class="Child"/> - </map> -</class> - -<class name="Child"> - <id name="id" column="child_id"/> - .... - <many-to-one name="parent" - class="Parent" - column="parent_id" - insert="false" - update="false" - not-null="true"/> -</class> - - - Note that in this mapping, the collection-valued end of the - association is responsible for updates to the foreign key. -
- -
- Ternary associations - - There are three possible approaches to mapping a ternary - association. One approach is to use a Map with an - association as its index: - - - Ternary association mapping - - @Entity -public class Company { - @Id - int id; - ... - @OneToMany // unidirectional - @MapKeyJoinColumn(name="employee_id") - Map<Employee, Contract> contracts; -} - -// or - -<map name="contracts"> - <key column="employer_id" not-null="true"/> - <map-key-many-to-many column="employee_id" class="Employee"/> - <one-to-many class="Contract"/> -</map> - - - A second approach is to remodel the association as an entity - class. This is the most common approach. A final alternative is to use - composite elements, which will be discussed later. -
- -
- <literal>Using an <idbag></literal> - - The majority of the many-to-many associations and collections of - values shown previously all map to tables with composite keys, even - though it has been suggested that entities should have synthetic - identifiers (surrogate keys). A pure association table does not seem to - benefit much from a surrogate key, although a collection of composite - values might. For this reason Hibernate provides a - feature that allows you to map many-to-many associations and collections - of values to a table with a surrogate key. - - The <idbag> element lets you map a - List (or Collection) with bag - semantics. For example: - - <idbag name="lovers" table="LOVERS"> - <collection-id column="ID" type="long"> - <generator class="sequence"/> - </collection-id> - <key column="PERSON1"/> - <many-to-many column="PERSON2" class="Person" fetch="join"/> -</idbag> - - An <idbag> has a synthetic id generator, - just like an entity class. A different surrogate key is assigned to each - collection row. Hibernate does not, however, provide any mechanism for - discovering the surrogate key value of a particular row. - - The update performance of an <idbag> - supersedes a regular <bag>. Hibernate can - locate individual rows efficiently and update or delete them - individually, similar to a list, map or set. - - In the current implementation, the native - identifier generation strategy is not supported for - <idbag> collection identifiers. -
-
- - - - - -
- Collection examples - - This section covers collection examples. - - The following class has a collection of Child - instances: - - - Example classes <classname>Parent</classname> and - <classname>Child</classname> - - public class Parent { - private long id; - private Set<Child> children; - - // getter/setter - ... -} - - -public class Child { - private long id; - private String name - - - // getter/setter - ... -} - - - If each child has, at most, one parent, the most natural mapping is - a one-to-many association: - - - One to many unidirectional <classname>Parent-Child</classname> - relationship using annotations - - public class Parent { - @Id - @GeneratedValue - private long id; - - @OneToMany - private Set<Child> children; - - // getter/setter - ... -} - - -public class Child { - @Id - @GeneratedValue - private long id; - private String name; - - - // getter/setter - ... -} - - - - One to many unidirectional <classname>Parent-Child</classname> - relationship using mapping files - - <hibernate-mapping> - - <class name="Parent"> - <id name="id"> - <generator class="sequence"/> - </id> - <set name="children"> - <key column="parent_id"/> - <one-to-many class="Child"/> - </set> - </class> - - <class name="Child"> - <id name="id"> - <generator class="sequence"/> - </id> - <property name="name"/> - </class> - -</hibernate-mapping> - - - This maps to the following table definitions: - - - Table definitions for unidirectional - <classname>Parent</classname>-<classname>Child</classname> - relationship - - create table parent ( id bigint not null primary key ) -create table child ( id bigint not null primary key, name varchar(255), parent_id bigint ) -alter table child add constraint childfk0 (parent_id) references parent - - - If the parent is required, use a bidirectional - one-to-many association: - - - One to many bidirectional <classname>Parent-Child</classname> - relationship using annotations - - public class Parent { - @Id - @GeneratedValue - private long id; - - @OneToMany(mappedBy="parent") - private Set<Child> children; - - // getter/setter - ... -} - - -public class Child { - @Id - @GeneratedValue - private long id; - - private String name; - - @ManyToOne - private Parent parent; - - - // getter/setter - ... -} - - - - One to many bidirectional <classname>Parent-Child</classname> - relationship using mapping files - - <hibernate-mapping> - - <class name="Parent"> - <id name="id"> - <generator class="sequence"/> - </id> - <set name="children" inverse="true"> - <key column="parent_id"/> - <one-to-many class="Child"/> - </set> - </class> - - <class name="Child"> - <id name="id"> - <generator class="sequence"/> - </id> - <property name="name"/> - <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/> - </class> - -</hibernate-mapping> - - - Notice the NOT NULL constraint: - - - Table definitions for bidirectional - <classname>Parent</classname>-<classname>Child</classname> - relationship - - create table parent ( id bigint not null primary key ) -create table child ( id bigint not null - primary key, - name varchar(255), - parent_id bigint not null ) -alter table child add constraint childfk0 (parent_id) references parent - - - Alternatively, if this association must be unidirectional you can - enforce the NOT NULL constraint. - - - Enforcing NOT NULL constraint in unidirectional relation using - annotations - - public class Parent { - @Id - @GeneratedValue - private long id; - - @OneToMany(optional=false) - private Set<Child> children; - - // getter/setter - ... -} - - -public class Child { - @Id - @GeneratedValue - private long id; - private String name; - - - // getter/setter - ... -} - - - - Enforcing NOT NULL constraint in unidirectional relation using - mapping files - - <hibernate-mapping> - - <class name="Parent"> - <id name="id"> - <generator class="sequence"/> - </id> - <set name="children"> - <key column="parent_id" not-null="true"/> - <one-to-many class="Child"/> - </set> - </class> - - <class name="Child"> - <id name="id"> - <generator class="sequence"/> - </id> - <property name="name"/> - </class> - -</hibernate-mapping> - - - On the other hand, if a child has multiple parents, a many-to-many - association is appropriate. - - - Many to many <classname>Parent-Child</classname> relationship - using annotations - - public class Parent { - @Id - @GeneratedValue - private long id; - - @ManyToMany - private Set<Child> children; - - // getter/setter - ... -} - - -public class Child { - @Id - @GeneratedValue - private long id; - - private String name; - - - // getter/setter - ... -} - - - - Many to many <classname>Parent-Child</classname> relationship - using mapping files - - <hibernate-mapping> - - <class name="Parent"> - <id name="id"> - <generator class="sequence"/> - </id> - <set name="children" table="childset"> - <key column="parent_id"/> - <many-to-many class="Child" column="child_id"/> - </set> - </class> - - <class name="Child"> - <id name="id"> - <generator class="sequence"/> - </id> - <property name="name"/> - </class> - -</hibernate-mapping> - - - Table definitions: - - - Table definitions for many to many releationship - - create table parent ( id bigint not null primary key ) -create table child ( id bigint not null primary key, name varchar(255) ) -create table childset ( parent_id bigint not null, - child_id bigint not null, - primary key ( parent_id, child_id ) ) -alter table childset add constraint childsetfk0 (parent_id) references parent -alter table childset add constraint childsetfk1 (child_id) references child - - - For more examples and a complete explanation of a parent/child - relationship mapping, see for more - information. Even more complex association mappings are covered in the - next chapter. -
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/component_mapping.xml b/documentation/src/main/docbook/manual-old/en-US/content/component_mapping.xml deleted file mode 100644 index c15aa919cd27..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/component_mapping.xml +++ /dev/null @@ -1,419 +0,0 @@ - - - - - Component Mapping - - - The notion of a component is re-used in several different contexts and purposes - throughout Hibernate. - - -
- Dependent objects - - - A component is a contained object that is persisted as a value type and not an entity - reference. The term "component" refers to the object-oriented notion of composition - and not to architecture-level components. For example, you can model a person like this: - - - - - - - - Now Name can be persisted as a component of - Person. Name defines getter - and setter methods for its persistent properties, but it does not need to declare - any interfaces or identifier properties. - - - - Our Hibernate mapping would look like this: - - - - - - - - - - - - -]]> - - - The person table would have the columns pid, - birthday, - initial, - first and - last. - - - - Like value types, components do not support shared references. In other words, two - persons could have the same name, but the two person objects would contain two independent - name objects that were only "the same" by value. The null value semantics of a component are - ad hoc. When reloading the containing object, Hibernate will assume - that if all component columns are null, then the entire component is null. This is suitable for most purposes. - - - - The properties of a component can be of any Hibernate type (collections, many-to-one - associations, other components, etc). Nested components should not - be considered an exotic usage. Hibernate is intended to support a fine-grained - object model. - - - - The <component> element allows a <parent> - subelement that maps a property of the component class as a reference back to the - containing entity. - - - - - - - - - - - - - -]]> - -
- -
- Collections of dependent objects - - - Collections of components are supported (e.g. an array of type - Name). Declare your component collection by - replacing the <element> tag with a - <composite-element> tag: - - - - - - - - - -]]> - - - - If you define a Set of composite elements, it is - important to implement equals() and - hashCode() correctly. - - - - - Composite elements can contain components but not collections. If your - composite element contains - components, use the <nested-composite-element> - tag. This case is a collection of components which - themselves have components. You may want to consider if - a one-to-many association is more appropriate. Remodel the - composite element as an entity, but be aware that even though the Java model - is the same, the relational model and persistence semantics are still - slightly different. - - - - A composite element mapping does not support null-able properties - if you are using a <set>. There is no separate primary key column - in the composite element table. Hibernate - uses each column's value to identify a record when deleting objects, - which is not possible with null values. You have to either use only - not-null properties in a composite-element or choose a - <list>, <map>, - <bag> or <idbag>. - - - - A special case of a composite element is a composite element with a nested - <many-to-one> element. This mapping allows - you to map extra columns of a many-to-many association table to the - composite element class. The following is a many-to-many association - from Order to Item, where - purchaseDate, price and - quantity are properties of the association: - - - - .... - - - - - - - - - -]]> - - - There cannot be a reference to the purchase on the other side for - bidirectional association navigation. Components are value types and - do not allow shared references. A single Purchase can be in the - set of an Order, but it cannot be referenced by the Item - at the same time. - - - Even ternary (or quaternary, etc) associations are possible: - - - .... - - - - - - - -]]> - - - Composite elements can appear in queries using the same syntax as - associations to other entities. - - -
- -
- Components as Map indices - - - The <composite-map-key> element allows you to map a - component class as the key of a Map. Ensure that you override - hashCode() and equals() correctly on - the component class. - -
- -
- Components as composite identifiers - - - You can use a component as an identifier of an entity class. Your component - class must satisfy certain requirements: - - - - - - It must implement java.io.Serializable. - - - - - It must re-implement equals() and - hashCode() consistently with the database's - notion of composite key equality. - - - - - - Note - - In Hibernate, although the second requirement is not an absolutely hard - requirement of Hibernate, it is recommended. - - - - - You cannot use an IdentifierGenerator to generate composite keys. - Instead the application must assign its own identifiers. - - - - Use the <composite-id> tag, with nested - <key-property> elements, in place of the usual - <id> declaration. For example, the - OrderLine class has a primary key that depends upon - the (composite) primary key of Order. - - - - - - - - - - - - - - - - - .... - -]]> - - - Any foreign keys referencing the OrderLine table are now - composite. Declare this in your mappings for other classes. An association - to OrderLine is mapped like this: - - - - - - - -]]> - - - - The column element is an alternative to the - column attribute everywhere. Using the - column element just gives more declaration - options, which are mostly useful when utilizing - hbm2ddl - - - - - A many-to-many association to OrderLine also - uses the composite foreign key: - - - - - - - - - -]]> - - - The collection of OrderLines in Order would - use: - - - - - - - - -]]> - - - The <one-to-many> element declares no columns. - - - - If OrderLine itself owns a collection, it also has a composite - foreign key. - - - - .... - .... - - - - - - - - - ... - - -]]> - -
- -
- Dynamic components - - - You can also map a property of type Map: - - - - - - -]]> - - - The semantics of a <dynamic-component> mapping are identical - to <component>. The advantage of this kind of mapping is - the ability to determine the actual properties of the bean at deployment time just - by editing the mapping document. Runtime manipulation of the mapping document is - also possible, using a DOM parser. You can also access, and change, Hibernate's - configuration-time metamodel via the Configuration object. - - -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/events.xml b/documentation/src/main/docbook/manual-old/en-US/content/events.xml deleted file mode 100755 index ec167f3f1e8d..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/events.xml +++ /dev/null @@ -1,229 +0,0 @@ - - - - - Interceptors and events - - - It is useful for the application to react to certain events that occur - inside Hibernate. This allows for the implementation of generic - functionality and the extension of Hibernate functionality. - - -
- Interceptors - - - The Interceptor interface provides callbacks from the session to the - application, allowing the application to inspect and/or manipulate properties of a - persistent object before it is saved, updated, deleted or loaded. One - possible use for this is to track auditing information. For example, the following - Interceptor automatically sets the createTimestamp - when an Auditable is created and updates the - lastUpdateTimestamp property when an Auditable is - updated. - - - - You can either implement Interceptor directly or extend - EmptyInterceptor. - - - - - - There are two kinds of inteceptors: Session-scoped and - SessionFactory-scoped. - - - - A Session-scoped interceptor is specified - when a session is opened. - - - - - - A SessionFactory-scoped interceptor is registered with the Configuration - object prior to building the SessionFactory. Unless - a session is opened explicitly specifying the interceptor to use, the supplied interceptor - will be applied to all sessions opened from that SessionFactory. SessionFactory-scoped - interceptors must be thread safe. Ensure that you do not store session-specific states, since multiple - sessions will use this interceptor potentially concurrently. - - - - -
- -
- Event system - - - If you have to react to particular events in your persistence layer, you can - also use the Hibernate event architecture. The event - system can be used in addition, or as a replacement, for interceptors. - - - - Many methods of the Session interface correlate to an event type. The - full range of defined event types is declared as enum values on - org.hibernate.event.spi.EventType. When a request is made of one of - these methods, the Hibernate Session generates an appropriate - event and passes it to the configured event listeners for that type. Out-of-the-box, - these listeners implement the same processing in which those methods always resulted. - However, you are free to implement a customization of one of the listener interfaces - (i.e., the LoadEvent is processed by the registered implementation - of the LoadEventListener interface), in which case their - implementation would be responsible for processing any load() requests - made of the Session. - - - - - See the Hibernate Developer Guide for information on registering - custom event listeners. - - - - - The listeners should be considered stateless; they are shared between requests, and should not save any - state as instance variables. - - - - A custom listener implements the appropriate interface for the event it wants to - process and/or extend one of the convenience base classes (or even the default event - listeners used by Hibernate out-of-the-box as these are declared non-final for this - purpose). Here is an example of a custom load event listener: - - - - -
- -
- Hibernate declarative security - - Usually, declarative security in Hibernate applications is managed in a session facade - layer. Hibernate allows certain actions to be permissioned via JACC, and authorized - via JAAS. This is an optional functionality that is built on top of the event architecture. - - - - First, you must configure the appropriate event listeners, to enable the use of JACC - authorization. Again, see Hibernate Developer Guide - for the details. Below is an example of an appropriate - org.hibernate.integrator.spi.Integrator implementation for this purpose. - - - - - - You must also decide how to configure your JACC provider. One option is to tell Hibernate what permissions - to bind to what roles and have it configure the JACC provider. This would be done in the - hibernate.cfg.xml file. - - - -]]> - - -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/example_mappings.xml b/documentation/src/main/docbook/manual-old/en-US/content/example_mappings.xml deleted file mode 100644 index c541c2503eaf..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/example_mappings.xml +++ /dev/null @@ -1,668 +0,0 @@ - - - - - - Example: Various Mappings - - - - This chapters explores some more complex association mappings. - - -
- Employer/Employee - - - The following model of the relationship between Employer and - Employee uses an entity class (Employment) - to represent the association. You can do this when there might be more than one - period of employment for the same two parties. Components are used to model monetary - values and employee names. - - - - - - - - - - - - - Here is a possible mapping document: - - - - - - - - employer_id_seq - - - - - - - - - - employment_id_seq - - - - - - - - - - - - - - - - - - - - - employee_id_seq - - - - - - - - - - -]]> - - - Here is the table schema generated by SchemaExport. - - - - -
- -
- Author/Work - - - Consider the following model of the relationships between Work, - Author and Person. In the example, the relationship - between Work and Author is represented as a many-to-many - association and the relationship between Author - and Person is represented as one-to-one association. Another possibility would be to - have Author extend Person. - - - - - - - - - - - - - The following mapping document correctly represents these relationships: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - - - There are four tables in this mapping: works, - authors and persons hold work, author - and person data respectively. author_work is an association - table linking authors to works. Here is the table schema, as generated by - SchemaExport: - - - - -
- -
- Customer/Order/Product - - - In this section we consider a model of the relationships between Customer, - Order, Line Item and Product. - There is a one-to-many association between Customer and - Order, but how can you represent Order / - LineItem / Product? In the example, - LineItem is mapped as an association class representing the many-to-many - association between Order and Product. In - Hibernate this is called a composite element. - - - - - - - - - - - - - The mapping document will look like this: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - - - customers, orders, line_items and - products hold customer, order, order line item and product data - respectively. line_items also acts as an association table linking - orders with products. - - - - -
- -
- Miscellaneous example mappings - - - These examples are available from the Hibernate test suite. You - will find many other useful example mappings there by searching in the - test folder of the Hibernate distribution. - - - - -
- "Typed" one-to-one association - - - - name - 'HOME' - - - name - 'MAILING' - - - - - - - - - - - -]]> -
- -
- Composite key example - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ( select sum(li.quantity*p.price) - from LineItem li, Product p - where li.productId = p.productId - and li.customerId = customerId - and li.orderNumber = orderNumber ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ( select sum(li.quantity) - from LineItem li - where li.productId = productId ) - - - -]]> -
- -
- Many-to-many with shared composite key attribute - - - - - - - - - - - - - org - - - - - - - - - - - - - - - - - - org - - - -]]> -
- -
- Content based discrimination - - - - - - - - - - case - when title is not null then 'E' - when salesperson is not null then 'C' - else 'P' - end - - - - - - - - - - - - - - - - - - - - - - - - -]]> -
- -
- Associations on alternate keys - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> -
- -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/example_parentchild.xml b/documentation/src/main/docbook/manual-old/en-US/content/example_parentchild.xml deleted file mode 100644 index 7e9712ea6638..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/example_parentchild.xml +++ /dev/null @@ -1,309 +0,0 @@ - - - - - Example: Parent/Child - - - One of the first things that new users want to do with Hibernate is to model a parent/child type - relationship. There are two different approaches to this. The most convenient - approach, especially for new users, is to model both Parent and Child - as entity classes with a <one-to-many> association from Parent - to Child. The alternative approach is to declare the Child as a - <composite-element>. The default semantics of a one-to-many - association in Hibernate are much less close to the usual semantics of a parent/child relationship than - those of a composite element mapping. We will explain how to use a bidirectional one-to-many - association with cascades to model a parent/child relationship efficiently and elegantly. - - - -
- A note about collections - - - Hibernate collections are considered to be a logical part of their owning entity and not of the - contained entities. Be aware that this is a critical distinction that has the following consequences: - - - - - - When you remove/add an object from/to a collection, the version number of the collection owner - is incremented. - - - - - If an object that was removed from a collection is an instance of a value type (e.g. a composite - element), that object will cease to be persistent and its state will be completely removed from - the database. Likewise, adding a value type instance to the collection will cause its state to be - immediately persistent. - - - - - Conversely, if an entity is removed from a collection (a one-to-many or many-to-many - association), it will not be deleted by default. This behavior is completely consistent; a - change to the internal state of another entity should not cause the associated entity to vanish. - Likewise, adding an entity to a collection does not cause that entity to become persistent, by - default. - - - - - - Adding an entity to a collection, by default, merely creates a link between - the two entities. Removing the entity will remove the link. This is appropriate for all sorts of cases. - However, it is not appropriate in the case of a parent/child relationship. In this case, the life of the - child is bound to the life cycle of the parent. - - -
- -
- Bidirectional one-to-many - - - Suppose we start with a simple <one-to-many> association from - Parent to Child. - - - - - -]]> - - - If we were to execute the following code: - - - - - - Hibernate would issue two SQL statements: - - - - - an INSERT to create the record for c - - - - an UPDATE to create the link from p to - c - - - - - - This is not only inefficient, but also violates any NOT NULL constraint on the - parent_id column. You can fix the nullability constraint violation by specifying - not-null="true" in the collection mapping: - - - - - -]]> - - - However, this is not the recommended solution. - - - The underlying cause of this behavior is that the link (the foreign key parent_id) - from p to c is not considered part of the state of the - Child object and is therefore not created in the INSERT. The - solution is to make the link part of the Child mapping. - - - ]]> - - - You also need to add the parent property to the Child class. - - - - Now that the Child entity is managing the state of the link, we tell the collection - not to update the link. We use the inverse attribute to do this: - - - - - -]]> - - - The following code would be used to add a new Child: - - - - - - Only one SQL INSERT would now be issued. - - - - You could also create an addChild() method of - Parent. - - - - - - The code to add a Child looks like this: - - - - -
- -
- Cascading life cycle - - - You can address the frustrations of the explicit call to save() by - using cascades. - - - - - -]]> - - - This simplifies the code above to: - - - - - - Similarly, we do not need to iterate over the children when saving or deleting a Parent. - The following removes p and all its children from the database. - - - - - - However, the following code: - - - - - - will not remove c from the database. In this case, it will only remove the link to p - and cause a NOT NULL constraint violation. You need to explicitly - delete() the Child. - - - - - - In our case, a Child cannot exist without its parent. So if we remove - a Child from the collection, we do want it to be deleted. To do this, we must - use cascade="all-delete-orphan". - - - - - -]]> - - - Even though the collection mapping specifies inverse="true", cascades are - still processed by iterating the collection elements. If you need an object be saved, - deleted or updated by cascade, you must add it to the collection. It is not enough to simply call - setParent(). - - -
- -
- Cascades and <literal>unsaved-value</literal> - - - Suppose we loaded up a Parent in one Session, made some changes - in a UI action and wanted to persist these changes in a new session by calling update(). - The Parent will contain a collection of children and, since the cascading update is enabled, - Hibernate needs to know which children are newly instantiated and which represent existing rows in the - database. We will also assume that both Parent and Child have generated - identifier properties of type Long. Hibernate will use the identifier and - version/timestamp property value to determine which of the children are new. (See - .) In Hibernate, it is no longer necessary to specify - an unsaved-value explicitly. - - - - The following code will update parent and child and insert - newChild: - - - - - - This may be suitable for the case of a generated identifier, but what about assigned identifiers - and composite identifiers? This is more difficult, since Hibernate cannot use the identifier property to - distinguish between a newly instantiated object, with an identifier assigned by the user, and an - object loaded in a previous session. In this case, Hibernate will either use the timestamp or version - property, or will actually query the second-level cache or, worst case, the database, to see if the - row exists. - - -
- -
- Conclusion - - - The sections we have just covered can be a bit confusing. However, in practice, - it all works out nicely. Most Hibernate applications use the parent/child pattern in many places. - - - - We mentioned an alternative in the first paragraph. None of the above issues exist in the case of - <composite-element> mappings, which have exactly the semantics of a parent/child - relationship. Unfortunately, there are two big limitations with composite element classes: composite elements - cannot own collections and they should not be the child of any entity other than the unique parent. - - -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/example_weblog.xml b/documentation/src/main/docbook/manual-old/en-US/content/example_weblog.xml deleted file mode 100644 index bc27595b8ce3..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/example_weblog.xml +++ /dev/null @@ -1,438 +0,0 @@ - - - - - Example: Weblog Application - -
- Persistent Classes - - - The persistent classes here represent a weblog and an item posted - in a weblog. They are to be modelled as a standard parent/child - relationship, but we will use an ordered bag, instead of a set: - - - - - - -
- -
- Hibernate Mappings - - - The XML mappings are now straightforward. For example: - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - - - - - - - - - - - - - - - - - - - - - - - - -]]> - -
- -
- Hibernate Code - - - The following class demonstrates some of the kinds of things - we can do with these classes using Hibernate: - - - :minDate" - ); - - Calendar cal = Calendar.getInstance(); - cal.roll(Calendar.MONTH, false); - q.setCalendar("minDate", cal); - - result = q.list(); - tx.commit(); - } - catch (HibernateException he) { - if (tx!=null) tx.rollback(); - throw he; - } - finally { - session.close(); - } - return result; - } -}]]> - -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/filters.xml b/documentation/src/main/docbook/manual-old/en-US/content/filters.xml deleted file mode 100755 index 6edebd0be549..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/filters.xml +++ /dev/null @@ -1,217 +0,0 @@ - - - - - Filtering data - - Hibernate provides an innovative new approach to handling data with - "visibility" rules. A Hibernate filter is a global, - named, parameterized filter that can be enabled or disabled for a particular - Hibernate session. - -
- Hibernate filters - - Hibernate has the ability to pre-define filter criteria and attach - those filters at both a class level and a collection level. A filter - criteria allows you to define a restriction clause similar to the existing - "where" attribute available on the class and various collection elements. - These filter conditions, however, can be parameterized. The application - can then decide at runtime whether certain filters should be enabled and - what their parameter values should be. Filters can be used like database - views, but they are parameterized inside the application. - - Using annotatons filters are defined via - @org.hibernate.annotations.FilterDef or - @org.hibernate.annotations.FilterDefs. A filter - definition has a name() and an array of - parameters(). A parameter will allow you to adjust the behavior of the - filter at runtime. Each parameter is defined by a - @ParamDef which has a name and a type. You can also - define a defaultCondition() parameter for a given - @FilterDef to set the default condition to use when - none are defined in each individual @Filter. - @FilterDef(s) can be defined at the class or package - level. - - We now need to define the SQL filter clause applied to either the - entity load or the collection load. @Filter is used and - placed either on the entity or the collection element. The connection - between @FilterName and - @Filter is a matching name. - - - @FilterDef and @Filter annotations - - @Entity -@FilterDef(name="minLength", parameters=@ParamDef( name="minLength", type="integer" ) ) -@Filters( { - @Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length"), - @Filter(name="minLength", condition=":minLength <= length") -} ) -public class Forest { ... } - - - When the collection use an association table as a relational - representation, you might want to apply the filter condition to the - association table itself or to the target entity table. To apply the - constraint on the target entity, use the regular - @Filter annotation. However, if you want to target the - association table, use the @FilterJoinTable - annotation. - - - Using <classname>@FilterJoinTable</classname> for filterting on - the association table - - @OneToMany -@JoinTable -//filter on the target entity table -@Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length") -//filter on the association table -@FilterJoinTable(name="security", condition=":userlevel >= requredLevel") -public Set<Forest> getForests() { ... } - - - By default, Hibernate attempts to automatically determine all points within the - @Filter SQL condition fragment that an alias should be injected. To control the alias injection, - set deduceAliasInjectionPoints to false within the - @Filter. Injection points are then marked using @SqlFragmentAlias annotations or - within the SQL's condition fragment using {alias}. - - In addition to allowing explicit alias control, deduceAliasInjectionPoints - provides an out when Hibernate assumes an ANSI SQL reserved keyword is a column and incorrectly aliases it. - - - @Filter annotation, disabling deduceAliasInjectionPoints - - @Entity -@Table(name="T_TREE") -@Filters({ - @Filter(name="isTall", condition="{alias}.LENGTH >= 100", deduceAliasInjectionPoints = false), - @Filter(name="isOak", condition="{t}.WOODTYPE like 'oak'", deduceAliasInjectionPoints = false, - aliases={@SqlFragmentAlias(alias="t", table="T_TREE")}) -}) -public class Tree { ... } - - - - Using Hibernate mapping files for defining filters the situtation is - very similar. The filters must first be defined and then attached to the - appropriate mapping elements. To define a filter, use the - <filter-def/> element within a - <hibernate-mapping/> element: - - - Defining a filter definition via - <literal><filter-def></literal> - - <filter-def name="myFilter"> - <filter-param name="myFilterParam" type="string"/> -</filter-def> - - - This filter can then be attached to a class or collection (or, to - both or multiples of each at the same time): - - - Attaching a filter to a class or collection using - <literal><filter></literal> - - <class name="myClass" ...> - ... - <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/> - - <set ...> - <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/> - </set> -</class> - - - The methods on Session are: - enableFilter(String filterName), - getEnabledFilter(String filterName), and - disableFilter(String filterName). By default, filters - are not enabled for a given session. Filters must be - enabled through use of the Session.enableFilter() - method, which returns an instance of the Filter - interface. If you used the simple filter defined above, it would look like - this: - - session.enableFilter("myFilter").setParameter("myFilterParam", "some-value"); - - Methods on the org.hibernate.Filter interface do allow the - method-chaining common to much of Hibernate. - - The following is a full example, using temporal data with an - effective record date pattern: - - <filter-def name="effectiveDate"> - <filter-param name="asOfDate" type="date"/> -</filter-def> - -<class name="Employee" ...> -... - <many-to-one name="department" column="dept_id" class="Department"/> - <property name="effectiveStartDate" type="date" column="eff_start_dt"/> - <property name="effectiveEndDate" type="date" column="eff_end_dt"/> -... - <!-- - Note that this assumes non-terminal records have an eff_end_dt set to - a max db date for simplicity-sake - --> - <filter name="effectiveDate" - condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> -</class> - -<class name="Department" ...> -... - <set name="employees" lazy="true"> - <key column="dept_id"/> - <one-to-many class="Employee"/> - <filter name="effectiveDate" - condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> - </set> -</class> - - In order to ensure that you are provided with currently effective - records, enable the filter on the session prior to retrieving employee - data: - - Session session = ...; -session.enableFilter("effectiveDate").setParameter("asOfDate", new Date()); -List results = session.createQuery("from Employee as e where e.salary > :targetSalary") - .setLong("targetSalary", new Long(1000000)) - .list(); - - - Even though a salary constraint was mentioned explicitly on the - results in the above HQL, because of the enabled filter, the query will - return only currently active employees who have a salary greater than one - million dollars. - - If you want to use filters with outer joining, either through HQL or - load fetching, be careful of the direction of the condition expression. It - is safest to set this up for left outer joining. Place the parameter first - followed by the column name(s) after the operator. - - After being defined, a filter might be attached to multiple entities - and/or collections each with its own condition. This can be problematic - when the conditions are the same each time. Using - <filter-def/> allows you to definine a default - condition, either as an attribute or CDATA: - - <filter-def name="myFilter" condition="abc > xyz">...</filter-def> -<filter-def name="myOtherFilter">abc=xyz</filter-def> - - This default condition will be used whenever the filter is attached - to something without specifying a condition. This means you can give a - specific condition as part of the attachment of the filter that overrides - the default condition in that particular case. -
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/inheritance_mapping.xml b/documentation/src/main/docbook/manual-old/en-US/content/inheritance_mapping.xml deleted file mode 100644 index 2f3853a2e9f8..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/inheritance_mapping.xml +++ /dev/null @@ -1,500 +0,0 @@ - - - - - Inheritance mapping - -
- The three strategies - - - Hibernate supports the three basic inheritance mapping strategies: - - - - - - table per class hierarchy - - - - - table per subclass - - - - - table per concrete class - - - - - - In addition, Hibernate supports a fourth, slightly different kind of - polymorphism: - - - - - - implicit polymorphism - - - - - - It is possible to use different mapping strategies for different - branches of the same inheritance hierarchy. You can then make use of implicit - polymorphism to achieve polymorphism across the whole hierarchy. However, - Hibernate does not support mixing <subclass>, - <joined-subclass> and - <union-subclass> mappings under the same root - <class> element. It is possible to mix together - the table per hierarchy and table per subclass strategies under the - the same <class> element, by combining the - <subclass> and <join> - elements (see below for an example). - - - - It is possible to define subclass, union-subclass, - and joined-subclass mappings in separate mapping documents directly beneath - hibernate-mapping. This allows you to extend a class hierarchy by adding - a new mapping file. You must specify an extends attribute in the subclass mapping, - naming a previously mapped superclass. Previously this feature made the ordering of the mapping - documents important. Since Hibernate, the ordering of mapping files is irrelevant when using the - extends keyword. The ordering inside a single mapping file still needs to be defined as superclasses - before subclasses. - - - - - - - ]]> - - -
- Table per class hierarchy - - - Suppose we have an interface Payment with the implementors - CreditCardPayment, CashPayment, - and ChequePayment. The table per hierarchy mapping would - display in the following way: - - - - - - - - - ... - - - ... - - - ... - - - ... - -]]> - - - Exactly one table is required. There is a limitation of this mapping - strategy: columns declared by the subclasses, such as CCTYPE, - cannot have NOT NULL constraints. - - -
- -
- Table per subclass - - - A table per subclass mapping looks like this: - - - - - - - - ... - - - - ... - - - - ... - - - - ... - -]]> - - - Four tables are required. The three subclass tables have primary - key associations to the superclass table so the relational model - is actually a one-to-one association. - - -
- -
- Table per subclass: using a discriminator - - - Hibernate's implementation of table per subclass - does not require a discriminator column. Other object/relational mappers use a - different implementation of table per subclass that requires a type - discriminator column in the superclass table. The approach taken by - Hibernate is much more difficult to implement, but arguably more - correct from a relational point of view. If you want to use - a discriminator column with the table per subclass strategy, you - can combine the use of <subclass> and - <join>, as follows: - - - - - - - - - ... - - - - - ... - - - - - - ... - - - - - - ... - - -]]> - - - The optional fetch="select" declaration tells Hibernate - not to fetch the ChequePayment subclass data using an - outer join when querying the superclass. - - -
- -
- Mixing table per class hierarchy with table per subclass - - - You can even mix the table per hierarchy and table per subclass strategies - using the following approach: - - - - - - - - - ... - - - - ... - - - - ... - - - ... - -]]> - - - For any of these mapping strategies, a polymorphic association to the root - Payment class is mapped using - <many-to-one>. - - - ]]> - -
- -
- Table per concrete class - - - There are two ways we can map the table per concrete class - strategy. First, you can use <union-subclass>. - - - - - - - - ... - - - ... - - - ... - - - ... - -]]> - - - Three tables are involved for the subclasses. Each table defines columns for - all properties of the class, including inherited properties. - - - - The limitation of this approach is that if a property is mapped on the - superclass, the column name must be the same on all subclass tables. - The identity generator strategy is not allowed in union subclass inheritance. - The primary key seed has to be shared across all unioned subclasses - of a hierarchy. - - - - - If your superclass is abstract, map it with abstract="true". - If it is not abstract, an additional table (it defaults to - PAYMENT in the example above), is needed to hold instances - of the superclass. - - -
- -
- Table per concrete class using implicit polymorphism - - - An alternative approach is to make use of implicit polymorphism: - - - - - - - - ... - - - - - - - - ... - - - - - - - - ... -]]> - - - Notice that the Payment interface - is not mentioned explicitly. Also notice that properties of Payment are - mapped in each of the subclasses. If you want to avoid duplication, consider - using XML entities - (for example, [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] - in the DOCTYPE declaration and - in the mapping). - - - - The disadvantage of this approach is that Hibernate does not generate SQL - UNIONs when performing polymorphic queries. - - - - For this mapping strategy, a polymorphic association to Payment - is usually mapped using <any>. - - - - - - - - -]]> - -
- -
- Mixing implicit polymorphism with other inheritance mappings - - - Since the subclasses - are each mapped in their own <class> element, and since - Payment is just an interface), each of the subclasses could - easily be part of another inheritance hierarchy. You can still use polymorphic - queries against the Payment interface. - - - - - - - - - ... - - - - - - - - - ... - - - - ... - - - - - ... - -]]> - - - Once again, Payment is not mentioned explicitly. If we - execute a query against the Payment interface, for - example from Payment, Hibernate - automatically returns instances of CreditCardPayment - (and its subclasses, since they also implement Payment), - CashPayment and ChequePayment, but - not instances of NonelectronicTransaction. - - -
- -
- -
- Limitations - - - There are limitations to the "implicit polymorphism" approach to - the table per concrete-class mapping strategy. There are somewhat less - restrictive limitations to <union-subclass> - mappings. - - - - The following table shows the limitations of table per concrete-class - mappings, and of implicit polymorphism, in Hibernate. - - - - Features of inheritance mappings - - - - - - - - - - - - - Inheritance strategy - Polymorphic many-to-one - Polymorphic one-to-one - Polymorphic one-to-many - Polymorphic many-to-many - Polymorphic load()/get() - Polymorphic queries - Polymorphic joins - Outer join fetching - - - - - table per class-hierarchy - <many-to-one> - <one-to-one> - <one-to-many> - <many-to-many> - s.get(Payment.class, id) - from Payment p - from Order o join o.payment p - supported - - - table per subclass - <many-to-one> - <one-to-one> - <one-to-many> - <many-to-many> - s.get(Payment.class, id) - from Payment p - from Order o join o.payment p - supported - - - table per concrete-class (union-subclass) - <many-to-one> - <one-to-one> - <one-to-many> (for inverse="true" only) - <many-to-many> - s.get(Payment.class, id) - from Payment p - from Order o join o.payment p - supported - - - table per concrete class (implicit polymorphism) - <any> - not supported - not supported - <many-to-any> - s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult() - from Payment p - not supported - not supported - - - -
- -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/performance.xml b/documentation/src/main/docbook/manual-old/en-US/content/performance.xml deleted file mode 100644 index 8949d40aa38b..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/performance.xml +++ /dev/null @@ -1,1754 +0,0 @@ - - - - - Improving performance - -
- Fetching strategies - - Hibernate uses a fetching strategy to retrieve - associated objects if the application needs to navigate the association. - Fetch strategies can be declared in the O/R mapping metadata, or - over-ridden by a particular HQL or Criteria - query. - - Hibernate defines the following fetching strategies: - - - - Join fetching: Hibernate retrieves the - associated instance or collection in the same - SELECT, using an OUTER - JOIN. - - - - Select fetching: a second - SELECT is used to retrieve the associated entity or - collection. Unless you explicitly disable lazy fetching by specifying - lazy="false", this second select will only be - executed when you access the association. - - - - Subselect fetching: a second - SELECT is used to retrieve the associated - collections for all entities retrieved in a previous query or fetch. - Unless you explicitly disable lazy fetching by specifying - lazy="false", this second select will only be - executed when you access the association. - - - - Batch fetching: an optimization strategy - for select fetching. Hibernate retrieves a batch of entity instances - or collections in a single SELECT by specifying a - list of primary or foreign keys. - - - - Hibernate also distinguishes between: - - - - Immediate fetching: an association, - collection or attribute is fetched immediately when the owner is - loaded. - - - - Lazy collection fetching: a collection is - fetched when the application invokes an operation upon that - collection. This is the default for collections. - - - - "Extra-lazy" collection fetching: - individual elements of the collection are accessed from the database - as needed. Hibernate tries not to fetch the whole collection into - memory unless absolutely needed. It is suitable for large - collections. - - - - Proxy fetching: a single-valued association - is fetched when a method other than the identifier getter is invoked - upon the associated object. - - - - "No-proxy" fetching: a single-valued - association is fetched when the instance variable is accessed. - Compared to proxy fetching, this approach is less lazy; the - association is fetched even when only the identifier is accessed. It - is also more transparent, since no proxy is visible to the - application. This approach requires buildtime bytecode instrumentation - and is rarely necessary. - - - - Lazy attribute fetching: an attribute or - single valued association is fetched when the instance variable is - accessed. This approach requires buildtime bytecode instrumentation - and is rarely necessary. - - - - We have two orthogonal notions here: when is - the association fetched and how is it fetched. It is - important that you do not confuse them. We use fetch to - tune performance. We can use lazy to define a contract - for what data is always available in any detached instance of a particular - class. - -
- Working with lazy associations - - By default, Hibernate uses lazy select fetching for collections - and lazy proxy fetching for single-valued associations. These defaults - make sense for most associations in the majority of applications. - - If you set hibernate.default_batch_fetch_size, - Hibernate will use the batch fetch optimization for lazy fetching. This - optimization can also be enabled at a more granular level. - - Please be aware that access to a lazy association outside of the - context of an open Hibernate session will result in an exception. For - example: - - s = sessions.openSession(); -Transaction tx = s.beginTransaction(); - -User u = (User) s.createQuery("from User u where u.name=:userName") - .setString("userName", userName).uniqueResult(); -Map permissions = u.getPermissions(); - -tx.commit(); -s.close(); - -Integer accessLevel = (Integer) permissions.get("accounts"); // Error! - - Since the permissions collection was not initialized when the - Session was closed, the collection will not be able - to load its state. Hibernate does not support lazy - initialization for detached objects. This can be fixed by - moving the code that reads from the collection to just before the - transaction is committed. - - Alternatively, you can use a non-lazy collection or association, - by specifying lazy="false" for the association - mapping. However, it is intended that lazy initialization be used for - almost all collections and associations. If you define too many non-lazy - associations in your object model, Hibernate will fetch the entire - database into memory in every transaction. - - On the other hand, you can use join fetching, which is non-lazy by - nature, instead of select fetching in a particular transaction. We will - now explain how to customize the fetching strategy. In Hibernate, the - mechanisms for choosing a fetch strategy are identical for single-valued - associations and collections. -
- -
- Tuning fetch strategies - - Select fetching (the default) is extremely vulnerable to N+1 - selects problems, so we might want to enable join fetching in the - mapping document: - - <set name="permissions" - fetch="join"> - <key column="userId"/> - <one-to-many class="Permission"/> -</set - - <many-to-one name="mother" class="Cat" fetch="join"/> - - The fetch strategy defined in the mapping - document affects: - - - - retrieval via get() or - load() - - - - retrieval that happens implicitly when an association is - navigated - - - - Criteria queries - - - - HQL queries if subselect fetching is - used - - - - Irrespective of the fetching strategy you use, the defined - non-lazy graph is guaranteed to be loaded into memory. This might, - however, result in several immediate selects being used to execute a - particular HQL query. - - Usually, the mapping document is not used to customize fetching. - Instead, we keep the default behavior, and override it for a particular - transaction, using left join fetch in HQL. This tells - Hibernate to fetch the association eagerly in the first select, using an - outer join. In the Criteria query API, you would use - setFetchMode(FetchMode.JOIN). - - If you want to change the fetching strategy used by - get() or load(), you can use a - Criteria query. For example: - - User user = (User) session.createCriteria(User.class) - .setFetchMode("permissions", FetchMode.JOIN) - .add( Restrictions.idEq(userId) ) - .uniqueResult(); - - This is Hibernate's equivalent of what some ORM solutions call a - "fetch plan". - - A completely different approach to problems with N+1 selects is to - use the second-level cache. -
- -
- Single-ended association proxies - - Lazy fetching for collections is implemented using Hibernate's own - implementation of persistent collections. However, a different mechanism - is needed for lazy behavior in single-ended associations. The target - entity of the association must be proxied. Hibernate implements lazy - initializing proxies for persistent objects using runtime bytecode - enhancement which is accessed via the bytecode provider. - - At startup, Hibernate generates proxies by default for all - persistent classes and uses them to enable lazy fetching of - many-to-one and one-to-one - associations. - - The mapping file may declare an interface to use as the proxy - interface for that class, with the proxy attribute. - By default, Hibernate uses a subclass of the class. The - proxied class must implement a default constructor with at least package - visibility. This constructor is recommended for all persistent - classes. - - There are potential problems to note when extending this approach - to polymorphic classes.For example: - - <class name="Cat" proxy="Cat"> - ...... - <subclass name="DomesticCat"> - ..... - </subclass> -</class> - - Firstly, instances of Cat will never be - castable to DomesticCat, even if the underlying - instance is an instance of DomesticCat: - - Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db) -if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy - DomesticCat dc = (DomesticCat) cat; // Error! - .... -} - - Secondly, it is possible to break proxy - ==: - - Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy -DomesticCat dc = - (DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy! -System.out.println(cat==dc); // false - - However, the situation is not quite as bad as it looks. Even - though we now have two references to different proxy objects, the - underlying instance will still be the same object: - - cat.setWeight(11.0); // hit the db to initialize the proxy -System.out.println( dc.getWeight() ); // 11.0 - - Third, you cannot use a bytecode provider generated proxy for a final - class or a class with any final methods. - - Finally, if your persistent object acquires any resources upon - instantiation (e.g. in initializers or default constructor), then those - resources will also be acquired by the proxy. The proxy class is an - actual subclass of the persistent class. - - These problems are all due to fundamental limitations in Java's - single inheritance model. To avoid these problems your persistent - classes must each implement an interface that declares its business - methods. You should specify these interfaces in the mapping file where - CatImpl implements the interface - Cat and DomesticCatImpl implements - the interface DomesticCat. For example: - - <class name="CatImpl" proxy="Cat"> - ...... - <subclass name="DomesticCatImpl" proxy="DomesticCat"> - ..... - </subclass> -</class> - - Then proxies for instances of Cat and - DomesticCat can be returned by - load() or iterate(). - - Cat cat = (Cat) session.load(CatImpl.class, catid); -Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate(); -Cat fritz = (Cat) iter.next(); - - - Note - - list() does not usually return - proxies. - - - Relationships are also lazily initialized. This means you must - declare any properties to be of type Cat, not - CatImpl. - - Certain operations do not require proxy - initialization: - - - - equals(): if the persistent class does not - override equals() - - - - hashCode(): if the persistent class does - not override hashCode() - - - - The identifier getter method - - - - Hibernate will detect persistent classes that override - equals() or hashCode(). - - By choosing lazy="no-proxy" instead of the - default lazy="proxy", you can avoid problems - associated with typecasting. However, buildtime bytecode instrumentation - is required, and all operations will result in immediate proxy - initialization. -
- -
- Initializing collections and proxies - - A LazyInitializationException will be thrown by - Hibernate if an uninitialized collection or proxy is accessed outside of - the scope of the Session, i.e., when the entity - owning the collection or having the reference to the proxy is in the - detached state. - - Sometimes a proxy or collection needs to be initialized before - closing the Session. You can force initialization by - calling cat.getSex() or - cat.getKittens().size(), for example. However, this - can be confusing to readers of the code and it is not convenient for - generic code. - - The static methods Hibernate.initialize() and - Hibernate.isInitialized(), provide the application - with a convenient way of working with lazily initialized collections or - proxies. Hibernate.initialize(cat) will force the - initialization of a proxy, cat, as long as its - Session is still open. Hibernate.initialize( - cat.getKittens() ) has a similar effect for the collection of - kittens. - - Another option is to keep the Session open - until all required collections and proxies have been loaded. In some - application architectures, particularly where the code that accesses - data using Hibernate, and the code that uses it are in different - application layers or different physical processes, it can be a problem - to ensure that the Session is open when a collection - is initialized. There are two basic ways to deal with this issue: - - - - In a web-based application, a servlet filter can be used to - close the Session only at the end of a user - request, once the rendering of the view is complete (the - Open Session in View pattern). Of course, this - places heavy demands on the correctness of the exception handling of - your application infrastructure. It is vitally important that the - Session is closed and the transaction ended - before returning to the user, even when an exception occurs during - rendering of the view. See the Hibernate Wiki for examples of this - "Open Session in View" pattern. - - - - In an application with a separate business tier, the business - logic must "prepare" all collections that the web tier needs before - returning. This means that the business tier should load all the - data and return all the data already initialized to the - presentation/web tier that is required for a particular use case. - Usually, the application calls - Hibernate.initialize() for each collection that - will be needed in the web tier (this call must occur before the - session is closed) or retrieves the collection eagerly using a - Hibernate query with a FETCH clause or a - FetchMode.JOIN in Criteria. - This is usually easier if you adopt the Command - pattern instead of a Session Facade. - - - - You can also attach a previously loaded object to a new - Session with merge() or - lock() before accessing uninitialized collections - or other proxies. Hibernate does not, and certainly - should not, do this automatically since it - would introduce impromptu transaction semantics. - - - - Sometimes you do not want to initialize a large collection, but - still need some information about it, like its size, for example, or a - subset of the data. - - You can use a collection filter to get the size of a collection - without initializing it: - - ( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue() - - The createFilter() method is also used to - efficiently retrieve subsets of a collection without needing to - initialize the whole collection: - - s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list(); -
- -
- Using batch fetching - - Using batch fetching, Hibernate can load several uninitialized - proxies if one proxy is accessed. Batch fetching is an optimization of - the lazy select fetching strategy. There are two ways you can configure - batch fetching: on the class level and the collection level. - - Batch fetching for classes/entities is easier to understand. - Consider the following example: at runtime you have 25 - Cat instances loaded in a Session, - and each Cat has a reference to its - owner, a Person. The - Person class is mapped with a proxy, - lazy="true". If you now iterate through all cats and - call getOwner() on each, Hibernate will, by default, - execute 25 SELECT statements to retrieve the proxied - owners. You can tune this behavior by specifying a - batch-size in the mapping of - Person: - - <class name="Person" batch-size="10">...</class> - - With this batch-size specified, Hibernate will now execute queries on demand when need to access the - uninitialized proxy, as above, but the difference is that instead of querying the exactly proxy entity that - being accessed, it will query more Person's owner at once, so, when accessing other person's owner, it may - already been initialized by this batch fetch with only a few ( much less than 25) queries will be executed. - - - This behavior is controlled by the batch-size and batch fetch style configuration. - The batch fetch style configuration ( hibernate.batch_fetch_style ) is a new performance - improvement since 4.2.0, there are 3 different strategies provided, which is legacy, - padded and dynamic. - - - - - LEGACY (default) - The legacy algorithm where we keep a set of pre-built batch sizes based on - org.hibernate.internal.util.collections.ArrayHelper#getBatchSizes. - Batches are performed using the next-smaller pre-built batch size from the number of existing batchable identifiers. - In the above example, with a batch-size setting of 25 the pre-built batch sizes would be [25, 12, 10, 9, 8, 7, .., 1]. - And since there are 25 persons' owner to be initialized, then only one query will be executed using these 25 owners' identifier. - But in another case, suppose there are only 24 persons, there will be 3 queries (12, 10, 2) will - be executed to go through all person's owner, and the query will looks like : - - select * from owner where id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - select * from owner where id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - select * from owner where id in (?, ?) - - - - - PADDED - This is kind of similar with the legacy algorithm, it uses the pre-build batch sizes based on same - org.hibernate.internal.util.collections.ArrayHelper#getBatchSizes. The difference - is that here hibernate will use the next-bigger batch size and pads the extra identifier placeholders. - So, using the same example above, initializing 25 persons the query would be same as above, - only 1 query will be executed to batch query all the owners. - - However, the attempt to batch load 24 owners would result just a single batch of size 25, the - identifiers to load would be "padded" (aka, repeated) to make up the difference. - - - - - DYNAMIC - Dynamically builds its SQL based on the actual number of available ids. Does still limit to the batch-size defined on the entity. - - - - - - You can also enable batch fetching of collections. For example, if - each Person has a lazy collection of - Cats, and 10 persons are currently loaded in the - Session, iterating through all persons will generate - 10 SELECTs, one for every call to - getCats(). If you enable batch fetching for the - cats collection in the mapping of - Person, Hibernate can pre-fetch collections: - - <class name="Person"> - <set name="cats" batch-size="3"> - ... - </set> -</class> - - For example, with a batch-size of 3 and using legacy batch style, - Hibernate will load 3, 3, 3, 1 collections in four SELECTs. Again, the value - of the attribute depends on the expected number of uninitialized - collections in a particular Session. - - Batch fetching of collections is particularly useful if you have a - nested tree of items, i.e. the typical bill-of-materials pattern. - However, a nested set or a materialized - path might be a better option for read-mostly trees. -
- -
- Using subselect fetching - - If one lazy collection or single-valued proxy has to be fetched, - Hibernate will load all of them, re-running the original query in a - subselect. This works in the same way as batch-fetching but without the - piecemeal loading. - - -
- -
- Fetch profiles - - Another way to affect the fetching strategy for loading associated - objects is through something called a fetch profile, which is a named - configuration associated with the - org.hibernate.SessionFactory but enabled, - by name, on the org.hibernate.Session. - Once enabled on a org.hibernate.Session, - the fetch profile will be in affect for that - org.hibernate.Session until it is - explicitly disabled. - - So what does that mean? Well lets explain that by way of an - example which show the different available approaches to configure a - fetch profile: - - - Specifying a fetch profile using - <classname>@FetchProfile</classname> - - @Entity -@FetchProfile(name = "customer-with-orders", fetchOverrides = { - @FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN) -}) -public class Customer { - @Id - @GeneratedValue - private long id; - - private String name; - - private long customerNumber; - - @OneToMany - private Set<Order> orders; - - // standard getter/setter - ... -} - - - - Specifying a fetch profile using - <literal><fetch-profile></literal> outside - <literal><class></literal> node - - <hibernate-mapping> - <class name="Customer"> - ... - <set name="orders" inverse="true"> - <key column="cust_id"/> - <one-to-many class="Order"/> - </set> - </class> - <class name="Order"> - ... - </class> - <fetch-profile name="customer-with-orders"> - <fetch entity="Customer" association="orders" style="join"/> - </fetch-profile> -</hibernate-mapping> - - - - - Specifying a fetch profile using - <literal><fetch-profile></literal> inside - <literal><class></literal> node - - <hibernate-mapping> - <class name="Customer"> - ... - <set name="orders" inverse="true"> - <key column="cust_id"/> - <one-to-many class="Order"/> - </set> - <fetch-profile name="customer-with-orders"> - <fetch association="orders" style="join"/> - </fetch-profile> - </class> - <class name="Order"> - ... - </class> -</hibernate-mapping> - - - - Now normally when you get a reference to a particular customer, - that customer's set of orders will be lazy meaning we will not yet have - loaded those orders from the database. Normally this is a good thing. - Now lets say that you have a certain use case where it is more efficient - to load the customer and their orders together. One way certainly is to - use "dynamic fetching" strategies via an HQL or criteria queries. But - another option is to use a fetch profile to achieve that. The following - code will load both the customer and their - orders: - - - Activating a fetch profile for a given - <classname>Session</classname> - - Session session = ...; -session.enableFetchProfile( "customer-with-orders" ); // name matches from mapping -Customer customer = (Customer) session.get( Customer.class, customerId ); - - - - - @FetchProfile definitions are global and - it does not matter on which class you place them. You can place the - @FetchProfile annotation either onto a class or - package (package-info.java). In order to define multiple fetch - profiles for the same class or package - @FetchProfiles can be used. - - - - Currently only join style fetch profiles are supported, but they plan is to support additional styles. See - HHH-3414 - for details. - -
- -
- Using lazy property fetching - - Hibernate supports the lazy fetching of individual properties. - This optimization technique is also known as fetch - groups. Please note that this is mostly a marketing feature; - optimizing row reads is much more important than optimization of column - reads. However, only loading some properties of a class could be useful - in extreme cases. For example, when legacy tables have hundreds of - columns and the data model cannot be improved. - - To enable lazy property loading, set the lazy - attribute on your particular property mappings: - - <class name="Document"> - <id name="id"> - <generator class="native"/> - </id> - <property name="name" not-null="true" length="50"/> - <property name="summary" not-null="true" length="200" lazy="true"/> - <property name="text" not-null="true" length="2000" lazy="true"/> -</class> - - Lazy property loading requires buildtime bytecode instrumentation. - If your persistent classes are not enhanced, Hibernate will ignore lazy - property settings and return to immediate fetching. - - For bytecode instrumentation, use the following Ant task: - - <target name="instrument" depends="compile"> - <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"> - <classpath path="${jar.path}"/> - <classpath path="${classes.dir}"/> - <classpath refxml:id="lib.class.path"/> - </taskdef> - - <instrument verbose="true"> - <fileset dir="${testclasses.dir}/org/hibernate/auction/model"> - <include name="*.class"/> - </fileset> - </instrument> -</target> - - A different way of avoiding unnecessary column reads, at least for - read-only transactions, is to use the projection features of HQL or - Criteria queries. This avoids the need for buildtime bytecode processing - and is certainly a preferred solution. - - You can force the usual eager fetching of properties using - fetch all properties in HQL. -
-
- -
- The Second Level Cache - - A Hibernate Session is a transaction-level cache - of persistent data. It is possible to configure a cluster or JVM-level - (SessionFactory-level) cache on a class-by-class and - collection-by-collection basis. You can even plug in a clustered cache. Be - aware that caches are not aware of changes made to the persistent store by - another application. They can, however, be configured to regularly expire - cached data. - - You have the option to tell Hibernate which caching - implementation to use by specifying the name of a class that implements - org.hibernate.cache.spi.CacheProvider using the property - hibernate.cache.provider_class. Hibernate is bundled - with a number of built-in integrations with the open-source cache - providers that are listed in . You can - also implement your own and plug it in as outlined above. Note that - versions prior to Hibernate 3.2 use EhCache as the default cache - provider. - - - Cache Providers - - - - - - - - - - - - - - - Cache - - Provider class - - Type - - Cluster Safe - - Query Cache Supported - - - - - - ConcurrentHashMap (only for testing purpose, in hibernate-testing module) - - org.hibernate.testing.cache.CachingRegionFactory - - memory - - - - yes - - - - EHCache - - org.hibernate.cache.ehcache.EhCacheRegionFactory - - memory, disk, transactional, clustered - - yes - - yes - - - - Infinispan - - org.hibernate.cache.infinispan.InfinispanRegionFactory - - clustered (ip multicast), transactional - - yes (replication or invalidation) - - yes (clock sync req.) - - - -
- -
- Cache mappings - - As we have done in previous chapters we are looking at the two - different possibiltites to configure caching. First configuration via - annotations and then via Hibernate mapping files. - - By default, entities are not part of the second level cache and we - recommend you to stick to this setting. However, you can override this - by setting the shared-cache-mode element in your - persistence.xml file or by using the - javax.persistence.sharedCache.mode property in your - configuration. The following values are possible: - - - - ENABLE_SELECTIVE (Default and recommended - value): entities are not cached unless explicitly marked as - cacheable. - - - - DISABLE_SELECTIVE: entities are cached - unless explicitly marked as not cacheable. - - - - ALL: all entities are always cached even if - marked as non cacheable. - - - - NONE: no entity are cached even if marked - as cacheable. This option can make sense to disable second-level - cache altogether. - - - - The cache concurrency strategy used by default can be set globaly - via the - hibernate.cache.default_cache_concurrency_strategy - configuration property. The values for this property are: - - - - read-only - - - - read-write - - - - nonstrict-read-write - - - - transactional - - - - - It is recommended to define the cache concurrency strategy per - entity rather than using a global one. Use the - @org.hibernate.annotations.Cache annotation for - that. - - - - Definition of cache concurrency strategy via - <classname>@Cache</classname> - - @Entity -@Cacheable -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public class Forest { ... } - - - Hibernate also let's you cache the content of a collection or the - identifiers if the collection contains other entities. Use the - @Cache annotation on the collection - property. - - - Caching collections using annotations - - @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) -@JoinColumn(name="CUST_ID") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public SortedSet<Ticket> getTickets() { - return tickets; -} - - - shows - the @org.hibernate.annotations.Cache annotations with - its attributes. It allows you to define the caching strategy and region - of a given second level cache. - - - <classname>@Cache</classname> annotation with - attributes - - - - - - - - - - - @Cache( - CacheConcurrencyStrategy usage(); - String region() default ""; - String include() default "all"; -) - - - - usage: the given cache concurrency strategy (NONE, - READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, - TRANSACTIONAL) - - - - region (optional): the cache region (default to the fqcn - of the class or the fq role name of the collection) - - - - include (optional): all to include all - properties, non-lazy to only include non lazy properties - (default all). - - - - - - Let's now take a look at Hibernate mapping files. There the - <cache> element of a class or collection - mapping is used to configure the second level cache. Looking at the parallels to - anotations is obvious. - - - The Hibernate <literal><cache></literal> mapping - element - - - - - - - - - - - <cache - usage="transactional|read-write|nonstrict-read-write|read-only" - region="RegionName" - include="all|non-lazy" -/> - - - - usage (required) specifies the caching - strategy: transactional, - read-write, - nonstrict-read-write or - read-only - - - - region (optional: defaults to the class - or collection role name): specifies the name of the second level - cache region - - - - include (optional: defaults to - all) non-lazy: specifies - that properties of the entity mapped with - lazy="true" cannot be cached when - attribute-level lazy fetching is enabled - - - - - - Alternatively to <cache>, you can use - <class-cache> and - <collection-cache> elements in - hibernate.cfg.xml. - - Let's now have a closer look at the different usage - strategies -
- -
- Strategy: read only - - If your application needs to read, but not modify, instances of a - persistent class, a read-only cache can be used. This - is the simplest and optimal performing strategy. It is even safe for use - in a cluster. -
- -
- Strategy: read/write - - If the application needs to update data, a - read-write cache might be appropriate. This cache - strategy should never be used if serializable transaction isolation - level is required. If the cache is used in a JTA environment, you must - specify the property - hibernate.transaction.manager_lookup_class and naming - a strategy for obtaining the JTA TransactionManager. - In other environments, you should ensure that the transaction is - completed when Session.close() or - Session.disconnect() is called. If you want to use - this strategy in a cluster, you should ensure that the underlying cache - implementation supports locking. The built-in cache providers - do not support locking. -
- -
- Strategy: nonstrict read/write - - If the application only occasionally needs to update data (i.e. if - it is extremely unlikely that two transactions would try to update the - same item simultaneously), and strict transaction isolation is not - required, a nonstrict-read-write cache might be - appropriate. If the cache is used in a JTA environment, you must specify - hibernate.transaction.manager_lookup_class. In other - environments, you should ensure that the transaction is completed when - Session.close() or - Session.disconnect() is called. -
- -
- Strategy: transactional - - The transactional cache strategy provides - support for fully transactional cache providers such as JBoss TreeCache. - Such a cache can only be used in a JTA environment and you must specify - hibernate.transaction.manager_lookup_class. -
- -
- Cache-provider/concurrency-strategy compatibility - - The following table shows which providers are compatible with - which concurrency strategies. - - - Cache Concurrency Strategy Support - - - - - - - - - - - - - - - Cache - - read-only - - nonstrict-read-write - - read-write - - transactional - - - - - - ConcurrentHashMap (not intended for production use) - - yes - - yes - - yes - - - - - - EHCache - - yes - - yes - - yes - - yes - - - - Infinispan - - yes - - - - - - yes - - - -
-
-
- -
- Managing the caches - - Whenever you pass an object to save(), - update() or saveOrUpdate(), and - whenever you retrieve an object using load(), - get(), list(), - iterate() or scroll(), that object - is added to the internal cache of the Session. - - When flush() is subsequently called, the state of - that object will be synchronized with the database. If you do not want - this synchronization to occur, or if you are processing a huge number of - objects and need to manage memory efficiently, the - evict() method can be used to remove the object and its - collections from the first-level cache. - - - Explcitly evicting a cached instance from the first level cache - using <methodname>Session.evict()</methodname> - - ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set -while ( cats.next() ) { - Cat cat = (Cat) cats.get(0); - doSomethingWithACat(cat); - sess.evict(cat); -} - - - The Session also provides a - contains() method to determine if an instance belongs - to the session cache. - - To evict all objects from the session cache, call - Session.clear() - - For the second-level cache, there are methods defined on - SessionFactory for evicting the cached state of an - instance, entire class, collection instance or entire collection - role. - - - Second-level cache eviction via - <methodname>SessionFactoty.evict() </methodname>and - <methodname>SessionFacyory.evictCollection()</methodname> - - sessionFactory.evict(Cat.class, catId); //evict a particular Cat -sessionFactory.evict(Cat.class); //evict all Cats -sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens -sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections - - - The CacheMode controls how a particular session - interacts with the second-level cache: - - - - CacheMode.NORMAL: will read items from and - write items to the second-level cache - - - - CacheMode.GET: will read items from the - second-level cache. Do not write to the second-level cache except when - updating data - - - - CacheMode.PUT: will write items to the - second-level cache. Do not read from the second-level cache - - - - CacheMode.REFRESH: will write items to the - second-level cache. Do not read from the second-level cache. Bypass - the effect of hibernate.cache.use_minimal_puts - forcing a refresh of the second-level cache for all items read from - the database - - - - To browse the contents of a second-level or query cache region, use - the Statistics API: - - - Browsing the second-level cache entries via the - <classname>Statistics</classname> API - - Map cacheEntries = sessionFactory.getStatistics() - .getSecondLevelCacheStatistics(regionName) - .getEntries(); - - - You will need to enable statistics and, optionally, force Hibernate - to keep the cache entries in a more readable format: - - - Enabling Hibernate statistics - - hibernate.generate_statistics true -hibernate.cache.use_structured_entries true - -
- -
- The Query Cache - - Query result sets can also be cached. This is only useful for - queries that are run frequently with the same parameters. - -
- Enabling query caching - - Caching of query results introduces some overhead in terms of your - applications normal transactional processing. For example, if you cache - results of a query against Person Hibernate will need to keep track of - when those results should be invalidated because changes have been - committed against Person. That, coupled with the fact that most - applications simply gain no benefit from caching query results, leads - Hibernate to disable caching of query results by default. To use query - caching, you will first need to enable the query cache: - - hibernate.cache.use_query_cache true - - This setting creates two new cache regions: - - org.hibernate.cache.internal.StandardQueryCache, - holding the cached query results - - - - org.hibernate.cache.spi.UpdateTimestampsCache, - holding timestamps of the most recent updates to queryable tables. - These are used to validate the results as they are served from the - query cache. - - - - - If you configure your underlying cache implementation to use - expiry or timeouts is very important that the cache timeout of the - underlying cache region for the UpdateTimestampsCache be set to a - higher value than the timeouts of any of the query caches. In fact, we - recommend that the the UpdateTimestampsCache region not be configured - for expiry at all. Note, in particular, that an LRU cache expiry - policy is never appropriate. - - - As mentioned above, most queries do not benefit from caching or - their results. So by default, individual queries are not cached even - after enabling query caching. To enable results caching for a particular - query, call org.hibernate.Query.setCacheable(true). - This call allows the query to look for existing cache results or add its - results to the cache when it is executed. - - - The query cache does not cache the state of the actual entities - in the cache; it caches only identifier values and results of value - type. For this reaso, the query cache should always be used in - conjunction with the second-level cache for those entities expected to - be cached as part of a query result cache (just as with collection - caching). - -
- -
- Query cache regions - - If you require fine-grained control over query cache expiration - policies, you can specify a named cache region for a particular query by - calling Query.setCacheRegion(). - - List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger") - .setEntity("blogger", blogger) - .setMaxResults(15) - .setCacheable(true) - .setCacheRegion("frontpages") - .list(); - - If you want to force the query cache to refresh one of its regions - (disregard any cached results it finds there) you can use - org.hibernate.Query.setCacheMode(CacheMode.REFRESH). - In conjunction with the region you have defined for the given query, - Hibernate will selectively force the results cached in that particular - region to be refreshed. This is particularly useful in cases where - underlying data may have been updated via a separate process and is a - far more efficient alternative to bulk eviction of the region via - org.hibernate.SessionFactory.evictQueries(). -
-
-
- Bytecode Enhancement - Hibernate internally needs an entry ( org.hibernate.engine.spi.EntityEntry ) to tell - the current state of an object with respect to its persistent state, when the object is associated with a - Session. However, maintaining this association was kind of heavy operation due to lots of - other rules must by applied, since 4.2.0, there is a new improvement designed for this purpose, which will reduce - session-related memory and CPU overloads. - - Basically, the idea is, instead of having a customized ( kind of heavy and which was usually identified as hotspot ) - map to do the look up, we change it to - - - There are three ways to get benefits from this new improvement: - -
- Implementing <classname>org.hibernate.engine.spi.ManagedEntity</classname> interface - An entity can choose to implement this interface by itself, then it is the entity's responsibility to maintain - the bi-association that essentially provides access to information about an instance's association to a - Session/EntityManager. - More info about org.hibernate.engine.spi.ManagedEntity please find from its javadoc. - -
- -
- Runtime instrument - Sometimes, you probably don't want to implement an intrusive interface, maybe due to portable concern, - which is fine and Hibernate will take care of this internally with a wrapper class which implements that interface, - and also an internal cache that maps this entity instance and the wrapper together. - - Obviously, this is the easiest way to choose, since it doesn't require any change of the project source code, - but it also cost more memory and CUP usage, comparing to the first one. -
- -
- Build-time instrument - Besides the above two approaches, Hibernate also provides a - third choice which is build time bytecode enhancement. Applications - can use enhanced entity classes, annotated with either javax.persistence.Entity - or composite javax.persistence.Embeddable. - -
- Ant Task - To use the task org.hibernate.tool.enhance.EnhancementTask - define a taskdef and call the task, as shown below. This code uses a - pre-defined classpathref and a property referencing the compiled classes - directory. - <taskdef name="enhance" classname="org.hibernate.tool.enhance.EnhancementTask" classpathref="enhancement.classpath" /> -<enhance> - <fileset dir="${ejb-classes}/org/hibernate/auction/model" includes="**/*.class"/> -</enhance> - - - The EnhancementTask is intended as a total replacement for InstrumentTask. - Further, it is also incompatible with InstrumentTask, so any existing instrumented classes - will need to be built from source again. - -
-
- Maven Plugin - The Maven Plugin uses a Mojo descriptor to attach the Mojo to the compile - phase for your project. - <dependencies> - <dependency> - <groupId>org.hibernate.javax.persistence</groupId> - <artifactId>hibernate-jpa-[SPEC-VERSION]-api</artifactId> - <version>[IMPL-VERSION]</version> - <scope>compile</scope> - </dependency> -</dependencies> -<plugins> -<plugin> - <groupId>org.hibernate.orm.tooling</groupId> - <artifactId>hibernate-enhance-maven-plugin</artifactId> - <version>VERSION</version> - <executions> - <execution> - <goals> - <goal>enhance</goal> - </goals> - </execution> - </executions> -</plugin> - -
-
- Gradle Plugin - The Gradle plugin adds an enhance task using the output directory of - the compile task as the source location of entity class files to enhance. - apply plugin: 'java' -apply plugin: 'maven' -apply plugin: 'hibernate' -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath 'org.hibernate:hibernate-gradle-plugin:VERSION' - } -} -dependencies { - compile group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-[SPEC-VERSION]-api', version: '[IMPL-VERSION]' -} - - -
-
-
- -
-
- Understanding Collection performance - - In the previous sections we have covered collections and their - applications. In this section we explore some more issues in relation to - collections at runtime. - -
- Taxonomy - - Hibernate defines three basic kinds of collections: - - - - collections of values - - - - one-to-many associations - - - - many-to-many associations - - - - This classification distinguishes the various table and foreign - key relationships but does not tell us quite everything we need to know - about the relational model. To fully understand the relational structure - and performance characteristics, we must also consider the structure of - the primary key that is used by Hibernate to update or delete collection - rows. This suggests the following classification: - - - - indexed collections - - - - sets - - - - bags - - - - All indexed collections (maps, lists, and arrays) have a primary - key consisting of the <key> and - <index> columns. In this case, collection - updates are extremely efficient. The primary key can be efficiently - indexed and a particular row can be efficiently located when Hibernate - tries to update or delete it. - - Sets have a primary key consisting of - <key> and element columns. This can be less - efficient for some types of collection element, particularly composite - elements or large text or binary fields, as the database may not be able - to index a complex primary key as efficiently. However, for one-to-many - or many-to-many associations, particularly in the case of synthetic - identifiers, it is likely to be just as efficient. If you want - SchemaExport to actually create the primary key of a - <set>, you must declare all columns as - not-null="true". - - <idbag> mappings define a surrogate key, - so they are efficient to update. In fact, they are the best case. - - Bags are the worst case since they permit duplicate element values - and, as they have no index column, no primary key can be defined. - Hibernate has no way of distinguishing between duplicate rows. Hibernate - resolves this problem by completely removing in a single - DELETE and recreating the collection whenever it - changes. This can be inefficient. - - For a one-to-many association, the "primary key" may not be the - physical primary key of the database table. Even in this case, the above - classification is still useful. It reflects how Hibernate "locates" - individual rows of the collection. -
- -
- Lists, maps, idbags and sets are the most efficient collections - to update - - From the discussion above, it should be clear that indexed - collections and sets allow the most efficient operation in terms of - adding, removing and updating elements. - - There is, arguably, one more advantage that indexed collections - have over sets for many-to-many associations or collections of values. - Because of the structure of a Set, Hibernate does not - UPDATE a row when an element is "changed". Changes to - a Set always work via INSERT and - DELETE of individual rows. Once again, this - consideration does not apply to one-to-many associations. - - After observing that arrays cannot be lazy, you can conclude that - lists, maps and idbags are the most performant (non-inverse) collection - types, with sets not far behind. You can expect sets to be the most - common kind of collection in Hibernate applications. This is because the - "set" semantics are most natural in the relational model. - - However, in well-designed Hibernate domain models, most - collections are in fact one-to-many associations with - inverse="true". For these associations, the update is - handled by the many-to-one end of the association, and so considerations - of collection update performance simply do not apply. -
- -
- Bags and lists are the most efficient inverse collections - - There is a particular case, however, in which bags, and also - lists, are much more performant than sets. For a collection with - inverse="true", the standard bidirectional - one-to-many relationship idiom, for example, we can add elements to a - bag or list without needing to initialize (fetch) the bag elements. This - is because, unlike a set, - Collection.add() or - Collection.addAll() must always return true for a bag - or List. This can make the following common code much - faster: - - Parent p = (Parent) sess.load(Parent.class, id); -Child c = new Child(); -c.setParent(p); -p.getChildren().add(c); //no need to fetch the collection! -sess.flush(); -
- -
- One shot delete - - Deleting collection elements one by one can sometimes be extremely - inefficient. Hibernate knows not to do that in the case of an - newly-empty collection (if you called list.clear(), - for example). In this case, Hibernate will issue a single - DELETE. - - Suppose you added a single element to a collection of size twenty - and then remove two elements. Hibernate will issue one - INSERT statement and two DELETE - statements, unless the collection is a bag. This is certainly - desirable. - - However, suppose that we remove eighteen elements, leaving two and - then add thee new elements. There are two possible ways to - proceed - - - - delete eighteen rows one by one and then insert three - rows - - - - remove the whole collection in one SQL - DELETE and insert all five current elements one - by one - - - - Hibernate cannot know that the second option is probably quicker. - It would probably be undesirable for Hibernate to be that intuitive as - such behavior might confuse database triggers, etc. - - Fortunately, you can force this behavior (i.e. the second - strategy) at any time by discarding (i.e. dereferencing) the original - collection and returning a newly instantiated collection with all the - current elements. - - One-shot-delete does not apply to collections mapped - inverse="true". -
-
- -
- Monitoring performance - - - - Optimization is not much use without monitoring and access to - performance numbers. Hibernate provides a full range of figures about its - internal operations. Statistics in Hibernate are available per - SessionFactory. - -
- Monitoring a SessionFactory - - You can access SessionFactory metrics in two - ways. Your first option is to call - sessionFactory.getStatistics() and read or display - the Statistics yourself. - - Hibernate can also use JMX to publish metrics if you enable the - StatisticsService MBean. You can enable a single - MBean for all your SessionFactory or one per factory. - See the following code for minimalistic configuration examples: - - // MBean service registration for a specific SessionFactory -Hashtable tb = new Hashtable(); -tb.put("type", "statistics"); -tb.put("sessionFactory", "myFinancialApp"); -ObjectName on = new ObjectName("hibernate", tb); // MBean object name - -StatisticsService stats = new StatisticsService(); // MBean implementation -stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory -server.registerMBean(stats, on); // Register the Mbean on the server - - // MBean service registration for all SessionFactory's -Hashtable tb = new Hashtable(); -tb.put("type", "statistics"); -tb.put("sessionFactory", "all"); -ObjectName on = new ObjectName("hibernate", tb); // MBean object name - -StatisticsService stats = new StatisticsService(); // MBean implementation -server.registerMBean(stats, on); // Register the MBean on the server - - You can activate and deactivate the monitoring for a - SessionFactory: - - - - at configuration time, set - hibernate.generate_statistics to - false - - - - - - at runtime: - sf.getStatistics().setStatisticsEnabled(true) or - hibernateStatsBean.setStatisticsEnabled(true) - - - - Statistics can be reset programmatically using the - clear() method. A summary can be sent to a logger - (info level) using the logSummary() method. -
- -
- Metrics - - Hibernate provides a number of metrics, from basic information to - more specialized information that is only relevant in certain scenarios. - All available counters are described in the - Statistics interface API, in three categories: - - - - Metrics related to the general Session - usage, such as number of open sessions, retrieved JDBC connections, - etc. - - - - Metrics related to the entities, collections, queries, and - caches as a whole (aka global metrics). - - - - Detailed metrics related to a particular entity, collection, - query or cache region. - - - - For example, you can check the cache hit, miss, and put ratio of - entities, collections and queries, and the average time a query needs. - Be aware that the number of milliseconds is subject to approximation in - Java. Hibernate is tied to the JVM precision and on some platforms this - might only be accurate to 10 seconds. - - Simple getters are used to access the global metrics (i.e. not - tied to a particular entity, collection, cache region, etc.). You can - access the metrics of a particular entity, collection or cache region - through its name, and through its HQL or SQL representation for queries. - Please refer to the Statistics, - EntityStatistics, - CollectionStatistics, - SecondLevelCacheStatistics, and - QueryStatistics API Javadoc for more information. The - following code is a simple example: - - Statistics stats = HibernateUtil.sessionFactory.getStatistics(); - -double queryCacheHitCount = stats.getQueryCacheHitCount(); -double queryCacheMissCount = stats.getQueryCacheMissCount(); -double queryCacheHitRatio = - queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount); - -log.info("Query Hit ratio:" + queryCacheHitRatio); - -EntityStatistics entityStats = - stats.getEntityStatistics( Cat.class.getName() ); -long changes = - entityStats.getInsertCount() - + entityStats.getUpdateCount() - + entityStats.getDeleteCount(); -log.info(Cat.class.getName() + " changed " + changes + "times" ); - - You can work on all entities, collections, queries and region - caches, by retrieving the list of names of entities, collections, - queries and region caches using the following methods: - getQueries(), getEntityNames(), - getCollectionRoleNames(), and - getSecondLevelCacheRegionNames(). -
-
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/persistent_classes.xml b/documentation/src/main/docbook/manual-old/en-US/content/persistent_classes.xml deleted file mode 100644 index 05ced46836a7..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/persistent_classes.xml +++ /dev/null @@ -1,682 +0,0 @@ - - - - - Persistent Classes - - - Persistent classes are classes in an application that implement the entities of the business problem - (e.g. Customer and Order in an E-commerce application). The term "persistent" here means that the classes - are able to be persisted, not that they are in the persistent state (see - for discussion). - - - - Hibernate works best if these classes follow some simple rules, also known as the Plain Old Java Object (POJO) - programming model. However, none of these rules are hard requirements. Indeed, Hibernate assumes very little - about the nature of your persistent objects. You can express a domain model in other ways (using trees of - java.util.Map instances, for example). - - -
- A simple POJO example - - - Simple POJO representing a cat - package eg; -import java.util.Set; -import java.util.Date; - -public class Cat { -private Long id; // identifier - -private Date birthdate; -private Color color; -private char sex; -private float weight; - private int litterId; - - private Cat mother; - private Set kittens = new HashSet(); - - private void setId(Long id) { - this.xml:id=id; - } - public Long getId() { - return id; - } - - void setBirthdate(Date date) { - birthdate = date; - } - public Date getBirthdate() { - return birthdate; - } - - void setWeight(float weight) { - this.weight = weight; - } - public float getWeight() { - return weight; - } - - public Color getColor() { - return color; - } - void setColor(Color color) { - this.color = color; - } - - void setSex(char sex) { - this.sex=sex; - } - public char getSex() { - return sex; - } - - void setLitterId(int id) { - this.litterId = id; - } - public int getLitterId() { - return litterId; - } - - void setMother(Cat mother) { - this.mother = mother; - } - public Cat getMother() { - return mother; - } - void setKittens(Set kittens) { - this.kittens = kittens; - } - public Set getKittens() { - return kittens; - } - - // addKitten not needed by Hibernate - public void addKitten(Cat kitten) { - kitten.setMother(this); - kitten.setLitterId( kittens.size() ); - kittens.add(kitten); - } -} - - - - - The four main rules of persistent classes are explored in more detail in the following sections. - - -
- Implement a no-argument constructor - - - Cat has a no-argument constructor. All persistent classes must have a default - constructor (which can be non-public) so that Hibernate can instantiate them using - java.lang.reflect.Constructor.newInstance(). It is recommended - that this constructor be defined with at least package visibility in order for - runtime proxy generation to work properly. - -
- -
- Provide an identifier property - - - - Historically this was considered option. While still not (yet) enforced, this should be considered - a deprecated feature as it will be completely required to provide an identifier property in an - upcoming release. - - - - - Cat has a property named id. This property maps to the - primary key column(s) of the underlying database table. The type of the identifier property can - be any "basic" type (see ). See - for information on mapping composite (multi-column) identifiers. - - - - - Identifiers do not necessarily need to identify column(s) in the database physically defined - as a primary key. They should just identify columns that can be used to uniquely identify rows - in the underlying table. - - - - - We recommend that you declare consistently-named identifier properties on persistent classes and that you use - a nullable (i.e., non-primitive) type. - -
- - -
- Prefer non-final classes (semi-optional) - - - A central feature of Hibernate, proxies (lazy loading), depends upon the - persistent class being either non-final, or the implementation of an interface that declares all public - methods. You can persist final classes that do not implement an interface with - Hibernate; you will not, however, be able to use proxies for lazy association fetching which will - ultimately limit your options for performance tuning. To persist a final - class which does not implement a "full" interface you must disable proxy generation. See - and - . - - - - Disabling proxies in <literal>hbm.xml</literal> - ...]]> - - - - Disabling proxies in annotations - - - - - If the final class does implement a proper interface, you could alternatively tell - Hibernate to use the interface instead when generating the proxies. See - and - . - - - - - Proxying an interface in <literal>hbm.xml</literal> - ...]]> - - - - Proxying an interface in annotations - - - - - You should also avoid declaring public final methods as this will again limit - the ability to generate proxies from this class. If you want to use a - class with public final methods, you must explicitly disable proxying. Again, see - and - . - -
- -
- Declare accessors and mutators for persistent fields (optional) - - - Cat declares accessor methods for all its persistent fields. Many other ORM - tools directly persist instance variables. It is better to provide an indirection between the relational - schema and internal data structures of the class. By default, Hibernate persists JavaBeans style - properties and recognizes method names of the form getFoo, isFoo - and setFoo. If required, you can switch to direct field access for particular - properties. - - - - Properties need not be declared public. Hibernate can persist a property declared - with package, protected or private visibility - as well. - -
-
- -
- Implementing inheritance - - A subclass must also observe the first and second rules. It inherits - its identifier property from the superclass, Cat. For - example: - - package eg; - -public class DomesticCat extends Cat { - private String name; - - public String getName() { - return name; - } - protected void setName(String name) { - this.name=name; - } -} -
- -
- Implementing <literal>equals()</literal> and - <literal>hashCode()</literal> - - You have to override the equals() and - hashCode() methods if you: - - - - intend to put instances of persistent classes in a - Set (the recommended way to represent many-valued - associations); and - - - - intend to use reattachment of detached instances - - - - Hibernate guarantees equivalence of persistent identity (database - row) and Java identity only inside a particular session scope. When you - mix instances retrieved in different sessions, you must implement - equals() and hashCode() if you wish - to have meaningful semantics for Sets. - - The most obvious way is to implement - equals()/hashCode() by comparing the - identifier value of both objects. If the value is the same, both must be - the same database row, because they are equal. If both are added to a - Set, you will only have one element in the - Set). Unfortunately, you cannot use that approach with - generated identifiers. Hibernate will only assign identifier values to - objects that are persistent; a newly created instance will not have any - identifier value. Furthermore, if an instance is unsaved and currently in - a Set, saving it will assign an identifier value to the - object. If equals() and hashCode() - are based on the identifier value, the hash code would change, breaking - the contract of the Set. See the Hibernate website for - a full discussion of this problem. This is not a Hibernate issue, but - normal Java semantics of object identity and equality. - - It is recommended that you implement equals() and - hashCode() using Business key - equality. Business key equality means that the - equals() method compares only the properties that form - the business key. It is a key that would identify our instance in the real - world (a natural candidate key): - - public class Cat { - - ... - public boolean equals(Object other) { - if (this == other) return true; - if ( !(other instanceof Cat) ) return false; - - final Cat cat = (Cat) other; - - if ( !cat.getLitterId().equals( getLitterId() ) ) return false; - if ( !cat.getMother().equals( getMother() ) ) return false; - - return true; - } - - public int hashCode() { - int result; - result = getMother().hashCode(); - result = 29 * result + getLitterId(); - return result; - } - -} - - A business key does not have to be as solid as a database primary - key candidate (see ). - Immutable or unique properties are usually good candidates for a business - key. -
- -
- Dynamic models - - - Note - - The following features are currently considered - experimental and may change in the near future. - - - Persistent entities do not necessarily have to be represented as - POJO classes or as JavaBean objects at runtime. Hibernate also supports - dynamic models (using Maps of Maps - at runtime). With this approach, you do not write persistent classes, - only mapping files. - - By default, Hibernate works in normal POJO mode. You can set a - default entity representation mode for a particular - SessionFactory using the - default_entity_mode configuration option (see ). - - The following examples demonstrate the representation using - Maps. First, in the mapping file an - entity-name has to be declared instead of, or in - addition to, a class name: - - <hibernate-mapping> - - <class entity-name="Customer"> - - <id name="id" - type="long" - column="ID"> - <generator class="sequence"/> - </id> - - <property name="name" - column="NAME" - type="string"/> - - <property name="address" - column="ADDRESS" - type="string"/> - - <many-to-one name="organization" - column="ORGANIZATION_ID" - class="Organization"/> - - <bag name="orders" - inverse="true" - lazy="false" - cascade="all"> - <key column="CUSTOMER_ID"/> - <one-to-many class="Order"/> - </bag> - - </class> - -</hibernate-mapping> - - Even though associations are declared using target class names, the - target type of associations can also be a dynamic entity instead of a - POJO. - - After setting the default entity mode to - dynamic-map for the SessionFactory, - you can, at runtime, work with Maps of - Maps: - - Session s = openSession(); -Transaction tx = s.beginTransaction(); - -// Create a customer -Map david = new HashMap(); -david.put("name", "David"); - -// Create an organization -Map foobar = new HashMap(); -foobar.put("name", "Foobar Inc."); - -// Link both -david.put("organization", foobar); - -// Save both -s.save("Customer", david); -s.save("Organization", foobar); - -tx.commit(); -s.close(); - - One of the main advantages of dynamic mapping is quick turnaround - time for prototyping, without the need for entity class implementation. - However, you lose compile-time type checking and will likely deal with - many exceptions at runtime. As a result of the Hibernate mapping, the - database schema can easily be normalized and sound, allowing to add a - proper domain model implementation on top later on. - - Entity representation modes can also be set on a per - Session basis: - - Session dynamicSession = pojoSession.getSession(EntityMode.MAP); - -// Create a customer -Map david = new HashMap(); -david.put("name", "David"); -dynamicSession.save("Customer", david); -... -dynamicSession.flush(); -dynamicSession.close() -... -// Continue on pojoSession - - - Please note that the call to getSession() using - an EntityMode is on the Session API, - not the SessionFactory. That way, the new - Session shares the underlying JDBC connection, - transaction, and other context information. This means you do not have to - call flush() and close() on the - secondary Session, and also leave the transaction and - connection handling to the primary unit of work. -
- - -
- Tuplizers - - - org.hibernate.tuple.Tuplizer and its sub-interfaces are responsible for - managing a particular representation of a piece of data given that representation's - org.hibernate.EntityMode. If a given piece of data is thought of as a data - structure, then a tuplizer is the thing that knows how to create such a data structure, how to extract - values from such a data structure and how to inject values into such a data structure. For example, for - the POJO entity mode, the corresponding tuplizer knows how create the POJO through its constructor. - It also knows how to access the POJO properties using the defined property accessors. - - - - There are two (high-level) types of Tuplizers: - - - - org.hibernate.tuple.entity.EntityTuplizer which is - responsible for managing the above mentioned contracts in regards to entities - - - - - org.hibernate.tuple.component.ComponentTuplizer which does the - same for components - - - - - - - Users can also plug in their own tuplizers. Perhaps you require that - java.util.Map implementation other than - java.util.HashMap be used while in the dynamic-map entity-mode. Or perhaps you - need to define a different proxy generation strategy than the one used by default. Both would be achieved - by defining a custom tuplizer implementation. Tuplizer definitions are attached to the entity or component - mapping they are meant to manage. Going back to the example of our Customer entity, - shows how to specify a custom - org.hibernate.tuple.entity.EntityTuplizer using annotations while - shows how to do the same in hbm.xml - - - - Specify custom tuplizers in annotations -@Entity -@Tuplizer(impl = DynamicEntityTuplizer.class) -public interface Cuisine { - @Id - @GeneratedValue - public Long getId(); - public void setId(Long id); - - public String getName(); - public void setName(String name); - - @Tuplizer(impl = DynamicComponentTuplizer.class) - public Country getCountry(); - public void setCountry(Country country); -} - - - Specify custom tuplizers in <literal>hbm.xml</literal> -<hibernate-mapping> - <class entity-name="Customer"> - <!-- - Override the dynamic-map entity-mode - tuplizer for the customer entity - --> - <tuplizer entity-mode="dynamic-map" - class="CustomMapTuplizerImpl"/> - - <id name="id" type="long" column="ID"> - <generator class="sequence"/> - </id> - - <!-- other properties --> - ... - </class> -</hibernate-mapping> - -
- -
- EntityNameResolvers - - - org.hibernate.EntityNameResolver is a contract for resolving the entity name - of a given entity instance. The interface defines a single method resolveEntityName - which is passed the entity instance and is expected to return the appropriate entity name (null is - allowed and would indicate that the resolver does not know how to resolve the entity name of the given entity - instance). Generally speaking, an org.hibernate.EntityNameResolver is going - to be most useful in the case of dynamic models. One example might be using proxied interfaces as your - domain model. The hibernate test suite has an example of this exact style of usage under the - org.hibernate.test.dynamicentity.tuplizer2. Here is some of the code from that package - for illustration. - - -/** - * A very trivial JDK Proxy InvocationHandler implementation where we proxy an - * interface as the domain model and simply store persistent state in an internal - * Map. This is an extremely trivial example meant only for illustration. - */ -public final class DataProxyHandler implements InvocationHandler { - private String entityName; - private HashMap data = new HashMap(); - - public DataProxyHandler(String entityName, Serializable id) { - this.entityName = entityName; - data.put( "Id", id ); - } - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String methodName = method.getName(); - if ( methodName.startsWith( "set" ) ) { - String propertyName = methodName.substring( 3 ); - data.put( propertyName, args[0] ); - } - else if ( methodName.startsWith( "get" ) ) { - String propertyName = methodName.substring( 3 ); - return data.get( propertyName ); - } - else if ( "toString".equals( methodName ) ) { - return entityName + "#" + data.get( "Id" ); - } - else if ( "hashCode".equals( methodName ) ) { - return new Integer( this.hashCode() ); - } - return null; - } - - public String getEntityName() { - return entityName; - } - - public HashMap getData() { - return data; - } -} - -public class ProxyHelper { - public static String extractEntityName(Object object) { - // Our custom java.lang.reflect.Proxy instances actually bundle - // their appropriate entity name, so we simply extract it from there - // if this represents one of our proxies; otherwise, we return null - if ( Proxy.isProxyClass( object.getClass() ) ) { - InvocationHandler handler = Proxy.getInvocationHandler( object ); - if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) { - DataProxyHandler myHandler = ( DataProxyHandler ) handler; - return myHandler.getEntityName(); - } - } - return null; - } - - // various other utility methods .... - -} - -/** - * The EntityNameResolver implementation. - * - * IMPL NOTE : An EntityNameResolver really defines a strategy for how entity names - * should be resolved. Since this particular impl can handle resolution for all of our - * entities we want to take advantage of the fact that SessionFactoryImpl keeps these - * in a Set so that we only ever have one instance registered. Why? Well, when it - * comes time to resolve an entity name, Hibernate must iterate over all the registered - * resolvers. So keeping that number down helps that process be as speedy as possible. - * Hence the equals and hashCode implementations as is - */ -public class MyEntityNameResolver implements EntityNameResolver { - public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver(); - - public String resolveEntityName(Object entity) { - return ProxyHelper.extractEntityName( entity ); - } - - public boolean equals(Object obj) { - return getClass().equals( obj.getClass() ); - } - - public int hashCode() { - return getClass().hashCode(); - } -} - -public class MyEntityTuplizer extends PojoEntityTuplizer { - public MyEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) { - super( entityMetamodel, mappedEntity ); - } - - public EntityNameResolver[] getEntityNameResolvers() { - return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE }; - } - - public String determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) { - String entityName = ProxyHelper.extractEntityName( entityInstance ); - if ( entityName == null ) { - entityName = super.determineConcreteSubclassEntityName( entityInstance, factory ); - } - return entityName; - } - - ... - - - - In order to register an org.hibernate.EntityNameResolver users must either: - - - - Implement a custom tuplizer (see ), implementing - the getEntityNameResolvers method - - - - - Register it with the org.hibernate.impl.SessionFactoryImpl (which is the - implementation class for org.hibernate.SessionFactory) using the - registerEntityNameResolver method. - - - - -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/portability.xml b/documentation/src/main/docbook/manual-old/en-US/content/portability.xml deleted file mode 100644 index cf58b1abbcd8..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/portability.xml +++ /dev/null @@ -1,192 +0,0 @@ - - - - - Database Portability Considerations - -
- Portability Basics - - - One of the selling points of Hibernate (and really Object/Relational Mapping as a whole) is - the notion of database portability. This could mean an internal IT user migrating from one - database vendor to another, or it could mean a framework or deployable application consuming - Hibernate to simultaneously target multiple database products by their users. Regardless of - the exact scenario, the basic idea is that you want Hibernate to help you run against any number - of databases without changes to your code, and ideally without any changes to the mapping metadata. - -
- -
- Dialect - - - The first line of portability for Hibernate is the dialect, which is a specialization of the - org.hibernate.dialect.Dialect contract. A dialect encapsulates all - the differences in how Hibernate must communicate with a particular database to accomplish some - task like getting a sequence value or structuring a SELECT query. Hibernate bundles a wide range - of dialects for many of the most popular databases. If you find that your particular database is - not among them, it is not terribly difficult to write your own. - -
- -
- Dialect resolution - - - Originally, Hibernate would always require that users specify which dialect to use. In the case - of users looking to simultaneously target multiple databases with their build that was problematic. - Generally this required their users to configure the Hibernate dialect or defining their own method - of setting that value. - - - - Starting with version 3.2, Hibernate introduced the notion of automatically detecting the dialect - to use based on the java.sql.DatabaseMetaData obtained from a - java.sql.Connection to that database. This was much better, expect - that this resolution was limited to databases Hibernate know about ahead of time and was in no way - configurable or overrideable. - - - - Starting with version 3.3, Hibernate has a fare more powerful way to automatically determine - which dialect to should be used by relying on a series of delegates which implement the - org.hibernate.dialect.resolver.DialectResolver which defines only a - single method: - - - - The basic contract here is that if the resolver 'understands' the given database metadata then - it returns the corresponding Dialect; if not it returns null and the process continues to the next - resolver. The signature also identifies org.hibernate.exception.JDBCConnectionException - as possibly being thrown. A JDBCConnectionException here is interpreted to imply a "non transient" - (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution - attempts. All other exceptions result in a warning and continuing on to the next resolver. - - - - The cool part about these resolvers is that users can also register their own custom resolvers - which will be processed ahead of the built-in Hibernate ones. This might be useful in a number of - different situations: it allows easy integration for auto-detection of dialects beyond those - shipped with HIbernate itself; it allows you to specify to use a custom dialect when a particular - database is recognized; etc. To register one or more resolvers, simply specify them (seperated by - commas, tabs or spaces) using the 'hibernate.dialect_resolvers' configuration setting (see the - DIALECT_RESOLVERS constant on - org.hibernate.cfg.Environment). - -
- -
- Identifier generation - - - When considering portability between databases, another important decision is selecting the - identifier generation stratagy you want to use. Originally Hibernate provided the - native generator for this purpose, which was intended to select between - a sequence, identity, or table - strategy depending on the capability of the underlying database. However, an insidious implication - of this approach comes about when targtetting some databases which support identity - generation and some which do not. identity generation relies on the SQL - definition of an IDENTITY (or auto-increment) column to manage the identifier value; it is what is - known as a post-insert generation strategy becauase the insert must actually happen before we can - know the identifier value. Because Hibernate relies on this identifier value to uniquely reference - entities within a persistence context it must then issue the insert - immediately when the users requests the entitiy be associated with the session (like via - save() e.g.) regardless of current transactional semantics. - - - - Hibernate was changed slightly once the implication of this was better understood so that - the insert is delayed in cases where that is feasible. - - - - The underlying issue is that the actual semanctics of the application itself changes in these cases. - - - - Starting with version 3.2.3, Hibernate comes with a set of - enhanced - identifier generators targetting - portability in a much different way. - - - There are specifically 2 bundled enhancedgenerators: - - - - org.hibernate.id.enhanced.SequenceStyleGenerator - - - - - org.hibernate.id.enhanced.TableGenerator - - - - - - The idea behind these generators is to port the actual semantics of the identifer value - generation to the different databases. For example, the - org.hibernate.id.enhanced.SequenceStyleGenerator mimics the behavior of - a sequence on databases which do not support sequences by using a table. - -
- -
- Database functions - - - - This is an area in Hibernate in need of improvement. In terms of portability concerns, - this function handling currently works pretty well from HQL; however, it is quite lacking - in all other aspects. - - - - - SQL functions can be referenced in many ways by users. However, not all databases - support the same set of functions. Hibernate, provides a means of mapping a - logical function name to a delegate which knows how to render - that particular function, perhaps even using a totally different physical function call. - - - Technically this function registration is handled through the - org.hibernate.dialect.function.SQLFunctionRegistry class - which is intended to allow users to provide custom function definitions without - having to provide a custom dialect. This specific behavior is not fully completed - as of yet. - - - It is sort of implemented such that users can programatically register functions - with the org.hibernate.cfg.Configuration and those functions - will be recognized for HQL. - - - -
- -
- Type mappings - - - This section scheduled for completion at a later date... - - - -
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/preface.xml b/documentation/src/main/docbook/manual-old/en-US/content/preface.xml deleted file mode 100644 index 094a11527ed7..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/preface.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Preface - - - Developing Object-Oriented software that deals with data from Relational Databases can be cumbersome and - resource consuming. Development costs are significantly higher due to a paradigm mismatch between how data is - represented in objects versus relational databases. Hibernate is an Object/Relational Mapping (ORM) solution - for Java environments. ORM refers to the technique of mapping data between an object model representation to - a relational data model representation. See - Wikipedia - for a good high-level discussion. Also, Martin Fowler's - OrmHate article takes a look at many of - the mentioned mismatch problems. - - - - Although having a strong background in SQL is not required to use Hibernate, having a basic understanding of the - concepts can help you understand Hibernate more quickly and fully. An understanding of data modeling principles - is especially important. Both and - are good starting points for understanding these - data modeling principles. - - - - Understanding the basics of transactions and design patterns such as "Unit of Work"PoEAA - or "ApplicationTransaction" are important as well. These topics will be discussed in the documentation, but - a prior understanding will certainly help. - - - - Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to - SQL data types), but also provides data query and retrieval facilities. It can significantly reduce - development time otherwise spent with manual data handling in SQL and JDBC. Hibernate’s design goal is to - relieve the developer from 95% of common data persistence-related programming tasks by eliminating the need for - manual, hand-crafted data processing using SQL and JDBC. However, unlike many other persistence solutions, - Hibernate does not hide the power of SQL from you and guarantees that your investment in relational technology - and knowledge is as valid as always. - - - - Hibernate may not be the best solution for data-centric applications that only use stored-procedures to - implement the business logic in the database, it is most useful with object-oriented domain models and business - logic in the Java-based middle-tier. However, Hibernate can certainly help you to remove or encapsulate - vendor-specific SQL code and will help with the common task of result set translation from a tabular - representation to a graph of objects. - - - - See for information on getting involved. - - - - - This documentation is intended as a reference manual. As such, it is very detailed and great when you - know what to look for. - - - If you are just getting started with using Hibernate you may want to start with the - Hibernate Getting Started Guide available from the - documentation page. It contains quick-start - style tutorials as well as lots of introductory information. There is also a series of topical guides - providing deep dives into various topics. - - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/content/query_criteria.xml b/documentation/src/main/docbook/manual-old/en-US/content/query_criteria.xml deleted file mode 100644 index b2e8f585d0ec..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/query_criteria.xml +++ /dev/null @@ -1,533 +0,0 @@ - - - - - Criteria Queries - - - Hibernate features an intuitive, extensible criteria query API. - - -
- Creating a <literal>Criteria</literal> instance - - - The interface org.hibernate.Criteria represents a query against - a particular persistent class. The Session is a factory for - Criteria instances. - - - - -
- -
- Narrowing the result set - - - An individual query criterion is an instance of the interface - org.hibernate.criterion.Criterion. The class - org.hibernate.criterion.Restrictions defines - factory methods for obtaining certain built-in - Criterion types. - - - - - - Restrictions can be grouped logically. - - - - - - - - There are a range of built-in criterion types (Restrictions - subclasses). One of the most useful allows you to specify SQL directly. - - - - - - The {alias} placeholder will be replaced by the row alias - of the queried entity. - - - - You can also obtain a criterion from a - Property instance. You can create a Property - by calling Property.forName(): - - - - -
- -
- Ordering the results - - - You can order the results using org.hibernate.criterion.Order. - - - - - - -
- -
- Associations - - - By navigating - associations using createCriteria() you can specify constraints upon related entities: - - - - - - The second createCriteria() returns a new - instance of Criteria that refers to the elements of - the kittens collection. - - - - There is also an alternate form that is useful in certain circumstances: - - - - - - (createAlias() does not create a new instance of - Criteria.) - - - - The kittens collections held by the Cat instances - returned by the previous two queries are not pre-filtered - by the criteria. If you want to retrieve just the kittens that match the - criteria, you must use a ResultTransformer. - - - - - - Additionally you may manipulate the result set using a left outer join: - - - - - This will return all of the Cats with a mate whose name starts with "good" - ordered by their mate's age, and all cats who do not have a mate. - This is useful when there is a need to order or limit in the database - prior to returning complex/large result sets, and removes many instances where - multiple queries would have to be performed and the results unioned - by java in memory. - - - Without this feature, first all of the cats without a mate would need to be loaded in one query. - - - A second query would need to retreive the cats with mates who's name started with "good" sorted by the mates age. - - - Thirdly, in memory; the lists would need to be joined manually. - -
- -
- Dynamic association fetching - - - You can specify association fetching semantics at runtime using - setFetchMode(). - - - - - - This query will fetch both mate and kittens - by outer join. See for more information. - - -
- -
- Components - - - To add a restriction against a property of an embedded component, the component property - name should be prepended to the property name when creating the Restriction. - The criteria object should be created on the owning entity, and cannot be created on the component - itself. For example, suppose the Cat has a component property fullName - with sub-properties firstName and lastName: - - - - - - - Note: this does not apply when querying collections of components, for that see below - - - -
- -
- Collections - - When using criteria against collections, there are two distinct cases. One is if - the collection contains entities (eg. <one-to-many/> - or <many-to-many/>) or components - (<composite-element/> ), - and the second is if the collection contains scalar values - (<element/>). - In the first case, the syntax is as given above in the section - where we restrict the kittens - collection. Essentially we create a Criteria object against the collection - property and restrict the entity or component properties using that instance. - - - For queryng a collection of basic values, we still create the Criteria - object against the collection, but to reference the value, we use the special property - "elements". For an indexed collection, we can also reference the index property using - the special property "indices". - - - -
- -
- Example queries - - - The class org.hibernate.criterion.Example allows - you to construct a query criterion from a given instance. - - - - - - Version properties, identifiers and associations are ignored. By default, - null valued properties are excluded. - - - - You can adjust how the Example is applied. - - - - - - You can even use examples to place criteria upon associated objects. - - - - -
- -
- Projections, aggregation and grouping - - The class org.hibernate.criterion.Projections is a - factory for Projection instances. You can apply a - projection to a query by calling setProjection(). - - - - - - - - There is no explicit "group by" necessary in a criteria query. Certain - projection types are defined to be grouping projections, - which also appear in the SQL group by clause. - - - - An alias can be assigned to a projection so that the projected value - can be referred to in restrictions or orderings. Here are two different ways to - do this: - - - - - - - - The alias() and as() methods simply wrap a - projection instance in another, aliased, instance of Projection. - As a shortcut, you can assign an alias when you add the projection to a - projection list: - - - - - - - - You can also use Property.forName() to express projections: - - - - - - -
- -
- Detached queries and subqueries - - The DetachedCriteria class allows you to create a query outside the scope - of a session and then execute it using an arbitrary Session. - - - - - - A DetachedCriteria can also be used to express a subquery. Criterion - instances involving subqueries can be obtained via Subqueries or - Property. - - - - - - - - Correlated subqueries are also possible: - - - - - - Example of multi-column restriction based on a subquery: - - - - -
- - - -
- Queries by natural identifier - - - For most queries, including criteria queries, the query cache is not efficient - because query cache invalidation occurs too frequently. However, there is a special - kind of query where you can optimize the cache invalidation algorithm: lookups by a - constant natural key. In some applications, this kind of query occurs frequently. - The criteria API provides special provision for this use case. - - - - First, map the natural key of your entity using - <natural-id> and enable use of the second-level cache. - - - - - - - - - - - - -]]> - - - This functionality is not intended for use with entities with - mutable natural keys. - - - - Once you have enabled the Hibernate query cache, - the Restrictions.naturalId() allows you to make use of - the more efficient cache algorithm. - - - - -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/query_hql.xml b/documentation/src/main/docbook/manual-old/en-US/content/query_hql.xml deleted file mode 100644 index a7c9992c7470..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/query_hql.xml +++ /dev/null @@ -1,1255 +0,0 @@ - - - - - HQL: The Hibernate Query Language - - - Hibernate uses a powerful query language (HQL) that is similar in appearance to - SQL. Compared with SQL, however, HQL is fully object-oriented - and understands notions like inheritance, polymorphism and association. - - -
- Case Sensitivity - - - With the exception of names of Java classes and properties, queries are case-insensitive. - So SeLeCT is the same as - sELEct is the same as - SELECT, but - org.hibernate.eg.FOO is not - org.hibernate.eg.Foo, and - foo.barSet is not - foo.BARSET. - - - - This manual uses lowercase HQL keywords. Some users find queries with uppercase keywords - more readable, but this convention is unsuitable for queries embedded in Java code. - - -
- -
- The from clause - - - The simplest possible Hibernate query is of the form: - - - - - - This returns all instances of the class eg.Cat. - You do not usually need to qualify the class name, since auto-import - is the default. For example: - - - - - - - In order to refer to the Cat in other parts of the - query, you will need to assign an alias. For example: - - - - - - This query assigns the alias cat to Cat - instances, so you can use that alias later in the query. The as - keyword is optional. You could also write: - - - - - - Multiple classes can appear, resulting in a cartesian product or "cross" join. - - - - - - - It is good practice to name query aliases using an initial lowercase as this is - consistent with Java naming standards for local variables - (e.g. domesticCat). - - -
- -
- Associations and joins - - - You can also assign aliases to associated entities or to elements of a - collection of values using a join. For example: - - - - - - - - - - The supported join types are borrowed from ANSI SQL: - - - - - - inner join - - - - - left outer join - - - - - right outer join - - - - - full join (not usually useful) - - - - - - The inner join, left outer join and - right outer join constructs may be abbreviated. - - - - - - You may supply extra join conditions using the HQL with - keyword. - - - 10.0]]> - - - A "fetch" join allows associations or collections of values to be - initialized along with their parent objects using a single select. This is particularly - useful in the case of a collection. It effectively overrides the outer join and - lazy declarations of the mapping file for associations and collections. See - for more information. - - - - - - A fetch join does not usually need to assign an alias, because the associated objects - should not be used in the where clause (or any other clause). - The associated objects are also not returned directly in the query results. Instead, they may - be accessed via the parent object. The only reason you might need an alias is if you are - recursively join fetching a further collection: - - - - - - The fetch construct cannot be used in queries called using - iterate() (though scroll() can be used). - Fetch should not be used together with setMaxResults() or - setFirstResult(), as these operations are based on the result rows which - usually contain duplicates for eager collection fetching, hence, the number of rows is not what - you would expect. - Fetch should also not be used together with impromptu with condition. - It is possible to create a cartesian product by join fetching more than one collection in a - query, so take care in this case. Join fetching multiple collection roles can produce - unexpected results for bag mappings, so user discretion is advised when formulating queries in this - case. Finally, note that full join fetch and right join fetch - are not meaningful. - - - - If you are using property-level lazy fetching (with bytecode instrumentation), it is - possible to force Hibernate to fetch the lazy properties in the first query immediately - using fetch all properties. - - - - - -
- -
- Forms of join syntax - - - HQL supports two forms of association joining: implicit and explicit. - - - - The queries shown in the previous section all use the explicit form, that is, where - the join keyword is explicitly used in the from clause. This is the recommended form. - - - - The implicit form does not use the join keyword. Instead, the - associations are "dereferenced" using dot-notation. implicit joins - can appear in any of the HQL clauses. implicit join result - in inner joins in the resulting SQL statement. - - - -
- -
- Referring to identifier property - - - There are 2 ways to refer to an entity's identifier property: - - - - - The special property (lowercase) id may be used to reference the identifier - property of an entity provided that the entity does not define a non-identifier property - named id. - - - - - If the entity defines a named identifier property, you can use that property name. - - - - - - References to composite identifier properties follow the same naming rules. If the - entity has a non-identifier property named id, the composite identifier property can only - be referenced by its defined named. Otherwise, the special id property - can be used to reference the identifier property. - - - - - Please note that, starting in version 3.2.2, this has changed significantly. In previous versions, - id always referred to the identifier property - regardless of its actual name. A ramification of that decision was that non-identifier - properties named id could never be referenced in Hibernate queries. - - - -
- -
- The select clause - - - The select clause picks which objects and properties to return in - the query result set. Consider the following: - - - - - - The query will select mates of other Cats. - You can express this query more compactly as: - - - - - - Queries can return properties of any value type including properties of component type: - - - - - - - - Queries can return multiple objects and/or properties as an array of type - Object[]: - - - - - - Or as a List: - - - - - - Or - assuming that the class Family has an appropriate constructor - as an actual typesafe Java object: - - - - - - - You can assign aliases to selected expressions using as: - - - - - - This is most useful when used together with select new map: - - - - - - This query returns a Map from aliases to selected values. - - -
- -
- Aggregate functions - - - HQL queries can even return the results of aggregate functions on properties: - - - - - - - - The supported aggregate functions are: - - - - - - avg(...), sum(...), min(...), max(...) - - - - - count(*) - - - - - count(...), count(distinct ...), count(all...) - - - - - - You can use arithmetic operators, concatenation, and recognized SQL functions - in the select clause: - - - - - - - - The distinct and all keywords can be used and - have the same semantics as in SQL. - - - - -
- -
- Polymorphic queries - - - A query like: - - - - - - returns instances not only of Cat, but also of subclasses like - DomesticCat. Hibernate queries can name any Java - class or interface in the from clause. The query will return instances - of all persistent classes that extend that class or implement the interface. The following - query would return all persistent objects: - - - - - - The interface Named might be implemented by various persistent - classes: - - - - - - These last two queries will require more than one SQL SELECT. This - means that the order by clause does not correctly order the whole result set. - It also means you cannot call these queries using Query.scroll(). - - -
- -
- The where clause - - - The where clause allows you to refine the list of instances returned. - If no alias exists, you can refer to properties by name: - - - - - - If there is an alias, use a qualified property name: - - - - - - This returns instances of Cat named 'Fritz'. - - - - The following query: - - - - - returns all instances of Foo with an - instance of bar with a - date property equal to the - startDate property of the - Foo. Compound path expressions make the - where clause extremely powerful. Consider the following: - - - - - - This query translates to an SQL query with a table (inner) join. For example: - - - - - - - would result in a query that would require four table joins in SQL. - - - - The = operator can be used to compare not only properties, but also - instances: - - - - - - - - The special property (lowercase) id can be used to reference the - unique identifier of an object. See - for more information. - - - - - - The second query is efficient and does not require a table join. - - - - Properties of composite identifiers can also be used. Consider the following example where Person - has composite identifiers consisting of country and - medicareNumber: - - - - - - - - Once again, the second query does not require a table join. - - - - See - for more information regarding referencing identifier properties) - - - - The special property class accesses the discriminator value - of an instance in the case of polymorphic persistence. A Java class name embedded in the - where clause will be translated to its discriminator value. - - - - - - You can also use components or composite user types, or properties of said - component types. See for more information. - - - - An "any" type has the special properties id and class that allows you - to express a join in the following way (where AuditLog.item - is a property mapped with <any>): - - - - - - The log.item.class and payment.class - would refer to the values of completely different database columns in the above query. - - -
- -
- Expressions - - - Expressions used in the where clause include the following: - - - - - - - mathematical operators: +, -, *, / - - - - - binary comparison operators: =, >=, <=, <>, !=, like - - - - - logical operations and, or, not - - - - - Parentheses ( ) that indicates grouping - - - - - in, - not in, - between, - is null, - is not null, - is empty, - is not empty, - member of and - not member of - - - - - "Simple" case, case ... when ... then ... else ... end, and - "searched" case, case when ... then ... else ... end - - - - - string concatenation ...||... or concat(...,...) - - - - - current_date(), current_time(), and - current_timestamp() - - - - - second(...), minute(...), - hour(...), day(...), - month(...), and year(...) - - - - - Any function or operator defined by EJB-QL 3.0: substring(), trim(), - lower(), upper(), length(), locate(), abs(), sqrt(), bit_length(), mod() - - - - - coalesce() and nullif() - - - - - str() for converting numeric or temporal values to a - readable string - - - - - cast(... as ...), where the second argument is the name of - a Hibernate type, and extract(... from ...) if ANSI - cast() and extract() is supported by - the underlying database - - - - - the HQL index() function, that applies to aliases of - a joined indexed collection - - - - - HQL functions that take collection-valued path expressions: size(), - minelement(), maxelement(), minindex(), maxindex(), along with the - special elements() and indices functions - that can be quantified using some, all, exists, any, in. - - - - - Any database-supported SQL scalar function like sign(), - trunc(), rtrim(), and sin() - - - - - JDBC-style positional parameters ? - - - - - named parameters :name, :start_date, and :x1 - - - - - SQL literals 'foo', 69, 6.66E+2, - '1970-01-01 10:00:01.0' - - - - - Java public static final constants eg.Color.TABBY - - - - - - in and between can be used as follows: - - - - - - - - The negated forms can be written as follows: - - - - - - - - Similarly, is null and is not null can be used to test - for null values. - - - - Booleans can be easily used in expressions by declaring HQL query substitutions in Hibernate - configuration: - - - true 1, false 0]]> - - - This will replace the keywords true and false with the - literals 1 and 0 in the translated SQL from this HQL: - - - - - - You can test the size of a collection with the special property size or - the special size() function. - - - 0]]> - - 0]]> - - - For indexed collections, you can refer to the minimum and maximum indices using - minindex and maxindex functions. Similarly, - you can refer to the minimum and maximum elements of a collection of basic type - using the minelement and maxelement - functions. For example: - - - current_date]]> - - 100]]> - - 10000]]> - - - The SQL functions any, some, all, exists, in are supported when passed the element - or index set of a collection (elements and indices functions) - or the result of a subquery (see below): - - - - - - - - - all elements(p.scores)]]> - - - - - Note that these constructs - size, elements, - indices, minindex, maxindex, - minelement, maxelement - can only be used in - the where clause in Hibernate. - - - - Elements of indexed collections (arrays, lists, and maps) can be referred to by - index in a where clause only: - - - - - - - - - - - - The expression inside [] can even be an arithmetic expression: - - - - - - HQL also provides the built-in index() function for elements - of a one-to-many association or collection of values. - - - - - - Scalar SQL functions supported by the underlying database can be used: - - - - - - Consider how much longer and less readable the - following query would be in SQL: - - - - - - Hint: something like - - - - -
- -
- The order by clause - - - The list returned by a query can be ordered by any property of a returned class or components: - - - - - - The optional asc or desc indicate ascending or descending order - respectively. - - - - The optional nulls first or nulls last indicate precedence of null - values while sorting. - -
- -
- The group by clause - - - A query that returns aggregate values can be grouped by any property of a returned class or components: - - - - - - - - A having clause is also allowed. - - - - - - SQL functions and aggregate functions are allowed in the having - and order by clauses if they are supported by the underlying database - (i.e., not in MySQL). - - - 100 -order by count(kitten) asc, sum(kitten.weight) desc]]> - - - Neither the group by clause nor the - order by clause can contain arithmetic expressions. - Hibernate also does not currently expand a grouped entity, - so you cannot write group by cat if all properties - of cat are non-aggregated. You have to list all - non-aggregated properties explicitly. - - -
- -
- Subqueries - - - For databases that support subselects, Hibernate supports subqueries within queries. A subquery must - be surrounded by parentheses (often by an SQL aggregate function call). Even correlated subqueries - (subqueries that refer to an alias in the outer query) are allowed. - - - ( - select avg(cat.weight) from DomesticCat cat -)]]> - - - - - - - - - - - Note that HQL subqueries can occur only in the select or where clauses. - - - - Note that subqueries can also utilize row value constructor syntax. See - for more information. - - -
- -
- HQL examples - - - Hibernate queries can be quite powerful and complex. In fact, the power of the query language - is one of Hibernate's main strengths. The following example queries are similar to queries - that have been used on recent projects. Please note that most queries you will write will be much simpler than the following examples. - - - - The following query returns the order id, number of items, the given minimum total value and the total value of the order for all - unpaid orders for a particular customer. The results are ordered by - total value. In determining the prices, it uses the current catalog. The resulting SQL query, - against the ORDER, ORDER_LINE, PRODUCT, - CATALOG and PRICE tables has four inner joins and an - (uncorrelated) subselect. - - - = all ( - select cat.effectiveDate - from Catalog as cat - where cat.effectiveDate < sysdate - ) -group by order -having sum(price.amount) > :minAmount -order by sum(price.amount) desc]]> - - - What a monster! Actually, in real life, I'm not very keen on subqueries, so my query was - really more like this: - - - :minAmount -order by sum(price.amount) desc]]> - - - The next query counts the number of payments in each status, excluding all payments in the - AWAITING_APPROVAL status where the most recent status change was made by the - current user. It translates to an SQL query with two inner joins and a correlated subselect - against the PAYMENT, PAYMENT_STATUS and - PAYMENT_STATUS_CHANGE tables. - - - PaymentStatus.AWAITING_APPROVAL - or ( - statusChange.timeStamp = ( - select max(change.timeStamp) - from PaymentStatusChange change - where change.payment = payment - ) - and statusChange.user <> :currentUser - ) -group by status.name, status.sortOrder -order by status.sortOrder]]> - - - If the statusChanges collection was mapped as a list, instead of a set, - the query would have been much simpler to write. - - - PaymentStatus.AWAITING_APPROVAL - or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser -group by status.name, status.sortOrder -order by status.sortOrder]]> - - - The next query uses the MS SQL Server isNull() function to return all - the accounts and unpaid payments for the organization to which the current user belongs. - It translates to an SQL query with three inner joins, an outer join and a subselect against - the ACCOUNT, PAYMENT, PAYMENT_STATUS, - ACCOUNT_TYPE, ORGANIZATION and - ORG_USER tables. - - - - - - For some databases, we would need to do away with the (correlated) subselect. - - - - -
- -
- Bulk update and delete - - - HQL now supports update, delete and - insert ... select ... statements. - See for more information. - -
- -
- Tips & Tricks - - - You can count the number of query results without returning them: - - - - - - To order a result by the size of a collection, use the following query: - - - - - - If your database supports subselects, you can place a condition upon selection - size in the where clause of your query: - - - = 1]]> - - - If your database does not support subselects, use the following query: - - - = 1]]> - - - - As this solution cannot return a User with zero messages - because of the inner join, the following form is also useful: - - - - - - Properties of a JavaBean can be bound to named query parameters: - - - - - - Collections are pageable by using the Query interface with a filter: - - - - - - Collection elements can be ordered or grouped using a query filter: - - - - - - You can find the size of a collection without initializing it: - - - - -
- -
- Components - - - Components can be used similarly to the simple value types that are used in HQL - queries. They can appear in the select clause as follows: - - - - - - - where the Person's name property is a component. Components can also be used - in the where clause: - - - - - - - Components can also be used in the order by clause: - - - - - - - Another common use of components is in row value constructors. - -
- -
- Row value constructor syntax - - - HQL supports the use of ANSI SQL row value constructor syntax, sometimes - referred to AS tuple syntax, even though the underlying database may not support - that notion. Here, we are generally referring to multi-valued comparisons, typically associated - with components. Consider an entity Person which defines a name component: - - - - - - That is valid syntax although it is a little verbose. You can make this more concise by using - row value constructor syntax: - - - - - - It can also be useful to specify this in the select clause: - - - - - - Using row value constructor syntax can also be beneficial - when using subqueries that need to compare against multiple values: - - - - - - One thing to consider when deciding if you want to use this syntax, is that the query will - be dependent upon the ordering of the component sub-properties in the metadata. - - -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/query_sql.xml b/documentation/src/main/docbook/manual-old/en-US/content/query_sql.xml deleted file mode 100644 index e1569cd143c6..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/query_sql.xml +++ /dev/null @@ -1,1126 +0,0 @@ - - - - - Native SQL - - You can also express queries in the native SQL dialect of your - database. This is useful if you want to utilize database-specific features - such as query hints or the CONNECT keyword in Oracle. It - also provides a clean migration path from a direct SQL/JDBC based - application to Hibernate. - - Hibernate allows you to specify handwritten SQL, including stored - procedures, for all create, update, delete, and load operations. - -
- Using a <literal>SQLQuery</literal> - - Execution of native SQL queries is controlled via the - SQLQuery interface, which is obtained by calling - Session.createSQLQuery(). The following sections - describe how to use this API for querying. - -
- Scalar queries - - The most basic SQL query is to get a list of scalars - (values). - - sess.createSQLQuery("SELECT * FROM CATS").list(); -sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list(); - - - These will return a List of Object arrays (Object[]) with scalar - values for each column in the CATS table. Hibernate will use - ResultSetMetadata to deduce the actual order and types of the returned - scalar values. - - To avoid the overhead of using - ResultSetMetadata, or simply to be more explicit in - what is returned, one can use addScalar(): - - sess.createSQLQuery("SELECT * FROM CATS") - .addScalar("ID", Hibernate.LONG) - .addScalar("NAME", Hibernate.STRING) - .addScalar("BIRTHDATE", Hibernate.DATE) - - - This query specified: - - - - the SQL query string - - - - the columns and types to return - - - - This will return Object arrays, but now it will not use - ResultSetMetadata but will instead explicitly get the - ID, NAME and BIRTHDATE column as respectively a Long, String and a Short - from the underlying resultset. This also means that only these three - columns will be returned, even though the query is using - * and could return more than the three listed - columns. - - It is possible to leave out the type information for all or some - of the scalars. - - sess.createSQLQuery("SELECT * FROM CATS") - .addScalar("ID", Hibernate.LONG) - .addScalar("NAME") - .addScalar("BIRTHDATE") - - - This is essentially the same query as before, but now - ResultSetMetaData is used to determine the type of - NAME and BIRTHDATE, where as the type of ID is explicitly - specified. - - How the java.sql.Types returned from ResultSetMetaData is mapped - to Hibernate types is controlled by the Dialect. If a specific type is - not mapped, or does not result in the expected type, it is possible to - customize it via calls to registerHibernateType in - the Dialect. -
- -
- Entity queries - - The above queries were all about returning scalar values, - basically returning the "raw" values from the resultset. The following - shows how to get entity objects from a native sql query via - addEntity(). - - sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class); -sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class); - - - This query specified: - - - - the SQL query string - - - - the entity returned by the query - - - - Assuming that Cat is mapped as a class with the columns ID, NAME - and BIRTHDATE the above queries will both return a List where each - element is a Cat entity. - - If the entity is mapped with a many-to-one to - another entity it is required to also return this when performing the - native query, otherwise a database specific "column not found" error - will occur. The additional columns will automatically be returned when - using the * notation, but we prefer to be explicit as in the following - example for a many-to-one to a - Dog: - - sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class); - - - This will allow cat.getDog() to function properly. -
- -
- Handling associations and collections - - It is possible to eagerly join in the Dog to - avoid the possible extra roundtrip for initializing the proxy. This is - done via the addJoin() method, which allows you to - join in an association or collection. - - sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID") - .addEntity("cat", Cat.class) - .addJoin("cat.dog"); - - - In this example, the returned Cat's will have - their dog property fully initialized without any - extra roundtrip to the database. Notice that you added an alias name - ("cat") to be able to specify the target property path of the join. It - is possible to do the same eager joining for collections, e.g. if the - Cat had a one-to-many to Dog - instead. - - sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID") - .addEntity("cat", Cat.class) - .addJoin("cat.dogs"); - - - At this stage you are reaching the limits of what is possible with - native queries, without starting to enhance the sql queries to make them - usable in Hibernate. Problems can arise when returning multiple entities - of the same type or when the default alias/column names are not - enough. -
- -
- Returning multiple entities - - Until now, the result set column names are assumed to be the same - as the column names specified in the mapping document. This can be - problematic for SQL queries that join multiple tables, since the same - column names can appear in more than one table. - - Column alias injection is needed in the following query (which - most likely will fail): - - sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID") - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class) - - - The query was intended to return two Cat instances per row: a cat - and its mother. The query will, however, fail because there is a - conflict of names; the instances are mapped to the same column names. - Also, on some databases the returned column aliases will most likely be - on the form "c.ID", "c.NAME", etc. which are not equal to the columns - specified in the mappings ("ID" and "NAME"). - - The following form is not vulnerable to column name - duplication: - - sess.createSQLQuery("SELECT {cat.*}, {m.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = m.ID") - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class) - - - This query specified: - - - - the SQL query string, with placeholders for Hibernate to - inject column aliases - - - - the entities returned by the query - - - - The {cat.*} and {mother.*} notation used above is a shorthand for - "all properties". Alternatively, you can list the columns explicitly, - but even in this case Hibernate injects the SQL column aliases for each - property. The placeholder for a column alias is just the property name - qualified by the table alias. In the following example, you retrieve - Cats and their mothers from a different table (cat_log) to the one - declared in the mapping metadata. You can even use the property aliases - in the where clause. - - String sql = "SELECT ID as {c.id}, NAME as {c.name}, " + - "BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " + - "FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID"; - -List loggedCats = sess.createSQLQuery(sql) - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class).list() - - -
- Alias and property references - - In most cases the above alias injection is needed. For queries - relating to more complex mappings, like composite properties, - inheritance discriminators, collections etc., you can use specific - aliases that allow Hibernate to inject the proper aliases. - - The following table shows the different ways you can use the - alias injection. Please note that the alias names in the result are - simply examples; each alias will have a unique and probably different - name when used. - - - Alias injection names - - - - - - - - - - - Description - - Syntax - - Example - - - - - - A simple property - - {[aliasname].[propertyname]} - - A_NAME as {item.name} - - - - A composite property - - {[aliasname].[componentname].[propertyname]} - - CURRENCY as {item.amount.currency}, VALUE as - {item.amount.value} - - - - Discriminator of an entity - - {[aliasname].class} - - DISC as {item.class} - - - - All properties of an entity - - {[aliasname].*} - - {item.*} - - - - A collection key - - {[aliasname].key} - - ORGID as {coll.key} - - - - The id of an collection - - {[aliasname].id} - - EMPID as {coll.id} - - - - The element of an collection - - {[aliasname].element} - - XID as {coll.element} - - - - property of the element in the collection - - {[aliasname].element.[propertyname]} - - NAME as {coll.element.name} - - - - All properties of the element in the collection - - {[aliasname].element.*} - - {coll.element.*} - - - - All properties of the collection - - {[aliasname].*} - - {coll.*} - - - -
-
-
- -
- Returning non-managed entities - - It is possible to apply a ResultTransformer to native SQL queries, - allowing it to return non-managed entities. - - sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") - .setResultTransformer(Transformers.aliasToBean(CatDTO.class)) - - This query specified: - - - - the SQL query string - - - - a result transformer - - - - The above query will return a list of CatDTO - which has been instantiated and injected the values of NAME and - BIRTHNAME into its corresponding properties or fields. -
- -
- Handling inheritance - - Native SQL queries which query for entities that are mapped as - part of an inheritance must include all properties for the baseclass and - all its subclasses. -
- -
- Parameters - - Native SQL queries support positional as well as named - parameters: - - Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class); -List pusList = query.setString(0, "Pus%").list(); - -query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class); -List pusList = query.setString("name", "Pus%").list(); -
-
- -
- Named SQL queries - - Named SQL queries can also be defined in the mapping document and - called in exactly the same way as a named HQL query (see ). In this case, you do - not need to call - addEntity(). - - - Named sql query using the <sql-query> maping - element - - <sql-query name="persons"> - <return alias="person" class="eg.Person"/> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex} - FROM PERSON person - WHERE person.NAME LIKE :namePattern -</sql-query> - - - - Execution of a named query - - List people = sess.getNamedQuery("persons") - .setString("namePattern", namePattern) - .setMaxResults(50) - .list(); - - - The <return-join> element is use to join - associations and the <load-collection> element is - used to define queries which initialize collections, - - - Named sql query with association - - <sql-query name="personsWith"> - <return alias="person" class="eg.Person"/> - <return-join alias="address" property="person.mailingAddress"/> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex}, - address.STREET AS {address.street}, - address.CITY AS {address.city}, - address.STATE AS {address.state}, - address.ZIP AS {address.zip} - FROM PERSON person - JOIN ADDRESS address - ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' - WHERE person.NAME LIKE :namePattern -</sql-query> - - - A named SQL query may return a scalar value. You must declare the - column alias and Hibernate type using the - <return-scalar> element: - - - Named query returning a scalar - - <sql-query name="mySqlQuery"> - <return-scalar column="name" type="string"/> - <return-scalar column="age" type="long"/> - SELECT p.NAME AS name, - p.AGE AS age, - FROM PERSON p WHERE p.NAME LIKE 'Hiber%' -</sql-query> - - - You can externalize the resultset mapping information in a - <resultset> element which will allow you to - either reuse them across several named queries or through the - setResultSetMapping() API. - - - <resultset> mapping used to externalize mapping - information - - <resultset name="personAddress"> - <return alias="person" class="eg.Person"/> - <return-join alias="address" property="person.mailingAddress"/> -</resultset> - -<sql-query name="personsWith" resultset-ref="personAddress"> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex}, - address.STREET AS {address.street}, - address.CITY AS {address.city}, - address.STATE AS {address.state}, - address.ZIP AS {address.zip} - FROM PERSON person - JOIN ADDRESS address - ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' - WHERE person.NAME LIKE :namePattern -</sql-query> - - - You can, alternatively, use the resultset mapping information in - your hbm files directly in java code. - - - Programmatically specifying the result mapping information - - - List cats = sess.createSQLQuery( - "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id" - ) - .setResultSetMapping("catAndKitten") - .list(); - - - So far we have only looked at externalizing SQL queries using - Hibernate mapping files. The same concept is also available with - anntations and is called named native queries. You can use - @NamedNativeQuery - (@NamedNativeQueries) in conjunction with - @SqlResultSetMapping - (@SqlResultSetMappings). Like - @NamedQuery, @NamedNativeQuery - and @SqlResultSetMapping can be defined at class level, - but their scope is global to the application. Lets look at a view - examples. - - - shows how a resultSetMapping parameter is defined in - @NamedNativeQuery. It represents the name of a defined - @SqlResultSetMapping. The resultset mapping declares - the entities retrieved by this native query. Each field of the entity is - bound to an SQL alias (or column name). All fields of the entity including - the ones of subclasses and the foreign key columns of related entities - have to be present in the SQL query. Field definitions are optional - provided that they map to the same column name as the one declared on the - class property. In the example 2 entities, Night and - Area, are returned and each property is declared and - associated to a column name, actually the column name retrieved by the - query. - - In the result - set mapping is implicit. We only describe the entity class of the result - set mapping. The property / column mappings is done using the entity - mapping values. In this case the model property is bound to the model_txt - column. - - Finally, if the association to a related entity involve a composite - primary key, a @FieldResult element should be used for - each foreign key column. The @FieldResult name is - composed of the property name for the relationship, followed by a dot - ("."), followed by the name or the field or property of the primary key. - This can be seen in . - - - Named SQL query using <classname>@NamedNativeQuery</classname> - together with <classname>@SqlResultSetMapping</classname> - - @NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, " - + " night.night_date, area.id aid, night.area_id, area.name " - + "from Night night, Area area where night.area_id = area.id", - resultSetMapping="joinMapping") -@SqlResultSetMapping(name="joinMapping", entities={ - @EntityResult(entityClass=Night.class, fields = { - @FieldResult(name="id", column="nid"), - @FieldResult(name="duration", column="night_duration"), - @FieldResult(name="date", column="night_date"), - @FieldResult(name="area", column="area_id"), - discriminatorColumn="disc" - }), - @EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = { - @FieldResult(name="id", column="aid"), - @FieldResult(name="name", column="name") - }) - } -) - - - - Implicit result set mapping - - @Entity -@SqlResultSetMapping(name="implicit", - entities=@EntityResult(entityClass=SpaceShip.class)) -@NamedNativeQuery(name="implicitSample", - query="select * from SpaceShip", - resultSetMapping="implicit") -public class SpaceShip { - private String name; - private String model; - private double speed; - - @Id - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Column(name="model_txt") - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } -} - - - - Using dot notation in @FieldResult for specifying associations - - - @Entity -@SqlResultSetMapping(name="compositekey", - entities=@EntityResult(entityClass=SpaceShip.class, - fields = { - @FieldResult(name="name", column = "name"), - @FieldResult(name="model", column = "model"), - @FieldResult(name="speed", column = "speed"), - @FieldResult(name="captain.firstname", column = "firstn"), - @FieldResult(name="captain.lastname", column = "lastn"), - @FieldResult(name="dimensions.length", column = "length"), - @FieldResult(name="dimensions.width", column = "width") - }), - columns = { @ColumnResult(name = "surface"), - @ColumnResult(name = "volume") } ) - -@NamedNativeQuery(name="compositekey", - query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip", - resultSetMapping="compositekey") -} ) -public class SpaceShip { - private String name; - private String model; - private double speed; - private Captain captain; - private Dimensions dimensions; - - @Id - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @ManyToOne(fetch= FetchType.LAZY) - @JoinColumns( { - @JoinColumn(name="fname", referencedColumnName = "firstname"), - @JoinColumn(name="lname", referencedColumnName = "lastname") - } ) - public Captain getCaptain() { - return captain; - } - - public void setCaptain(Captain captain) { - this.captain = captain; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } - - public Dimensions getDimensions() { - return dimensions; - } - - public void setDimensions(Dimensions dimensions) { - this.dimensions = dimensions; - } -} - -@Entity -@IdClass(Identity.class) -public class Captain implements Serializable { - private String firstname; - private String lastname; - - @Id - public String getFirstname() { - return firstname; - } - - public void setFirstname(String firstname) { - this.firstname = firstname; - } - - @Id - public String getLastname() { - return lastname; - } - - public void setLastname(String lastname) { - this.lastname = lastname; - } -} - - - - - If you retrieve a single entity using the default mapping, you can - specify the resultClass attribute instead of - resultSetMapping: - - @NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultClass=SpaceShip.class) -public class SpaceShip { - - - In some of your native queries, you'll have to return scalar values, - for example when building report queries. You can map them in the - @SqlResultsetMapping through - @ColumnResult. You actually can even mix, entities and - scalar returns in the same native query (this is probably not that common - though). - - - Scalar values via <classname>@ColumnResult</classname> - - @SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension")) -@NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar") - - - An other query hint specific to native queries has been introduced: - org.hibernate.callable which can be true or false - depending on whether the query is a stored procedure or not. - -
- Using return-property to explicitly specify column/alias - names - - You can explicitly tell Hibernate what column aliases to use with - <return-property>, instead of using the - {}-syntax to let Hibernate inject its own aliases.For - example: - - <sql-query name="mySqlQuery"> - <return alias="person" class="eg.Person"> - <return-property name="name" column="myName"/> - <return-property name="age" column="myAge"/> - <return-property name="sex" column="mySex"/> - </return> - SELECT person.NAME AS myName, - person.AGE AS myAge, - person.SEX AS mySex, - FROM PERSON person WHERE person.NAME LIKE :name -</sql-query> - - - <return-property> also works with - multiple columns. This solves a limitation with the - {}-syntax which cannot allow fine grained control of - multi-column properties. - - <sql-query name="organizationCurrentEmployments"> - <return alias="emp" class="Employment"> - <return-property name="salary"> - <return-column name="VALUE"/> - <return-column name="CURRENCY"/> - </return-property> - <return-property name="endDate" column="myEndDate"/> - </return> - SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer}, - STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate}, - REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY - FROM EMPLOYMENT - WHERE EMPLOYER = :id AND ENDDATE IS NULL - ORDER BY STARTDATE ASC -</sql-query> - - In this example <return-property> was - used in combination with the {}-syntax for injection. - This allows users to choose how they want to refer column and - properties. - - If your mapping has a discriminator you must use - <return-discriminator> to specify the - discriminator column. -
- -
- Using stored procedures for querying - - Hibernate provides support for queries via stored procedures and - functions. Most of the following documentation is equivalent for both. - The stored procedure/function must return a resultset as the first - out-parameter to be able to work with Hibernate. An example of such a - stored function in Oracle 9 and higher is as follows: - - CREATE OR REPLACE FUNCTION selectAllEmployments - RETURN SYS_REFCURSOR -AS - st_cursor SYS_REFCURSOR; -BEGIN - OPEN st_cursor FOR - SELECT EMPLOYEE, EMPLOYER, - STARTDATE, ENDDATE, - REGIONCODE, EID, VALUE, CURRENCY - FROM EMPLOYMENT; - RETURN st_cursor; - END; - - To use this query in Hibernate you need to map it via a named - query. - - <sql-query name="selectAllEmployees_SP" callable="true"> - <return alias="emp" class="Employment"> - <return-property name="employee" column="EMPLOYEE"/> - <return-property name="employer" column="EMPLOYER"/> - <return-property name="startDate" column="STARTDATE"/> - <return-property name="endDate" column="ENDDATE"/> - <return-property name="regionCode" column="REGIONCODE"/> - <return-property name="id" column="EID"/> - <return-property name="salary"> - <return-column name="VALUE"/> - <return-column name="CURRENCY"/> - </return-property> - </return> - { ? = call selectAllEmployments() } -</sql-query> - - Stored procedures currently only return scalars and entities. - <return-join> and - <load-collection> are not supported. - -
- Rules/limitations for using stored procedures - - You cannot use stored procedures with Hibernate unless you - follow some procedure/function rules. If they do not follow those - rules they are not usable with Hibernate. If you still want to use - these procedures you have to execute them via - session.connection(). The rules are different for - each database, since database vendors have different stored procedure - semantics/syntax. - - Stored procedure queries cannot be paged with - setFirstResult()/setMaxResults(). - - The recommended call form is standard SQL92: { ? = call - functionName(<parameters>) } or { ? = call - procedureName(<parameters>}. Native call syntax is not - supported. - - For Oracle the following rules apply: - - - - A function must return a result set. The first parameter of - a procedure must be an OUT that returns a - result set. This is done by using a - SYS_REFCURSOR type in Oracle 9 or 10. In Oracle - you need to define a REF CURSOR type. See - Oracle literature for further information. - - - - For Sybase or MS SQL server the following rules apply: - - - - The procedure must return a result set. Note that since - these servers can return multiple result sets and update counts, - Hibernate will iterate the results and take the first result that - is a result set as its return value. Everything else will be - discarded. - - - - If you can enable SET NOCOUNT ON in your - procedure it will probably be more efficient, but this is not a - requirement. - - -
-
-
- -
- Custom SQL for create, update and delete - - Hibernate can use custom SQL for create, update, and delete - operations. The SQL can be overridden at the statement level or - inidividual column level. This section describes statement overrides. For - columns, see . shows how to define - custom SQL operatons using annotations. - - - Custom CRUD via annotations - - @Entity -@Table(name="CHAOS") -@SQLInsert( sql="INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)") -@SQLUpdate( sql="UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?") -@SQLDelete( sql="DELETE CHAOS WHERE id = ?") -@SQLDeleteAll( sql="DELETE CHAOS") -@Loader(namedQuery = "chaos") -@NamedNativeQuery(name="chaos", query="select id, size, name, lower( nickname ) as nickname from CHAOS where xml:id= ?", resultClass = Chaos.class) -public class Chaos { - @Id - private Long id; - private Long size; - private String name; - private String nickname; - - - @SQLInsert, @SQLUpdate, - @SQLDelete, @SQLDeleteAll - respectively override the INSERT, UPDATE, DELETE, and DELETE all - statement. The same can be achieved using Hibernate mapping files and the - <sql-insert>, - <sql-update> and - <sql-delete> nodes. This can be seen in . - - - Custom CRUD XML - - <class name="Person"> - <id name="id"> - <generator class="increment"/> - </id> - <property name="name" not-null="true"/> - <sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert> - <sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE xml:id=?</sql-update> - <sql-delete>DELETE FROM PERSON WHERE xml:id=?</sql-delete> -</class> - - - If you expect to call a store procedure, be sure to set the - callable attribute to true. In - annotations as well as in xml. - - To check that the execution happens correctly, Hibernate allows you - to define one of those three strategies: - - - - none: no check is performed: the store procedure is expected to - fail upon issues - - - - count: use of rowcount to check that the update is - successful - - - - param: like COUNT but using an output parameter rather that the - standard mechanism - - - - To define the result check style, use the check - parameter which is again available in annoations as well as in xml. - - You can use the exact same set of annotations respectively xml nodes - to override the collection related statements -see . - - - Overriding SQL statements for collections using - annotations - - @OneToMany -@JoinColumn(name="chaos_fk") -@SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?") -@SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?") -private Set<CasimirParticle> particles = new HashSet<CasimirParticle>(); - - - - The parameter order is important and is defined by the order - Hibernate handles properties. You can see the expected order by enabling - debug logging for the org.hibernate.persister.entity - level. With this level enabled Hibernate will print out the static SQL - that is used to create, update, delete etc. entities. (To see the - expected sequence, remember to not include your custom SQL through - annotations or mapping files as that will override the Hibernate - generated static sql) - - - Overriding SQL statements for secondary tables is also possible - using @org.hibernate.annotations.Table and either (or - all) attributes sqlInsert, - sqlUpdate, sqlDelete: - - - Overriding SQL statements for secondary tables - - @Entity -@SecondaryTables({ - @SecondaryTable(name = "`Cat nbr1`"), - @SecondaryTable(name = "Cat2"}) -@org.hibernate.annotations.Tables( { - @Table(appliesTo = "Cat", comment = "My cat table" ), - @Table(appliesTo = "Cat2", foreignKey = @ForeignKey(name="FK_CAT2_CAT"), fetch = FetchMode.SELECT, - sqlInsert=@SQLInsert(sql="insert into Cat2(storyPart2, id) values(upper(?), ?)") ) -} ) -public class Cat implements Serializable { - - - The previous example also shows that you can give a comment to a - given table (primary or secondary): This comment will be used for DDL - generation. - - - The SQL is directly executed in your database, so you can use any - dialect you like. This will, however, reduce the portability of your - mapping if you use database specific SQL. - - - Last but not least, stored procedures are in most cases required to - return the number of rows inserted, updated and deleted. Hibernate always - registers the first statement parameter as a numeric output parameter for - the CUD operations: - - - Stored procedures and their return value - - CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2) - RETURN NUMBER IS -BEGIN - - update PERSON - set - NAME = uname, - where - ID = uid; - - return SQL%ROWCOUNT; - -END updatePerson; - -
- -
- Custom SQL for loading - - You can also declare your own SQL (or HQL) queries for entity - loading. As with inserts, updates, and deletes, this can be done at the - individual column level as described in or at the statement level. Here - is an example of a statement level override: - - <sql-query name="person"> - <return alias="pers" class="Person" lock-mode="upgrade"/> - SELECT NAME AS {pers.name}, ID AS {pers.id} - FROM PERSON - WHERE xml:id=? - FOR UPDATE -</sql-query> - - This is just a named query declaration, as discussed earlier. You - can reference this named query in a class mapping: - - <class name="Person"> - <id name="id"> - <generator class="increment"/> - </id> - <property name="name" not-null="true"/> - <loader query-ref="person"/> -</class> - - This even works with stored procedures. - - You can even define a query for collection loading: - - <set name="employments" inverse="true"> - <key/> - <one-to-many class="Employment"/> - <loader query-ref="employments"/> -</set> - - <sql-query name="employments"> - <load-collection alias="emp" role="Person.employments"/> - SELECT {emp.*} - FROM EMPLOYMENT emp - WHERE EMPLOYER = :id - ORDER BY STARTDATE ASC, EMPLOYEE ASC -</sql-query> - - You can also define an entity loader that loads a collection by join - fetching: - - <sql-query name="person"> - <return alias="pers" class="Person"/> - <return-join alias="emp" property="pers.employments"/> - SELECT NAME AS {pers.*}, {emp.*} - FROM PERSON pers - LEFT OUTER JOIN EMPLOYMENT emp - ON pers.ID = emp.PERSON_ID - WHERE xml:id=? -</sql-query> - - The annotation equivalent <loader> is the - @Loader annotation as seen in . -
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/readonly.xml b/documentation/src/main/docbook/manual-old/en-US/content/readonly.xml deleted file mode 100644 index fabddbef674a..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/readonly.xml +++ /dev/null @@ -1,835 +0,0 @@ - - - - - Read-only entities - - - - Hibernate's treatment of read-only entities may - differ from what you may have encountered elsewhere. Incorrect usage - may cause unexpected results. - - - - - When an entity is read-only: - - - - - Hibernate does not dirty-check the entity's simple - properties or single-ended associations; - - - - - Hibernate will not update simple properties or updatable - single-ended associations; - - - - - Hibernate will not update the version of the read-only - entity if only simple properties or single-ended - updatable associations are changed; - - - - - - - In some ways, Hibernate treats read-only entities the same as entities that are - not read-only: - - - - - Hibernate cascades operations to associations as - defined in the entity mapping. - - - - - Hibernate updates the version if the entity has a - collection with changes that dirties the entity; - - - - - A read-only entity can be deleted. - - - - - - - Even if an entity is not read-only, its collection association can - be affected if it contains a read-only entity. - - - - For details about the affect of read-only entities on different - property and association types, see - . - - - - For details about how to make entities read-only, see - - - - - Hibernate does some optimizing for read-only entities: - - - - - It saves execution time by not dirty-checking simple properties or - single-ended associations. - - - - - It saves memory by deleting database snapshots. - - - - -
- Making persistent entities read-only - - - Only persistent entities can be made read-only. Transient and - detached entities must be put in persistent state before they - can be made read-only. - - - - Hibernate provides the following ways to make persistent entities read-only: - - - - - - you can map an entity class as immutable; - when an entity of an immutable class is made persistent, - Hibernate automatically makes it read-only. - see for details - - - - - you can change a default so that entities loaded - into the session by Hibernate are automatically - made read-only; see for details - - - - - you can make an HQL query or criteria read-only so - that entities loaded when the query or criteria executes, - scrolls, or iterates, are automatically - made read-only; see for details - - - - - you can make a persistent entity that is already in the - in the session read-only; see - for details - - - - -
- Entities of immutable classes - - - When an entity instance of an immutable class is made - persistent, Hibernate automatically makes it read-only. - - - An entity of an immutable class can created - and deleted the same as an entity of a mutable class. - - - - Hibernate treats a persistent entity of an immutable - class the same way as a read-only persistent entity - of a mutable class. The only exception is that - Hibernate will not allow an entity of an immutable - class to be changed so it is not read-only. - - -
- -
- Loading persistent entities as read-only - - - - Entities of immutable classes are automatically loaded - as read-only. - - - - - To change the default behavior so Hibernate loads entity - instances of mutable classes into the session and automatically - makes them read-only, call: - - Session.setDefaultReadOnly( true ); - - - To change the default back so entities loaded by Hibernate are not - made read-only, call: - - Session.setDefaultReadOnly( false ); - - - You can determine the current setting by calling: - - Session.isDefaultReadOnly(); - - - If Session.isDefaultReadOnly() returns true, entities loaded by - the following are automatically made read-only: - - - - - Session.load() - - - - - Session.get() - - - - - Session.merge() - - - - - executing, scrolling, or iterating HQL queries and - criteria; to override this setting for a particular - HQL query or criteria see - - - - - - - Changing this default has no effect on: - - - - - persistent entities already in the session when the - default was changed - - - - - persistent entities that are refreshed via - Session.refresh(); a refreshed persistent - entity will only be read-only if it was - read-only before refreshing - - - - - persistent entities added by the application via - Session.persist(), Session.save(), and Session.update() - Session.saveOrUpdate() - - - - -
- -
- Loading read-only entities from an HQL query/criteria - - - - Entities of immutable classes are automatically loaded - as read-only. - - - - - If Session.isDefaultReadOnly() returns false (the default) - when an HQL query or criteria executes, then entities - and proxies of mutable classes loaded by the query will - not be read-only. - - - - You can override this behavior so that entities and proxies loaded - by an HQL query or criteria are automatically made read-only. - - - - For an HQL query, call: - - Query.setReadOnly( true ); - - - Query.setReadOnly( true ) must be called before - Query.list(), Query.uniqueResult(), - Query.scroll(), or Query.iterate() - - - - For an HQL criteria, call: - - Criteria.setReadOnly( true ); - - - Criteria.setReadOnly( true ) must be called before - Criteria.list(), Criteria.uniqueResult(), - or Criteria.scroll() - - - - Entities and proxies that exist in the session before being returned - by an HQL query or criteria are not affected. - - - - Uninitialized persistent collections returned by the query are - not affected. Later, when the collection is initialized, - entities loaded into the session will be read-only if - Session.isDefaultReadOnly() returns true. - - - - Using Query.setReadOnly( true ) or - Criteria.setReadOnly( true ) works well - when a single HQL query or criteria loads all the entities and - intializes all the proxies and collections that the application - needs to be read-only. - - - - When it is not possible to load and initialize all - necessary entities in a single query or criteria, - you can temporarily change the session default to load - entities as read-only before the query is executed. - Then you can explicitly initialize proxies and collections - before restoring the session default. - - - -Session session = factory.openSession(); -Transaction tx = session.beginTransaction(); - -setDefaultReadOnly( true ); -Contract contract = - ( Contract ) session.createQuery( - "from Contract where customerName = 'Sherman'" ) - .uniqueResult(); -Hibernate.initialize( contract.getPlan() ); -Hibernate.initialize( contract.getVariations() ); -Hibernate.initialize( contract.getNotes() ); -setDefaultReadOnly( false ); -... -tx.commit(); -session.close(); - - - - - If Session.isDefaultReadOnly() returns true, then you can - use Query.setReadOnly( false ) and Criteria.setReadOnly( false ) - to override this session setting and load entities that are - not read-only. - - -
- -
- Making a persistent entity read-only - - - Persistent entities of immutable classes are automatically - made read-only. - - - - - To make a persistent entity or proxy read-only, call: - - Session.setReadOnly(entityOrProxy, true) - - - To change a read-only entity or proxy of a mutable class so - it is no longer read-only, call: - - Session.setReadOnly(entityOrProxy, false) - - - - When a read-only entity or proxy is changed so it is no longer - read-only, Hibernate assumes that the current state of the - read-only entity is consistent with its database representation. - If this is not true, then any non-flushed changes made before - or while the entity was read-only, will be ignored. - - - - - To throw away non-flushed changes and make the persistent entity - consistent with its database representation, call: - session.refresh( entity ); - - - To flush changes made before or while the entity - was read-only and make the database representation - consistent with the current state of the persistent - entity: - - -// evict the read-only entity so it is detached -session.evict( entity ); - -// make the detached entity (with the non-flushed changes) persistent -session.update( entity ); - -// now entity is no longer read-only and its changes can be flushed -s.flush(); - -
-
- -
- Read-only affect on property type - - - The following table summarizes how different property types are - affected by making an entity read-only. - - - - Affect of read-only entity on property types - - - - - - Property/Association Type - Changes flushed to DB? - - - - - - Simple - - () - - - no* - - - - Unidirectional one-to-one - Unidirectional many-to-one - - () - - - - - no* - no* - - - - - Unidirectional one-to-many - Unidirectional many-to-many - - () - - - - yes - yes - - - - - Bidirectional one-to-one - - () - - - only if the owning entity is not read-only* - - - - Bidirectional one-to-many/many-to-one - inverse collection - non-inverse collection - - () - - - - - only added/removed entities that are not read-only* - yes - - - - - Bidirectional many-to-many - - () - - - yes - - - -
- - - * Behavior is different when the entity having the property/association - is read-only, compared to when it is not read-only. - - -
- Simple properties - - - When a persistent object is read-only, Hibernate does not - dirty-check simple properties. - - - - Hibernate will not synchronize simple property state changes - to the database. If you have automatic versioning, Hibernate - will not increment the version if any simple properties change. - - - -Session session = factory.openSession(); -Transaction tx = session.beginTransaction(); - -// get a contract and make it read-only -Contract contract = ( Contract ) session.get( Contract.class, contractId ); -session.setReadOnly( contract, true ); - -// contract.getCustomerName() is "Sherman" -contract.setCustomerName( "Yogi" ); -tx.commit(); - -tx = session.beginTransaction(); - -contract = ( Contract ) session.get( Contract.class, contractId ); -// contract.getCustomerName() is still "Sherman" -... -tx.commit(); -session.close(); - - -
- -
- Unidirectional associations - -
- Unidirectional one-to-one and many-to-one - - - Hibernate treats unidirectional one-to-one and many-to-one - associations in the same way when the owning entity is - read-only. - - - - We use the term unidirectional single-ended - association when referring to functionality - that is common to unidirectional one-to-one and many-to-one - associations. - - - - Hibernate does not dirty-check unidirectional single-ended - associations when the owning entity is read-only. - - - - If you change a read-only entity's reference to a - unidirectional single-ended association to null, - or to refer to a different entity, that change - will not be flushed to the database. - - - - - If an entity is of an immutable class, - then its references to unidirectional single-ended - associations must be assigned when that - entity is first created. Because the entity is - automatically made read-only, these references can - not be updated. - - - - - If automatic versioning is used, Hibernate will not - increment the version due to local changes to - unidirectional single-ended associations. - - - - In the following examples, Contract has a unidirectional - many-to-one association with Plan. Contract cascades save and - update operations to the association. - - - - The following shows that changing a read-only entity's - many-to-one association reference to null has no effect - on the entity's database representation. - - -// get a contract with an existing plan; -// make the contract read-only and set its plan to null -tx = session.beginTransaction(); -Contract contract = ( Contract ) session.get( Contract.class, contractId ); -session.setReadOnly( contract, true ); -contract.setPlan( null ); -tx.commit(); - -// get the same contract -tx = session.beginTransaction(); -contract = ( Contract ) session.get( Contract.class, contractId ); - -// contract.getPlan() still refers to the original plan; - -tx.commit(); -session.close(); - - - The following shows that, even though - an update to a read-only entity's many-to-one - association has no affect on the entity's - database representation, flush still cascades - the save-update operation to the locally - changed association. - - -// get a contract with an existing plan; -// make the contract read-only and change to a new plan -tx = session.beginTransaction(); -Contract contract = ( Contract ) session.get( Contract.class, contractId ); -session.setReadOnly( contract, true ); -Plan newPlan = new Plan( "new plan" -contract.setPlan( newPlan); -tx.commit(); - -// get the same contract -tx = session.beginTransaction(); -contract = ( Contract ) session.get( Contract.class, contractId ); -newPlan = ( Contract ) session.get( Plan.class, newPlan.getId() ); - -// contract.getPlan() still refers to the original plan; -// newPlan is non-null because it was persisted when -// the previous transaction was committed; - -tx.commit(); -session.close(); - -
- -
- Unidirectional one-to-many and many-to-many - - - Hibernate treats unidirectional one-to-many - and many-to-many associations owned by a read-only - entity the same as when owned by an entity that is not - read-only. - - - - Hibernate dirty-checks unidirectional one-to-many and - many-to-many associations; - - - - The collection can contain entities that - are read-only, as well as entities - that are not read-only. - - - - Entities can be added and removed from the - collection; changes are flushed to the database. - - - - If automatic versioning is used, Hibernate will - update the version due to changes in the collection - if they dirty the owning entity. - - -
- -
- -
- Bidirectional associations - -
- Bidirectional one-to-one - - - If a read-only entity owns a bidirectional - one-to-one association: - - - - - - Hibernate does not dirty-check the association. - - - - - updates that change the association reference - to null or to refer to a different entity - will not be flushed to the database. - - - - - If automatic versioning is used, Hibernate will not - increment the version due to local changes to - the association. - - - - - - - If an entity is of an immutable class, - and it owns a bidirectional one-to-one - association, then its reference must be - assigned when that entity is first created. - Because the entity is automatically made - read-only, these references cannot be updated. - - - - - When the owner is not read-only, Hibernate treats - an association with a read-only entity the same - as when the association is with an entity that is - not read-only. - - -
- -
- Bidirectional one-to-many/many-to-one - - - A read-only entity has no impact on a bidirectional - one-to-many/many-to-one association if: - - - - - - the read-only entity is on the one-to-many side - using an inverse collection; - - - - - the read-only entity is on the one-to-many side - using a non-inverse collection; - - - - - the one-to-many side uses a non-inverse collection - that contains the read-only entity - - - - - - When the one-to-many side uses an inverse collection: - - - - - - a read-only entity can only be added to the collection - when it is created; - - - - - a read-only entity can only be removed from the - collection by an orphan delete or by explicitly - deleting the entity. - - - - -
- -
- Bidirectional many-to-many - - Hibernate treats bidirectional many-to-many - associations owned by a read-only entity the - same as when owned by an entity that is not - read-only. - - - - Hibernate dirty-checks bidirectional many-to-many - associations. - - - - The collection on either side of the association - can contain entities that are read-only, as well - as entities that are not read-only. - - - - Entities are added and removed from both sides - of the collection; changes are flushed to the - database. - - - - If automatic versioning is used, Hibernate will - update the version due to changes in both sides of - the collection if they dirty the entity owning the - respective collections. - - -
- -
-
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/session_api.xml b/documentation/src/main/docbook/manual-old/en-US/content/session_api.xml deleted file mode 100644 index 8f14acd7909f..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/session_api.xml +++ /dev/null @@ -1,1210 +0,0 @@ - - - - - Working with objects - - Hibernate is a full object/relational mapping solution that not only - shields the developer from the details of the underlying database management - system, but also offers state management of objects. - This is, contrary to the management of SQL statements in - common JDBC/SQL persistence layers, a natural object-oriented view of - persistence in Java applications. - - In other words, Hibernate application developers should always think - about the state of their objects, and not necessarily - about the execution of SQL statements. This part is taken care of by - Hibernate and is only relevant for the application developer when tuning the - performance of the system. - -
- Hibernate object states - - Hibernate defines and supports the following object states: - - - - Transient - an object is transient if it - has just been instantiated using the new operator, - and it is not associated with a Hibernate Session. - It has no persistent representation in the database and no identifier - value has been assigned. Transient instances will be destroyed by the - garbage collector if the application does not hold a reference - anymore. Use the Hibernate Session to make an - object persistent (and let Hibernate take care of the SQL statements - that need to be executed for this transition). - - - - Persistent - a persistent instance has a - representation in the database and an identifier value. It might just - have been saved or loaded, however, it is by definition in the scope - of a Session. Hibernate will detect any changes - made to an object in persistent state and synchronize the state with - the database when the unit of work completes. Developers do not - execute manual UPDATE statements, or - DELETE statements when an object should be made - transient. - - - - Detached - a detached instance is an object - that has been persistent, but its Session has been - closed. The reference to the object is still valid, of course, and the - detached instance might even be modified in this state. A detached - instance can be reattached to a new Session at a - later point in time, making it (and all the modifications) persistent - again. This feature enables a programming model for long running units - of work that require user think-time. We call them - application transactions, i.e., a unit of work - from the point of view of the user. - - - - We will now discuss the states and state transitions (and the - Hibernate methods that trigger a transition) in more detail. -
- -
- Making objects persistent - - Newly instantiated instances of a persistent class are considered - transient by Hibernate. We can make a transient - instance persistent by associating it with a - session: - - DomesticCat fritz = new DomesticCat(); -fritz.setColor(Color.GINGER); -fritz.setSex('M'); -fritz.setName("Fritz"); -Long generatedId = (Long) sess.save(fritz); - - If Cat has a generated identifier, the identifier - is generated and assigned to the cat when - save() is called. If Cat has an - assigned identifier, or a composite key, the identifier - should be assigned to the cat instance before calling - save(). You can also use persist() - instead of save(), with the semantics defined in the - EJB3 early draft. - - - - persist() makes a transient instance - persistent. However, it does not guarantee that the identifier value - will be assigned to the persistent instance immediately, the - assignment might happen at flush time. persist() - also guarantees that it will not execute an INSERT - statement if it is called outside of transaction boundaries. This is - useful in long-running conversations with an extended - Session/persistence context. - - - - save() does guarantee to return an - identifier. If an INSERT has to be executed to get the identifier ( - e.g. "identity" generator, not "sequence"), this INSERT happens - immediately, no matter if you are inside or outside of a transaction. - This is problematic in a long-running conversation with an extended - Session/persistence context. - - - - Alternatively, you can assign the identifier using an overloaded - version of save(). - - DomesticCat pk = new DomesticCat(); -pk.setColor(Color.TABBY); -pk.setSex('F'); -pk.setName("PK"); -pk.setKittens( new HashSet() ); -pk.addKitten(fritz); -sess.save( pk, new Long(1234) ); - - If the object you make persistent has associated objects (e.g. the - kittens collection in the previous example), these - objects can be made persistent in any order you like unless you have a - NOT NULL constraint upon a foreign key column. There is - never a risk of violating foreign key constraints. However, you might - violate a NOT NULL constraint if you - save() the objects in the wrong order. - - Usually you do not bother with this detail, as you will normally use - Hibernate's transitive persistence feature to save - the associated objects automatically. Then, even NOT - NULL constraint violations do not occur - Hibernate will take - care of everything. Transitive persistence is discussed later in this - chapter. -
- -
- Loading an object - - The load() methods of Session - provide a way of retrieving a persistent instance if you know its - identifier. load() takes a class object and loads the - state into a newly instantiated instance of that class in a persistent - state. - - Cat fritz = (Cat) sess.load(Cat.class, generatedId); - - // you need to wrap primitive identifiers -long id = 1234; -DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) ); - - Alternatively, you can load state into a given instance: - - Cat cat = new DomesticCat(); -// load pk's state into cat -sess.load( cat, new Long(pkId) ); -Set kittens = cat.getKittens(); - - Be aware that load() will throw an unrecoverable - exception if there is no matching database row. If the class is mapped - with a proxy, load() just returns an uninitialized - proxy and does not actually hit the database until you invoke a method of - the proxy. This is useful if you wish to create an association to an - object without actually loading it from the database. It also allows - multiple instances to be loaded as a batch if - batch-size is defined for the class mapping. - - If you are not certain that a matching row exists, you should use - the get() method which hits the database immediately - and returns null if there is no matching row. - - Cat cat = (Cat) sess.get(Cat.class, id); -if (cat==null) { - cat = new Cat(); - sess.save(cat, id); -} -return cat; - - You can even load an object using an SQL SELECT ... FOR - UPDATE, using a LockMode. See the API - documentation for more information. - - Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE); - - Any associated instances or contained collections will - not be selected FOR UPDATE, unless - you decide to specify lock or all as - a cascade style for the association. - - It is possible to re-load an object and all its collections at any - time, using the refresh() method. This is useful when - database triggers are used to initialize some of the properties of the - object. - - sess.save(cat); -sess.flush(); //force the SQL INSERT -sess.refresh(cat); //re-read the state (after the trigger executes) - - How much does Hibernate load from the database and how many SQL - SELECTs will it use? This depends on the - fetching strategy. This is explained in . -
- -
- Querying - - If you do not know the identifiers of the objects you are looking - for, you need a query. Hibernate supports an easy-to-use but powerful - object oriented query language (HQL). For programmatic query creation, - Hibernate supports a sophisticated Criteria and Example query feature (QBC - and QBE). You can also express your query in the native SQL of your - database, with optional support from Hibernate for result set conversion - into objects. - -
- Executing queries - - HQL and native SQL queries are represented with an instance of - org.hibernate.Query. This interface offers methods - for parameter binding, result set handling, and for the execution of the - actual query. You always obtain a Query using the - current Session: - - List cats = session.createQuery( - "from Cat as cat where cat.birthdate < ?") - .setDate(0, date) - .list(); - -List mothers = session.createQuery( - "select mother from Cat as cat join cat.mother as mother where cat.name = ?") - .setString(0, name) - .list(); - -List kittens = session.createQuery( - "from Cat as cat where cat.mother = ?") - .setEntity(0, pk) - .list(); - -Cat mother = (Cat) session.createQuery( - "select cat.mother from Cat as cat where cat = ?") - .setEntity(0, izi) - .uniqueResult();]] - -Query mothersWithKittens = (Cat) session.createQuery( - "select mother from Cat as mother left join fetch mother.kittens"); -Set uniqueMothers = new HashSet(mothersWithKittens.list()); - - A query is usually executed by invoking list(). - The result of the query will be loaded completely into a collection in - memory. Entity instances retrieved by a query are in a persistent state. - The uniqueResult() method offers a shortcut if you - know your query will only return a single object. Queries that make use - of eager fetching of collections usually return duplicates of the root - objects, but with their collections initialized. You can filter these - duplicates through a Set. - -
- Iterating results - - Occasionally, you might be able to achieve better performance by - executing the query using the iterate() method. - This will usually be the case if you expect that the actual entity - instances returned by the query will already be in the session or - second-level cache. If they are not already cached, - iterate() will be slower than - list() and might require many database hits for a - simple query, usually 1 for the initial select - which only returns identifiers, and n additional - selects to initialize the actual instances. - - // fetch ids -Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate(); -while ( iter.hasNext() ) { - Qux qux = (Qux) iter.next(); // fetch the object - // something we couldnt express in the query - if ( qux.calculateComplicatedAlgorithm() ) { - // delete the current instance - iter.remove(); - // dont need to process the rest - break; - } -} -
- -
- Queries that return tuples - - Hibernate queries sometimes return tuples of objects. Each tuple - is returned as an array: - - Iterator kittensAndMothers = sess.createQuery( - "select kitten, mother from Cat kitten join kitten.mother mother") - .list() - .iterator(); - -while ( kittensAndMothers.hasNext() ) { - Object[] tuple = (Object[]) kittensAndMothers.next(); - Cat kitten = (Cat) tuple[0]; - Cat mother = (Cat) tuple[1]; - .... -} -
- -
- Scalar results - - Queries can specify a property of a class in the - select clause. They can even call SQL aggregate - functions. Properties or aggregates are considered "scalar" results - and not entities in persistent state. - - Iterator results = sess.createQuery( - "select cat.color, min(cat.birthdate), count(cat) from Cat cat " + - "group by cat.color") - .list() - .iterator(); - -while ( results.hasNext() ) { - Object[] row = (Object[]) results.next(); - Color type = (Color) row[0]; - Date oldest = (Date) row[1]; - Integer count = (Integer) row[2]; - ..... -} -
- -
- Bind parameters - - Methods on Query are provided for binding - values to named parameters or JDBC-style ? - parameters. Contrary to JDBC, Hibernate numbers parameters - from zero. Named parameters are identifiers of the form - :name in the query string. The advantages of named - parameters are as follows: - - - - named parameters are insensitive to the order they occur in - the query string - - - - they can occur multiple times in the same query - - - - they are self-documenting - - - - //named parameter (preferred) -Query q = sess.createQuery("from DomesticCat cat where cat.name = :name"); -q.setString("name", "Fritz"); -Iterator cats = q.iterate(); - - //positional parameter -Query q = sess.createQuery("from DomesticCat cat where cat.name = ?"); -q.setString(0, "Izi"); -Iterator cats = q.iterate(); - - //named parameter list -List names = new ArrayList(); -names.add("Izi"); -names.add("Fritz"); -Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)"); -q.setParameterList("namesList", names); -List cats = q.list(); -
- -
- Pagination - - If you need to specify bounds upon your result set, that is, the - maximum number of rows you want to retrieve and/or the first row you - want to retrieve, you can use methods of the Query - interface: - - Query q = sess.createQuery("from DomesticCat cat"); -q.setFirstResult(20); -q.setMaxResults(10); -List cats = q.list(); - - Hibernate knows how to translate this limit query into the - native SQL of your DBMS. -
- -
- Scrollable iteration - - If your JDBC driver supports scrollable - ResultSets, the Query interface - can be used to obtain a ScrollableResults object - that allows flexible navigation of the query results. - - Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " + - "order by cat.name"); -ScrollableResults cats = q.scroll(); -if ( cats.first() ) { - - // find the first name on each page of an alphabetical list of cats by name - firstNamesOfPages = new ArrayList(); - do { - String name = cats.getString(0); - firstNamesOfPages.add(name); - } - while ( cats.scroll(PAGE_SIZE) ); - - // Now get the first page of cats - pageOfCats = new ArrayList(); - cats.beforeFirst(); - int i=0; - while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) ); - -} -cats.close() - - Note that an open database connection and cursor is required for - this functionality. Use - setMaxResult()/setFirstResult() - if you need offline pagination functionality. -
- -
- Externalizing named queries - - Queries can also be configured as so called named queries using - annotations or Hibernate mapping documents. - @NamedQuery and @NamedQueries - can be defined at the class level as seen in . However their - definitions are global to the session factory/entity manager factory - scope. A named query is defined by its name and the actual query - string. - - - Defining a named query using - <classname>@NamedQuery</classname> - - @Entity -@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date") -public class Night { - ... -} - -public class MyDao { - doStuff() { - Query q = s.getNamedQuery("night.moreRecentThan"); - q.setDate( "date", aMonthAgo ); - List results = q.list(); - ... - } - ... -} - - - Using a mapping document can be configured using the - <query> node. Remember to use a - CDATA section if your query contains characters - that could be interpreted as markup. - - - Defining a named query using - <literal><query></literal> - - <query name="ByNameAndMaximumWeight"><![CDATA[ - from eg.DomesticCat as cat - where cat.name = ? - and cat.weight > ? -] ]></query> - - - Parameter binding and executing is done programatically as seen - in . - - - Parameter binding of a named query - - Query q = sess.getNamedQuery("ByNameAndMaximumWeight"); -q.setString(0, name); -q.setInt(1, minWeight); -List cats = q.list(); - - - The actual program code is independent of the query language - that is used. You can also define native SQL queries in metadata, or - migrate existing queries to Hibernate by placing them in mapping - files. - - Also note that a query declaration inside a - <hibernate-mapping> element requires a global - unique name for the query, while a query declaration inside a - <class> element is made unique automatically - by prepending the fully qualified name of the class. For example - eg.Cat.ByNameAndMaximumWeight. -
-
- -
- Filtering collections - - A collection filter is a special type of - query that can be applied to a persistent collection or array. The query - string can refer to this, meaning the current - collection element. - - Collection blackKittens = session.createFilter( - pk.getKittens(), - "where this.color = ?") - .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) ) - .list() -); - - The returned collection is considered a bag that is a copy of the - given collection. The original collection is not modified. This is - contrary to the implication of the name "filter", but consistent with - expected behavior. - - Observe that filters do not require a from - clause, although they can have one if required. Filters are not limited - to returning the collection elements themselves. - - Collection blackKittenMates = session.createFilter( - pk.getKittens(), - "select this.mate where this.color = eg.Color.BLACK.intValue") - .list(); - - Even an empty filter query is useful, e.g. to load a subset of - elements in a large collection: - - Collection tenKittens = session.createFilter( - mother.getKittens(), "") - .setFirstResult(0).setMaxResults(10) - .list(); -
- -
- Criteria queries - - HQL is extremely powerful, but some developers prefer to build - queries dynamically using an object-oriented API, rather than building - query strings. Hibernate provides an intuitive - Criteria query API for these cases: - - Criteria crit = session.createCriteria(Cat.class); -crit.add( Restrictions.eq( "color", eg.Color.BLACK ) ); -crit.setMaxResults(10); -List cats = crit.list(); - - The Criteria and the associated - Example API are discussed in more detail in . -
- -
- Queries in native SQL - - You can express a query in SQL, using - createSQLQuery() and let Hibernate manage the mapping - from result sets to objects. You can at any time call - session.connection() and use the JDBC - Connection directly. If you choose to use the - Hibernate API, you must enclose SQL aliases in braces: - - List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10") - .addEntity("cat", Cat.class) -.list(); - - List cats = session.createSQLQuery( - "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " + - "{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " + - "FROM CAT {cat} WHERE ROWNUM<10") - .addEntity("cat", Cat.class) -.list() - - SQL queries can contain named and positional parameters, just like - Hibernate queries. More information about native SQL queries in - Hibernate can be found in . -
-
- -
- Modifying persistent objects - - Transactional persistent instances (i.e. - objects loaded, saved, created or queried by the - Session) can be manipulated by the application, and any - changes to persistent state will be persisted when the - Session is flushed. This is - discussed later in this chapter. There is no need to call a particular - method (like update(), which has a different purpose) - to make your modifications persistent. The most straightforward way to - update the state of an object is to load() it and then - manipulate it directly while the Session is - open: - - DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) ); -cat.setName("PK"); -sess.flush(); // changes to cat are automatically detected and persisted - - Sometimes this programming model is inefficient, as it requires in - the same session both an SQL SELECT to load an object - and an SQL UPDATE to persist its updated state. - Hibernate offers an alternate approach by using detached instances. - -
- -
- Modifying detached objects - - Many applications need to retrieve an object in one transaction, - send it to the UI layer for manipulation, then save the changes in a new - transaction. Applications that use this kind of approach in a - high-concurrency environment usually use versioned data to ensure - isolation for the "long" unit of work. - - Hibernate supports this model by providing for reattachment of - detached instances using the Session.update() or - Session.merge() methods: - - // in the first session -Cat cat = (Cat) firstSession.load(Cat.class, catId); -Cat potentialMate = new Cat(); -firstSession.save(potentialMate); - -// in a higher layer of the application -cat.setMate(potentialMate); - -// later, in a new session -secondSession.update(cat); // update cat -secondSession.update(mate); // update mate - - If the Cat with identifier - catId had already been loaded by - secondSession when the application tried to reattach - it, an exception would have been thrown. - - Use update() if you are certain that the session - does not contain an already persistent instance with the same identifier. - Use merge() if you want to merge your modifications at - any time without consideration of the state of the session. In other - words, update() is usually the first method you would - call in a fresh session, ensuring that the reattachment of your detached - instances is the first operation that is executed. - - The application should individually update() - detached instances that are reachable from the given detached instance - only if it wants their state to be updated. This can - be automated using transitive persistence. See for more information. - - The lock() method also allows an application to - reassociate an object with a new session. However, the detached instance - has to be unmodified. - - //just reassociate: -sess.lock(fritz, LockMode.NONE); -//do a version check, then reassociate: -sess.lock(izi, LockMode.READ); -//do a version check, using SELECT ... FOR UPDATE, then reassociate: -sess.lock(pk, LockMode.UPGRADE); - - Note that lock() can be used with various - LockModes. See the API documentation and the chapter on - transaction handling for more information. Reattachment is not the only - usecase for lock(). - - Other models for long units of work are discussed in . -
- -
- Automatic state detection - - Hibernate users have requested a general purpose method that either - saves a transient instance by generating a new identifier or - updates/reattaches the detached instances associated with its current - identifier. The saveOrUpdate() method implements this - functionality. - - // in the first session -Cat cat = (Cat) firstSession.load(Cat.class, catID); - -// in a higher tier of the application -Cat mate = new Cat(); -cat.setMate(mate); - -// later, in a new session -secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id) -secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id) - - The usage and semantics of saveOrUpdate() seems - to be confusing for new users. Firstly, so long as you are not trying to - use instances from one session in another new session, you should not need - to use update(), saveOrUpdate(), or - merge(). Some whole applications will never use either - of these methods. - - Usually update() or - saveOrUpdate() are used in the following - scenario: - - - - the application loads an object in the first session - - - - the object is passed up to the UI tier - - - - some modifications are made to the object - - - - the object is passed back down to the business logic tier - - - - the application persists these modifications by calling - update() in a second session - - - - saveOrUpdate() does the following: - - - - if the object is already persistent in this session, do - nothing - - - - if another object associated with the session has the same - identifier, throw an exception - - - - if the object has no identifier property, - save() it - - - - if the object's identifier has the value assigned to a newly - instantiated object, save() it - - - - if the object is versioned by a - <version> or - <timestamp>, and the version property value - is the same value assigned to a newly instantiated object, - save() it - - - - otherwise update() the object - - - - and merge() is very different: - - - - if there is a persistent instance with the same identifier - currently associated with the session, copy the state of the given - object onto the persistent instance - - - - if there is no persistent instance currently associated with the - session, try to load it from the database, or create a new persistent - instance - - - - the persistent instance is returned - - - - the given instance does not become associated with the session, - it remains detached - - -
- -
- Deleting persistent objects - - Session.delete() will remove an object's state - from the database. Your application, however, can still hold a reference - to a deleted object. It is best to think of delete() as - making a persistent instance, transient. - - sess.delete(cat); - - You can delete objects in any order, without risk of foreign key - constraint violations. It is still possible to violate a NOT - NULL constraint on a foreign key column by deleting objects in - the wrong order, e.g. if you delete the parent, but forget to delete the - children. -
- -
- Replicating object between two different datastores - - It is sometimes useful to be able to take a graph of persistent - instances and make them persistent in a different datastore, without - regenerating identifier values. - - //retrieve a cat from one database -Session session1 = factory1.openSession(); -Transaction tx1 = session1.beginTransaction(); -Cat cat = session1.get(Cat.class, catId); -tx1.commit(); -session1.close(); - -//reconcile with a second database -Session session2 = factory2.openSession(); -Transaction tx2 = session2.beginTransaction(); -session2.replicate(cat, ReplicationMode.LATEST_VERSION); -tx2.commit(); -session2.close(); - - The ReplicationMode determines how - replicate() will deal with conflicts with existing rows - in the database: - - - - ReplicationMode.IGNORE: ignores the object - when there is an existing database row with the same identifier - - - - ReplicationMode.OVERWRITE: overwrites any - existing database row with the same identifier - - - - ReplicationMode.EXCEPTION: throws an - exception if there is an existing database row with the same - identifier - - - - ReplicationMode.LATEST_VERSION: overwrites - the row if its version number is earlier than the version number of - the object, or ignore the object otherwise - - - - Usecases for this feature include reconciling data entered into - different database instances, upgrading system configuration information - during product upgrades, rolling back changes made during non-ACID - transactions and more. -
- -
- Flushing the Session - - Sometimes the Session will execute the SQL - statements needed to synchronize the JDBC connection's state with the - state of objects held in memory. This process, called - flush, occurs by default at the following - points: - - - - before some query executions - - - - from - org.hibernate.Transaction.commit() - - - - from Session.flush() - - - - The SQL statements are issued in the following order: - - - - all entity insertions in the same order the corresponding - objects were saved using Session.save() - - - - all entity updates - - - - all collection deletions - - - - all collection element deletions, updates and insertions - - - - all collection insertions - - - - all entity deletions in the same order the corresponding objects - were deleted using Session.delete() - - - - An exception is that objects using native ID - generation are inserted when they are saved. - - Except when you explicitly flush(), there are - absolutely no guarantees about when the - Session executes the JDBC calls, only the - order in which they are executed. However, Hibernate - does guarantee that the Query.list(..) will never - return stale or incorrect data. - - It is possible to change the default behavior so that flush occurs - less frequently. The FlushMode class defines three - different modes: only flush at commit time when the Hibernate - Transaction API is used, flush automatically using the - explained routine, or never flush unless flush() is - called explicitly. The last mode is useful for long running units of work, - where a Session is kept open and disconnected for a - long time (see ). - - sess = sf.openSession(); -Transaction tx = sess.beginTransaction(); -sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state - -Cat izi = (Cat) sess.load(Cat.class, id); -izi.setName(iznizi); - -// might return stale data -sess.find("from Cat as cat left outer join cat.kittens kitten"); - -// change to izi is not flushed! -... -tx.commit(); // flush occurs -sess.close(); - - During flush, an exception might occur (e.g. if a DML operation - violates a constraint). Since handling exceptions involves some - understanding of Hibernate's transactional behavior, we discuss it in - . -
- -
- Transitive persistence - - It is quite cumbersome to save, delete, or reattach individual - objects, especially if you deal with a graph of associated objects. A - common case is a parent/child relationship. Consider the following - example: - - If the children in a parent/child relationship would be value typed - (e.g. a collection of addresses or strings), their life cycle would depend - on the parent and no further action would be required for convenient - "cascading" of state changes. When the parent is saved, the value-typed - child objects are saved and when the parent is deleted, the children will - be deleted, etc. This works for operations such as the removal of a child - from the collection. Since value-typed objects cannot have shared - references, Hibernate will detect this and delete the child from the - database. - - Now consider the same scenario with parent and child objects being - entities, not value-types (e.g. categories and items, or parent and child - cats). Entities have their own life cycle and support shared references. - Removing an entity from the collection does not mean it can be deleted), - and there is by default no cascading of state from one entity to any other - associated entities. Hibernate does not implement persistence by - reachability by default. - - For each basic operation of the Hibernate session - including - persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), - evict(), replicate() - there is a corresponding cascade style. - Respectively, the cascade styles are named create, merge, - save-update, delete, lock, refresh, evict, replicate. If you - want an operation to be cascaded along an association, you must indicate - that in the mapping document. For example: - - <one-to-one name="person" cascade="persist"/> - - Cascade styles my be combined: - - <one-to-one name="person" cascade="persist,delete,lock"/> - - You can even use cascade="all" to specify that - all operations should be cascaded along the - association. The default cascade="none" specifies that - no operations are to be cascaded. - - In case you are using annotatons you probably have noticed the - cascade attribute taking an array of - CascadeType as a value. The cascade concept in JPA - is very is similar to the transitive persistence and cascading of - operations as described above, but with slightly different semantics and - cascading types: - - - - CascadeType.PERSIST: cascades the persist - (create) operation to associated entities persist() is called or if - the entity is managed - - - - CascadeType.MERGE: cascades the merge - operation to associated entities if merge() is called or if the entity - is managed - - - - CascadeType.REMOVE: cascades the remove - operation to associated entities if delete() is called - - - - CascadeType.REFRESH: cascades the refresh - operation to associated entities if refresh() is called - - - - CascadeType.DETACH: cascades the detach - operation to associated entities if detach() is called - - - - CascadeType.ALL: all of the above - - - - - CascadeType.ALL also covers Hibernate specific operations like - save-update, lock etc... - - - A special cascade style, delete-orphan, applies - only to one-to-many associations, and indicates that the - delete() operation should be applied to any child - object that is removed from the association. Using annotations there is no - CascadeType.DELETE-ORPHAN equivalent. Instead you can - use the attribute orphanRemoval as seen in . If an entity is - removed from a @OneToMany collection or an - associated entity is dereferenced from a @OneToOne - association, this associated entity can be marked for deletion if - orphanRemoval is set to true. - - - <literal>@OneToMany</literal> with - <literal>orphanRemoval</literal> - - @Entity -public class Customer { - private Set<Order> orders; - - @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true) - public Set<Order> getOrders() { return orders; } - - public void setOrders(Set<Order> orders) { this.orders = orders; } - - [...] -} - -@Entity -public class Order { ... } - -Customer customer = em.find(Customer.class, 1l); -Order order = em.find(Order.class, 1l); -customer.getOrders().remove(order); //order will be deleted by cascade - - - Recommendations: - - - - It does not usually make sense to enable cascade on a - many-to-one or many-to-many association. In fact the - @ManyToOne and @ManyToMany don't - even offer a orphanRemoval attribute. Cascading is - often useful for one-to-one and one-to-many associations. - - - - If the child object's lifespan is bounded by the lifespan of the - parent object, make it a life cycle object by - specifying - cascade="all,delete-orphan" (@OneToMany(cascade=CascadeType.ALL, - orphanRemoval=true)). - - - - - Otherwise, you might not need cascade at all. But if you think - that you will often be working with the parent and children together - in the same transaction, and you want to save yourself some typing, - consider using - cascade="persist,merge,save-update". - - - - Mapping an association (either a single valued association, or a - collection) with cascade="all" marks the association as - a parent/child style relationship where - save/update/delete of the parent results in save/update/delete of the - child or children. - - Furthermore, a mere reference to a child from a persistent parent - will result in save/update of the child. This metaphor is incomplete, - however. A child which becomes unreferenced by its parent is - not automatically deleted, except in the case of a - one-to-many association mapped with - cascade="delete-orphan". The precise semantics of - cascading operations for a parent/child relationship are as - follows: - - - - If a parent is passed to persist(), all - children are passed to persist() - - - - If a parent is passed to merge(), all - children are passed to merge() - - - - If a parent is passed to save(), - update() or saveOrUpdate(), all - children are passed to saveOrUpdate() - - - - If a transient or detached child becomes referenced by a - persistent parent, it is passed to - saveOrUpdate() - - - - If a parent is deleted, all children are passed to - delete() - - - - If a child is dereferenced by a persistent parent, - nothing special happens - the application should - explicitly delete the child if necessary - unless - cascade="delete-orphan", in which case the - "orphaned" child is deleted. - - - - Finally, note that cascading of operations can be applied to an - object graph at call time or at flush - time. All operations, if enabled, are cascaded to associated - entities reachable when the operation is executed. However, - save-update and delete-orphan are - transitive for all associated entities reachable during flush of the - Session. -
- -
- Using metadata - - Hibernate requires a rich meta-level model of all entity and value - types. This model can be useful to the application itself. For example, - the application might use Hibernate's metadata to implement a "smart" - deep-copy algorithm that understands which objects should be copied (eg. - mutable value types) and which objects that should not (e.g. immutable - value types and, possibly, associated entities). - - Hibernate exposes metadata via the ClassMetadata - and CollectionMetadata interfaces and the - Type hierarchy. Instances of the metadata interfaces - can be obtained from the SessionFactory. - - Cat fritz = ......; -ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class); - -Object[] propertyValues = catMeta.getPropertyValues(fritz); -String[] propertyNames = catMeta.getPropertyNames(); -Type[] propertyTypes = catMeta.getPropertyTypes(); - -// get a Map of all properties which are not collections or associations -Map namedValues = new HashMap(); -for ( int i=0; i<propertyNames.length; i++ ) { - if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) { - namedValues.put( propertyNames[i], propertyValues[i] ); - } -} -
-
diff --git a/documentation/src/main/docbook/manual-old/en-US/content/toolset_guide.xml b/documentation/src/main/docbook/manual-old/en-US/content/toolset_guide.xml deleted file mode 100644 index d42b5b0bdc4b..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/toolset_guide.xml +++ /dev/null @@ -1,615 +0,0 @@ - - - - - Toolset Guide - - - Roundtrip engineering with Hibernate is possible using a set of Eclipse plugins, - commandline tools, and Ant tasks. - - - - Hibernate Tools currently include plugins for the Eclipse - IDE as well as Ant tasks for reverse engineering of existing databases: - - - - - Mapping Editor: an editor for Hibernate XML mapping files that - supports auto-completion and syntax highlighting. It also supports semantic - auto-completion for class names and property/field names, making it more versatile than a normal XML editor. - - - Console: the console is a new view in Eclipse. In addition to - a tree overview of your console configurations, you are also provided with an interactive view - of your persistent classes and their relationships. The console allows you to - execute HQL queries against your database and browse the result directly in - Eclipse. - - - Development Wizards: several wizards are provided with the - Hibernate Eclipse tools. You can use a wizard to quickly generate Hibernate configuration - (cfg.xml) files, or to reverse engineer an existing database schema - into POJO source files and Hibernate mapping files. The reverse engineering wizard - supports customizable templates. - - - - - - - - - Please refer to the Hibernate Tools package documentation - for more information. - - - - However, the Hibernate main package comes bundled with an integrated tool : SchemaExport aka - hbm2ddl.It can even - be used from "inside" Hibernate. - - -
- Automatic schema generation - - - DDL can be generated from your mapping files by a Hibernate utility. The generated - schema includes referential integrity constraints, primary and foreign keys, for - entity and collection tables. Tables and sequences are also created for mapped - identifier generators. - - - - You must specify a SQL Dialect via the - hibernate.dialect property when using this tool, as DDL - is highly vendor-specific. - - - - First, you must customize your mapping files to improve the generated schema. The next section covers schema customization. - - -
- Customizing the schema - - - Many Hibernate mapping elements define optional attributes named length, - precision and scale. You can set the length, precision - and scale of a column with this attribute. - - - - ]]> - ]]> - - - Some tags also accept a not-null attribute for generating a - NOT NULL constraint on table columns, and a unique - attribute for generating UNIQUE constraint on table columns. - - - ]]> - - ]]> - - - A unique-key attribute can be used to group columns in - a single, unique key constraint. The attribute overrides - the name of any generated unique key constraint. - - - -]]> - - - An index attribute specifies the name of an index that - will be created using the mapped column or columns. Multiple columns can be - grouped into the same index by simply specifying the same index name. - - - -]]> - - - A foreign-key attribute can be used to override the name - of any generated foreign key constraint. - - - ]]> - - - Many mapping elements also accept a child <column> element. - This is particularly useful for mapping multi-column types: - - - - - - -]]> - - - The default attribute allows you to specify a default value for - a column.You should assign the same value to the mapped property before - saving a new instance of the mapped class. - - - - -]]> - - - -]]> - - - The sql-type attribute allows the user to override the default - mapping of a Hibernate type to SQL datatype. - - - - -]]> - - - The check attribute allows you to specify a check constraint. - - - - -]]> - - - ... - -]]> - - - The following table summarizes these optional attributes. - - - Summary - - - - - - - Attribute - Values - Interpretation - - - - - length - number - column length - - - precision - number - column decimal precision - - - scale - number - column decimal scale - - - not-null - true|false - specifies that the column should be non-nullable - - - unique - true|false - specifies that the column should have a unique constraint - - - index - index_name - specifies the name of a (multi-column) index - - - unique-key - unique_key_name - specifies the name of a multi-column unique constraint - - - foreign-key - foreign_key_name - - specifies the name of the foreign key constraint generated - for an association, for a <one-to-one>, - <many-to-one>, <key>, - or <many-to-many> mapping element. Note that - inverse="true" sides will not be considered - by SchemaExport. - - - - sql-type - SQL column type - - overrides the default column type (attribute of - <column> element only) - - - - default - SQL expression - - specify a default value for the column - - - - check - SQL expression - - create an SQL check constraint on either column or table - - - - -
- - - The <comment> element allows you to specify comments - for the generated schema. - - - - Current customers only - ... -]]> - - - - Balance in USD - -]]> - - - This results in a comment on table or - comment on column statement in the generated - DDL where supported. - - -
- -
- Running the tool - - - The SchemaExport tool writes a DDL script to standard out and/or - executes the DDL statements. - - - The following table displays the SchemaExport command line options - - - java -cp hibernate_classpaths - org.hibernate.tool.hbm2ddl.SchemaExport options mapping_files - - - - <literal>SchemaExport</literal> Command Line Options - - - - - - Option - Description - - - - - --quiet - do not output the script to stdout - - - --drop - only drop the tables - - - --create - only create the tables - - - --text - do not export to the database - - - --output=my_schema.ddl - output the ddl script to a file - - - --naming=eg.MyNamingStrategy - select a NamingStrategy - - - --config=hibernate.cfg.xml - read Hibernate configuration from an XML file - - - --properties=hibernate.properties - read database properties from a file - - - --format - format the generated SQL nicely in the script - - - --delimiter=; - set an end of line delimiter for the script - - - -
- - - You can even embed SchemaExport in your application: - - - - -
- -
- Properties - - - Database properties can be specified: - - - - - as system properties with -D<property> - - - in hibernate.properties - - - in a named properties file with --properties - - - - - The needed properties are: - - - - SchemaExport Connection Properties - - - - - - Property Name - Description - - - - - hibernate.connection.driver_class - jdbc driver class - - - hibernate.connection.url - jdbc url - - - hibernate.connection.username - database user - - - hibernate.connection.password - user password - - - hibernate.dialect - dialect - - - -
- -
- -
- Using Ant - - - You can call SchemaExport from your Ant build script: - - - - - - - - - - -]]> - -
- -
- Incremental schema updates - - - The SchemaUpdate tool will update an existing schema with "incremental" changes. - The SchemaUpdate depends upon the JDBC metadata API and, as such, will - not work with all JDBC drivers. - - - - java -cp hibernate_classpaths - org.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files - - - - <literal>SchemaUpdate</literal> Command Line Options - - - - - - Option - Description - - - - - --quiet - do not output the script to stdout - - - --text - do not export the script to the database - - - --naming=eg.MyNamingStrategy - select a NamingStrategy - - - --properties=hibernate.properties - read database properties from a file - - - --config=hibernate.cfg.xml - specify a .cfg.xml file - - - -
- - - You can embed SchemaUpdate in your application: - - - - -
- -
- Using Ant for incremental schema updates - - - You can call SchemaUpdate from the Ant script: - - - - - - - - - - -]]> - -
- -
- Schema validation - - - The SchemaValidator tool will validate that the existing database schema "matches" - your mapping documents. The SchemaValidator depends heavily upon the JDBC - metadata API and, as such, will not work with all JDBC drivers. This tool is extremely useful for testing. - - - - java -cp hibernate_classpaths - org.hibernate.tool.hbm2ddl.SchemaValidator options mapping_files - - The following table displays the SchemaValidator command line options: - - - <literal>SchemaValidator</literal> Command Line Options - - - - - - Option - Description - - - - - --naming=eg.MyNamingStrategy - select a NamingStrategy - - - --properties=hibernate.properties - read database properties from a file - - - --config=hibernate.cfg.xml - specify a .cfg.xml file - - - -
- - - You can embed SchemaValidator in your application: - - - - -
- -
- Using Ant for schema validation - - - You can call SchemaValidator from the Ant script: - - - - - - - - - - -]]> - -
- -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/transactions.xml b/documentation/src/main/docbook/manual-old/en-US/content/transactions.xml deleted file mode 100644 index 24ac353618c2..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/transactions.xml +++ /dev/null @@ -1,1134 +0,0 @@ - - - - - Transactions and Concurrency - - - The most important point about Hibernate and concurrency control is that it is - easy to understand. Hibernate directly uses JDBC connections and JTA resources without - adding any additional locking behavior. It is recommended that you spend some time with the - JDBC, ANSI, and transaction isolation specification of your database management system. - - - - Hibernate does not lock objects in memory. Your application can expect the behavior as - defined by the isolation level of your database transactions. Through - Session, which is also a transaction-scoped cache, Hibernate - provides repeatable reads for lookup by identifier and entity queries and not - reporting queries that return scalar values. - - - - In addition to versioning for automatic optimistic concurrency control, Hibernate also - offers, using the - SELECT FOR UPDATE syntax, a (minor) API for pessimistic locking of rows. Optimistic concurrency control and - this API are discussed later in this chapter. - - - - The discussion of concurrency control in Hibernate begins with the granularity of - Configuration, SessionFactory, and - Session, as well as database transactions and long conversations. - - -
- Session and transaction scopes - - - A SessionFactory is an expensive-to-create, threadsafe object, - intended to be shared by all application threads. It is created once, usually on - application startup, from a Configuration instance. - - - - A Session is an inexpensive, non-threadsafe object that should be - used once and then discarded for: a single request, a conversation or a single unit of work. - A Session will not obtain a JDBC Connection, - or a Datasource, unless it is needed. It will not consume any - resources until used. - - - - In order to reduce lock contention in the database, a database transaction has to be as short as possible. - Long database transactions will prevent your application from scaling - to a highly concurrent load. It is not recommended that you hold a - database transaction open during user think time until the unit of work is - complete. - - - - What is the scope of a unit of work? Can a single Hibernate Session - span several database transactions, or is this a one-to-one relationship of scopes? When - should you open and close a Session and how do you demarcate the - database transaction boundaries? These questions are addressed in the following sections. - - -
- Unit of work - - - First, let's define a unit of work. A unit of work is a - design pattern described by Martin Fowler as - - [maintaining] a list of objects affected by a business - transaction and coordinates the writing out of changes - and the resolution of concurrency problems. - PoEAA - In other words, its a series of operations we wish to carry out - against the database together. Basically, it is a transaction, - though fulfilling a unit of work will often span multiple - physical database transactions (see ). - So really we are talking about a more abstract notion of a - transaction. The term "business transaction" is also sometimes - used in lieu of unit of work. - - - - Do not use the session-per-operation antipattern: - do not open and close a Session for every simple database call in - a single thread. The same is true for database transactions. Database calls - in an application are made using a planned sequence; they are grouped into atomic - units of work. This also means that auto-commit after every single - SQL statement is useless in an application as this mode is intended for ad-hoc SQL - console work. Hibernate disables, or expects the application server to disable, - auto-commit mode immediately. Database transactions are never optional. All - communication with a database has to occur inside a transaction. Auto-commit behavior for reading data - should be avoided, as many small transactions are unlikely to perform better than - one clearly defined unit of work. The latter is also more maintainable - and extensible. - - - - The most common pattern in a multi-user client/server application is - session-per-request. In this model, a request from the client - is sent to the server, where the Hibernate persistence layer runs. A new Hibernate - Session is opened, and all database operations are executed in this unit - of work. On completion of the work, and once the response for the client has been prepared, - the session is flushed and closed. Use a single database transaction to - serve the clients request, starting and committing it when you open and close the - Session. The relationship between the two is one-to-one and this - model is a perfect fit for many applications. - - - - The challenge lies in the implementation. Hibernate provides built-in management of - the "current session" to simplify this pattern. Start a - transaction when a server request has to be processed, and end the transaction - before the response is sent to the client. Common solutions are ServletFilter, AOP interceptor with a - pointcut on the service methods, or a proxy/interception container. An EJB container - is a standardized way to implement cross-cutting aspects such as transaction - demarcation on EJB session beans, declaratively with CMT. If you - use programmatic transaction demarcation, for ease of use and code portability use the Hibernate Transaction - API shown later in this chapter. - - - - Your application code can access a "current session" to process the request - by calling sessionFactory.getCurrentSession(). - You will always get a Session scoped - to the current database transaction. This has to be configured for either - resource-local or JTA environments, see . - - - - You can extend the scope of a Session and - database transaction until the "view has been rendered". This is especially useful - in servlet applications that utilize a separate rendering phase after the request - has been processed. Extending the database transaction until view rendering, is achieved by implementing - your own interceptor. However, this will be difficult - if you rely on EJBs with container-managed transactions. A - transaction will be completed when an EJB method returns, before rendering of any - view can start. See the Hibernate website and forum for tips and examples relating to - this Open Session in View pattern. - - -
- -
- Long conversations - - - The session-per-request pattern is not the only way of designing - units of work. Many business processes require a whole series of interactions with the user that are - interleaved with database accesses. In web and enterprise applications, it is - not acceptable for a database transaction to span a user interaction. Consider the following - example: - - - - - - The first screen of a dialog opens. The data seen by the user has been loaded in - a particular Session and database transaction. The user is free to - modify the objects. - - - - - The user clicks "Save" after 5 minutes and expects their modifications to be made - persistent. The user also expects that they were the only person editing this information and - that no conflicting modification has occurred. - - - - - - From the point of view of the user, we call this unit of work a long-running - conversation or application transaction. - There are many ways to implement this in your application. - - - - A first naive implementation might keep the Session and database - transaction open during user think time, with locks held in the database to prevent - concurrent modification and to guarantee isolation and atomicity. This is - an anti-pattern, since lock contention would not allow the application to scale with - the number of concurrent users. - - - - You have to use several database transactions to implement the conversation. - In this case, maintaining isolation of business processes becomes the - partial responsibility of the application tier. A single conversation - usually spans several database transactions. It will be atomic if only one of - these database transactions (the last one) stores the updated data. All others - simply read data (for example, in a wizard-style dialog spanning several request/response - cycles). This is easier to implement than it might sound, especially if - you utilize some of Hibernate's features: - - - - - - Automatic Versioning: Hibernate can perform automatic - optimistic concurrency control for you. It can automatically detect - if a concurrent modification occurred during user think time. Check for this at - the end of the conversation. - - - - - Detached Objects: if you decide to use the - session-per-request pattern, all loaded instances - will be in the detached state during user think time. Hibernate allows you to - reattach the objects and persist the modifications. The pattern is called - session-per-request-with-detached-objects. Automatic - versioning is used to isolate concurrent modifications. - - - - - Extended (or Long) Session: the Hibernate - Session can be disconnected from the underlying JDBC - connection after the database transaction has been committed and reconnected - when a new client request occurs. This pattern is known as - session-per-conversation and makes - even reattachment unnecessary. Automatic versioning is used to isolate - concurrent modifications and the Session will not - be allowed to be flushed automatically, but explicitly. - - - - - - Both session-per-request-with-detached-objects and - session-per-conversation have advantages and disadvantages. - These disadvantages are discussed later in this chapter in the context of optimistic concurrency control. - - -
- -
- Considering object identity - - - An application can concurrently access the same persistent state in two - different Sessions. However, an instance of a persistent class - is never shared between two Session instances. It is for this reason that there are - two different notions of identity: - - - - - Database Identity - - - foo.getId().equals( bar.getId() ) - - - - - JVM Identity - - - foo==bar - - - - - - - For objects attached to a particular Session - (i.e., in the scope of a Session), the two notions are equivalent and - JVM identity for database identity is guaranteed by Hibernate. While the application - might concurrently access the "same" (persistent identity) business object in two different - sessions, the two instances will actually be "different" (JVM identity). Conflicts are - resolved using an optimistic approach and automatic versioning at flush/commit time. - - - - This approach leaves Hibernate and the database to worry about concurrency. It also provides - the best scalability, since guaranteeing identity in single-threaded units of work means that it does not - need expensive locking or other means of synchronization. The application does not need to - synchronize on any business object, as long as it maintains a single thread per - Session. Within a Session the application can safely use - == to compare objects. - - - - However, an application that uses == outside of a Session - might produce unexpected results. This might occur even in some unexpected places. For example, - if you put two detached instances into the same Set, both might have the same - database identity (i.e., they represent the same row). JVM identity, however, is by definition not - guaranteed for instances in a detached state. The developer has to override the equals() - and hashCode() methods in persistent classes and implement - their own notion of object equality. There is one caveat: never use the database - identifier to implement equality. Use a business key that is a combination of unique, usually - immutable, attributes. The database identifier will change if a transient object is made - persistent. If the transient instance (usually together with detached instances) is held in a - Set, changing the hashcode breaks the contract of the Set. - Attributes for business keys do not have to be as stable as database primary keys; you only - have to guarantee stability as long as the objects are in the same Set. See - the Hibernate website for a more thorough discussion of this issue. Please note that this is not - a Hibernate issue, but simply how Java object identity and equality has to be implemented. - - -
- -
- Common issues - - - Do not use the anti-patterns session-per-user-session or - session-per-application (there are, however, rare exceptions to - this rule). Some of the following issues might also arise within the recommended - patterns, so ensure that you understand the implications before making a design decision: - - - - - - A Session is not thread-safe. Things that work - concurrently, like HTTP requests, session beans, or Swing workers, will cause race - conditions if a Session instance is shared. If you keep your - Hibernate Session in your HttpSession (this is discussed - later in the chapter), you should consider synchronizing access to your Http session. Otherwise, - a user that clicks reload fast enough can use the same Session in - two concurrently running threads. - - - - - An exception thrown by Hibernate means you have to rollback your database transaction - and close the Session immediately (this is discussed in more detail later in the chapter). - If your Session is bound to the application, you have to stop - the application. Rolling back the database transaction does not put your business - objects back into the state they were at the start of the transaction. This means that the - database state and the business objects will be out of sync. Usually this is not a - problem, because exceptions are not recoverable and you will have to start over after - rollback anyway. - - - - - The Session caches every object that is in a persistent state (watched - and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too - much data, it will grow endlessly until you - get an OutOfMemoryException. One solution is to call clear() and evict() - to manage the Session cache, but you should consider a - Stored Procedure if you need mass data operations. Some solutions are shown in - . Keeping a Session open for the duration - of a user session also means a higher probability of stale data. - - - - -
- -
- -
- Database transaction demarcation - - - Database, or system, transaction boundaries are always necessary. No communication with - the database can occur outside of a database transaction (this seems to confuse many developers - who are used to the auto-commit mode). Always use clear transaction boundaries, even for - read-only operations. Depending on your isolation level and database capabilities this might not - be required, but there is no downside if you always demarcate transactions explicitly. Certainly, - a single database transaction is going to perform better than many small transactions, even - for reading data. - - - - A Hibernate application can run in non-managed (i.e., standalone, simple Web- or Swing applications) - and managed J2EE environments. In a non-managed environment, Hibernate is usually responsible for - its own database connection pool. The application developer has to manually set transaction - boundaries (begin, commit, or rollback database transactions) themselves. A managed environment - usually provides container-managed transactions (CMT), with the transaction assembly defined declaratively - (in deployment descriptors of EJB session beans, for example). Programmatic transaction demarcation is - then no longer necessary. - - - - However, it is often desirable to keep your persistence layer portable between non-managed - resource-local environments, and systems that can rely on JTA but use BMT instead of CMT. - In both cases use programmatic transaction demarcation. Hibernate offers a wrapper - API called Transaction that translates into the native transaction system of - your deployment environment. This API is actually optional, but we strongly encourage its use - unless you are in a CMT session bean. - - - - Ending a Session usually involves four distinct phases: - - - - - - flush the session - - - - - commit the transaction - - - - - close the session - - - - - handle exceptions - - - - - - We discussed Flushing the session earlier, so we will now have a closer look at transaction - demarcation and exception handling in both managed and non-managed environments. - - - -
- Non-managed environment - - - If a Hibernate persistence layer runs in a non-managed environment, database connections - are usually handled by simple (i.e., non-DataSource) connection pools from which - Hibernate obtains connections as needed. The session/transaction handling idiom looks - like this: - - - - - - You do not have to flush() the Session explicitly: - the call to commit() automatically triggers the synchronization depending - on the FlushMode for the session. - A call to close() marks the end of a session. The main implication - of close() is that the JDBC connection will be relinquished by the - session. This Java code is portable and runs in both non-managed and JTA environments. - - - - As outlined earlier, a much more flexible solution is Hibernate's built-in "current session" context - management: - - - - - - You will not see these code snippets in a regular application; - fatal (system) exceptions should always be caught at the "top". In other words, the - code that executes Hibernate calls in the persistence layer, and the code that handles - RuntimeException (and usually can only clean up and exit), are in - different layers. The current context management by Hibernate can significantly - simplify this design by accessing a SessionFactory. - Exception handling is discussed later in this chapter. - - - - You should select org.hibernate.transaction.JDBCTransactionFactory, - which is the default, and for the second example select "thread" as your - hibernate.current_session_context_class. - - -
- -
- Using JTA - - - If your persistence layer runs in an application server (for example, behind EJB session beans), - every datasource connection obtained by Hibernate will automatically be part of the global - JTA transaction. You can also install a standalone JTA implementation and use it without - EJB. Hibernate offers two strategies for JTA integration. - - - - If you use bean-managed transactions (BMT), Hibernate will tell the application server to start - and end a BMT transaction if you use the Transaction API. The - transaction management code is identical to the non-managed environment. - - - - - - If you want to use a transaction-bound Session, that is, the - getCurrentSession() functionality for easy context propagation, - use the JTA UserTransaction API directly: - - - - - - With CMT, transaction demarcation is completed in session bean deployment descriptors, not programmatically. - The code is reduced to: - - - - - - In a CMT/EJB, even rollback happens automatically. An unhandled RuntimeException - thrown by a session bean method tells the container to set the global transaction to rollback. - You do not need to use the Hibernate Transaction API at - all with BMT or CMT, and you get automatic propagation of the "current" Session bound to the - transaction. - - - - When configuring Hibernate's transaction factory, choose org.hibernate.transaction.JTATransactionFactory - if you use JTA directly (BMT), and org.hibernate.transaction.CMTTransactionFactory - in a CMT session bean. Remember to also set - hibernate.transaction.manager_lookup_class. Ensure - that your hibernate.current_session_context_class is either unset (backwards - compatibility), or is set to "jta". - - - - The getCurrentSession() operation has one downside in a JTA environment. - There is one caveat to the use of after_statement connection release - mode, which is then used by default. Due to a limitation of the JTA spec, it is not - possible for Hibernate to automatically clean up any unclosed ScrollableResults or - Iterator instances returned by scroll() or - iterate(). You must release the underlying database - cursor by calling ScrollableResults.close() or - Hibernate.close(Iterator) explicitly from a finally - block. Most applications can easily avoid using scroll() or - iterate() from the JTA or CMT code.) - - -
- -
- Exception handling - - - If the Session throws an exception, including any - SQLException, immediately rollback the database - transaction, call Session.close() and discard the - Session instance. Certain methods of Session - will not leave the session in a consistent state. No - exception thrown by Hibernate can be treated as recoverable. Ensure that the - Session will be closed by calling close() - in a finally block. - - - - The HibernateException, which wraps most of the errors that - can occur in a Hibernate persistence layer, is an unchecked exception. It was not - in older versions of Hibernate. In our opinion, we should not force the application - developer to catch an unrecoverable exception at a low layer. In most systems, unchecked - and fatal exceptions are handled in one of the first frames of the method call - stack (i.e., in higher layers) and either an error message is presented to the application - user or some other appropriate action is taken. Note that Hibernate might also throw - other unchecked exceptions that are not a HibernateException. These - are not recoverable and appropriate action should be taken. - - - - Hibernate wraps SQLExceptions thrown while interacting with the database - in a JDBCException. In fact, Hibernate will attempt to convert the exception - into a more meaningful subclass of JDBCException. The underlying - SQLException is always available via JDBCException.getCause(). - Hibernate converts the SQLException into an appropriate - JDBCException subclass using the SQLExceptionConverter - attached to the SessionFactory. By default, the - SQLExceptionConverter is defined by the configured dialect. However, it is - also possible to plug in a custom implementation. See the javadocs for the - SQLExceptionConverterFactory class for details. The standard - JDBCException subtypes are: - - - - - - JDBCConnectionException: indicates an error - with the underlying JDBC communication. - - - - - SQLGrammarException: indicates a grammar - or syntax problem with the issued SQL. - - - - - ConstraintViolationException: indicates some - form of integrity constraint violation. - - - - - LockAcquisitionException: indicates an error - acquiring a lock level necessary to perform the requested operation. - - - - - GenericJDBCException: a generic exception - which did not fall into any of the other categories. - - - - -
- -
- Transaction timeout - - - An important feature provided by a managed environment like EJB, - that is never provided for non-managed code, is transaction timeout. Transaction - timeouts ensure that no misbehaving transaction can indefinitely tie up - resources while returning no response to the user. Outside a managed (JTA) - environment, Hibernate cannot fully provide this functionality. However, - Hibernate can at least control data access operations, ensuring that database - level deadlocks and queries with huge result sets are limited by a defined - timeout. In a managed environment, Hibernate can delegate transaction timeout - to JTA. This functionality is abstracted by the Hibernate - Transaction object. - - - - - - setTimeout() cannot be called in a CMT bean, - where transaction timeouts must be defined declaratively. - - -
- -
- -
- Optimistic concurrency control - - - The only approach that is consistent with high concurrency and high - scalability, is optimistic concurrency control with versioning. Version - checking uses version numbers, or timestamps, to detect conflicting updates - and to prevent lost updates. Hibernate provides three possible approaches - to writing application code that uses optimistic concurrency. The use cases - we discuss are in the context of long conversations, but version checking - also has the benefit of preventing lost updates in single database transactions. - - -
- Application version checking - - - In an implementation without much help from Hibernate, each interaction with the - database occurs in a new Session and the developer is responsible - for reloading all persistent instances from the database before manipulating them. - The application is forced to carry out its own version checking to ensure - conversation transaction isolation. This approach is the least efficient in terms of - database access. It is the approach most similar to entity EJBs. - - - - - - The version property is mapped using <version>, - and Hibernate will automatically increment it during flush if the entity is - dirty. - - - - If you are operating in a low-data-concurrency environment, and do not - require version checking, you can use this approach and skip the version - check. In this case, last commit wins is the default - strategy for long conversations. Be aware that this might - confuse the users of the application, as they might experience lost updates without - error messages or a chance to merge conflicting changes. - - - - Manual version checking is only feasible in trivial circumstances - and not practical for most applications. Often not only single instances, but - complete graphs of modified objects, have to be checked. Hibernate offers automatic - version checking with either an extended Session or detached instances - as the design paradigm. - - -
- -
- Extended session and automatic versioning - - - A single Session instance and its persistent instances that are - used for the whole conversation are known as session-per-conversation. - Hibernate checks instance versions at flush time, throwing an exception if concurrent - modification is detected. It is up to the developer to catch and handle this exception. - Common options are the opportunity for the user to merge changes or to restart the - business conversation with non-stale data. - - - - The Session is disconnected from any underlying JDBC connection - when waiting for user interaction. This approach is the most efficient in terms - of database access. The application does not version check or - reattach detached instances, nor does it have to reload instances in every - database transaction. - - - - - The foo object knows which Session it was - loaded in. Beginning a new database transaction on an old session obtains a new connection - and resumes the session. Committing a database transaction disconnects a session - from the JDBC connection and returns the connection to the pool. After reconnection, to - force a version check on data you are not updating, you can call Session.lock() - with LockMode.READ on any objects that might have been updated by another - transaction. You do not need to lock any data that you are updating. - Usually you would set FlushMode.MANUAL on an extended Session, - so that only the last database transaction cycle is allowed to actually persist all - modifications made in this conversation. Only this last database transaction - will include the flush() operation, and then - close() the session to end the conversation. - - - - This pattern is problematic if the Session is too big to - be stored during user think time (for example, an HttpSession should - be kept as small as possible). As the Session is also the - first-level cache and contains all loaded objects, we can probably - use this strategy only for a few request/response cycles. Use a - Session only for a single conversation as it will soon - have stale data. - - - - Note - Earlier versions of Hibernate required explicit disconnection and reconnection - of a Session. These methods are deprecated, as beginning and - ending a transaction has the same effect. - - - - - Keep the disconnected Session close - to the persistence layer. Use an EJB stateful session bean to - hold the Session in a three-tier environment. Do not transfer - it to the web layer, or even serialize it to a separate tier, to store it in the - HttpSession. - - - - The extended session pattern, or session-per-conversation, is - more difficult to implement with automatic current session context management. - You need to supply your own implementation of the CurrentSessionContext - for this. See the Hibernate Wiki for examples. - - -
- -
- Detached objects and automatic versioning - - - Each interaction with the persistent store occurs in a new Session. - However, the same persistent instances are reused for each interaction with the database. - The application manipulates the state of detached instances originally loaded in another - Session and then reattaches them using Session.update(), - Session.saveOrUpdate(), or Session.merge(). - - - - - - Again, Hibernate will check instance versions during flush, throwing an - exception if conflicting updates occurred. - - - - You can also call lock() instead of update(), - and use LockMode.READ (performing a version check and bypassing all - caches) if you are sure that the object has not been modified. - - -
- -
- Customizing automatic versioning - - - You can disable Hibernate's automatic version increment for particular properties and - collections by setting the optimistic-lock mapping attribute to - false. Hibernate will then no longer increment versions if the - property is dirty. - - - - Legacy database schemas are often static and cannot be modified. Or, other applications - might access the same database and will not know how to handle version numbers or - even timestamps. In both cases, versioning cannot rely on a particular column in a table. - To force a version check with a - comparison of the state of all fields in a row but without a version or timestamp property mapping, - turn on optimistic-lock="all" - in the <class> mapping. This conceptually only works - if Hibernate can compare the old and the new state (i.e., if you use a single long - Session and not session-per-request-with-detached-objects). - - - - Concurrent modification can be permitted in instances where the changes that have been - made do not overlap. If you set optimistic-lock="dirty" when mapping the - <class>, Hibernate will only compare dirty fields during flush. - - - - In both cases, with dedicated version/timestamp columns or with a full/dirty field - comparison, Hibernate uses a single UPDATE statement, with an - appropriate WHERE clause, per entity to execute the version check - and update the information. If you use transitive persistence to cascade reattachment - to associated entities, Hibernate may execute unnecessary updates. This is usually - not a problem, but on update triggers in the database might be - executed even when no changes have been made to detached instances. You can customize - this behavior by setting select-before-update="true" in the - <class> mapping, forcing Hibernate to SELECT - the instance to ensure that changes did occur before updating the row. - - -
- -
- -
- Pessimistic locking - - - It is not intended that users spend much time worrying about locking strategies. It is usually - enough to specify an isolation level for the JDBC connections and then simply let the - database do all the work. However, advanced users may wish to obtain - exclusive pessimistic locks or re-obtain locks at the start of a new transaction. - - - - Hibernate will always use the locking mechanism of the database; it never lock objects - in memory. - - - - The LockMode class defines the different lock levels that can be acquired - by Hibernate. A lock is obtained by the following mechanisms: - - - - - - LockMode.WRITE is acquired automatically when Hibernate updates or inserts - a row. - - - - - LockMode.UPGRADE can be acquired upon explicit user request using - SELECT ... FOR UPDATE on databases which support that syntax. - - - - - LockMode.UPGRADE_NOWAIT can be acquired upon explicit user request using a - SELECT ... FOR UPDATE NOWAIT under Oracle. - - - - - LockMode.READ is acquired automatically when Hibernate reads data - under Repeatable Read or Serializable isolation level. It can be re-acquired by explicit user - request. - - - - - LockMode.NONE represents the absence of a lock. All objects switch to this - lock mode at the end of a Transaction. Objects associated with the session - via a call to update() or saveOrUpdate() also start out - in this lock mode. - - - - - - The "explicit user request" is expressed in one of the following ways: - - - - - - A call to Session.load(), specifying a LockMode. - - - - - A call to Session.lock(). - - - - - A call to Query.setLockMode(). - - - - - - If Session.load() is called with UPGRADE or - UPGRADE_NOWAIT, and the requested object was not yet loaded by - the session, the object is loaded using SELECT ... FOR UPDATE. - If load() is called for an object that is already loaded with - a less restrictive lock than the one requested, Hibernate calls - lock() for that object. - - - - Session.lock() performs a version number check if the specified lock - mode is READ, UPGRADE or - UPGRADE_NOWAIT. In the case of UPGRADE or - UPGRADE_NOWAIT, SELECT ... FOR UPDATE is used. - - - - If the requested lock mode is not supported by the database, Hibernate uses an appropriate - alternate mode instead of throwing an exception. This ensures that applications are - portable. - - -
- -
- Connection release modes - - - One of the legacies of Hibernate 2.x JDBC connection management - meant that a Session would obtain a connection when it was first - required and then maintain that connection until the session was closed. - Hibernate 3.x introduced the notion of connection release modes that would instruct a session - how to handle its JDBC connections. The following discussion is pertinent - only to connections provided through a configured ConnectionProvider. - User-supplied connections are outside the breadth of this discussion. The different - release modes are identified by the enumerated values of - org.hibernate.ConnectionReleaseMode: - - - - - - ON_CLOSE: is the legacy behavior described above. The - Hibernate session obtains a connection when it first needs to perform some JDBC access - and maintains that connection until the session is closed. - - - - - AFTER_TRANSACTION: releases connections after a - org.hibernate.Transaction has been completed. - - - - - AFTER_STATEMENT (also referred to as aggressive release): - releases connections after every statement execution. This aggressive releasing - is skipped if that statement leaves open resources associated with the given session. - Currently the only situation where this occurs is through the use of - org.hibernate.ScrollableResults. - - - - - - The configuration parameter hibernate.connection.release_mode is used - to specify which release mode to use. The possible values are as follows: - - - - - - auto (the default): this choice delegates to the release mode - returned by the org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode() - method. For JTATransactionFactory, this returns ConnectionReleaseMode.AFTER_STATEMENT; for - JDBCTransactionFactory, this returns ConnectionReleaseMode.AFTER_TRANSACTION. Do not - change this default behavior as failures due to the value of this setting - tend to indicate bugs and/or invalid assumptions in user code. - - - - - on_close: uses ConnectionReleaseMode.ON_CLOSE. This setting - is left for backwards compatibility, but its use is discouraged. - - - - - after_transaction: uses ConnectionReleaseMode.AFTER_TRANSACTION. - This setting should not be used in JTA environments. Also note that with - ConnectionReleaseMode.AFTER_TRANSACTION, if a session is considered to be in auto-commit - mode, connections will be released as if the release mode were AFTER_STATEMENT. - - - - - after_statement: uses ConnectionReleaseMode.AFTER_STATEMENT. Additionally, - the configured ConnectionProvider is consulted to see if it supports this - setting (supportsAggressiveRelease()). If not, the release mode is reset - to ConnectionReleaseMode.AFTER_TRANSACTION. This setting is only safe in environments where - we can either re-acquire the same underlying JDBC connection each time you make a call into - ConnectionProvider.getConnection() or in auto-commit environments where - it does not matter if we re-establish the same connection. - - - - -
- -
- diff --git a/documentation/src/main/docbook/manual-old/en-US/content/type.xml b/documentation/src/main/docbook/manual-old/en-US/content/type.xml deleted file mode 100644 index fdb3e159e7b0..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/content/type.xml +++ /dev/null @@ -1,1028 +0,0 @@ - - - - Types - - - As an Object/Relational Mapping solution, Hibernate deals with both the Java and JDBC representations of - application data. An online catalog application, for example, most likely has Product - object with a number of attributes such as a sku, name, etc. For these - individual attributes, Hibernate must be able to read the values out of the database and write them back. This - 'marshalling' is the function of a Hibernate type, which is an implementation of the - org.hibernate.type.Type interface. In addition, a - Hibernate type describes various aspects of behavior of the Java type such as "how is - equality checked?" or "how are values cloned?". - - - - - A Hibernate type is neither a Java type nor a SQL datatype; it provides a information about both. - - - When you encounter the term type in regards to Hibernate be aware that usage might - refer to the Java type, the SQL/JDBC type or the Hibernate type. - - - - - Hibernate categorizes types into two high-level groups: value types (see ) and - entity types (see ). - - -
- Value types - - - The main distinguishing characteristic of a value type is the fact that they do not define their own - lifecycle. We say that they are "owned" by something else (specifically an entity, as we will see later) - which defines their lifecycle. Value types are further classified into 3 sub-categories: basic types (see - ), composite types (see ) - amd collection types (see ). - - -
- Basic value types - - The norm for basic value types is that they map a single database value (column) to a single, - non-aggregated Java type. Hibernate provides a number of built-in basic types, which we will present - in the following sections by the Java type. Mainly these follow the natural mappings recommended in the - JDBC specification. We will later cover how to override these mapping and how to provide and use - alternative type mappings. - -
- java.lang.String - - - org.hibernate.type.StringType - - - Maps a string to the JDBC VARCHAR type. This is the standard mapping for a string if - no Hibernate type is specified. - - - Registered under string and java.lang.String - in the type registry (see ). - - - - - org.hibernate.type.MaterializedClob - - - Maps a string to a JDBC CLOB type - - - Registered under materialized_clob in the type registry (see - ). - - - - - org.hibernate.type.TextType - - - Maps a string to a JDBC LONGVARCHAR type - - - Registered under text in the type registry (see - ). - - - - -
-
- <classname>java.lang.Character</classname> (or char primitive) - - - org.hibernate.type.CharacterType - - - Maps a char or java.lang.Character to a JDBC CHAR - - - Registered under char and java.lang.Character in the - type registry (see ). - - - - -
-
- <classname>java.lang.Boolean</classname> (or boolean primitive) - - - org.hibernate.type.BooleanType - - - Maps a boolean to a JDBC BIT type - - - Registered under boolean and java.lang.Boolean in - the type registry (see ). - - - - - org.hibernate.type.NumericBooleanType - - - Maps a boolean to a JDBC INTEGER type as 0 = false, 1 = true - - - Registered under numeric_boolean in the type registry (see - ). - - - - - org.hibernate.type.YesNoType - - - Maps a boolean to a JDBC CHAR type as ('N' | 'n') = false, ( 'Y' | 'y' ) = true - - - Registered under yes_no in the type registry (see - ). - - - - - org.hibernate.type.TrueFalseType - - - Maps a boolean to a JDBC CHAR type as ('F' | 'f') = false, ( 'T' | 't' ) = true - - - Registered under true_false in the type registry (see - ). - - - - -
-
- <classname>java.lang.Byte</classname> (or byte primitive) - - - org.hibernate.type.ByteType - - - Maps a byte or java.lang.Byte to a JDBC TINYINT - - - Registered under byte and java.lang.Byte in the - type registry (see ). - - - - -
-
- <classname>java.lang.Short</classname> (or short primitive) - - - org.hibernate.type.ShortType - - - Maps a short or java.lang.Short to a JDBC SMALLINT - - - Registered under short and java.lang.Short in the - type registry (see ). - - - - -
-
- <classname>java.lang.Integer</classname> (or int primitive) - - - org.hibernate.type.IntegerTypes - - - Maps an int or java.lang.Integer to a JDBC INTEGER - - - Registered under int and java.lang.Integerin the - type registry (see ). - - - - -
-
- <classname>java.lang.Long</classname> (or long primitive) - - - org.hibernate.type.LongType - - - Maps a long or java.lang.Long to a JDBC BIGINT - - - Registered under long and java.lang.Long in the - type registry (see ). - - - - -
-
- <classname>java.lang.Float</classname> (or float primitive) - - - org.hibernate.type.FloatType - - - Maps a float or java.lang.Float to a JDBC FLOAT - - - Registered under float and java.lang.Float in the - type registry (see ). - - - - -
-
- <classname>java.lang.Double</classname> (or double primitive) - - - org.hibernate.type.DoubleType - - - Maps a double or java.lang.Double to a JDBC DOUBLE - - - Registered under double and java.lang.Double in the - type registry (see ). - - - - -
-
- <classname>java.math.BigInteger</classname> - - - org.hibernate.type.BigIntegerType - - - Maps a java.math.BigInteger to a JDBC NUMERIC - - - Registered under big_integer and java.math.BigInteger in the - type registry (see ). - - - - -
-
- <classname>java.math.BigDecimal</classname> - - - org.hibernate.type.BigDecimalType - - - Maps a java.math.BigDecimal to a JDBC NUMERIC - - - Registered under big_decimal and java.math.BigDecimal in the - type registry (see ). - - - - -
-
- <classname>java.util.Date</classname> or <classname>java.sql.Timestamp</classname> - - - org.hibernate.type.TimestampType - - - Maps a java.sql.Timestamp to a JDBC TIMESTAMP - - - Registered under timestamp, java.sql.Timestamp and - java.util.Date in the type registry (see ). - - - - -
-
- <classname>java.sql.Time</classname> - - - org.hibernate.type.TimeType - - - Maps a java.sql.Time to a JDBC TIME - - - Registered under time and java.sql.Time in the - type registry (see ). - - - - -
-
- <classname>java.sql.Date</classname> - - - org.hibernate.type.DateType - - - Maps a java.sql.Date to a JDBC DATE - - - Registered under date and java.sql.Date in the - type registry (see ). - - - - -
-
- <classname>java.util.Calendar</classname> - - - org.hibernate.type.CalendarType - - - Maps a java.util.Calendar to a JDBC TIMESTAMP - - - Registered under calendar, java.util.Calendar and - java.util.GregorianCalendar in the type registry (see - ). - - - - - org.hibernate.type.CalendarDateType - - - Maps a java.util.Calendar to a JDBC DATE - - - Registered under calendar_date in the type registry (see - ). - - - - -
-
- <classname>java.util.Currency</classname> - - - org.hibernate.type.CurrencyType - - - Maps a java.util.Currency to a JDBC VARCHAR (using the Currency code) - - - Registered under currency and java.util.Currency in the - type registry (see ). - - - - -
-
- <classname>java.util.Locale</classname> - - - org.hibernate.type.LocaleType - - - Maps a java.util.Locale to a JDBC VARCHAR (using the Locale code) - - - Registered under locale and java.util.Locale in the - type registry (see ). - - - - -
-
- <classname>java.util.TimeZone</classname> - - - org.hibernate.type.TimeZoneType - - - Maps a java.util.TimeZone to a JDBC VARCHAR (using the TimeZone ID) - - - Registered under timezone and java.util.TimeZone in the - type registry (see ). - - - - -
-
- <classname>java.net.URL</classname> - - - org.hibernate.type.UrlType - - - Maps a java.net.URL to a JDBC VARCHAR (using the external form) - - - Registered under url and java.net.URL in the - type registry (see ). - - - - -
-
- <classname>java.lang.Class</classname> - - - org.hibernate.type.ClassType - - - Maps a java.lang.Class to a JDBC VARCHAR (using the Class name) - - - Registered under class and java.lang.Class in the - type registry (see ). - - - - -
-
- <classname>java.sql.Blob</classname> - - - org.hibernate.type.BlobType - - - Maps a java.sql.Blob to a JDBC BLOB - - - Registered under blob and java.sql.Blob in the - type registry (see ). - - - - -
-
- <classname>java.sql.Clob</classname> - - - org.hibernate.type.ClobType - - - Maps a java.sql.Clob to a JDBC CLOB - - - Registered under clob and java.sql.Clob in the - type registry (see ). - - - - -
-
- byte[] - - - org.hibernate.type.BinaryType - - - Maps a primitive byte[] to a JDBC VARBINARY - - - Registered under binary and byte[] in the - type registry (see ). - - - - - org.hibernate.type.MaterializedBlobType - - - Maps a primitive byte[] to a JDBC BLOB - - - Registered under materialized_blob in the type registry (see - ). - - - - - org.hibernate.type.ImageType - - - Maps a primitive byte[] to a JDBC LONGVARBINARY - - - Registered under image in the type registry (see - ). - - - - -
-
- Byte[] - - - org.hibernate.type.BinaryType - - - Maps a java.lang.Byte[] to a JDBC VARBINARY - - - Registered under wrapper-binary, Byte[] and - java.lang.Byte[] in the type registry (see ). - - - - -
-
- char[] - - - org.hibernate.type.CharArrayType - - - Maps a char[] to a JDBC VARCHAR - - - Registered under characters and char[] - in the type registry (see ). - - - - -
-
- Character[] - - - org.hibernate.type.CharacterArrayType - - - Maps a java.lang.Character[] to a JDBC VARCHAR - - - Registered under wrapper-characters, Character[] - and java.lang.Character[] in the type registry (see ). - - - - -
-
- <classname>java.util.UUID</classname> - - - org.hibernate.type.UUIDBinaryType - - - Maps a java.util.UUID to a JDBC BINARY - - - Registered under uuid-binary and java.util.UUID - in the type registry (see ). - - - - - org.hibernate.type.UUIDCharType - - - Maps a java.util.UUID to a JDBC CHAR (though VARCHAR is fine too for existing schemas) - - - Registered under uuid-char in the type registry (see - ). - - - - - org.hibernate.type.PostgresUUIDType - - - Maps a java.util.UUID to the PostgreSQL UUID data type (through - Types#OTHER which is how the PostgreSQL JDBC driver defines it). - - - Registered under pg-uuid in the type registry (see - ). - - - - -
-
- <interfacename>java.io.Serializable</interfacename> - - - org.hibernate.type.SerializableType - - - Maps implementors of java.lang.Serializable to a JDBC VARBINARY - - - Unlike the other value types, there are multiple instances of this type. It - gets registered once under java.io.Serializable. - Additionally it gets registered under the specific - java.io.Serializable implementation class names. - - - - -
-
- -
- Composite types - - - The Java Persistence API calls these embedded types, while Hibernate traditionally called them - components. Just be aware that both terms are used and mean the same thing in the scope of - discussing Hibernate. - - - - Components represent aggregations of values into a single Java type. For example, you might have - an Address class that aggregates street, city, state, etc information or a Name class that - aggregates the parts of a person's Name. In many ways a component looks exactly like an entity. They - are both (generally speaking) classes written specifically for the application. They both might have - references to other application-specific classes, as well as to collections and simple JDK types. As - discussed before, the only distinguishing factory is the fact that a component does not own its own - lifecycle nor does it define an identifier. - -
- -
- Collection types - - - It is critical understand that we mean the collection itself, not its contents. - The contents of the collection can in turn be basic, component or entity types (though not - collections), but the collection itself is owned. - - - - Collections are covered in . - -
- -
- -
- Entity types - - The definition of entities is covered in detail in . For the purpose of - this discussion, it is enough to say that entities are (generally application-specific) classes which - correlate to rows in a table. Specifically they correlate to the row by means of a unique identifier. - Because of this unique identifier, entities exist independently and define their own lifecycle. As an example, - when we delete a Membership, both the User and - Group entities remain. - - - This notion of entity independence can be modified by the application developer using the concept of - cascades. Cascades allow certain operations to continue (or "cascade") across an association from - one entity to another. Cascades are covered in detail in . - - - -
- -
- Significance of type categories - - Why do we spend so much time categorizing the various types of types? What is the significance of the - distinction? - - - The main categorization was between entity types and value types. To review we said that entities, by - nature of their unique identifier, exist independently of other objects whereas values do not. An - application cannot "delete" a Product sku; instead, the sku is removed when the Product itself is - deleted (obviously you can update the sku of that Product to null to make it - "go away", but even there the access is done through the Product). - - - Nor can you define an association to that Product sku. You can - define an association to Product based on its sku, assuming sku is unique, but that - is totally different. - - - TBC... - -
- -
- Custom types - - Hibernate makes it relatively easy for developers to create their own value types. For - example, you might want to persist properties of type java.lang.BigInteger to - VARCHAR columns. Custom types are not limited to mapping values to a single table - column. So, for example, you might want to concatenate together FIRST_NAME, - INITIAL and SURNAME columns into a java.lang.String. - - - - There are 3 approaches to developing a custom Hibernate type. As a means of illustrating the different - approaches, lets consider a use case where we need to compose a java.math.BigDecimal - and java.util.Currency together into a custom Money class. - - -
- Custom types using <interfacename>org.hibernate.type.Type</interfacename> - - The first approach is to directly implement the org.hibernate.type.Type - interface (or one of its derivatives). Probably, you will be more interested in the more specific - org.hibernate.type.BasicType contract which would allow registration of - the type (see ). The benefit of this registration is that whenever - the metadata for a particular property does not specify the Hibernate type to use, Hibernate will - consult the registry for the exposed property type. In our example, the property type would be - Money, which is the key we would use to register our type in the registry: - - - - Defining and registering the custom Type - - - - - It is important that we registered the type before adding mappings. - - -
- -
- Custom types using <interfacename>org.hibernate.usertype.UserType</interfacename> - - - Both org.hibernate.usertype.UserType and - org.hibernate.usertype.CompositeUserType were originally - added to isolate user code from internal changes to the org.hibernate.type.Type - interfaces. - - - - The second approach is the use the org.hibernate.usertype.UserType - interface, which presents a somewhat simplified view of the org.hibernate.type.Type - interface. Using a org.hibernate.usertype.UserType, our - Money custom type would look as follows: - - - Defining the custom UserType - - - - There is not much difference between the org.hibernate.type.Type example - and the org.hibernate.usertype.UserType example, but that is only because - of the snippets shown. If you choose the org.hibernate.type.Type approach - there are quite a few more methods you would need to implement as compared to the - org.hibernate.usertype.UserType. - -
- -
- Custom types using <interfacename>org.hibernate.usertype.CompositeUserType</interfacename> - - The third and final approach is the use the org.hibernate.usertype.CompositeUserType - interface, which differs from org.hibernate.usertype.UserType in that it - gives us the ability to provide Hibernate the information to handle the composition within the - Money class (specifically the 2 attributes). This would give us the capability, - for example, to reference the amount attribute in an HQL query. Using a - org.hibernate.usertype.CompositeUserType, our - Money custom type would look as follows: - - - - Defining the custom CompositeUserType - - -
- -
- -
- Type registry - - Internally Hibernate uses a registry of basic types (see ) when - it needs to resolve the specific org.hibernate.type.Type to use in certain - situations. It also provides a way for applications to add extra basic type registrations as well as - override the standard basic type registrations. - - - To register a new type or to override an existing type registration, applications would make use of the - registerTypeOverride method of the org.hibernate.cfg.Configuration - class when bootstrapping Hibernate. For example, lets say you want Hibernate to use your custom - SuperDuperStringType; during bootstrap you would call: - - - Overriding the standard <classname>StringType</classname> - - - - The argument to registerTypeOverride is a org.hibernate.type.BasicType - which is a specialization of the org.hibernate.type.Type we saw before. It - adds a single method: - - - Snippet from BasicType.java - - /** - * Get the names under which this type should be registered in the type registry. - * - * @return The keys under which to register this type. - */ - public String[] getRegistrationKeys(); - - - - One approach is to use inheritance (SuperDuperStringType extends - org.hibernate.type.StringType); another is to use delegation. - -
- -
diff --git a/documentation/src/main/docbook/manual-old/en-US/extras/jacc-event-reg-example.java b/documentation/src/main/docbook/manual-old/en-US/extras/jacc-event-reg-example.java deleted file mode 100644 index 060cbc461024..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/extras/jacc-event-reg-example.java +++ /dev/null @@ -1,45 +0,0 @@ -import org.hibernate.event.service.spi.DuplicationStrategy; -import org.hibernate.event.service.spi.EventListenerRegistry; -import org.hibernate.integrator.spi.Integrator; -import org.hibernate.secure.internal.JACCPreDeleteEventListener; -import org.hibernate.secure.internal.JACCPreInsertEventListener; -import org.hibernate.secure.internal.JACCPreLoadEventListener; -import org.hibernate.secure.internal.JACCPreUpdateEventListener; -import org.hibernate.secure.internal.JACCSecurityListener; - -public class JaccEventListenerIntegrator implements Integrator { - - private static final DuplicationStrategy JACC_DUPLICATION_STRATEGY = new DuplicationStrategy() { - @Override - public boolean areMatch(Object listener, Object original) { - return listener.getClass().equals( original.getClass() ) && - JACCSecurityListener.class.isInstance( original ); - } - - @Override - public Action getAction() { - return Action.KEEP_ORIGINAL; - } - }; - - @Override - @SuppressWarnings( {"unchecked"}) - public void integrate( - Configuration configuration, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - boolean isSecurityEnabled = configuration.getProperties().containsKey( AvailableSettings.JACC_ENABLED ); - if ( !isSecurityEnabled ) { - return; - } - - final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); - eventListenerRegistry.addDuplicationStrategy( JACC_DUPLICATION_STRATEGY ); - - final String jaccContextId = configuration.getProperty( Environment.JACC_CONTEXTID ); - eventListenerRegistry.prependListeners( EventType.PRE_DELETE, new JACCPreDeleteEventListener(jaccContextId) ); - eventListenerRegistry.prependListeners( EventType.PRE_INSERT, new JACCPreInsertEventListener(jaccContextId) ); - eventListenerRegistry.prependListeners( EventType.PRE_UPDATE, new JACCPreUpdateEventListener(jaccContextId) ); - eventListenerRegistry.prependListeners( EventType.PRE_LOAD, new JACCPreLoadEventListener(jaccContextId) ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/manual-old/en-US/extras/namespacing.xml_sample b/documentation/src/main/docbook/manual-old/en-US/extras/namespacing.xml_sample deleted file mode 100644 index 7b2ff6ec54a5..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/extras/namespacing.xml_sample +++ /dev/null @@ -1,15 +0,0 @@ - - -]> - - - - - ... - - - &types; - diff --git a/documentation/src/main/docbook/manual-old/en-US/fallback_content/Conventions.xml b/documentation/src/main/docbook/manual-old/en-US/fallback_content/Conventions.xml deleted file mode 100644 index 3ce1c817a4c5..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/fallback_content/Conventions.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - -
- Document Conventions - - This manual uses several conventions to highlight certain words and phrases and draw attention to specific pieces of information. - - - In PDF and paper editions, this manual uses typefaces drawn from the Liberation Fonts set. The Liberation Fonts set is also used in HTML editions if the set is installed on your system. If not, alternative but equivalent typefaces are displayed. Note: Red Hat Enterprise Linux 5 and later includes the Liberation Fonts set by default. - -
- Typographic Conventions - - Four typographic conventions are used to call attention to specific words and phrases. These conventions, and the circumstances they apply to, are as follows. - - - Mono-spaced Bold - - - Used to highlight system input, including shell commands, file names and paths. Also used to highlight keycaps and key combinations. For example: - -
- - To see the contents of the file my_next_bestselling_novel in your current working directory, enter the cat my_next_bestselling_novel command at the shell prompt and press Enter to execute the command. - -
- - The above includes a file name, a shell command and a keycap, all presented in mono-spaced bold and all distinguishable thanks to context. - - - Key combinations can be distinguished from keycaps by the hyphen connecting each part of a key combination. For example: - -
- - Press Enter to execute the command. - - - Press CtrlAltF1 to switch to the first virtual terminal. Press CtrlAltF7 to return to your X-Windows session. - -
- - The first paragraph highlights the particular keycap to press. The second highlights two key combinations (each a set of three keycaps with each set pressed simultaneously). - - - If source code is discussed, class names, methods, functions, variable names and returned values mentioned within a paragraph will be presented as above, in mono-spaced bold. For example: - -
- - File-related classes include filesystem for file systems, file for files, and dir for directories. Each class has its own associated set of permissions. - -
- - Proportional Bold - - - This denotes words or phrases encountered on a system, including application names; dialog box text; labeled buttons; check-box and radio button labels; menu titles and sub-menu titles. For example: - -
- - Choose SystemPreferencesMouse from the main menu bar to launch Mouse Preferences. In the Buttons tab, click the Left-handed mouse check box and click Close to switch the primary mouse button from the left to the right (making the mouse suitable for use in the left hand). - - - To insert a special character into a gedit file, choose ApplicationsAccessoriesCharacter Map from the main menu bar. Next, choose SearchFind… from the Character Map menu bar, type the name of the character in the Search field and click Next. The character you sought will be highlighted in the Character Table. Double-click this highlighted character to place it in the Text to copy field and then click the Copy button. Now switch back to your document and choose EditPaste from the gedit menu bar. - -
- - The above text includes application names; system-wide menu names and items; application-specific menu names; and buttons and text found within a GUI interface, all presented in proportional bold and all distinguishable by context. - - - Mono-spaced Bold Italic or Proportional Bold Italic - - - Whether mono-spaced bold or proportional bold, the addition of italics indicates replaceable or variable text. Italics denotes text you do not input literally or displayed text that changes depending on circumstance. For example: - -
- - To connect to a remote machine using ssh, type ssh username@domain.name at a shell prompt. If the remote machine is example.com and your username on that machine is john, type ssh john@example.com. - - - The mount -o remount file-system command remounts the named file system. For example, to remount the /home file system, the command is mount -o remount /home. - - - To see the version of a currently installed package, use the rpm -q package command. It will return a result as follows: package-version-release. - -
- - Note the words in bold italics above — username, domain.name, file-system, package, version and release. Each word is a placeholder, either for text you enter when issuing a command or for text displayed by the system. - - - Aside from standard usage for presenting the title of a work, italics denotes the first use of a new and important term. For example: - -
- - Publican is a DocBook publishing system. - -
-
- -
- Pull-quote Conventions - - Terminal output and source code listings are set off visually from the surrounding text. - - - Output sent to a terminal is set in mono-spaced roman and presented thus: - - -books Desktop documentation drafts mss photos stuff svn -books_tests Desktop1 downloads images notes scripts svgs - - - Source-code listings are also set in mono-spaced roman but add syntax highlighting as follows: - - -package org.jboss.book.jca.ex1; - -import javax.naming.InitialContext; - -public class ExClient -{ - public static void main(String args[]) - throws Exception - { - InitialContext iniCtx = new InitialContext(); - Object ref = iniCtx.lookup("EchoBean"); - EchoHome home = (EchoHome) ref; - Echo echo = home.create(); - - System.out.println("Created Echo"); - - System.out.println("Echo.echo('Hello') = " + echo.echo("Hello")); - } -} - -
- -
- Notes and Warnings - - Finally, we use three visual styles to draw attention to information that might otherwise be overlooked. - - - Note - - Notes are tips, shortcuts or alternative approaches to the task at hand. Ignoring a note should have no negative consequences, but you might miss out on a trick that makes your life easier. - - - - Important - - Important boxes detail things that are easily missed: configuration changes that only apply to the current session, or services that need restarting before an update will apply. Ignoring a box labeled 'Important' won't cause data loss but may cause irritation and frustration. - - - - Warning - - Warnings should not be ignored. Ignoring warnings will most likely cause data loss. - - -
- -
- - diff --git a/documentation/src/main/docbook/manual-old/en-US/fallback_content/Feedback.xml b/documentation/src/main/docbook/manual-old/en-US/fallback_content/Feedback.xml deleted file mode 100644 index e6e77599e83f..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/fallback_content/Feedback.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - -
- We Need Feedback! - - feedback - contact information for this manual - - - You should over ride this by creating your own local Feedback.xml file. - -
- - diff --git a/documentation/src/main/docbook/manual-old/en-US/fallback_content/Legal_Notice.xml b/documentation/src/main/docbook/manual-old/en-US/fallback_content/Legal_Notice.xml deleted file mode 100644 index 90c0acf1f008..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/fallback_content/Legal_Notice.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Copyright &YEAR; &HOLDER; This material may only be distributed subject to the terms and conditions set forth in the GNU Free Documentation License (GFDL), V1.2 or later (the latest version is presently available at http://www.gnu.org/licenses/fdl.txt). - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/images/AuthorWork.png b/documentation/src/main/docbook/manual-old/en-US/images/AuthorWork.png deleted file mode 100644 index ef4ab7227ae9..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/AuthorWork.png and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/AuthorWork.zargo b/documentation/src/main/docbook/manual-old/en-US/images/AuthorWork.zargo deleted file mode 100644 index f249b229518e..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/AuthorWork.zargo and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/CustomerOrderProduct.png b/documentation/src/main/docbook/manual-old/en-US/images/CustomerOrderProduct.png deleted file mode 100644 index 7034cbe8cd54..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/CustomerOrderProduct.png and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/CustomerOrderProduct.zargo b/documentation/src/main/docbook/manual-old/en-US/images/CustomerOrderProduct.zargo deleted file mode 100644 index 016c559eee0c..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/CustomerOrderProduct.zargo and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/EmployerEmployee.png b/documentation/src/main/docbook/manual-old/en-US/images/EmployerEmployee.png deleted file mode 100644 index a7ecff483fe4..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/EmployerEmployee.png and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/EmployerEmployee.zargo b/documentation/src/main/docbook/manual-old/en-US/images/EmployerEmployee.zargo deleted file mode 100644 index 487368e8c7fc..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/EmployerEmployee.zargo and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/hibernate_logo_a.png b/documentation/src/main/docbook/manual-old/en-US/images/hibernate_logo_a.png deleted file mode 100644 index 0a343c4bca60..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/hibernate_logo_a.png and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/overview.png b/documentation/src/main/docbook/manual-old/en-US/images/overview.png deleted file mode 100644 index 1593a9522d53..000000000000 Binary files a/documentation/src/main/docbook/manual-old/en-US/images/overview.png and /dev/null differ diff --git a/documentation/src/main/docbook/manual-old/en-US/images/overview.svg b/documentation/src/main/docbook/manual-old/en-US/images/overview.svg deleted file mode 100644 index 1b58a483c4e4..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/images/overview.svg +++ /dev/null @@ -1,250 +0,0 @@ - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Application - - - -Persistent Objects - - - -Database - - - -HIBERNATE - - - - - - - - - - - - - - -hibernate. - -properties - - - -XML Mapping - - - diff --git a/documentation/src/main/docbook/manual-old/en-US/legal_notice.xml b/documentation/src/main/docbook/manual-old/en-US/legal_notice.xml deleted file mode 100644 index 6f3260c34a7e..000000000000 --- a/documentation/src/main/docbook/manual-old/en-US/legal_notice.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - Legal Notice - -
- 1801 Varsity Drive - Raleigh, NC27606-2072USA - Phone: +1 919 754 3700 - Phone: 888 733 4281 - Fax: +1 919 754 3701 - PO Box 13588Research Triangle Park, NC27709USA -
-
- - Copyright 2007 by Red Hat, Inc. This copyrighted material is made available to - anyone wishing to use, modify, copy, or redistribute it subject to the terms and conditions of the - GNU Lesser General Public License, as published - by the Free Software Foundation. - - - Red Hat and the Red Hat "Shadow Man" logo are registered trademarks of Red Hat, Inc. in the United States and other countries. - - - All other trademarks referenced herein are the property of their respective owners. - - - The GPG fingerprint of the security@redhat.com key is: - - - CA 20 86 86 2B D6 9D FC 65 F6 EC C4 21 91 80 CD DB 42 A6 0E - -
diff --git a/documentation/src/main/docbook/manual-old/publican.cfg b/documentation/src/main/docbook/manual-old/publican.cfg deleted file mode 100644 index 23db451dbd4d..000000000000 --- a/documentation/src/main/docbook/manual-old/publican.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Config::Simple 4.59 -# Wed Dec 9 09:53:51 2009 - -debug: 1 -xml_lang: en-US -brand: jboss-community-hibernate diff --git a/documentation/src/main/docbook/mappingGuide/en-US/Hibernate_Mapping_Guide.ent b/documentation/src/main/docbook/mappingGuide/en-US/Hibernate_Mapping_Guide.ent deleted file mode 100644 index 97f377d3e7ba..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/Hibernate_Mapping_Guide.ent +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/documentation/src/main/docbook/mappingGuide/en-US/Hibernate_Mapping_Guide.xml b/documentation/src/main/docbook/mappingGuide/en-US/Hibernate_Mapping_Guide.xml deleted file mode 100644 index db3e695df3fb..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/Hibernate_Mapping_Guide.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - -%BOOK_ENTITIES; -]> - - - - Hibernate Domain Model Mapping Guide - Hibernate - Relational Persistence for Idiomatic Java - &version; - Hibernate ORM - &version; - &today; - - - - - - - - - - ©rightYear; - ©rightHolder; - - - - - The Hibernate Team - - - The JBoss Visual Design Team - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/mappingGuide/en-US/Preface.xml b/documentation/src/main/docbook/mappingGuide/en-US/Preface.xml deleted file mode 100644 index 42208648359c..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/Preface.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Preface - - - The goal of this document is to discuss how to map an application's domain model to a relational - database. - - - - Historically applications using Hibernate would have used its proprietary XML mapping file format for - this purpose. With the coming of JPA, most of this information is now defined in a way that is portable - across ORM/JPA providers using annotations (and/or standardized XML format). This document will focus on - JPA mapping where possible. For Hibernate mapping features not supported by JPA we will prefer Hibernate - extension annotations, again where possible. - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/Basic_Types.xml b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/Basic_Types.xml deleted file mode 100644 index eb5909b19147..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/Basic_Types.xml +++ /dev/null @@ -1,1030 +0,0 @@ - - - - - - Basic Types - - - This chapter will discuss actual basic type mappings as well as how to override those - mappings and provide extra mappings. - - - - - - Basic value types usually map a single database value, or column, to a single, non-aggregated Java - type. Hibernate provides a number of built-in basic types, which follow the natural mappings - recommended in the JDBC specifications. - - - - Internally Hibernate uses a registry of basic types when it needs to resolve the specific - org.hibernate.type.Type to use in certain situations. - - -
- Hibernate-provided BasicTypes - - Standard BasicTypes - - - - Hibernate type (org.hibernate.type package) - JDBC type - Java type - BasicTypeRegistry key(s) - - - - - StringType - VARCHAR - java.lang.String - string, java.lang.String - - - MaterializedClob - CLOB - java.lang.String - materialized_clob - - - TextType - LONGVARCHAR - java.lang.String - text - - - CharacterType - CHAR - char, java.lang.Character - char, java.lang.Character - - - BooleanType - BIT - boolean, java.lang.Boolean - boolean, java.lang.Boolean - - - NumericBooleanType - INTEGER, 0 is false, 1 is true - boolean, java.lang.Boolean - numeric_boolean - - - YesNoType - CHAR, 'N'/'n' is false, 'Y'/'y' is true. The uppercase value is written to the database. - boolean, java.lang.Boolean - yes_no - - - TrueFalseType - CHAR, 'F'/'f' is false, 'T'/'t' is true. The uppercase value is written to the database. - boolean, java.lang.Boolean - true_false - - - ByteType - TINYINT - byte, java.lang.Byte - byte, java.lang.Byte - - - ShortType - SMALLINT - short, java.lang.Short - short, java.lang.Short - - - IntegerTypes - INTEGER - int, java.lang.Integer - int, java.lang.Integer - - - LongType - BIGINT - long, java.lang.Long - long, java.lang.Long - - - FloatType - FLOAT - float, java.lang.Float - float, java.lang.Float - - - DoubleType - DOUBLE - double, java.lang.Double - double, java.lang.Double - - - BigIntegerType - NUMERIC - java.math.BigInteger - big_integer, java.math.BigInteger - - - BigDecimalType - NUMERIC - java.math.BigDecimal - big_decimal, java.math.bigDecimal - - - TimestampType - TIMESTAMP - java.sql.Timestamp - timestamp, java.sql.Timestamp - - - TimeType - TIME - java.sql.Time - time, java.sql.Time - - - DateType - DATE - java.sql.Date - date, java.sql.Date - - - CalendarType - TIMESTAMP - java.util.Calendar - calendar, java.util.Calendar - - - CalendarDateType - DATE - java.util.Calendar - calendar_date - - - CurrencyType - java.util.Currency - VARCHAR - currency, java.util.Currency - - - LocaleType - VARCHAR - java.util.Locale - locale, java.utility.locale - - - TimeZoneType - VARCHAR, using the TimeZone ID - java.util.TimeZone - timezone, java.util.TimeZone - - - UrlType - VARCHAR - java.net.URL - url, java.net.URL - - - ClassType - VARCHAR (class FQN) - java.lang.Class - class, java.lang.Class - - - BlobType - BLOB - java.sql.Blob - blog, java.sql.Blob - - - ClobType - CLOB - java.sql.Clob - clob, java.sql.Clob - - - BinaryType - VARBINARY - byte[] - binary, byte[] - - - MaterializedBlobType - BLOB - byte[] - materized_blob - - - ImageType - LONGVARBINARY - byte[] - image - - - WrapperBinaryType - VARBINARY - java.lang.Byte[] - wrapper-binary, Byte[], java.lang.Byte[] - - - CharArrayType - VARCHAR - char[] - characters, char[] - - - CharacterArrayType - VARCHAR - java.lang.Character[] - wrapper-characters, Character[], java.lang.Character[] - - - UUIDBinaryType - BINARY - java.util.UUID - uuid-binary, java.util.UUID - - - UUIDCharType - CHAR, can also read VARCHAR - java.util.UUID - uuid-char - - - PostgresUUIDType - PostgreSQL UUID, through Types#OTHER, which complies to the PostgreSQL JDBC driver definition - java.util.UUID - pg-uuid - - - SerializableType - VARBINARY - implementors of java.lang.Serializable - Unlike the other value types, multiple instances of this type are registered. It is registered - once under java.io.Serializable, and registered under the specific java.io.Serializable implementation - class names. - - - - StringNVarcharType - NVARCHAR - java.lang.String - nstring - - - NTextType - LONGNVARCHAR - java.lang.String - ntext - - - NClobType - NCLOB - java.sql.NClob - nclob, java.sql.NClob - - - MaterializedNClobType - NCLOB - java.lang.String - materialized_nclob - - - PrimitiveCharacterArrayNClobType - NCHAR - char[] - N/A - - - CharacterNCharType - NCHAR - java.lang.Character - ncharacter - - - CharacterArrayNClobType - NCLOB - java.lang.Character[] - N/A - - - -
- - - - BasicTypes added by hibernate-java8 - - - - Hibernate type (org.hibernate.type package) - JDBC type - Java type - BasicTypeRegistry key(s) - - - - - DurationType - BIGINT - java.time.Duration - Duration, java.time.Duration - - - InstantType - TIMESTAMP - java.time.Instant - Instant, java.time.Instant - - - LocalDateTimeType - TIMESTAMP - java.time.LocalDateTime - LocalDateTime, java.time.LocalDateTime - - - LocalDateType - DATE - java.time.LocalDate - LocalDate, java.time.LocalDate - - - LocalTimeType - TIME - java.time.LocalTime - LocalTime, java.time.LocalTime - - - OffsetDateTimeType - TIMESTAMP - java.time.OffsetDateTime - OffsetDateTime, java.time.OffsetDateTime - - - OffsetTimeType - TIME - java.time.OffsetTime - OffsetTime, java.time.OffsetTime - - - OffsetTimeType - TIMESTAMP - java.time.ZonedDateTime - ZonedDateTime, java.time.ZonedDateTime - - - -
- - - - To use these hibernate-java8 types just add the hibernate-java8 jar to your classpath; Hibernate - will take care of the rest. See - - - - - - - These mappings are managed by a service inside Hibernate called the - org.hibernate.type.BasicTypeRegistry, which essentially maintains a map of - org.hibernate.type.BasicType (a org.hibernate.type.Type - specialization) instances keyed by a name. That is the purpose of the "BasicTypeRegistry key(s)" column - in the previous tables. We will revisit this detail later. - -
- - -
- The <literal>@Basic</literal> annotation - - - Strictly speaking, a basic type is denoted with the javax.persistence.Basic - annotation. Generally speaking the @Basic annotation can be ignored. Both of the - following examples are ultimately the same. - - - - With <literal>@Basic</literal> - - - - Without <literal>@Basic</literal> - - - - - - The JPA specification strictly limits the Java types that can be marked as - basic to the following: - - Java primitive types (boolean, int, etc) - wrappers for the primitive types (java.lang.Boolean, java.lang.Integer, etc) - java.lang.String - java.math.BigInteger - java.math.BigDecimal - java.util.Date - java.util.Calendar - java.sql.Date - java.sql.Time - java.sql.Timestamp - byte[] - Byte[] - char[] - Character[] - enums - any other type that implements Serializable* - - * JPA's "support" for Serializable types is to directly serialize their state to the database. - - - If provider portability is a concern, you should stick to just these basic types. Note that JPA - 2.1 did add the notion of an javax.persistence.AttributeConverter - to help alleviate some of these concerns; see - - - - - The @Basic annotation defines 2 attributes. - - - - optional - boolean (defaults to true) - Defines whether this attribute - allows nulls. JPA defines this as "a hint", which essentially means that it affect is - specifically required. As long as the type is not primitive, Hibernate takes this to mean - that the underlying column should be NULLABLE. - - - - - fetch - FetchType (defaults to EAGER) - Defines whether this attribute - should be fetched eagerly or lazily. JPA says that EAGER is a requirement to the provider - (Hibernate) that the value should be fetched when the owner is fetched but that - LAZY is merely a hint that the value be fetched when the attribute is accessed. Hibernate - ignores this setting for basic types unless you are using bytecode enhancement. See - the Hibernate User Guide for additional information on - fetching and on bytecode enhancement. - - - - -
- -
- The <literal>@Column</literal> annotation - - JPA defines rules for implicitly determining the name of tables and columns. For a detailed discussion - of implicit naming see . - - - - For basic type attributes, the implicit naming rule is that the column name is the same as the attribute - name. If that implicit naming rule does not meet your requirements, you can explicitly tell Hibernate - (and other providers) the column name to use. - - - - Explicit column naming - - - - - Here we use @Column to explicitly map the description attribute to the - NOTES column, as opposed to the implicit column name description. - - - - The @Column annotation defines other mapping information as well. See its javadocs - for details. - -
- -
- BasicTypeRegistry - - We said before that a Hibernate type is not a Java type, nor a SQL type, but that it - understands both and performs the marshalling between them. But looking at the - basic type mappings from the previous examples, how did Hibernate know to use - its org.hibernate.type.StringType for mapping for - java.lang.String attributes or its - org.hibernate.type.IntegerType for mapping - java.lang.Integer attributes? - - - - The answer lies in a service inside Hibernate called the - org.hibernate.type.BasicTypeRegistry, which essentially maintains a map of - org.hibernate.type.BasicType (a org.hibernate.type.Type - specialization) instances keyed by a name. - - - - We will see later () that we can explicitly tell Hibernate which - BasicType to use for a particular attribute. But first let's explore how implicit resolution works - and how applications can adjust implicit resolution. - - - - - A thorough discussion of the BasicTypeRegistry and all the different ways to contribute types to it - is beyond the scope of this documentation. Please see Integrations Guide - for complete details. - - - - - As an example, take a String attribute such as we saw before with Product#sku. Since there was no - explicit type mapping, Hibernate looks to the BasicTypeRegistry to find the registered mapping - for java.lang.String. This goes back to the "BasicTypeRegistry key(s)" column - we saw in the tables at the start of this chapter. - - - - As a baseline within BasicTypeRegistry, Hibernate follows the recommended mappings of JDBC - for Java types. JDBC recommends mapping Strings to VARCHAR, which is the exact mapping that - StringType handles. So that is the baseline mapping within BasicTypeRegistry for Strings. - - - - Applications can also extend (add new BasicType registrations) or override (replace an exiting BasicType - registration) using one of the MetadataBuilder#applyBasicType methods - or the MetadataBuilder#applyTypes method during bootstrap. For more details, see - - -
- -
- Explicit BasicTypes - - - Sometimes you want a particular attribute to be handled differently. Occasionally Hibernate will - implicitly pick a BasicType that you do not want (and for some reason you do not want to adjust the - BasicTypeRegistry). - - - - In these cases you must explicitly tell Hibernate the BasicType to use, via the - org.hibernate.annotations.Type annotation. - - - - Using @org.hibernate.annotations.Type - - - - - This tells Hibernate to store the Strings as nationalized data. This is just for illustration purposes; - for better ways to indicate nationalized character data see - - - - Additionally the description is to be handled as a LOB. Again, for better ways to indicate - LOBs see . - - - - The org.hibernate.annotations.Type#type attribute can name any of the following: - - FQN of any org.hibernate.type.Type implementation - Any key registered with BasicTypeRegistry - The name of any known "type definitions" - - -
- -
- Custom BasicTypes - - - Hibernate makes it relatively easy for developers to create their own basic type mappings type. For - example, you might want to persist properties of type java.lang.BigInteger to - VARCHAR columns, or support completely new types. - - - - There are 2 approaches to developing a custom BasicType. As a means of illustrating the different - approaches, lets consider a use case where we need to support a class called Fizzywig from a third party - library. Lets assume that Fizzywig naturally stores as a VARCHAR. - - - - The first approach is to directly implement the BasicType interface. - - - - Custom BasicType implementation - - - - - - The second approach is to implement the UserType interface. - - - - Custom UserType implementation - - - - - - For additional information on developing and registering custom types, see the - Hibernate Integration Guide. - -
- -
- Mapping enums - - - Hibernate supports the mapping of Java enums as basic value types in a number of different ways. - - -
- @Enumerated - - The original JPA-compliant way to map enums was via the @Enumerated - and @MapKeyEnumerated for map keys annotations which works on the principle that - the enum values are stored according to one of 2 strategies indicated by - javax.persistence.EnumType: - - - - ORDINAL - stored according to the enum value's ordinal position within - the enum class, as indicated by java.lang.Enum#ordinal - - - - - STRING - stored according to the enum value's name, as indicated by - java.lang.Enum#name - - - - - - - @Enumerated(ORDINAL) example - - - - - In the ORDINAL example, the gender column is defined as an (nullable) INTEGER type and would hold: - - - NULL - null - - - 0 - MALE - - - 1 - FEMALE - - - - - - @Enumerated(STRING) example - - - - - In the STRING example, the gender column is defined as an (nullable) VARCHAR type and would hold: - - - NULL - null - - - MALE - MALE - - - FEMALE - FEMALE - - - -
- -
- AttributeConverter - - You can also map enums in a JPA compliant way using a JPA 2.1 AttributeConverter. Let's revisit the - Gender enum example, but instead we want to store the more standardized 'M' - and 'F' codes. - - - - Enum mapping with AttributeConverter example - - - - - Here, the gender column is defined as a CHAR type and would hold: - - - NULL - null - - - 'M' - MALE - - - 'F' - FEMALE - - - - - - For additional details on using AttributeConverters, see . - - - - Note that JPA explicitly disallows the use of an AttributeConverter with an attribute marked - as @Enumerated. So if using the AttributeConverter approach, be sure to not mark the - attribute as @Enumerated. - -
- -
- Custom type - - - You can also map enums using a Hibernate custom type mapping. Let's again revisit the Gender enum - example, this time using a custom Type to store the more standardized 'M' - and 'F' codes. - - - - Enum mapping with custom Type example - - - - - Again, the gender column is defined as a CHAR type and would hold: - - - NULL - null - - - 'M' - MALE - - - 'F' - FEMALE - - - - - - For additional details on using custom types, see . - -
-
- -
- Mapping LOBs - - - Mapping LOBs (database Large OBjects) come in 2 forms, those using the JDBC locator types and those - materializing the LOB data. - - - - Locator versus materialized - - JDBC LOB locators exist to allow efficient access to the LOB data. They allow the JDBC driver to - stream parts of the LOB data as needed, potentially freeing up memory space. However they can be - unnatural to deal with and have certain limitations. For example, a LOB locator is only portably - valid during the duration of the transaction in which it was obtained. - - - The idea of materialized LOBs is to trade-off the potential efficiency (not all drivers handle LOB - data efficiently) for a more natural programming paradigm using familiar Java types such as - String or byte[], etc for these LOBs. - - - Materialized deals with the entire LOB contents in memory, whereas LOB locators (in theory) allow - streaming parts of the LOB contents into memory as needed. - - - - - The JDBC LOB locator types include: - - - java.sql.Blob - java.sql.Clob - java.sql.NClob - - - - - - Mapping materialized forms of these LOB values would use more familiar Java types - such as String, char[], byte[], etc. The trade off for "more familiar" is usually - performance. - - - - For a first look lets assume we have a CLOB column that we would like to map (NCLOB character LOB data - will be covered in ). - - - - CLOB - SQL - - - - - Let's first map this using the JDBC locator. - - - - CLOB - locator mapping - - - - - We could also map a materialized form. - - - - CLOB - materialized mapping - - - - - - How JDBC deals with LOB data varies from driver to driver. Hibernate tries to handle all these variances - for you. However some drivers do not allow Hibernate to always do that in an automatic fashion - (looking directly at you PostgreSQL JDBC drivers). In such cases you may have to do some extra - to get LOBs working. Such discussions are beyond the scope of this guide however. - - - - - - We might even want the materialized data as a char array (for some crazy reason). - - - - CLOB - materialized char[] mapping - - - - - We'd map BLOB data in a similar fashion. - - - - BLOB - SQL - - - - - Let's first map this using the JDBC locator. - - - - BLOB - locator mapping - - - - - We could also map a materialized BLOB form. - - - - BLOB - materialized mapping - - - - -
- -
- Mapping Nationalized Character Data - - - JDBC 4 added the ability to explicitly handle nationalized character data. To this end - it added specific nationalized character data types. - - - - NCHAR - NVARCHAR - LONGNVARCHAR - NCLOB - - - - - - To map a specific attribute to a nationalized variant datatype, Hibernate defines the - @Nationalized annotation. - - - - NVARCHAR mapping - - - - - NCLOB (locator) mapping - - - - - NCLOB (materialized) mapping - - - - - If you application and database are entirely nationalized you may instead want to enable nationalized - character data as the default. You can do this via the - hibernate.use_nationalized_character_data setting or by calling - MetadataBuilder#enableGlobalNationalizedCharacterDataSupport during bootstrap. - -
- -
- Mapping UUID Values - - - Hibernate also allows you to map UUID values, again in a number of ways. - - - - - The default UUID mapping is as binary because it represents more efficient storage. However - many applications prefer the readability of character storage. To switch the default mapping, - simply call MetadataBuilder.applyBasicType( UUIDCharType.INSTANCE, UUID.class.getName() ) - - - -
- UUID as binary - - As mentioned, the default mapping for UUID attributes. Maps the UUID to a byte[] - using java.util.UUID#getMostSignificantBits and java.util.UUID#getLeastSignificantBits - and stores that as BINARY data. - - - Chosen as the default simply because it is generally more efficient from storage perspective. - -
- -
- UUID as (var)char - - Maps the UUID to a String using java.util.UUID#toString and java.util.UUID#fromString - and stores that as CHAR or VARCHAR data. - -
- -
- PostgeSQL-specific UUID - - - When using one of the PostgreSQL Dialects, this becomes the default UUID mapping - - - - Maps the UUID using PostgreSQL's specific UUID data type. The PostgreSQL JDBC driver choses to - map its UUID type to the OTHER code. Note that this can cause difficulty as the - driver chooses to map many different data types to OTHER. - -
- -
- UUID as identifier - - Hibernate supports using UUID values as identifiers. They can even be generated! For - details see the discussion of generators in - -
-
- -
- Mapping Date/Time Values - - - - blah blah blah - -
- -
- JPA 2.1 AttributeConverters - - - blah blah blah - -
- -
diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/Blob.sql b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/Blob.sql deleted file mode 100644 index cdf673cd2659..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/Blob.sql +++ /dev/null @@ -1,5 +0,0 @@ -create table step( - ... - instruction BLOB not null, - ... -) \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/BlobLocator.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/BlobLocator.java deleted file mode 100644 index 814926ed534a..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/BlobLocator.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Step { - ... - @Lob - @Basic - public Blob instructions; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/BlobMaterialized.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/BlobMaterialized.java deleted file mode 100644 index 14810e013155..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/BlobMaterialized.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Step { - ... - @Lob - @Basic - public byte[] instructions; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/Clob.sql b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/Clob.sql deleted file mode 100644 index fbdfb3cfb479..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/Clob.sql +++ /dev/null @@ -1,5 +0,0 @@ -create table product( - ... - description CLOB not null, - ... -) \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobLocator.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobLocator.java deleted file mode 100644 index 9f20f73537d9..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobLocator.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Product { - ... - @Lob - @Basic - public Clob description; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobMaterialized.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobMaterialized.java deleted file mode 100644 index 3090bcd2cb46..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobMaterialized.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Product { - ... - @Lob - @Basic - public String description; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobMaterializedCharArray.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobMaterializedCharArray.java deleted file mode 100644 index 3c25ef0b4baa..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ClobMaterializedCharArray.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Product { - ... - @Lob - @Basic - public char[] description; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumAttributeConverter.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumAttributeConverter.java deleted file mode 100644 index d11552a92a6c..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumAttributeConverter.java +++ /dev/null @@ -1,53 +0,0 @@ -@Entity -public class Person { - ... - @Basic - @Convert( converter=GenderConverter.class ) - public Gender gender; -} - -public enum Gender { - MALE( 'M' ), - FEMALE( 'F' ); - - private final char code; - - private Gender(char code) { - this.code = code; - } - - public char getCode() { - return code; - } - - public static Gender fromCode(char code) { - if ( code == 'M' || code == 'm' ) { - return MALE; - } - if ( code == 'F' || code == 'f' ) { - return FEMALE; - } - throw ... - } -} - -@Converter -public class GenderConverter - implements AttributeConverter { - - public Character convertToDatabaseColumn(Gender value) { - if ( value == null ) { - return null; - } - - return value.getCode(); - } - - public Gender convertToEntityAttribute(Character value) { - if ( value == null ) { - return null; - } - - return Gender.fromCode( value ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumCustomType.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumCustomType.java deleted file mode 100644 index 5aeab77cb301..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumCustomType.java +++ /dev/null @@ -1,82 +0,0 @@ -import org.hibernate.type.descriptor.java.CharacterTypeDescriptor; - -@Entity -public class Person { - ... - @Basic - @Type( type = GenderType.class ) - public Gender gender; -} - -public enum Gender { - MALE( 'M' ), - FEMALE( 'F' ); - - private final char code; - - private Gender(char code) { - this.code = code; - } - - public char getCode() { - return code; - } - - public static Gender fromCode(char code) { - if ( code == 'M' || code == 'm' ) { - return MALE; - } - if ( code == 'F' || code == 'f' ) { - return FEMALE; - } - throw ... - } -} - -@Converter -public class GenderType - extends AbstractSingleColumnStandardBasicType { - - public static final GenderType INSTANCE = new GenderType(); - - private GenderType() { - super( - CharTypeDescriptor.INSTANCE, - GenderJavaTypeDescriptor.INSTANCE - ); - } - - public String getName() { - return "gender"; - } - - @Override - protected boolean registerUnderJavaType() { - return true; - } -} - -public static class GenderJavaTypeDescriptor - extends AbstractTypeDescriptor { - public static final GenderJavaTypeDescriptor INSTANCE = new GenderJavaTypeDescriptor(); - - public String toString(Gender value) { - return value == null ? null : value.name(); - } - - public Gender fromString(String string) { - return string == null ? null : Gender.valueOf( string ); - } - - public X unwrap(Gender value, Class type, WrapperOptions options) { - return CharacterTypeDescriptor.INSTANCE.unwrap( - value == null ? null : value.getCode(), - type, - options - ); - } - - public Gender wrap(X value, WrapperOptions options) { - return CharacterTypeDescriptor.INSTANCE.wrap( value, options ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumeratedOrdinal.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumeratedOrdinal.java deleted file mode 100644 index 9680fb88db60..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumeratedOrdinal.java +++ /dev/null @@ -1,11 +0,0 @@ -@Entity -public class Person { - ... - @Enumerated - public Gender gender; - - public static enum Gender { - MALE, - FEMALE - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumeratedString.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumeratedString.java deleted file mode 100644 index e5541a53c8e5..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/EnumeratedString.java +++ /dev/null @@ -1,11 +0,0 @@ -@Entity -public class Person { - ... - @Enumerated(STRING) - public Gender gender; - - public static enum Gender { - MALE, - FEMALE - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ExplicitColumnNaming.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ExplicitColumnNaming.java deleted file mode 100644 index 9eded3a8a4f4..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ExplicitColumnNaming.java +++ /dev/null @@ -1,13 +0,0 @@ -@Entity -public class Product { - @Id - @Basic - private Integer id; - @Basic - private String sku; - @Basic - private String name; - @Basic - @Column( name = "NOTES" ) - private String description; -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType1.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType1.java deleted file mode 100644 index 3255aaf05dc5..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType1.java +++ /dev/null @@ -1,44 +0,0 @@ -public class FizzywigType1 implements org.hibernate.type.BasicType { - public static final FizzywigType1 INSTANCE = new FizzywigType1(); - - @Override - public String[] getRegistrationKeys() { - return new String[] { Fizzywig.class.getName() }; - } - - @Override - public int[] sqlTypes(Mapping mapping) { - return new int[] { java.sql.Types.VARCHAR }; - } - - @Override - public Class getReturnedClass() { - return Money.class; - } - - @Override - public Object nullSafeGet( - ResultSet rs, - String[] names, - SessionImplementor session, - Object owner) throws SQLException { - return Fizzwig.fromString( - StringType.INSTANCE.get( rs, names[0], sesson ) - ); - } - - @Override - public void nullSafeSet( - PreparedStatement st, - Object value, - int index, - boolean[] settable, - SessionImplementor session) throws SQLException { - final String dbValue = value == null - ? null - : ( (Fizzywig) value ).asString(); - StringType.INSTANCE.nullSafeSet( st, value, index, settable, session ); - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType1_reg.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType1_reg.java deleted file mode 100644 index c1846ad1d6a8..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType1_reg.java +++ /dev/null @@ -1,5 +0,0 @@ -MetadataSources metadataSources = ...; - -metadataSources.getMetaDataBuilder() - .applyBasicType( FizzwigType1.INSTANCE ) - ... \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType2.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType2.java deleted file mode 100644 index a75cbd7b0cef..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType2.java +++ /dev/null @@ -1,39 +0,0 @@ -public class FizzywigType2 implements org.hibernate.usertype.UserType { - public static final String KEYS = new String[] { Fizzywig.class.getName() }; - public static final FizzywigType1 INSTANCE = new FizzywigType1(); - - @Override - public int[] sqlTypes(Mapping mapping) { - return new int[] { java.sql.Types.VARCHAR }; - } - - @Override - public Class getReturnedClass() { - return Fizzywig.class; - } - - @Override - public Object nullSafeGet( - ResultSet rs, - String[] names, - SessionImplementor session, - Object owner) throws SQLException { - return Fizzwig.fromString( - StringType.INSTANCE.get( rs, names[0], sesson ) - ); - } - - @Override - public void nullSafeSet( - PreparedStatement st, - Object value, - int index, - SessionImplementor session) throws SQLException { - final String dbValue = value == null - ? null - : ( (Fizzywig) value ).asString(); - StringType.INSTANCE.nullSafeSet( st, value, index, session ); - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType2_reg.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType2_reg.java deleted file mode 100644 index b8ae369f0834..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/FizzywigType2_reg.java +++ /dev/null @@ -1,5 +0,0 @@ -MetadataSources metadataSources = ...; - -metadataSources.getMetaDataBuilder() - .applyBasicType( FizzwigType2.KEYS, FizzwigType2.INSTANCE ) - ... \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NCLOB_locator.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NCLOB_locator.java deleted file mode 100644 index 41a691007f4c..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NCLOB_locator.java +++ /dev/null @@ -1,12 +0,0 @@ -@Entity -public class Product { - ... - @Lob - @Basic - @Nationalized - public NClob description; - // Clob also works, because NClob - // extends Clob. The db type is - // still NCLOB either way and - // handled as such -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NCLOB_materialized.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NCLOB_materialized.java deleted file mode 100644 index 443686c04618..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NCLOB_materialized.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Product { - ... - @Lob - @Basic - @Nationalized - public String description; -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NVARCHAR.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NVARCHAR.java deleted file mode 100644 index 0786749d667b..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/NVARCHAR.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Product { - ... - @Basic - @Nationalized - public String description; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ex1.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ex1.java deleted file mode 100644 index 473f14ca9c2f..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ex1.java +++ /dev/null @@ -1,12 +0,0 @@ -@Entity -public class Product { - @Id - @Basic - private Integer id; - @Basic - private String sku; - @Basic - private String name; - @Basic - private String description; -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ex2.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ex2.java deleted file mode 100644 index 335bdb76951d..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/ex2.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Product { - @Id - private Integer id; - private String sku; - private String name; - private String description; -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/explicitType.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/explicitType.java deleted file mode 100644 index c68c578b03e3..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/basic/extras/explicitType.java +++ /dev/null @@ -1,5 +0,0 @@ -@org.hibernate.annotations.Type( type="nstring" ) -private String name; - -@org.hibernate.annotations.Type( type="materialized_nclob" ) -private String description; \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/Data_Categorizations.xml b/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/Data_Categorizations.xml deleted file mode 100644 index 8cbe88a1dd28..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/Data_Categorizations.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Data categorizations - - - - - Before diving into the actual topics of how to map different categories of things in your domain mode - it helps to understand what those categories are. Hibernate and JPA both express these categorizations. - - - - Hibernate understands both the Java and JDBC representations of application data. The ability to read and write - the this data to/from the database is the function of a Hibernate type. A type, in this usage, - is an implementation of the org.hibernate.type.Type interface. This Hibernate - type also describes various aspects of behavior of the Java type such as how to check for equality, how to - clone values, etc. - - - - Usage of the word <wordasword>type</wordasword> - - The Hibernate type is neither a Java type nor a SQL datatype. It provides information about both of these - as well as understanding marshalling between. - - - When you encounter the term type in discussions of Hibernate, it may refer to the Java type, the JDBC type, - or the Hibernate type, depending on context. - - - - - To help understand these categorizations, lets look at a simple table and domain model that we wish to map. - - - - Simple table and domain model - - - - - - In the broadest sense, Hibernate categorizes types into two groups: - - Value types () - Entity types () - - - -
- Value types - - - A value type is a piece of data that does not define its own lifecycle. It is, - in effect, owned by an entity, which defines its lifecycle. - - - - Looked at another way, all the state of an entity is made up entirely of value types. These - state fields or JavaBean-properties are termed persistent attributes. - The persistent attributes of the Contact class are value types. - - - - Value types are further classified into three sub-categories; - - - Basic types - In mapping the Contact table, all attributes except for name would be basic - types. Basic types are discussed in detail in - - - Composite (or Embeddable) types - the name attribute is an example of a composite type, which is - discussed in details in - - - Collection types - the example has no collections. Collection types are - discussed in - - - -
- -
- Entity Types - - - Entities are application-specific classes which correlate to rows in a table, using a unique identifier. - Because of the requirement for a unique identifier, entities exist independently and define their own - lifecycle. The Contact class itself would be an example of an entity. - - - - Mapping entities is discussed in detail in . - -
- -
- Significance of type categories - - Why do we spend so much time categorizing the various types of types? What is the significance of the - distinction? - - - The main categorization was between entity types and value types. To review we said that entities, by - nature of their unique identifier, exist independently of other objects whereas values do not. An - application cannot "delete" the Contact website; instead, the website is removed when the Contact itself is - deleted (obviously you can update the website of that website to null to make it - "go away", but even there the access is done through the website). - - - Nor can you define an association to that Contact website. You can - define an association to Contact based on its website (assuming website is unique), - but that is totally different. - - - equality - - - TBC... - -
- -
diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/extras/Contact.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/extras/Contact.java deleted file mode 100644 index bd6d889e535a..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/extras/Contact.java +++ /dev/null @@ -1,15 +0,0 @@ -public class Contact { - private Integer id; - private Name name; - private String notes; - private URL website; - private boolean starred; - // getters and setters ommitted -} - -public class Name { - private String first; - private String middle; - private String last; - // getters and setters ommitted -} diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/extras/Contact.sql b/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/extras/Contact.sql deleted file mode 100644 index 768afdbff22f..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/categoizations/extras/Contact.sql +++ /dev/null @@ -1,9 +0,0 @@ -create table Contact ( - id INTEGER NOT NULL, - first_name VARCHAR, - middle_name VARCHAR, - last_name VARCHAR, - notes VARCHAR, - starred BIT, - website VARCHAR -) \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/collection/Collection.xml b/documentation/src/main/docbook/mappingGuide/en-US/chapters/collection/Collection.xml deleted file mode 100644 index 85eb043d15cc..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/collection/Collection.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - Collections - -
- Collections as a value type - - discussions of what it means for them to be value types - lifecycle, opt-locking - - - Collections have the usual behavior of value types. They are automatically persisted when referenced by - a persistent object and are automatically deleted when unreferenced. If a collection is passed from one - persistent object to another, its elements might be moved from one table to another. Two entities cannot - share a reference to the same collection instance. - - - Collection attributes do not support null value semantics; Hibernate does not distinguish between a null - collection reference and an empty collection. - - - It is important that collections be defined using the appropriate Java Collections Framework interface - rather than a specific implementation. From a theoretical perspective, this just follows good design - principles. From a practical perspective, Hibernate (really all persistence providers) will use - their own collection implementations which conform to the Java Collections Framework interfaces. - -
- -
- Collections of value types - - collection of values - elements can be of any value type except for collections (in fact even compositions as the element cannot contain collections) - * basics - * compositions - -
- -
- Collections of entities - - * one-to-many - * many-to-many - -
- -
- List - index - - - todo : discuss mapping list index - -
- -
- Map - key - - - todo : discuss mapping map key - -
- -
- Bags - - - todo : discuss mapping bags and idbags - -
- -
- Arrays - - - todo : discuss mapping arrays - -
- -
- Collections as basic value type - - Notice how all the previous examples explicitly mark the collection attribute as either - ElementCollection, OneToMany or ManyToMany. Collections not marked as such, or collections explicitly - maked with @Basic are treated as JPA basic values. Meaning there value is stored into a single - column in the containing table. - - - This is sometimes beneficial. Consider a use-case such as a VARCHAR column that represents a - delimited list or set of Strings. - - - Delimited set of tags - - - - - See the Hibernate Integrations Guide for more details on developing - custom value type mappings. Without the special type mapping above the "set of tags" would have - simply been marshalled using serialization. - -
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/collection/extras/DelimitedStringTagsExample.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/collection/extras/DelimitedStringTagsExample.java deleted file mode 100644 index 3571beeb0188..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/collection/extras/DelimitedStringTagsExample.java +++ /dev/null @@ -1,59 +0,0 @@ -@Entity -public static class Post { - @Id - public Integer id; - @Basic - @Type( type = "delimited_strings" ) - Set tags; -} - - -public static class DelimitedStringsType extends AbstractSingleColumnStandardBasicType { - public DelimitedStringsType() { - super( - VarcharTypeDescriptor.INSTANCE, - new DelimitedStringsJavaTypeDescriptor() - ); - } - - @Override - public String getName() { - return "delimited_strings"; - } -} - -public static class DelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor { - public DelimitedStringsJavaTypeDescriptor() { - super( - Set.class, - new MutableMutabilityPlan() { - @Override - protected Set deepCopyNotNull(Set value) { - Set copy = new HashSet(); - copy.addAll( value ); - return copy; - } - } - ); - } - - @Override - public String toString(Set value) { - return null; - } - - @Override - public Set fromString(String string) { - return null; - } - - @Override - public X unwrap(Set value, Class type, WrapperOptions options) { - return null; - } - - @Override - public Set wrap(X value, WrapperOptions options) { - return null; - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/Composition.xml b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/Composition.xml deleted file mode 100644 index acbf66d60ae8..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/Composition.xml +++ /dev/null @@ -1,210 +0,0 @@ - - - - Compositions - - - Related topics - - - - - - - - - - - - - - - Historically Hibernate called these components. JPA calls them embeddables. Either way the concept is the - same: a composition of values. For example we might have a Name class that is a composition of - first-name and last-name, or an Address class that is a composition of street, city, postal code, etc. - - - - Simple composition example - - - - - - A composition is another form of value type. The lifecycle of a composition is defined by the thing that - contains it. - - - - A composition inherits the attribute access of its parent. For details on attribute access, see - . - - - - Compositions can be made up of basic values as well as associations, with the caveat that compositions which - are used as collection elements cannot themselves define collections. - - -
- Component / Embedded - - This is the form of composition you will see most often. Here an entity or another composition - is the container. - - - - Simple Embedded - - - - - - Notice that JPA defines 2 terms for composition: Embeddable and Embedded. Embeddable is used to - describe the composition class itself (Name). Embedded is used to describe a usage of that - composition (Person.name). - - - - - The composition here is the Name type related to Person.name. - - - - Person table - - - - - The composed values are mapped to the same table as the parent table. Composition is part of good - OO data modeling (idiomatic java). In fact that table could also be mapped by the following entity instead. - - - - Alternative to composition - - - - - The composition form is certainly more OO. And that becomes more evident as we work with multiple - compositions. - -
- -
- Multiple compositions - - - Multiple compositions - - - - - It is certainly more convenient to work with the compositions. However, an interesting thing happens - in this particular example. By default, this mapping actually will not work as-is. - The problem is in how JPA defines implicit naming rules for columns that are part of a composition, which - say that all of the Address compositions would map to the same implicit column names. - - - - This occurs any time we have multiple compositions based on the same embeddable in a given parent. - We have a few options to handle this issue. - - -
- JPA's AttributeOverride - - - The JPA-defined way to handle this situation is through the use of its AttributeOverride annotation. - - - - JPA's AttributeOverride - - - - - Now, essentially there are no implicit column names in the Address compositions. We have explicitly - named them. - -
- -
- ImplicitNamingStrategy - - - - This is a Hibernate specific feature. Users concerned with JPA provider portability should instead - prefer explicit column naming with AttributeOverride as per - - - - - Hibernate naming strategies are covered in detail in . However, for the purposes - of this discussion, Hibernate has the capability to interpret implicit column names in a way that is - safe for use with multiple compositions. - - - - Enabling composition-safe implicit naming - - - - - Now the "path" to attributes are used in the implicit column naming. - - - - Enabling composition-safe implicit naming - - - - - You could even develop your own to do special implicit naming. - -
-
- -
- Collections of compositions - - Collections of compositions are specifically value collections (as compositions are a value type). Value - collections are covered in detail in . - - - The one thing to add to the discussion of value collections in regards to compositions is that - the composition cannot, in turn, define collections. - -
- -
- Compositions as Map key - - Compositions can also be used as the key values for Maps. Mapping Maps and their keys is convered in - detail in . - - - Again, compositions used as a Map key cannot, in turn, define collections. - -
- -
- Compositions as identifiers - - Compositions can also be used as entity identifiers. This usage is covered in detail in - - - - Again, compositions used as an entity identifier cannot, in turn, define collections. - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Address.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Address.java deleted file mode 100644 index fc13041d014c..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Address.java +++ /dev/null @@ -1,15 +0,0 @@ -@Embeddable -public class Address { - private String line1; - private String line2; - @Embedded - private ZipCode zipCode; - ... - - @Embeddable - public static class Zip { - private String postalCode; - private String plus4; - ... - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact-AttributeOverride.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact-AttributeOverride.java deleted file mode 100644 index f8c23dda1dd0..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact-AttributeOverride.java +++ /dev/null @@ -1,68 +0,0 @@ -@Entity -public class Contact { - @Id - private Integer id; - @Embedded - private Name name; - @Embedded - @AttributeOverrides( - @AttributeOverride( - name="line1", - column = @Column(name = "home_address_line1"), - ), - @AttributeOverride( - name="line2", - column = @Column(name = "home_address_line2") - ), - @AttributeOverride( - name="zipCode.postalCode", - column = @Column(name = "home_address_postal_cd") - ), - @AttributeOverride( - name="zipCode.plus4", - column = @Column(name = "home_address_postal_plus4") - ) - ) - private Address homeAddress; - @Embedded - @AttributeOverrides( - @AttributeOverride( - name="line1", - column = @Column(name = "mailing_address_line1"), - ), - @AttributeOverride( - name="line2", - column = @Column(name = "mailing_address_line2") - ), - @AttributeOverride( - name="zipCode.postalCode", - column = @Column(name = "mailing_address_postal_cd") - ), - @AttributeOverride( - name="zipCode.plus4", - column = @Column(name = "mailing_address_postal_plus4") - ) - ) - private Address mailingAddress; - @Embedded - @AttributeOverrides( - @AttributeOverride( - name="line1", - column = @Column(name = "work_address_line1"), - ), - @AttributeOverride( - name="line2", - column = @Column(name = "work_address_line2") - ), - @AttributeOverride( - name="zipCode.postalCode", - column = @Column(name = "work_address_postal_cd") - ), - @AttributeOverride( - name="zipCode.plus4", - column = @Column(name = "work_address_postal_plus4") - ) - ) - private Address workAddress; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact-ImplicitNamingStrategy.sql b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact-ImplicitNamingStrategy.sql deleted file mode 100644 index 09f59a25c74e..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact-ImplicitNamingStrategy.sql +++ /dev/null @@ -1,19 +0,0 @@ -create table Contact( - id integer not null, - name_firstName VARCHAR, - name_middleName VARCHAR, - name_lastName VARCHAR, - homeAddress_line1 VARCHAR, - homeAddress_line2 VARCHAR, - homeAddress_zipCode_postalCode VARCHAR, - homeAddress_zipCode_plus4 VARCHAR, - mailingAddress_line1 VARCHAR, - mailingAddress_line2 VARCHAR, - mailingAddress_zipCode_postalCode VARCHAR, - mailingAddress_zipCode_plus4 VARCHAR, - workAddress_line1 VARCHAR, - workAddress_line2 VARCHAR, - workAddress_zipCode_postalCode VARCHAR, - workAddress_zipCode_plus4 VARCHAR, - ... -) \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact.java deleted file mode 100644 index 32cdea07b85d..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Contact.java +++ /dev/null @@ -1,14 +0,0 @@ -@Entity -public class Contact { - @Id - private Integer id; - @Embedded - private Name name; - @Embedded - private Address homeAddress; - @Embedded - private Address mailingAddress; - @Embedded - private Address workAddress; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Name.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Name.java deleted file mode 100644 index 69835c2999de..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Name.java +++ /dev/null @@ -1,7 +0,0 @@ -@Embeddable -public class Name { - private String firstName; - private String middleName; - private String lastName; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person.java deleted file mode 100644 index 37db28cf3a6f..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Person { - @Id - private Integer id; - @Embedded - private Name name; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person1.sql b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person1.sql deleted file mode 100644 index 7dbc0647c449..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person1.sql +++ /dev/null @@ -1,7 +0,0 @@ -create table Person ( - id integer not null, - firstName VARCHAR, - middleName VARCHAR, - lastName VARCHAR, - ... -) \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person_alt.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person_alt.java deleted file mode 100644 index 7f296932c36d..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/Person_alt.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -public class Person { - @Id - private Integer id; - private String firstName; - private String middleName; - private String lastName; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/component-safe-implicit-naming.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/component-safe-implicit-naming.java deleted file mode 100644 index 8fa6857948f3..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/composition/extras/component-safe-implicit-naming.java +++ /dev/null @@ -1,9 +0,0 @@ -MetadataSources sources = ...; -sources.addAnnotatedClass( Address.class ); -sources.addAnnotatedClass( Name.class ); -sources.addAnnotatedClass( Contact.class ); - -Metadata metadata = sources.getMetadataBuilder() - .applyImplicitNamingStrategy( ImplicitNamingStrategyComponentPathImpl.INSTANCE ) - ... - .build(); \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/Entity.xml b/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/Entity.xml deleted file mode 100644 index e18032199892..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/Entity.xml +++ /dev/null @@ -1,335 +0,0 @@ - - - - - Entity - - Discuss mapping entities in the application domain model. - - - - - Related Topics - - - - - - - - - - - - - -
- POJO Models - - - Section 2.1 The Entity Class of the JPA 2.1 specification - defines its requirements for an entity class. Applications that wish to remain portable across JPA providers - should adhere to these requirements. - - - - The entity class must be annotated with the javax.persistence.Entity - annotation (or be denoted as such in XML mapping) - - - - - The entity class must have a public or protected no-argument constructor. It may define - additional constructors as well. - - - - - The entity class must be a top-level class. - - - - - An enum or interface may not be designated as an entity. - - - - - The entity class must not be final. No methods or persistent instance variables of the entity - class may be final. - - - - - If an entity instance is to be used remotely as a detached object, the entity class must - implement the Serializable interface. - - - - - Both abstract and concrete classes can be entities. Entities may extend non-entity classes as - well as entity classes, and non-entity classes may extend entity classes. - - - - - The persistent state of an entity is represented by instance variables, which may correspond to - JavaBean-style properties. An instance variable must be directly accessed only from within the - methods of the entity by the entity instance itself. The state of the entity is available to - clients only through the entity’s accessor methods (getter/setter methods) or other business - methods. - - - - - - - Hibernate, however, is not as strict in its requirements. The differences from the list above include: - - - - The entity class must have a no-argument constructor, which may be public, protected or package - visibility. It may define additional constructors as well. - - - - - The entity class need not be a top-level class. - - - - - Technically Hibernate can persist final classes or classes with final persistent state - accessor (getter/setter) methods. However, it is generally not a good idea as doing - so will stop Hibernate from being able to generate proxies for lazy-loading the entity. - - - - - Hibernate does not really care if you expose direct access to your instance variables and - use them from outside the entity itself. The validity of such a paradigm, however, is debatable - at best. - - - - - - - Let's look at each requirement in detail. - - -
- Prefer non-final classes - - - This is a requirement for JPA. It is more of a recommendation for Hibernate. - - - - A central feature of Hibernate is the ability to lazy load an entity's data via runtime proxies. This - feature depends upon the entity class being non-final or else implementing an interface that declares - all the attribute getters/setters. You can still persist classes that are declared final and that do - not implement such an interface with Hibernate; you just will not be able to use proxies for lazy - association fetching which will ultimately limit your options for performance tuning. - - - - - Starting in 5.0 Hibernate offers a more robust version of bytecode enhancement as another means - for handling lazy loading. Hibernate had some bytecode re-writing capabilities prior to 5.0 but - they were very rudimentary. - - - - - - You should also avoid declaring persistent attribute getters and setters as final for the reasons - already mentioned. And of course making the instance variable hold the entiy's persistent state would - just simply not make any sense. - -
- -
- Implement a no-argument constructor - - - The entity class should have a no-argument constructor. Both Hibernate and JPA require this. - - - - JPA requires that this constructor be defined as public or protected. Hibernate for the most part does - note care about the visibility as long as the system's SecurityManager allows overriding the visibility. - That said, the constructor should be defined with at least package visibility if you wish to leverage - runtime proxy generation. - -
- -
- Declare getters and setters for persistent attributes - - - Standard, portable JPA essentially requires this. Otherwise your model would violate the requirement - quoted above in regards to accessing the entity persistent state fields directly from outside the - entity itself. - - - - Although Hibernate does not require it, it is recommended to follow JavaBean conventions by defining - getters and setters for you entities persistent attributes. You can still tell Hibernate to directly - access the entity's fields. - - - - Attributes (whether fields or getters/setters) need not be declared public. Hibernate can deal with - attributes declared with public, protected, package or private visibility. Again, if wanting to use - runtime proxy generation for lazy loading the visibility for the getter/setter should be at least - package visibility. - -
- - -
- Provide identifier attribute(s) - - - - Historically this was considered optional. However, not defining identifier attribute(s) on the - entity should be considered a deprecated feature that will be removed in an upcoming release. - - - - - The identifier attribute does not necessarily need to be mapped to the column(s) that physically - define the primary key. However, it should map to column(s) that can uniquely identify each row. - - - - We recommend that you declare consistently-named identifier attributes on persistent classes and - that you use a nullable (i.e., non-primitive) type. - -
- -
- Mapping the entity - - - The main piece in mapping the entity is the javax.persistence.Entity - annotation. The Entity annotation defines just one attribute name which - is used to give the entity a specific name for use in JPQL queries; by default the name is the - unqualified name of the entity class. - - - - Simple @Entity - - - - - An entity models a database table. The identifier uniquely identifies each row in that table. By - default the name of the table is assumed to be the same as the name of the entity. To explicitly - give the name of the table or to specify other information about the table, we would use the - javax.persistence.Table annotation. - - - - Simple @Entity with @Table - - - - - For details on mapping the identifier, see - -
- -
- Mapping optimistic locking - - - JPA defines support for optimistic locking based on either a version (sequential numeric) or timestamp - strategy. To enable this style of optimistic locking simply add the - javax.persistence.Version to the persistent attribute that defines the - optimistic locking value. According to JPA, the valid types for these attributes are limited to: - - - int, or Integer - - - short, or Short - - - long, or Long - - - java.sql.Timestamp - - - - - - Version - - - - - - - - Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". - This is intended mainly for use with modeling legacy schemas. The idea is that you can get Hibernate - to perform "version checks" using either all of the entity's attributes, or just the attributes that - have changed. This is achieved through the use of the - org.hibernate.annotations.OptimisticLocking annotation which defines a - single attribute of type org.hibernate.annotations.OptimisticLockType. - There are 4 available OptimisticLockTypes: - - - - NONE - optimistic locking is disabled. Even if there is a @Version - annotation present. - - - - - VERSION (the default) - performs optimistic locking based on a @Version - as described above. - - - - - ALL - Perform optimistic locking based on *all* fields as part of an - expanded WHERE clause restriction for the UPDATE/DELETE SQL statement. - - - - - DIRTY - Perform optimistic locking based on *dirty* fields as part of - an expanded WHERE clause restriction for the UPDATE/DELETE SQL statement - - - - -
- -
- Inheritance - - - blah blah blah - -
-
- - - * dynamic models (hbm.xml) - * Map mode - * proxy solutions (hibernate-core/src/test/java/org/hibernate/test/dynamicentity/tuplizer2) - -
\ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Instant.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Instant.java deleted file mode 100644 index b2994b4cd8c1..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Instant.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Thing2 { - @Id - private Integer id; - @Version - private Instant ts; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/SimpleEntity.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/SimpleEntity.java deleted file mode 100644 index 9d3a771fd74a..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/SimpleEntity.java +++ /dev/null @@ -1,13 +0,0 @@ -@Entity -public class Simple { - @Id - private Integer id; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/SimpleEntityWithTable.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/SimpleEntityWithTable.java deleted file mode 100644 index c033f4822942..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/SimpleEntityWithTable.java +++ /dev/null @@ -1,14 +0,0 @@ -@Entity -@Table( catalog="CRM", schema="purchasing", name="t_simple" ) -public class Simple { - @Id - private Integer id; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Timestamp.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Timestamp.java deleted file mode 100644 index d987f164710c..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Timestamp.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Thing { - @Id - private Integer id; - @Version - Timestamp ts; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Version.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Version.java deleted file mode 100644 index 94bad8cf04e9..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/entity/extras/Version.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Course { - @Id - private Integer id; - @Version - private Integer version; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/Identifiers.xml b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/Identifiers.xml deleted file mode 100644 index 73ff1f8015cd..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/Identifiers.xml +++ /dev/null @@ -1,607 +0,0 @@ - - - - - - Identifiers - - - This chapter discusses the characteristics of entity identifier attributes and modelling - them. - - - - - - Identifiers model the primary key of an entity. They are used to uniquely identify each specific entity. - - - - Hibernate and JPA both make the following assumptions about the corresponding database column(s): - - - - UNIQUE - The values must uniquely identify each row. - - - - - NOT NULL - The values cannot be null. For composite ids, no part can be null. - - - - - IMMUTABLE - The values, once inserted, can never be changed. This is more - a general guide, than a hard-fast rule as opinions vary. JPA defines the behavior of changing the - value of the identifier attribute to be undefined; Hibernate simply does not support that. In cases - where the values for the PK you have chosen will be updated, Hibernate recommends mapping the - mutable value as a natural id, and use a surrogate id for the PK. See . - - - - - - - - Technically the identifier does not have to map to the column(s) physically defined as the entity - table's primary key. They just need to map to column(s) that uniquely identify each row. However - this documentation will continue to use the terms identifier and primary key interchangeably. - - - - - Every entity must define an identifier. For entity inheritance hierarchies, the identifier must be - defined just on the entity that is the root of the hierarchy. - - - - An identifier might be simple (single value) or composite (multiple values). - - -
- Simple identifiers - - Simple identifiers map to a single basic attribute, and are denoted using the - javax.persistence.Id annotation. - - - - According to JPA only the following types should be used as identifier attribute types: - - any Java primitive type - any primitive wrapper type - java.lang.String - java.util.Date (TemporalType#DATE) - java.sql.Date - java.math.BigDecimal - java.math.BigInteger - - Any types used for identifier attributes beyond this list will not be portable. - - - - Values for simple identifiers can be assigned, as we have seen in the examples above. The expectation - for assigned identifier values is that the application assigns (sets them on the entity attribute) prior - to calling save/persist. - - - - Simple assigned identifier - - - - - Values for simple identifiers can be generated. To denote that an identifier attribute is - generated, it is annotated with javax.persistence.GeneratedValue - - - - Simple generated identifier - - - - - Additionally to the type restriction list above, JPA - says that if using generated identifier values (see below) only integer types (short, int, long) will be - portably supported. - - - - The expectation for generated identifier values is that Hibernate will generate the value - when the save/persist occurs. - - - - Identifier value generations strategies are discussed in detail in . - -
- -
- Composite identifiers - - - Composite identifiers correspond to one or more persistent attributes. Here are the rules governing - composite identifiers, as defined by the JPA specification. - - - - The composite identifier must be represented by a "primary key class". The primary key class - may be defined using the javax.persistence.EmbeddedId annotation - (see ) or defined using the - javax.persistence.IdClass annotation (see - ). - - - - - The primary key class must be public and must have a public no-arg constructor. - - - - - The primary key class must be serializable. - - - - - The primary key class must define equals and hashCode methods, consistent with equality for - the underlying database types to which the key is mapped. - - - - - - - - The restriction that a composite identifier has to be represented by a "primary key class" is - a JPA restriction. Hibernate does allow composite identifiers to be defined without a - "primary key class", but use of that modeling technique is deprecated and not discussed here. - - - - - The attributes making up the composition can be either basic, composite, ManyToOne. Note especially - that collections and one-to-ones are never appropriate. - - - -
- Composite identifiers - aggregated (EmbeddedId) - - - Modelling a composite identifier using an EmbeddedId simply means defining an - Embeddable to be a composition for the the one or more attributes making up the - identifier and then exposing an attribute of that Embeddable type on the entity. - - - - Basic EmbeddedId - - - - - As mentioned before, EmbeddedIds can even contain ManyToOne attributes. - - - - EmbeddedId with ManyToOne - - - - - - Hibernate supports directly modeling the ManyToOne in the PK class, whether EmbeddedId or IdClass. - However that is not portably supported by the JPA specification. In JPA terms one would - use "derived identifiers"; for details, see . - - -
- -
- Composite identifiers - non-aggregated (IdClass) - - - Modelling a composite identifier using an IdClass differs from using an EmbeddedId in that the entity - defines each individual attribute making up the composition. The IdClass simply acts as a "shadow". - - - - Basic IdClass - - - - - Non-aggregated composite identifiers can also contain ManyToOne attributes as we saw with aggregated - ones (still non-portably) - - - - IdClass with ManyToOne - - - - - With non-aggregated composite identifiers, Hibernate also supports "partial" generation of the - composite values. - - - - IdClass with partial generation - - - - - - This feature exists because of a highly questionable interpretation of the JPA specification - made by the SpecJ committee. Hibernate does not feel that JPA defines support for this, but - added the feature simply to be usable in SpecJ benchmarks. Use of this feature may or may not - be portable from a JPA perspective. - - -
- -
- -
- Generated identifier values - - - - - - For discussion of generated values for non-identifier attributes, see - - - - - Hibernate supports identifier value generation across a number of different types. Remember - that JPA portably defines identifier value generation just for integer types. - - - - Identifier value generation is indicates using the javax.persistence.GeneratedValue - annotation. The most important piece of information here is the specified - javax.persistence.GenerationType which indicates how values will be generated. - - - - - The discussions below assume that the application is using Hibernate's "new generator mappings" as - indicated by the hibernate.id.new_generator_mappings setting or - MetadataBuilder.enableNewIdentifierGeneratorSupport method during bootstrap. - This is set to true by default, however if applications set this to false the resolutions discussed - here will be very different. The rest of the discussion here assumes this setting is enabled (true). - - - - - GenerationTypes - - - AUTO (the default) - Indicates that the persistence provider (Hibernate) should - chose an appropriate generation strategy. See . - - - - - IDENTITY - Indicates that database IDENTITY columns will be used for - primary key value generation. See . - - - - - SEQUENCE - Indicates that database sequence should be used for obtaining - primary key values. See . - - - - - TABLE - Indicates that a database table should be used for obtaining - primary key values. See . - - - - -
- Interpreting AUTO - - - How a persistence provider interprets the AUTO generation type is left up to the provider. Hibernate - interprets it in the following order: - - - - If the given name matches the name for a javax.persistence.SequenceGenerator - annotation -> . - - - - - If the given name matches the name for a javax.persistence.TableGenerator - annotation -> . - - - - - If the given name matches the name for a org.hibernate.annotations.GenericGenerator - annotation -> . - - - - The fallback is to consult with the pluggable org.hibernate.boot.model.IdGeneratorStrategyInterpreter - contract, which is covered in detail in the Hibernate Integrations Guide. The default - behavior is to look at the java type of the identifier attribute: - - - - If it is UUID -> - - - - - Otherwise -> - - - - -
- -
- Using sequences - - - For implementing database sequence-based identifier value generation Hibernate makes use of its - org.hibernate.id.enhanced.SequenceStyleGenerator id generator. It is important - to note that SequenceStyleGenerator is capable of working against databases that do not support sequences - by switching to a table as the underlying backing. This gives Hibernate a huge degree of portability - across databases while still maintaining consistent id generation behavior (versus say choosing - between sequence and IDENTITY). This backing storage is completely transparent to the user. - - - - The preferred (and portable) way to configure this generator is using the JPA-defined - javax.persistence.SequenceGenerator annotation. - - - - The simplest form is to simply request sequence generation; Hibernate will use a single, implicitly-named - sequence (hibernate_sequence) for all such unnamed definitions. - - - - Unnamed sequence - - - - - Or a specifically named sequence can be requested - - - - Named sequence - - - - - Use javax.persistence.SequenceGenerator to specify additional configuration. - - - - Configured sequence - - - - -
- -
- Using IDENTITY columns - - - For implementing identifier value generation based on IDENTITY columns, Hibernate makes use of its - org.hibernate.id.IdentityGenerator id generator which expects the identifier - to generated by INSERT into the table. IdentityGenerator understands 3 different ways that the - INSERT-generated value might be retrieved: - - - - If Hibernate believes the JDBC environment supports java.sql.Statement#getGeneratedKeys, - then that approach will be used for extracting the IDENTITY generated keys. - - - - - Otherwise, if Dialect#supportsInsertSelectIdentity reports - true, Hibernate will use the Dialect specific INSERT+SELECT statement syntax. - - - - - Otherwise, Hibernate will expect that the database supports some form of asking - for the most recently inserted IDENTITY value via a separate SQL command as - indicated by Dialect#getIdentitySelectString - - - - - - - It is important to realize that this imposes a runtime behavior where the entity row *must* be - physically inserted prior to the identifier value being known. This can mess up extended persistence - contexts (conversations). Because of the runtime imposition/inconsistency Hibernate suggest other - forms of identifier value generation be used. - - - - There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not - be able to JDBC batching for inserts of the entities that use IDENTITY generation. The importance - of this depends on the application's specific use cases. If the application is not usually - creating many new instances of a given type of entity that uses IDENTITY generation, then - this is not an important impact since batching would not have been helpful anyway. - -
- -
- Using identifier table - - - Hibernate achieves table-based identifier generation based on its - org.hibernate.id.enhanced.TableGenerator id generator which defines - a table capable of holding multiple named value segments for any number of entities. - - - - Table generator table structure - - - - - The basic idea is that a given table-generator table (hibernate_sequences for example) - can hold multiple segments of identifier generation values. - - - - Unnamed table generator - - - - - If no table name is given Hibernate assumes an implicit name of hibernate_sequences. - Additionally, because no javax.persistence.TableGenerator#pkColumnValue is - specified, Hibernate will use the default segment (sequence_name='default') from the - hibernate_sequences table. - - - -
- -
- Using UUID generation - - - As mentioned above, Hibernate supports UUID identifier value generation. This is supported through its - org.hibernate.id.UUIDGenerator id generator. - - - - UUIDGenerator supports pluggable strategies for exactly how the UUID is generated. These strategies - are defined by the org.hibernate.id.UUIDGenerationStrategy contract. - The default strategy is a version 4 (random) strategy according to IETF RFC 4122. Hibernate does ship - with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using ip address - rather than mac address). - - - - Implicitly using the random UUID strategy - - - - - To specify an alternative generation strategy, we'd have to define some configuration via - @GenericGenerator. Here we choose the RFC 4122 version 1 compliant strategy named - org.hibernate.id.uuid.CustomVersionOneStrategy - - - - Implicitly using the random UUID strategy - - - -
- -
- Using @GenericGenerator - - @GenericGenerator allows integration of any Hibernate org.hibernate.id.IdentifierGenerator - implementation, including any of the specific ones discussed here and any custom ones. - -
- -
- Optimizers - - - Most of the Hibernate generators that separately obtain identifier values from database structures - support the use of pluggable optimizers. Optimizers help manage the number of times Hibernate - has to talk to the database in order to generate identifier values. For example, with no optimizer - applied to a sequence-generator, everytime the application asked Hibernate to generate an identifier - it would need to grab the next sequence value from the database. But if we can minimize the - number of times we need to communicate with the database here, the application will be able to perform - better. Which is in fact the role of these optimizers. - - - - - none - - - No optimization is performed. We communicate with the database each and every time - an identifier value is needed from the generator. - - - - - pooled-lo - - - The pooled-lo optimizer works on the principle that the increment-value is encoded into - the database table/sequence structure. In sequence-terms this means that the sequence - is defined with a greater-that-1 increment size. For example, consider a brand new sequence - defined as create sequence my_sequence start with 1 increment by 20. - This sequence essentially defines a "pool" of 20 usable id values each and every time - we ask it for its next-value. The pooled-lo optimizer interprets the next-value as the - low end of that pool. So when we first ask it for next-value, we'd get 1. We then assume - that the valid pool would be the values from 1-20 inclusive. The next call to - the sequence would result in 21, which would define 21-40 as the valid range. And so on. - The "lo" part of the name indicates that the value from the database table/sequence is - interpreted as the pool lo(w) end. - - - - - pooled - - - Just like pooled-lo, except that here the value from the table/sequence is interpreted - as the high end of the value pool. - - - - - hilo - legacy-hilo - - - Define a custom algorithm for generating pools of values based on a single value from - a table or sequence. These optimizers are not recommended for use. They are maintained - (and mentioned) here simply for use by legacy applications that used these strategies - previously. - - - - - - - Applications can also implement and use their own optimizer strategies, as defined by the - org.hibernate.id.enhanced.Optimizer contract. - -
-
- -
- Derived Identifiers - - - Ugh... - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/ConfiguredSequence.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/ConfiguredSequence.java deleted file mode 100644 index 2e18b380bb63..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/ConfiguredSequence.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class MyEntity { - @Id - @GeneratedValue(generation=SEQUENCE, name="my_sequence") - @SequenceGenerator( name = "my_sequence", schema = "globals", allocationSize = 30 ) - public Integer id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/EmbeddedId1.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/EmbeddedId1.java deleted file mode 100644 index df0cbbef0525..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/EmbeddedId1.java +++ /dev/null @@ -1,13 +0,0 @@ -@Entity -public class Login { - @Embeddable - public static class PK implements Serializable { - private String system; - private String username; - ... - } - - @EmbeddedId - private PK pk; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/EmbeddedId2.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/EmbeddedId2.java deleted file mode 100644 index 6df77ebaab4f..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/EmbeddedId2.java +++ /dev/null @@ -1,14 +0,0 @@ -@Entity -public class Login { - @Embeddable - public static class PK implements Serializable { - @ManyToOne - private System system; - private String username; - ... - } - - @EmbeddedId - private PK pk; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass1.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass1.java deleted file mode 100644 index 8f5ff2507adf..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass1.java +++ /dev/null @@ -1,15 +0,0 @@ -@Entity -@IdClass(PK.class) -public class Login { - public static class PK implements Serializable { - private String system; - private String username; - ... - } - - @Id - private String system; - @Id - private String username; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass2.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass2.java deleted file mode 100644 index b6eb2e59656f..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass2.java +++ /dev/null @@ -1,17 +0,0 @@ -@Entity -@IdClass(PK.class) -public class Login { - public static class PK implements Serializable { - private System system; - private String username; - ... - } - - @Id - @ManyToOne - private System system; - @Id - private String username; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass3.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass3.java deleted file mode 100644 index 50db4a4d5627..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/IdClass3.java +++ /dev/null @@ -1,20 +0,0 @@ -@Entity -@IdClass(PK.class) -public class LogFile { - public static class PK implements Serializable { - private String name; - private LocalDate date; - private Integer uniqueStamp; - ... - } - - @Id - private String name; - @Id - private LocalDate date; - @Id - @GeneratedValue - private Integer uniqueStamp; - ... -} - diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/NamedSequence.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/NamedSequence.java deleted file mode 100644 index 124408034143..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/NamedSequence.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class MyEntity { - @Id - @GeneratedValue(generation=SEQUENCE, name="my_sequence") - public Integer id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/SimpleAssigned.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/SimpleAssigned.java deleted file mode 100644 index 9e390730e94f..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/SimpleAssigned.java +++ /dev/null @@ -1,6 +0,0 @@ -@Entity -public class MyEntity { - @Id - public Integer id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/SimpleGenerated.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/SimpleGenerated.java deleted file mode 100644 index f4c61fcbdb97..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/SimpleGenerated.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class MyEntity { - @Id - @GeneratedValue - public Integer id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/TableGenerator.sql b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/TableGenerator.sql deleted file mode 100644 index d66d067f92a7..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/TableGenerator.sql +++ /dev/null @@ -1,4 +0,0 @@ -create table hibernate_sequences( - sequence_name VARCHAR NOT NULL, - next_val INTEGER NOT NULL -) \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UUIDCustomVersionOneStrategy.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UUIDCustomVersionOneStrategy.java deleted file mode 100644 index 4ac8590be669..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UUIDCustomVersionOneStrategy.java +++ /dev/null @@ -1,17 +0,0 @@ -@Entity -public class MyEntity { - @Id - @GeneratedValue( generator="uuid" ) - @GenericGenerator( - name="uuid", - strategy="org.hibernate.id.UUIDGenerator", - parameters = { - @Parameter( - name="uuid_gen_strategy_class", - value="org.hibernate.id.uuid.CustomVersionOneStrategy" - ) - } - ) - public UUID id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UUIDRandom.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UUIDRandom.java deleted file mode 100644 index 981f8d6f61e9..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UUIDRandom.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class MyEntity { - @Id - @GeneratedValue - public UUID id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UnnamedSequence.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UnnamedSequence.java deleted file mode 100644 index 4efddf38fee8..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UnnamedSequence.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class MyEntity { - @Id - @GeneratedValue(generation=SEQUENCE) - public Integer id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UnnamedTable.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UnnamedTable.java deleted file mode 100644 index 00a40dc97ee6..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/id/extras/UnnamedTable.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class MyEntity { - @Id - @GeneratedValue(generation=TABLE) - public Integer id; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/Natural_Id.xml b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/Natural_Id.xml deleted file mode 100644 index fe4cfab3a211..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/Natural_Id.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - Natural Ids - - - Natural ids represent unique identifiers that naturally exist within your domain model. Even if - a natural id does not make a good primary key, it still is useful to tell Hibernate about it. - As we will see later, Hibernate provides a dedicated, efficient API for loading and entity by its natural-id - much like it offers for loading by identifier (PK). - - -
- Natural Id Mapping - - - Natural ids are defined in terms of one or more persistent attributes. - - - - Natural id using single basic attribute - - - - - Natural id using single embedded attribute - - - - - Natural id using multiple persistent attributes - - - -
- -
- Natural Id API - - - As stated before, Hibernate provides an API for loading entities by natural id. This is represented by the - org.hibernate.NaturalIdLoadAccess contract obtained via - Session#byNaturalId. If the entity does not define a natural id, an exception - will be thrown there. - - - - Using NaturalIdLoadAccess - - - - - NaturalIdLoadAccess offers 2 distinct methods for obtaining the entity: - - - - load - obtains a reference to the entity, making sure - that the entity state is initialized. - - - - - getReference - obtains a reference to the entity. The state - may or may not be initialized. If the entity is associated with the Session already, - that reference (loaded or not) is returned; else if the entity supports proxy - generation, an uninitialized proxy is generated and returned; otherwise - the entity is loaded from the database and returned. - - - - - - - NaturalIdLoadAccess also allows to request locking for the load. We might use that to load an - entity by natural id and at the same time apply a pessimistic lock. For additional details on locking, - see the Hibernate User Guide. - - - - We will discuss the last method available on NaturalIdLoadAccess - (setSynchronizationEnabled) in . - - - - Because the Company and PostalCarrier entities define "simple" natural ids, we also allow simplified - access to load them based on the natural ids. - - - - Using SimpleNaturalIdLoadAccess - - - - - Here we see the use of the org.hibernate.SimpleNaturalIdLoadAccess - contract, obtained via Session#bySimpleNaturalId. SimpleNaturalIdLoadAccess is similar - to NaturalIdLoadAccess except that it does not define the using method. Instead, - because these "simple" natural ids are defined based on just one attribute we can directly pass the - corresponding value of that natural id attribute directly to the load - and getReference methods. If the entity does not define a natural id or if the - natural id it does define is not simple, an exception will be thrown there. - -
- -
- Natural Id - Mutability and Caching - - A natural id may be mutable or immutable. By default @NaturalId marks - an immutable natural id. An immutable natural id is expected to never change values. - If the values of the natural id attribute(s) can change, @NaturalId(mutable=true) - should be used instead. - - - - Mutable natural id - - - - - Within the Session, Hibernate maintains a mapping from natural id values to pk values. If natural ids - values have changed it is possible for this mapping to become out of date until a flush occurs. To work - around this condition, Hibernate will attempt to discover any such pending changes and adjust for them - when the load or getReference method is executed. To - be clear: this is only pertinent for mutable natural ids. - - - - This "discovery and adjustment" have a performance impact. If an application is certain that none of its - mutable natural ids already associated with the Session have changed, it can disable that checking by - calling setSynchronizationEnabled(false) (the default is true). This will force - Hibernate to circumvent the checking of mutable natural ids. - - - - Mutable natural id synchronization use-case - - - - - Not only can this NaturalId-to-PK resolution be cached in the Session, but we can also have it cached in - the second-level cache if second level caching is enabled. - - - - Natural id caching - - - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/MutableNaturalIdMapping.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/MutableNaturalIdMapping.java deleted file mode 100644 index ef4c94375ec0..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/MutableNaturalIdMapping.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Person { - @Id - private Integer id; - @NaturalId(mutable=true) - private String ssn; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/MutableNaturalIdSynchronization.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/MutableNaturalIdSynchronization.java deleted file mode 100644 index d3945140fa40..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/MutableNaturalIdSynchronization.java +++ /dev/null @@ -1,17 +0,0 @@ -Session session = ...; - -Person person = session.bySimpleNaturalId( Person.class ) - .load( "123-45-6789" ); -person.setSsn( "987-65-4321" ); - -... - -// returns null! -person = session.bySimpleNaturalId( Person.class ) - .setSynchronizationEnabled( false ) - .load( "987-65-4321" ); - -// returns correctly! -person = session.bySimpleNaturalId( Person.class ) - .setSynchronizationEnabled( true ) - .load( "987-65-4321" ); \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NaturalIdCaching.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NaturalIdCaching.java deleted file mode 100644 index 5c5fd193c4ee..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NaturalIdCaching.java +++ /dev/null @@ -1,9 +0,0 @@ -@Entity -@NaturalIdCache -public class Company { - @Id - private Integer id; - @NaturalId - private String taxIdentifier; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NaturalIdLoadAccessUsage.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NaturalIdLoadAccessUsage.java deleted file mode 100644 index ab61f73f720f..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NaturalIdLoadAccessUsage.java +++ /dev/null @@ -1,15 +0,0 @@ -Session session = ...; - -Company company = session.byNaturalId( Company.class ) - .using( "taxIdentifier", "abc-123-xyz" ) - .load(); - -PostalCarrier carrier = session.byNaturalId( PostalCarrier.class ) - .using( "postalCode", new PostalCode( ... ) ) - .load(); - -Department department = ...; -Course course = session.byNaturalId( Course.class ) - .using( "department", department ) - .using( "code", "101" ) - .load(); \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NonSimpleNaturalIdMapping.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NonSimpleNaturalIdMapping.java deleted file mode 100644 index 4e5baa6cbb25..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/NonSimpleNaturalIdMapping.java +++ /dev/null @@ -1,11 +0,0 @@ -@Entity -public class Course { - @Id - private Integer id; - @NaturalId - @ManyToOne - private Department department; - @NaturalId - private String code; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleBasicNaturalIdMapping.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleBasicNaturalIdMapping.java deleted file mode 100644 index 6cf78d4f5918..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleBasicNaturalIdMapping.java +++ /dev/null @@ -1,8 +0,0 @@ -@Entity -public class Company { - @Id - private Integer id; - @NaturalId - private String taxIdentifier; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleCompositeNaturalIdMapping.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleCompositeNaturalIdMapping.java deleted file mode 100644 index 204ea5b372b4..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleCompositeNaturalIdMapping.java +++ /dev/null @@ -1,15 +0,0 @@ -@Entity -public class PostalCarrier { - @Id - private Integer id; - @NaturalId - @Embedded - private PostalCode postalCode; - ... - -} - -@Embeddable -public class PostalCode { - ... -} diff --git a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleNaturalIdLoadAccessUsage.java b/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleNaturalIdLoadAccessUsage.java deleted file mode 100644 index 892ee549a6a9..000000000000 --- a/documentation/src/main/docbook/mappingGuide/en-US/chapters/natural_id/extras/SimpleNaturalIdLoadAccessUsage.java +++ /dev/null @@ -1,7 +0,0 @@ -Session session = ...; - -Company company = session.bySimpleNaturalId( Company.class ) - .load( "abc-123-xyz" ); - -PostalCarrier carrier = session.bySimpleNaturalId( PostalCarrier.class ) - .load( new PostalCode( ... ) ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/Bibliography.xml b/documentation/src/main/docbook/userGuide/en-US/Bibliography.xml deleted file mode 100644 index ae2891abb473..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/Bibliography.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - References - - - PoEAA - Patterns of Enterprise Application Architecture - 0-321-12742-0 - - - - Martin - Fowler - - - - - 2003 - Pearson Education, Inc. - - - Addison-Wesley Publishing Company - - - - - JPwH - Java Persistence with Hibernate - Second Edition of Hibernate in Action - 1-932394-88-5 - - - - - - - Christian - Bauer - - - - - Gavin - King - - - - - 2007 - Manning Publications Co. - - - Manning Publications Co. - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/Hibernate_User_Guide.ent b/documentation/src/main/docbook/userGuide/en-US/Hibernate_User_Guide.ent deleted file mode 100644 index 97f377d3e7ba..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/Hibernate_User_Guide.ent +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/Hibernate_User_Guide.xml b/documentation/src/main/docbook/userGuide/en-US/Hibernate_User_Guide.xml deleted file mode 100644 index c73aea9fc3a9..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/Hibernate_User_Guide.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - -%BOOK_ENTITIES; -]> - - - - Hibernate User Guide - Hibernate - Relational Persistence for Idiomatic Java - &version; - Hibernate ORM - &version; - &today; - - - - - - - - - - ©rightYear; - ©rightHolder; - - - - - The Hibernate Team - - - The JBoss Visual Design Team - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/Preface.xml b/documentation/src/main/docbook/userGuide/en-US/Preface.xml deleted file mode 100644 index 7dbae58f4cd7..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/Preface.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - Preface - - - Developing Object-Oriented software that deals with data from Relational Databases can be cumbersome and - resource consuming. Development costs are significantly higher due to a paradigm mismatch between how data is - represented in objects versus relational databases. Hibernate is an Object/Relational Mapping (ORM) solution - for Java environments. ORM refers to the technique of mapping data between an object model representation to - a relational data model representation. See - Wikipedia - for a good high-level discussion. Also, Martin Fowler's - OrmHate article takes a look at many of - the mentioned mismatch problems. - - - - Although having a strong background in SQL is not required to use Hibernate, having a basic understanding of the - concepts can help you understand Hibernate more quickly and fully. An understanding of data modeling principles - is especially important. Both and - are good starting points for understanding these - data modeling principles. - - - - Understanding the basics of transactions and design patterns such as "Unit of Work"PoEAA - or "ApplicationTransaction" are important as well. These topics will be discussed in the documentation, but - a prior understanding will certainly help. - - - - Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to - SQL data types), but also provides data query and retrieval facilities. It can significantly reduce - development time otherwise spent with manual data handling in SQL and JDBC. Hibernate’s design goal is to - relieve the developer from 95% of common data persistence-related programming tasks by eliminating the need for - manual, hand-crafted data processing using SQL and JDBC. However, unlike many other persistence solutions, - Hibernate does not hide the power of SQL from you and guarantees that your investment in relational technology - and knowledge is as valid as always. - - - - Hibernate may not be the best solution for data-centric applications that only use stored-procedures to - implement the business logic in the database, it is most useful with object-oriented domain models and business - logic in the Java-based middle-tier. However, Hibernate can certainly help you to remove or encapsulate - vendor-specific SQL code and will help with the common task of result set translation from a tabular - representation to a graph of objects. - - - - See for information on getting involved. - - - - - If you are just getting started with using Hibernate you may want to start with the - Hibernate Getting Started Guide available from the - documentation page. It contains quick-start - style tutorials as well as lots of introductory information. There is also a series of topical guides - providing deep dives into various topics. - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/appendices/Legacy_Bootstrap.xml b/documentation/src/main/docbook/userGuide/en-US/appendices/Legacy_Bootstrap.xml deleted file mode 100644 index 1cc2b6ffa91a..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/appendices/Legacy_Bootstrap.xml +++ /dev/null @@ -1,188 +0,0 @@ - - - Legacy Bootstrapping - - - The legacy way to bootstrap a SessionFactory is via the org.hibernate.cfg.Configuration - object. Configuration represents, essentially, a single point for specifying all aspects of building - the SessionFactory: everything from settings, to mappings, to strategies, etc. I like to think of - Configuration as a big pot to which we add a bunch of stuff (mappings, settings, etc) and from which - we eventually get a SessionFactory. - - - - - There are some significant draw backs to this approach which led to its deprecation and the development - of the new approach, which is discussed in . Configuration is - semi-deprecated but still available for use, in a limited form that eliminates these draw backs. - "Under the covers", Configuration uses the new bootstrapping code, so the things available there as also - available here in terms of auto-discovery. - - - - - You can obtain the Configuration by instantiating it directly. You then specify mapping metadata (XML - mapping documents, annotated classes) that describe your applications object model and its mapping to a - SQL database. - - - - Configuration usage - Configuration cfg = new Configuration() - // addResource does a classpath resource lookup - .addResource("Item.hbm.xml") - .addResource("Bid.hbm.xml") - - // calls addResource using "/org/hibernate/auction/User.hbm.xml" - .addClass(org.hibernate.auction.User.class) - - // parses Address class for mapping annotations - .addAnnotatedClass( Address.class ) - - // reads package-level (package-info.class) annotations in the named package - .addPackage( "org.hibernate.auction" ) - - .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") - .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test") - .setProperty("hibernate.order_updates", "true"); - - - - There are other ways to specify Configuration information, including: - - - Place a file named hibernate.properties in a root directory of the classpath - - - Pass an instance of java.util.Properties to Configuration#setProperties - - - Via a Hibernate cfg.xml file - - - System properties using java `-Dproperty=value` - - - - -
- Migration - - - Mapping Configuration methods to the corresponding methods in the new APIs.. - - - - Mapping metadata - - Configuration#addFile - MetadataSources#addFile - - - Configuration#add(XmlDocument) - No replacement - - - Configuration#addXML - No replacement - - - Configuration#addCacheableFile - MetadataSources#addCacheableFile - - - Configuration#addURL - MetadataSources#addURL - - - Configuration#addInputStream - MetadataSources#addInputStream - - - Configuration#addResource - MetadataSources#addResource - - - Configuration#addClass - MetadataSources#addClass - - - Configuration#addAnnotatedClass - MetadataSources#addAnnotatedClass - - - Configuration#addPackage - MetadataSources#addPackage - - - Configuration#addJar - MetadataSources#addJar - - - Configuration#addDirectory - MetadataSources#addDirectory - - - Configuration#registerTypeContributor - MetadataBuilder#applyTypes - - - Configuration#registerTypeOverride - MetadataBuilder#applyBasicType - - - - - Settings - - Configuration#setProperty - StandardServiceRegistryBuilder#applySetting - - - Configuration#setProperties - No replacement - - - Configuration#addProperties - StandardServiceRegistryBuilder#applySettings - - - Configuration#setNamingStrategy - No replacement. NamingStrategy split into implicit/physical strategies - - - Configuration#setImplicitNamingStrategy - MetadataBuilder#setImplicitNamingStrategy - - - Configuration#setPhysicalNamingStrategy - MetadataBuilder#setPhysicalNamingStrategy - - - Configuration#configure - StandardServiceRegistryBuilder#configure - - - Configuration#setInterceptor - SessionFactoryBuilder#applyInterceptor - - - Configuration#setEntityNotFoundDelegate - SessionFactoryBuilder#applyEntityNotFoundDelegate - - - Configuration#setSessionFactoryObserver - SessionFactoryBuilder#addSessionFactoryObservers - - - Configuration#setCurrentTenantIdentifierResolver - SessionFactoryBuilder#applyCurrentTenantIdentifierResolver - - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/appendices/Legacy_Criteria.xml b/documentation/src/main/docbook/userGuide/en-US/appendices/Legacy_Criteria.xml deleted file mode 100644 index 374dc6ba7ae4..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/appendices/Legacy_Criteria.xml +++ /dev/null @@ -1,549 +0,0 @@ - - - - - - Legacy Hibernate Criteria Queries - - - - - This appendix covers the legacy Hibernate org.hibernate.Criteria API, which - should be considered deprecated. New development should focus on the JPA - javax.persistence.criteria.CriteriaQuery API. Eventually, - Hibernate-specific criteria features will be ported as extensions to the JPA - javax.persistence.criteria.CriteriaQuery. For details on the JPA APIs, see - . - - - This information is copied as-is from the older Hibernate documentation. - - - - - Hibernate features an intuitive, extensible criteria query API. - - -
- Creating a <literal>Criteria</literal> instance - - - The interface org.hibernate.Criteria represents a query against - a particular persistent class. The Session is a factory for - Criteria instances. - - - - -
- -
- Narrowing the result set - - - An individual query criterion is an instance of the interface - org.hibernate.criterion.Criterion. The class - org.hibernate.criterion.Restrictions defines - factory methods for obtaining certain built-in - Criterion types. - - - - - - Restrictions can be grouped logically. - - - - - - - - There are a range of built-in criterion types (Restrictions - subclasses). One of the most useful allows you to specify SQL directly. - - - - - - The {alias} placeholder will be replaced by the row alias - of the queried entity. - - - - You can also obtain a criterion from a - Property instance. You can create a Property - by calling Property.forName(): - - - - -
- -
- Ordering the results - - - You can order the results using org.hibernate.criterion.Order. - - - - - - -
- -
- Associations - - - By navigating - associations using createCriteria() you can specify constraints upon related entities: - - - - - - The second createCriteria() returns a new - instance of Criteria that refers to the elements of - the kittens collection. - - - - There is also an alternate form that is useful in certain circumstances: - - - - - - (createAlias() does not create a new instance of - Criteria.) - - - - The kittens collections held by the Cat instances - returned by the previous two queries are not pre-filtered - by the criteria. If you want to retrieve just the kittens that match the - criteria, you must use a ResultTransformer. - - - - - - Additionally you may manipulate the result set using a left outer join: - - - - - This will return all of the Cats with a mate whose name starts with "good" - ordered by their mate's age, and all cats who do not have a mate. - This is useful when there is a need to order or limit in the database - prior to returning complex/large result sets, and removes many instances where - multiple queries would have to be performed and the results unioned - by java in memory. - - - Without this feature, first all of the cats without a mate would need to be loaded in one query. - - - A second query would need to retreive the cats with mates who's name started with "good" sorted by the mates age. - - - Thirdly, in memory; the lists would need to be joined manually. - -
- -
- Dynamic association fetching - - - You can specify association fetching semantics at runtime using - setFetchMode(). - - - - - - This query will fetch both mate and kittens - by outer join. - - -
- -
- Components - - - To add a restriction against a property of an embedded component, the component property - name should be prepended to the property name when creating the Restriction. - The criteria object should be created on the owning entity, and cannot be created on the component - itself. For example, suppose the Cat has a component property fullName - with sub-properties firstName and lastName: - - - - - - - Note: this does not apply when querying collections of components, for that see below - - - -
- -
- Collections - - When using criteria against collections, there are two distinct cases. One is if - the collection contains entities (eg. <one-to-many/> - or <many-to-many/>) or components - (<composite-element/> ), - and the second is if the collection contains scalar values - (<element/>). - In the first case, the syntax is as given above in the section - where we restrict the kittens - collection. Essentially we create a Criteria object against the collection - property and restrict the entity or component properties using that instance. - - - For queryng a collection of basic values, we still create the Criteria - object against the collection, but to reference the value, we use the special property - "elements". For an indexed collection, we can also reference the index property using - the special property "indices". - - - -
- -
- Example queries - - - The class org.hibernate.criterion.Example allows - you to construct a query criterion from a given instance. - - - - - - Version properties, identifiers and associations are ignored. By default, - null valued properties are excluded. - - - - You can adjust how the Example is applied. - - - - - - You can even use examples to place criteria upon associated objects. - - - - -
- -
- Projections, aggregation and grouping - - The class org.hibernate.criterion.Projections is a - factory for Projection instances. You can apply a - projection to a query by calling setProjection(). - - - - - - - - There is no explicit "group by" necessary in a criteria query. Certain - projection types are defined to be grouping projections, - which also appear in the SQL group by clause. - - - - An alias can be assigned to a projection so that the projected value - can be referred to in restrictions or orderings. Here are two different ways to - do this: - - - - - - - - The alias() and as() methods simply wrap a - projection instance in another, aliased, instance of Projection. - As a shortcut, you can assign an alias when you add the projection to a - projection list: - - - - - - - - You can also use Property.forName() to express projections: - - - - - - -
- -
- Detached queries and subqueries - - The DetachedCriteria class allows you to create a query outside the scope - of a session and then execute it using an arbitrary Session. - - - - - - A DetachedCriteria can also be used to express a subquery. Criterion - instances involving subqueries can be obtained via Subqueries or - Property. - - - - - - - - Correlated subqueries are also possible: - - - - - - Example of multi-column restriction based on a subquery: - - - - -
- - - -
- Queries by natural identifier - - - For most queries, including criteria queries, the query cache is not efficient - because query cache invalidation occurs too frequently. However, there is a special - kind of query where you can optimize the cache invalidation algorithm: lookups by a - constant natural key. In some applications, this kind of query occurs frequently. - The criteria API provides special provision for this use case. - - - - First, map the natural key of your entity using - <natural-id> and enable use of the second-level cache. - - - - - - - - - - - - -]]> - - - This functionality is not intended for use with entities with - mutable natural keys. - - - - Once you have enabled the Hibernate query cache, - the Restrictions.naturalId() allows you to make use of - the more efficient cache algorithm. - - - - -
- -
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/architecture/Architecture.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/architecture/Architecture.xml deleted file mode 100644 index a8a348f6ced8..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/architecture/Architecture.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - Architecture - -
- Overview - - - - - - - - - - - - Hibernate, as an ORM solution, effectively "sits between" the Java application and the Relational - Database, as can be seen in the diagram above. The Java application makes use of the Hibernate APIs - to load, store, query, etc its domain data. Here we will introduce the essential Hibernate APIs. - This will be a brief introduction; we will discuss these contracts in detail later. - - - - SessionFactory (org.hibernate.SessionFactory) - - - A thread-safe (and immutable) representation of the mapping of the application - domain model to a database. Acts as a factory for - org.hibernate.Session instances. - - - A SessionFactory is very expensive to create; there should be only - one SessionFactory for an application for a given database. Maintains - services that Hibernate uses across all Sessions such as second level caches, - connection pools, transaction system integrations, etc. - - - - - Session (org.hibernate.Session) - - - A single-threaded, short-lived object conceptually modeling a - "Unit of Work"PoEAA. - - - Wraps a JDBC java.sql.Connection. Acts as a factory for - org.hibernate.Transaction instances. Maintains a - generally "repeatable read" persistence context (first level cache) of the application's - domain model. - - - - - Transaction (org.hibernate.Transaction) - - - A single-threaded, short-lived object used by the application to demarcate individual - physical transaction boundaries. It acts as an abstraction API to isolate the application - from the underling transaction system in use (JDBC, JTA, CORBA, etc). - - - - - -
- -
- Contextual sessions - - Most applications using Hibernate need some form of "contextual" session, where a given - session is in effect throughout the scope of a given context. However, across applications - the definition of what constitutes a context is typically different; different contexts - define different scopes to the notion of current. Applications using Hibernate prior - to version 3.0 tended to utilize either home-grown ThreadLocal-based - contextual sessions, helper classes such as HibernateUtil, or utilized - third-party frameworks, such as Spring or Pico, which provided proxy/interception-based contextual sessions. - - - Starting with version 3.0.1, Hibernate added the SessionFactory.getCurrentSession() - method. Initially, this assumed usage of JTA transactions, where the - JTA transaction defined both the scope and context of a current session. - Given the maturity of the numerous stand-alone - JTA TransactionManager implementations, most, if not all, - applications should be using JTA transaction management, whether or not - they are deployed into a J2EE container. Based on that, the - JTA-based contextual sessions are all you need to use. - - - However, as of version 3.1, the processing behind - SessionFactory.getCurrentSession() is now pluggable. To that - end, a new extension interface, org.hibernate.context.spi.CurrentSessionContext, - and a new configuration parameter, hibernate.current_session_context_class, - have been added to allow pluggability of the scope and context of defining current sessions. - - - See the Javadocs for the org.hibernate.context.spi.CurrentSessionContext - interface for a detailed discussion of its contract. It defines a single method, - currentSession(), by which the implementation is responsible for - tracking the current contextual session. Out-of-the-box, Hibernate comes with three - implementations of this interface: - - - - - - org.hibernate.context.internal.JTASessionContext: current sessions - are tracked and scoped by a JTA transaction. The processing - here is exactly the same as in the older JTA-only approach. See the Javadocs - for details. - - - - - org.hibernate.context.internal.ThreadLocalSessionContext:current - sessions are tracked by thread of execution. See the Javadocs for details. - - - - - org.hibernate.context.internal.ManagedSessionContext: current - sessions are tracked by thread of execution. However, you are responsible to - bind and unbind a Session instance with static methods - on this class: it does not open, flush, or close a Session. - - - - - - Typically, the value of this parameter would just name the implementation class to - use. For the three out-of-the-box implementations, however, there are three corresponding - short names: "jta", "thread", and "managed". - - - - The first two implementations provide a "one session - one database transaction" programming - model. This is also known and used as session-per-request. The beginning - and end of a Hibernate session is defined by the duration of a database transaction. - If you use programmatic transaction demarcation in plain JSE without JTA, you are advised to - use the Hibernate Transaction API to hide the underlying transaction system - from your code. If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you - execute in an EJB container that supports CMT, transaction boundaries are defined declaratively - and you do not need any transaction or session demarcation operations in your code. - Refer to for more information and code examples. - - - - The hibernate.current_session_context_class configuration parameter - defines which org.hibernate.context.spi.CurrentSessionContext implementation - should be used. For backwards compatibility, if this configuration parameter is not set - but a org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform is configured, - Hibernate will use the org.hibernate.context.internal.JTASessionContext. - - -
- -
- diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/batch/Batching.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/batch/Batching.xml deleted file mode 100644 index a7412f9ff5a2..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/batch/Batching.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - Batching - - - First we need to decide what all to discuss here as that phrase has so many connotations. Do we cover - - JDBC batch updates? - Session w/ incremental flushing? - StatelessSession? - Java EE batching? - Any/all of the above? - Others? - - - -
- JDBC batching - - - JDBC offers support for batching together SQL statements that can be represented - as a single PreparedStatement. Implementation wise this generally means that drivers - will send the batched operation to the server in one call, which can save on network calls - to the database. Hibernate can leverage JDBC batching. The following settings control this - behavior. - - - - - - hibernate.jdbc.batch_size - Controls the maximum number of - statements Hibernate will batch together before asking the driver to execute - the batch. Zero or a negative number disables this feature. - - - - - hibernate.jdbc.batch_versioned_data - Some JDBC drivers - return incorrect row counts when a batch is executed. If your JDBC driver - falls into this category this setting should be set to false. - Otherwise it is safe to enable this which will allow Hibernate to still - batch the DML for versioned entities and still use the returned row counts for - optimitic lock checks. Currently defaults to false to be safe. - - - - - hibernate.jdbc.batch.builder - Names the implementation class - used to manage batching capabilities. It is almost never a good idea to switch from - Hibernate's default implementation. But if you wish to, this setting would name the - org.hibernate.engine.jdbc.batch.spi.BatchBuilder - implementation to use. - - - - - hibernate.order_updates - Forces Hibernate to order SQL updates by the - entity type and the primary key value of the items being updated. This allows for more batching - to be used. It will also result in fewer transaction deadlocks in highly concurrent systems. - Comes with a performance hit, so benchmark before and after to see if this actually helps or - hurts your application. - - - - - hibernate.order_inserts - Forces Hibernate to order inserts to allow for - more batching to be used. Comes with a performance hit, so benchmark before and after to see - if this actually helps or hurts your application. - - - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/Bootstrap.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/Bootstrap.xml deleted file mode 100644 index cdfafd306b44..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/Bootstrap.xml +++ /dev/null @@ -1,271 +0,0 @@ - - - - Bootstrap - - - The term bootstrapping refers to initializing and starting a software - component. In Hibernate we are specifically talking about the process of building a fully functional - SessionFactory instance or EntityManagerFactory instance for JPA. The process is very different for - each. - - - - - This chapter will not focus on all the possibilities of bootstrapping. Those will be covered - in each specific more-relevant chapters later on. Instead we focus here on the API calls needed - to perform the bootstrapping. - - - - - -
- Native Bootstrapping - - - This section discusses the process of bootstrapping a Hibernate SessionFactory. Specifically it discusses - the bootstrapping APIs as redesigned in 5.0. For a discussion of the legacy bootstrapping API, see - - - -
- Building the ServiceRegistry - - The first step in native bootstrapping is the building of a ServiceRegistry holding the - services Hibernate will need at bootstrap and run time. - - - - - - Actually we are concerned with building 2 different ServiceRegistries. First is the - org.hibernate.boot.registry.BootstrapServiceRegistry. The - BootstrapServiceRegistry is intended to hold services that Hibernate needs at both bootstrap and - run time. This boils down to 3 services: - - - - org.hibernate.boot.registry.classloading.spi.ClassLoaderService - - which controls how Hibernate interacts with ClassLoaders - - - - - org.hibernate.integrator.spi.IntegratorService - - which controls the management ands discovery of - org.hibernate.integrator.spi.Integrator instances. - - - - - org.hibernate.boot.registry.selector.spi.StrategySelector - - which control how Hibernate resolves implementations of various strategy contracts. This - is a very powerful service, but a full discussion of it is beyond the scope of this guide. - - - - - - - If you are ok with the default behavior of Hibernate in regards to these BootstrapServiceRegistry - services (which is quite often the case, especially in SE environments), then building the - BootstrapServiceRegistry can be skipped. - - - - If you wish to alter how the BootstrapServiceRegistry is built, that is controlled through the - org.hibernate.boot.registry.BootstrapServiceRegistryBuilder: - - - - Controlling BootstrapServiceRegistry building - - - - - The services of the BootstrapServiceRegistry cannot be extended (added to) nor overridden (replaced). - - - - The second ServiceRegistry is the org.hibernate.boot.registry.StandardServiceRegistry. - You will almost always need to configure the StandardServiceRegistry, which is done through - org.hibernate.boot.registry.StandardServiceRegistryBuilder: - - - - Building a BootstrapServiceRegistryBuilder - - - - - - A StandardServiceRegistry is also highly configurable via the StandardServiceRegistryBuilder API. - See the StandardServiceRegistryBuilder javadocs for full details. Some specific methods of interest: - - - - Controlling StandardServiceRegistry building - - -
- -
- Building the Metadata - - The second step in native bootstrapping is the building of a org.hibernate.boot.Metadata - object containing the parsed representations of an application's domain model and its mapping to - a database. The first thing we obviously need to build a parsed representation is the source - information to be parsed (annotated classes, `hbm.xml` files, `orm.xml` files). This is - the purpose of org.hibernate.boot.MetadataSources: - - - - Configuring a MetadataSources - - - - - MetadataSources has many other methods as well; explore its API and javadocs for more information. - Also, all methods on MetadataSources allow for chaining should you prefer that style: - - - - Configuring a MetadataSources with method chaining - - - - - Once we have the sources of mapping information defined, we need to build the Metadata object. If - you are ok with the default behavior in building the Metadata then you can simply call - MetadataSources#buildMetadata. - - - - - Notice that a ServiceRegistry can be passed at a number of points in this bootstrapping process. - The suggested approach is to build a StandardServiceRegistry yourself and pass that along to the - MetadataSources constructor. From there, MetadataBuilder, Metadata, SessionFactoryBuilder and - SessionFactory will all pick up that same StandardServiceRegistry. - - - - - However, if you wish to adjust the process of building Metadata from MetadataSources you will need - to use the MetadataBuilder as obtained via MetadataSources#getMetadataBuilder. MetadataBuilder - allows a lot of control over the Metadata building process. See its javadocs for full details. - - - - Building Metadata via MetadataBuilder - - -
- -
- Building the SessionFactory - - The final step in native bootstrapping is to build the SessionFactory itself. Much like - discussed above, if you are ok with the default behavior of building a SessionFactory from a Metadata - reference, you can simply call Metadata#buildSessionFactory. - - - - However, if you would like to adjust that building process you will need to use - SessionFactoryBuilder as obtained via Metadata#getSessionFactoryBuilder. Again, see its - javadocs for full details. - - - - Building SessionFactory via SessionFactoryBuilder - - - - - The bootstrapping API is quite flexible, but in most cases it makes the most sense to think of - it as a 3 step process: - - - Build the StandardServiceRegistry - - - Build the Metadata - - - Use those 2 things to build the SessionFactory - - - - - - Native Bootstrapping - Putting it all together - - -
-
- -
- JPA Bootstrapping - - - Bootstrapping Hibernate as a JPA provider can be done in a JPA-spec compliant manner or using a proprietary - bootstrapping approach. The standardized approach has some limitations in certain environments, but aside - from those limitations, it is *highly* recommended that you use JPA-standardized bootstrapping. - - - - -
- JPA-compliant bootstrapping - - - In JPA we are ultimately interested in bootstrapping an javax.persistence.EntityManagerFactory instance. - The JPA specification defines 2 primary standardized bootstrap approaches depending on how the - application intends to access the javax.persistence.EntityManager instances from an - EntityManagerFactory. It uses the terms "EE" and "SE" for these 2 approaches, but those terms are very - misleading in this context. What the JPA spec calls EE bootstrapping is cases where a container - (EE, OSGi, etc) will manage and inject the persistence context on behalf of the application. - What it calls SE bootstrapping is everything else. We will use the terms - container-bootstrapping and application-bootstrapping in this guide. - - - - - If you would like additional details on accessing and using EntityManager instances, sections 7.6 - and 7.7 of the JPA 2.1 specification cover container-managed and application-managed EntityManagers, - respectively. - - - - - For compliant container-bootstrapping, the container will build an EntityManagerFactory for each - persistent-unit defined in the deployment's META-INF/persistence.xml and make that available to the - application for injection via the javax.persistence.PersistenceUnit annotation or via JNDI lookup. - - - - Injecting a EntityManagerFactory - - - - - For compliant application-bootstrapping, rather than the container building the - EntityManagerFactory for the application, the application builds the EntityManagerFactory itself - using the javax.persistence.Persistence bootstrap class. The application creates an entity manager - factory by calling the createEntityManagerFactory method: - - - - Application bootstrapped EntityManagerFactory - - -
-
- Proprietary 2-phase bootstrapping - - - todo - -
-
-
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/jpa1.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/jpa1.java deleted file mode 100644 index 44f16f8368dc..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/jpa1.java +++ /dev/null @@ -1,2 +0,0 @@ -@PersistenceUnit -EntityManagerFactory emf; \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/jpa2.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/jpa2.java deleted file mode 100644 index fcbe40e04d4d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/jpa2.java +++ /dev/null @@ -1,2 +0,0 @@ -// Create an EMF for our CRM persistence-unit. -EntityManagerFactory emf = Persistence.createEntityManagerFactory("CRM"); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native1.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native1.java deleted file mode 100644 index 80b5a935c8dc..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native1.java +++ /dev/null @@ -1,10 +0,0 @@ -BootstrapServiceRegistryBuilder bootstrapRegistryBuilder - = new BootstrapServiceRegistryBuilder(); - -// add a special ClassLoader -bootstrapRegistryBuilder.applyClassLoader( mySpecialClassLoader ); -// manually add an Integrator -bootstrapRegistryBuilder.applyIntegrator( mySpecialIntegrator ); -... - -BootstrapServiceRegistry bootstrapRegistry = bootstrapRegistryBuilder.build(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native2.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native2.java deleted file mode 100644 index 9f40d190fda8..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native2.java +++ /dev/null @@ -1,3 +0,0 @@ -// An example using an implicitly built BootstrapServiceRegistry -StandardServiceRegistryBuilder standardRegistryBuilder - = new StandardServiceRegistryBuilder(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native3.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native3.java deleted file mode 100644 index fa809178ad7f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native3.java +++ /dev/null @@ -1,5 +0,0 @@ -// An example using an explicitly built BootstrapServiceRegistry -BootstrapServiceRegistry bootstrapRegistry = ...; - -StandardServiceRegistryBuilder standardRegistryBuilder - = new StandardServiceRegistryBuilder( bootstrapRegistry ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native4.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native4.java deleted file mode 100644 index a42c98f472da..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native4.java +++ /dev/null @@ -1,19 +0,0 @@ -StandardServiceRegistryBuilder standardRegistryBuilder = ...; - -// load some properties via resource lookup -standardRegistryBuilder.loadProperties( "org/hibernate/example/MyProperties.properties" ); - -// configure the registry from a resource lookup for a cfg.xml config file -standardRegistryBuilder.configure( "org/hibernate/example/my.cfg.xml" ); - -// apply a random setting -standardRegistryBuilder.applySetting( "myProp", "some value" ); - -// apply a service initiator -standardRegistryBuilder.addInitiator( new CustomServiceInitiator() ); - -// apply a service impl -standardRegistryBuilder.addService( SomeCustomService.class, new SomeCustomServiceImpl() ); - -// and finally build the StandardServiceRegistry -StandardServiceRegistry standardRegistry = standardRegistryBuilder.build(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native5.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native5.java deleted file mode 100644 index e48287c9c3fd..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native5.java +++ /dev/null @@ -1,23 +0,0 @@ -MetadataSources sources = new MetadataSources( standardRegistry ); - -// alternatively, we can build the MetadataSources without passing -// a service registry, in which case it will build a default -// BootstrapServiceRegistry to use. But the approach shown -// above is preferred -// MetadataSources sources = new MetadataSources(); - -// add a class using JPA/Hibernate annotations for mapping -sources.addAnnotatedClass( MyEntity.class ); - -// add the name of a class using JPA/Hibernate annotations for mapping. -// differs from above in that accessing the Class is deferred which is -// important if using runtime bytecode-enhancement -sources.addAnnotatedClassName( "org.hibernate.example.Customer" ); - -// Adds the named hbm.xml resource as a source: which performs the -// classpath lookup and parses the XML -sources.addResource( "org/hibernate/example/Order.hbm.xml" ); - -// Adds the named JPA orm.xml resource as a source: which performs the -// classpath lookup and parses the XML -sources.addResource( "org/hibernate/example/Product.orm.xml" ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native6.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native6.java deleted file mode 100644 index f9d30b7504db..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native6.java +++ /dev/null @@ -1,5 +0,0 @@ -MetadataSources sources = new MetadataSources( standardRegistry ) - .addAnnotatedClass( MyEntity.class ) - .addAnnotatedClassName( "org.hibernate.example.Customer" ) - .addResource( "org/hibernate/example/Order.hbm.xml" ) - .addResource( "org/hibernate/example/Product.orm.xml" ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native7.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native7.java deleted file mode 100644 index 13c2fe238133..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native7.java +++ /dev/null @@ -1,9 +0,0 @@ -MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); - -// Use the JPA-compliant implicit naming strategy -metadataBuilder.applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ); - -// specify the schema name to use for tables, etc when none is explicitly specified -metadataBuilder.applyImplicitSchemaName( "my_default_schema" ); - -Metadata metadata = metadataBuilder.build(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native8.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native8.java deleted file mode 100644 index 92bc6391597f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native8.java +++ /dev/null @@ -1,12 +0,0 @@ -SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder(); - -// Supply an SessionFactory-level Interceptor -sessionFactoryBuilder.applyInterceptor( new MySessionFactoryInterceptor() ); - -// Add a custom observer -sessionFactoryBuilder.addSessionFactoryObservers( new MySessionFactoryObserver() ); - -// Apply a CDI BeanManager (for JPA event listeners) -sessionFactoryBuilder.applyBeanManager( getBeanManagerFromSomewhere() ); - -SessionFactory sessionFactory = sessionFactoryBuilder.build(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native9.java b/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native9.java deleted file mode 100644 index fc7b53fab5e6..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/bootstrap/extras/native9.java +++ /dev/null @@ -1,16 +0,0 @@ -StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder() - .configure( "org/hibernate/example/MyCfg.xml" ) - .build(); - -Metadata metadata = new MetadataSources( standardRegistry ) - .addAnnotatedClass( MyEntity.class ) - .addAnnotatedClassName( "org.hibernate.example.Customer" ) - .addResource( "org/hibernate/example/Order.hbm.xml" ) - .addResource( "org/hibernate/example/Product.orm.xml" ) - .getMetadataBuilder() - .applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ) - .build(); - -SessionFactory sessionFactory = metadata.getSessionFactoryBuilder() - .applyBeanManager( getBeanManagerFromSomewhere() ) - .build(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/caching/Caching.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/caching/Caching.xml deleted file mode 100644 index a2ffbcd49466..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/caching/Caching.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - Caching - -
- Configuring second-level caching - - - Hibernate defines the ability to integrate with pluggable providers for the purpose of - caching data outside the context of a particular Session. This section defines - the settings which control that behavior. - - -
- RegionFactory - - - org.hibernate.cache.spi.RegionFactory defines the integration - between Hibernate and a pluggable caching provider. hibernate.cache.region.factory_class - is used to declare the provider to use. Hibernate comes with support for 2 popular caching - libraries: Ehcache and Infinispan. - - -
- Ehcache - - - Use of the build-in integration for Ehcache requires that the hibernate-ehcache module - jar (and all of its dependencies) are on the classpath. - - - - The hibernate-ehcache module defines 2 specific providers: - - - - - ehcache - todo : document - - - - - - ehcache-singleton - todo : document - - - - -
- -
- Infinispan - - - Use of the build-in integration for Infinispan requires that the hibernate-infinispan module - jar (and all of its dependencies) are on the classpath. - - - - The hibernate-infinispan module defines 2 specific providers: - - - - - infinispan - todo : document - - - - - - infinispan-jndi - todo : document - - - - -
-
- -
- Caching behavior - - - Besides specific provider configuration, there are a number of configurations options on the - Hibernate side of the integration that control various caching behavior: - - - - hibernate.cache.use_second_level_cache - Enable or disable - second level caching overall. Default is true. - - - - hibernate.cache.use_query_cache - Enable or disable second level - caching of query results. Default is false. - - - hibernate.cache.query_cache_factory - Query result caching is - handled by a special contract that deals with staleness-based invalidation of the results. - The default implementation does not allow stale results at all. Use this for applications - that would like to relax that. Names an implementation of - org.hibernate.cache.spi.QueryCacheFactory - - - hibernate.cache.use_minimal_puts - Optimizes second-level cache - operations to minimize writes, at the cost of more frequent reads. Providers typically - set this appropriately. - - - hibernate.cache.region_prefix - Defines a name to be used as a prefix to - all second-level cache region names. - - - hibernate.cache.default_cache_concurrency_strategy - In Hibernate - second-level caching, all regions can be configured differently including the concurrency - strategy to use when accessing the region. This setting allows to define a default strategy to - be used. This setting is very rarely required as the pluggable providers do specify the - default strategy to use. Valid values include: read-only, - read-write, nonstrict-read-write, - transactional. - - - hibernate.cache.use_structured_entries - If true, - forces Hibernate to store data in the second-level cache in a more human-friendly format. - Can be useful if you'd like to be able to "browse" the data directly in your cache, but does - have a performance impact. - - - hibernate.cache.auto_evict_collection_cache - Enables or disables the - automatic eviction of a bi-directional association's collection cache entry when the association - is changed just from the owning side. This is disabled by default, as it has a performance - impact to track this state. However if your application does not manage both sides - of bi-directional association where the collection side is cached, the alternative is to - have stale data in that collection cache. - - - -
-
- -
- Managing the Cached Data - - - At runtime Hibernate handles moving data into and out of the second-level cache - in response to the operations performed by the Session. - - - - - - The org.hibernate.Cache interface (or the javax.persistence.Cache - interface if using JPA) allow to clear data from the second-level cache. - -
-
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/DomainModel.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/DomainModel.xml deleted file mode 100644 index 3d86a4357094..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/DomainModel.xml +++ /dev/null @@ -1,320 +0,0 @@ - - - - Domain Model - - - The term domain model - comes from the realm of data modeling. It is the model that ultimately - describes the problem domain - you are working in. Sometimes you will also hear the term "persistent classes". - - - - Ultimately the application's domain model is the central character in an ORM. They make up the classes - you wish to map. Hibernate works best if these classes follow the Plain Old Java Object (POJO) / JavaBean - programming model. However, none of these rules are hard requirements. Indeed, Hibernate assumes very little - about the nature of your persistent objects. You can express a domain model in other ways (using trees of - java.util.Map instances, for example). - - - - - Even though Hibernate does not consider all of these rules as hard requirements, JPA does specify - some of them. Therefore, if you are concerned about JPA provider portability it is best to - stick to the strict POJO model. We will point out these concerns where applicable. - - - - - This chapter will describe the characteristics of a persistable domain model. However, it will not discuss - defining the mapping for the domain model. That is a massive topic in its own right and is the subject of an - entire dedicated manual. See the Hibernate Domain Model Mapping Guide from the - documentation site. - - -
- POJO Domain Models - - - This section explores domain models defined as POJOs. - - -
- Implement a no-argument constructor - - - The POJO should have a no-argument constructor. Both Hibernate and JPA require this. - - - - JPA requires that this constructor be defined as public or protected. Hibernate for the most part does - note care about the visibility as long as the system's SecurityManager allows overriding the visibility. - That said, the constructor should be defined with at least package visibility if you wish to leverage - runtime proxy generation. - -
- - -
- Provide identifier attribute(s) - - - - Historically this was considered optional. However, not defining identifier attribute(s) on the - entity should be considered a deprecated feature that will be removed in an upcoming release. - - - - - The identifier attribute does not necessarily need to be mapped to the column(s) that physically - define the primary key. However, it should map to column(s) that can uniquely identify each row. - - - - We recommend that you declare consistently-named identifier attributes on persistent classes and - that you use a nullable (i.e., non-primitive) type. - -
- - -
- Prefer non-final classes - - - A central feature of Hibernate is the ability to lazy load an entity's data via runtime proxies. This - feature depends upon the entity class being non-final or else implementing an interface that declares - all the attribute getters/setters. You can still persist final classes that do not implement such - an interface with Hibernate; you just will not be able to use proxies for lazy association fetching - which will ultimately limit your options for performance tuning. - - - - - Starting in 5.0 Hibernate offers a more robust version of bytecode enhancement as another means - for handling lazy loading. Hibernate had some bytecode re-writing capabilities prior to 5.0 but - they were very rudimentary. - - - - - - You should also avoid declaring persistent attribute getters and setters as final for the reasons - already mentioned. - -
- -
- Declare getters and setters for persistent attributes - - - Although not required, it is recommended to follow JavaBean conventions by defining getters and - setters for you entities persistent attributes. Hibernate can also directly access the entity's - fields. - - - - Attributes (whether fields or getters/setters) need not be declared public. Hibernate can deal with - attributes declared with public, protected, package or private visibility. - -
- - -
- Implementing <literal>equals()</literal> and <literal>hashCode()</literal> - - - - Much of the discussion in this section deals with the relation of an entity to - a Hibernate Session; whether the entity is managed or transient or detached. These - topics are explained in if you are unfamiliar with them. - - - - - Whether to implement equals() and hashCode() - methods in your domain model, let alone how to implement them, is a surprisingly tricky discussion - when it comes to ORM. - - - - There is really just one absolute case: a class that acts as an identifier must implement - equals/hashCode based on the id value(s). Generally this is pertinent for user classes used as - composite identifiers. Beyond this one absolute case and the few others we will discuss below, you - may want to consider not implementing equals/hashCode. - - - - So what's all the fuss? Normally, most Java objects provide a built-in equals() and hashCode() based - on the object's identity, so each new object will be different from all others. This is generally what - you want in ordinary Java programming. Conceptually however this starts to break down when you start - to think about the possibility multiple instances of a class representing the same data which is in - fact the case when we start dealing with data from a database. Every time we load a specific - Person from the database we would naturally get a unique instance. - Hibernate, however, works hard to make sure that does not happen within a given Session. In fact - Hibernate guarantees equivalence of persistent identity (database row) and Java identity inside a - particular session scope. So if we ask a Hibernate Session to load that specific Person multiple - times we will actually get back the same instance: - - - - Scope of identity - - - - - Consider another example using a persistent java.util.Set: - - - - Set usage with Session-scoped identity - - - - - However, the semantic changes when we mix instances loaded from different Sessions: - - - - Mixed Sessions - - - - - - Specifically the outcome in this last example will depend on whether the Person class implemented - equals/hashCode, and if so how. - - - - Consider yet another case: - - - - Sets with transient entities - - - - - In cases where you will be dealing with entities outside of a Session (whether - they be transient or detached), especially in cases where you will be using - them in Java collections, you should consider implementing equals/hashCode. - - - - A common initial approach is to use the entity's identifier attribute as the basis for - equals/hashCode calculations: - - - - Naive equals/hashCode implementation - - - - - It turns out that this still breaks when adding transient instance of Person to a set - as we saw in the last example: - - - - Still trouble - - - - - The issue here is a conflict between (1) the use of generated identifier and - (2) the contract of Set and (3) the equals/hashCode implementations. Set says that the - equals/hashCode value for an object should not change while it is part of the Set. - But that is exactly what happened here because the equals/hasCode are based on the (generated) id, - which was not set until the session.getTransaction().commit() call. - - - - Note that this is just a concern when using generated identifiers. If you are using assigned - identifiers this will not be a problem, assuming the identifier value is assigned prior to - adding to the Set. - - - - Another option is to force the identifier to be generated and set prior to adding to the Set: - - - - Forcing identifier generation - - - - - But this is often not feasible. - - - - The final approach is to use a "better" equals/hashCode implementation, making use of a natural-id - or business-key. - - - - Better equals/hashCode with natural-id - - - - - - - As you can see the question of equals/hashCode is not trivial. Nor is there a one-size-fits-all - solution. - -
- -
- -
- Dynamic models - - - Persistent entities do not necessarily have to be represented as - POJO/JavaBean classes. Hibernate also supports dynamic models (using Maps of - Maps at runtime). With this approach, you do not write persistent classes, - only mapping files. - - - - - The mapping of dynamic models is beyond the scope of this document. We will discuss - using such models with Hibernate, but for mapping see the - Hibernate Domain Model Mapping documentation. - - - - - A given entity has just one entity mode within a given SessionFactory. This - is a change from previous versions which allowed to define multiple entity modes for an entity and - to select which to load. Entity modes can now also be mixed within a domain model; a dynamic entity - might reference a POJO entity, and vice versa. - - - - Working with Dynamic Domain Models - - - - - The main advantage of dynamic models is quick turnaround time for prototyping without the need for - entity class implementation. The main down-fall is that you lose compile-time type checking and will - likely deal with many exceptions at runtime. However, as a result of the Hibernate mapping, the - database schema can easily be normalized and sound, allowing to add a proper domain model implementation - on top later on. - - - - It is also interesting to note that dynamic models are great for certain integration use cases as well. - Envers, for example, makes extensive use of dynamic models to represent the historical data. - -
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing1.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing1.java deleted file mode 100644 index 71707b71b9ba..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing1.java +++ /dev/null @@ -1,7 +0,0 @@ -Session session = ...; - -Person p1 = session.get( Person.class, 1 ); -Person p2 = session.get( Person.class, 1); - -// this evaluates to true -assert p1 == p2; \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing10.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing10.java deleted file mode 100644 index f8d90bf94975..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing10.java +++ /dev/null @@ -1,20 +0,0 @@ -Session s = openSession(); -Transaction tx = s.beginTransaction(); - -// Create a customer entity -Map david = new HashMap(); -david.put("name", "David"); - -// Create an organization entity -Map foobar = new HashMap(); -foobar.put("name", "Foobar Inc."); - -// Link both -david.put("organization", foobar); - -// Save both -s.save("Customer", david); -s.save("Organization", foobar); - -tx.commit(); -s.close(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing2.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing2.java deleted file mode 100644 index d401a686f258..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing2.java +++ /dev/null @@ -1,8 +0,0 @@ -Session session1 = ...; -Session session2 = ...; - -Person p1 = session1.get( Person.class, 1 ); -Person p2 = session2.get( Person.class, 1); - -// this evaluates to false -assert p1 == p2; \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing3.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing3.java deleted file mode 100644 index 5f55869c1955..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing3.java +++ /dev/null @@ -1,12 +0,0 @@ -Session session = ...; - -Club club = session.get( Club.class, 1 ); - -Person p1 = session.get( Person.class, 1 ); -Person p2 = session.get( Person.class, 1); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to true -assert club.getMembers.size() == 1; \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing4.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing4.java deleted file mode 100644 index 41258d724102..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing4.java +++ /dev/null @@ -1,13 +0,0 @@ -Session session1 = ...; -Session session2 = ...; - -Club club = session1.get( Club.class, 1 ); - -Person p1 = session1.get( Person.class, 1 ); -Person p2 = session2.get( Person.class, 1); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to ... well it depends -assert club.getMembers.size() == 1; \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing5.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing5.java deleted file mode 100644 index 01c0d5e33767..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing5.java +++ /dev/null @@ -1,12 +0,0 @@ -Session session = ...; - -Club club = session.get( Club.class, 1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to ... again, it depends -assert club.getMembers.size() == 1; \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing6.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing6.java deleted file mode 100644 index 789b348e4fd7..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing6.java +++ /dev/null @@ -1,30 +0,0 @@ -@Entity -public class Person { - @Id - @GeneratedValue - private Integer id; - - ... - - @Override - public int hashCode() { - return id != null ? id.hashCode() : 0; - } - - @Override - public boolean equals() { - if ( this == o ) { - return true; - } - if ( !( o instanceof Person ) ) { - return false; - } - - if ( id == null ) { - return false; - } - - final Person other = (Person) o; - return id.equals( other.id ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing7.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing7.java deleted file mode 100644 index a8200fbdadc5..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing7.java +++ /dev/null @@ -1,15 +0,0 @@ -Session session = ...; -session.getTransaction().begin(); - -Club club = session.get( Club.class, 1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -session.getTransaction().commit(); - -// will actually resolve to false! -assert club.getMembers().contains( p1 ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing8.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing8.java deleted file mode 100644 index 999cf4307963..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing8.java +++ /dev/null @@ -1,19 +0,0 @@ -Session session = ...; -session.getTransaction().begin(); - -Club club = session.get( Club.class, 1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -session.save( p1 ); -session.save( p2 ); -session.flush(); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -session.getTransaction().commit(); - -// will actually resolve to false! -assert club.getMembers().contains( p1 ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing9.java b/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing9.java deleted file mode 100644 index 0d438c663a7b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/domain/extras/listing9.java +++ /dev/null @@ -1,42 +0,0 @@ -@Entity -public class Person { - @Id - @GeneratedValue - private Integer id; - - @NaturalId - private String ssn; - - protected Person() { - // ctor for ORM - } - - public Person(String ssn) { - // ctor for app - this.ssn = ssn; - } - - ... - - @Override - public int hashCode() { - assert ssn != null; - return ssn.hashCode(); - } - - @Override - public boolean equals() { - if ( this == o ) { - return true; - } - if ( !( o instanceof Person ) ) { - return false; - } - - final Person other = (Person) o; - assert ssn != null; - assert other.ssn != null; - - return ssn.equals( other.ssn ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/BytecodeEnhancement.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/BytecodeEnhancement.xml deleted file mode 100644 index 4a743a424cc9..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/BytecodeEnhancement.xml +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - Bytecode Enhancement - - - Bytecode enhancement is the process of manipulating the bytecode (.class) representation of - a class for some purpose. This chapter explores Hibernate's ability to perform bytecode - enhancement. - - - - - - - - Hibernate "grew up" not supporting bytecode enhancement at all. At that time, Hibernate only - supported proxy-based for lazy loading and always used diff-based dirty calculation. Hibernate - 3.x saw the first attempts at bytecode enhancement support in Hibernate. We consider those initial - attempts (up until 5.0) completely as an incubation. The support for bytecode enhancement in 5.0 - onward is what we are discussing here. - - - -
- Capabilities - - Hibernate supports the enhancement of an application's Java domain model for the purpose of adding - various persistence-related capabilities directly into the class, including... - - -
- Lazy attribute loading - - Think of this as partial loading support. Essentially you can - tell Hibernate that only part(s) of an entity should be loaded and when the other part(s) - should be loaded. Note that this is very much different from proxy-based idea of lazy loading - which is entity-centric where the entity's state is loaded at once as needed. With bytecode - enhancement, individual attributes or groups of attributes are loaded as needed. - - - - Lazy attributes can be designated to be loaded together; this is called a "lazy group". - By default all singular attributes are part of a single group, meaning that when one lazy singular - attribute is accessed all lazy singular attributes are loaded. Lazy plural attributes, by default, - are each a lazy group by themselves. This behavior is explicitly controllable through the - @org.hibernate.annotations.LazyGroup annotation. - - - - @LazyGroup example - - - - - In the above example we have 2 lazy attributes: accountsPayableXrefId and image. Each is part of - a different fetch group (accountsPayableXrefId is part of the default fetch group). Which means - that accessing accountsPayableXrefId will not force the loading of image, and vice-versa. - - - - - As a hopefully temporary legacy hold-over, it is currently required that all lazy singular - associations (many-to-one and one-to-one) also include @LazyToOne(LazyToOneOption.NO_PROXY). - The plan is to relax that requirement later. - - -
- -
- In-line dirty tracking - - Historically Hibernate only supported diff-based dirty calculation for determining which - entities in a persistence context have changed. This essentially means that Hibernate - would keep track of the last known state of an entity in regards to the database (typically - the last read or write). Then, as part of flushing the persistence context, Hibernate would walk - every entity associated with the persistence context and check its current state against that - "last known database state". This is by far the most thorough approach to dirty checking - because it accounts for data-types that can change their internal state - (java.util.Date is the prime example of this). However, in a persistence - context with a large number of associated entities it can also be a performance-inhibiting - approach. - - - If your application does not need to care about "internal state changing data-type" use cases, - bytecode-enhanced dirty tracking might be a worthwhile alternative to consider, especially in - terms of performance. In this approach Hibernate will manipulate the bytecode of your classes - to add "dirty tracking" directly to the entity, allowing the entity itself to keep track of which - of its attributes have changed. Come flush time, Hibernate simply asks your entity what has - changed rather that having to perform the state-diff calculations. - -
- -
- Bi-directional association management - - Hibernate strives to keep your application as close to "normal Java usage" (idiomatic Java) - as possible. Consider a domain model with a normal Order/LineItem bi-directional association: - - - Incorrect normal Java usage - - - - This blows up in normal Java usage. The correct normal Java usage is: - - - Correct normal Java usage - - - - Bytecode-enhanced bi-directional association management makes that first example work by managing the - "other side" of a bi-directional association whenever one side is manipulated. - -
- -
- Internal performance optimizations - - Additionally we use the enhancement process to add some additional code that allows us to - optimized certain performance characteristics of the persistence context. These are hard to - discuss without diving into a discussion of Hibernate internals. - -
-
- -
- Performing enhancement - - There are two methods to perform bytecode enhancement. It can be done during run time or build time, - using one of the provided plugins for build automation tools. Each capability must be enabled - independently, using the respective property: - - - enableLazyInitialization - - - Whether enhancement for lazy attribute loading should be done. - - - - - enableDirtyTracking - - - Whether enhancement for self-dirty tracking should be done. - - - - - enableAssociationManagement - - - Whether enhancement for bi-directional association management should be done. - - - - - - - Ultimately all enhancement is handled by the org.hibernate.bytecode.enhance.spi.Enhancer class. - Custom means to enhancement can certainly be crafted on top of Enhancer, but that is beyond the scope of - this guide. Here we will focus on the means Hibernate already exposes for performing these enhancements. - - -
- Run-time enhancement - - Currently run-time enhancement of the domain model is only supported in managed JPA - environments following the JPA defined SPI for performing class transformations. Even then, this - support is disabled by default. To enable run-time enhancement, turn on one or more of the following - properties in the persistent unit: - - Example persistence.xml - - - - - Also, at the moment only annotated classes are supported for run-time enhancement. Only the - classes declared in the persistent unit will be enhanced. - -
- -
- Build-time enhancement - - Hibernate provides plugins for Gradle and Maven build tools. - - -
- Gradle plugin - - Hibernate provides a Gradle plugin that is capable of providing build-time enhancement of the - domain model as they are compiled as part of a Gradle build. To use the plugin a project would - first need to apply it. - - - Apply the Gradle plugin - - - - The configuration that is available is exposed through a registered Gradle DSL extension. The default - value for all configuration settings is false. - - - The enhance { } block is required in order for enhancement to occur. Enhancement - is disabled by default in preparation for additions capabilities (hbm2ddl, etc) in the plugin. - -
- -
- Maven plugin - - Hibernate provides a Maven plugin capable of providing build-time enhancement of the - domain model as they are compiled as part of a Maven build. The configuration has the same properties - as the Gradle plugin and the default value for all settings is also false. - - - The Maven plugin supports one additional configuration settings: failOnError, which controls - what happens in case of an error. Default behavior is to fail the build, but it can be set so - that only a warning is issued. - - - Apply the Maven plugin - - -
-
-
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/BiDirManagementNormalJavaCorrect.java b/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/BiDirManagementNormalJavaCorrect.java deleted file mode 100644 index 51acb8e13d80..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/BiDirManagementNormalJavaCorrect.java +++ /dev/null @@ -1,7 +0,0 @@ -Order order = new Order(); -LineItem lineItem = new LineItem(); -order.getLineItems().add( lineItem ); -lineItem.setOrder( order ); - -// Now this is OK... -lineItem.getOrder.getname(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/BiDirManagementNormalJavaIncorrect.java b/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/BiDirManagementNormalJavaIncorrect.java deleted file mode 100644 index 11d30f1d139b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/BiDirManagementNormalJavaIncorrect.java +++ /dev/null @@ -1,6 +0,0 @@ -Order order = new Order(); -LineItem lineItem = new LineItem(); -order.getLineItems().add( lineItem ); - -// This blows up (NPE) in normal Java usage -lineItem.getOrder.getname(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/LazyGroupExample.java b/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/LazyGroupExample.java deleted file mode 100644 index 0dd1cd968286..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/LazyGroupExample.java +++ /dev/null @@ -1,14 +0,0 @@ -@Entity -public class Customer { - @Id - private Integer id; - private String name; - @Basic( fetch = FetchType.LAZY ) - private UUID accountsPayableXrefId; - @Lob - @Basic( fetch = FetchType.LAZY ) - @LazyGroup( "lobs" ) - private Blob image; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/gradle-example.gradle b/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/gradle-example.gradle deleted file mode 100644 index 5ef9c1183b23..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/gradle-example.gradle +++ /dev/null @@ -1,17 +0,0 @@ -ext { - hibernateVersion = 'x.y.z.Final' -} - -buildscript { - dependencies { - classpath "org.hibernate:hibernate-gradle-plugin:$hibernateVersion" - } -} - -hibernate { - enhance { - enableLazyInitialization= false - enableDirtyTracking = false - enableAssociationManagement = false - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/maven-example.pom b/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/maven-example.pom deleted file mode 100644 index 6099bbd6ecaf..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/maven-example.pom +++ /dev/null @@ -1,24 +0,0 @@ - - - [...] - - org.hibernate.orm.tooling - hibernate-enhance-maven-plugin - $hibernateVersion - - - - true - false - false - false - - - enhance - - - - - [...] - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/persistence-example.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/persistence-example.xml deleted file mode 100644 index 506c9d05171d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/enhancement/extras/persistence-example.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - [...] - - [...] - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/envers/Envers.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/envers/Envers.xml deleted file mode 100644 index d55e045ae0df..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/envers/Envers.xml +++ /dev/null @@ -1,1759 +0,0 @@ - - - - - - Envers - - - The aim of Hibernate Envers is to provide historical versioning of your application's entity data. Much - like source control management tools such as Subversion or Git, Hibernate Envers manages a notion of revisions - if your application data through the use of audit tables. Each transaction relates to one global revision number - which can be used to identify groups of changes (much like a change set in source control). As the revisions - are global, having a revision number, you can query for various entities at that revision, retrieving a - (partial) view of the database at that revision. You can find a revision number having a date, and the other - way round, you can get the date at which a revision was committed. - - - - -
- Basics - - - To audit changes that are performed on an entity, you only need two things: the - hibernate-envers jar on the classpath and an @Audited annotation - on the entity. - - - - - Unlike in previous versions, you no longer need to specify listeners in the Hibernate configuration - file. Just putting the Envers jar on the classpath is enough - listeners will be registered - automatically. - - - - - And that's all - you can create, modify and delete the entities as always. If you look at the generated - schema for your entities, or at the data persisted by Hibernate, you will notice that there are no changes. - However, for each audited entity, a new table is introduced - entity_table_AUD, - which stores the historical data, whenever you commit a transaction. Envers automatically creates audit - tables if hibernate.hbm2ddl.auto option is set to create, - create-drop or update. Otherwise, to export complete database schema - programatically, use org.hibernate.envers.tools.hbm2ddl.EnversSchemaGenerator. Appropriate DDL - statements can be also generated with Ant task described later in this manual. - - - - Instead of annotating the whole class and auditing all properties, you can annotate - only some persistent properties with @Audited. This will cause only - these properties to be audited. - - - - The audit (history) of an entity can be accessed using the AuditReader interface, which - can be obtained having an open EntityManager or Session via - the AuditReaderFactory. See the javadocs for these classes for details on the - functionality offered. - -
- -
- Configuration - - It is possible to configure various aspects of Hibernate Envers behavior, such as table names, etc. - - - - Envers Configuration Properties - - - - - - - - Property name - Default value - Description - - - - - - - org.hibernate.envers.audit_table_prefix - - - - - String that will be prepended to the name of an audited entity to create the name of the - entity, that will hold audit information. - - - - - org.hibernate.envers.audit_table_suffix - - - _AUD - - - String that will be appended to the name of an audited entity to create the name of the - entity, that will hold audit information. If you audit an entity with a table name Person, - in the default setting Envers will generate a Person_AUD table to store - historical data. - - - - - org.hibernate.envers.revision_field_name - - - REV - - - Name of a field in the audit entity that will hold the revision number. - - - - - org.hibernate.envers.revision_type_field_name - - - REVTYPE - - - Name of a field in the audit entity that will hold the type of the revision (currently, - this can be: add, mod, del). - - - - - org.hibernate.envers.revision_on_collection_change - - - true - - - Should a revision be generated when a not-owned relation field changes (this can be either - a collection in a one-to-many relation, or the field using "mappedBy" attribute in a - one-to-one relation). - - - - - org.hibernate.envers.do_not_audit_optimistic_locking_field - - - true - - - When true, properties to be used for optimistic locking, annotated with - @Version, will be automatically not audited (their history won't be - stored; it normally doesn't make sense to store it). - - - - - org.hibernate.envers.store_data_at_delete - - - false - - - Should the entity data be stored in the revision when the entity is deleted (instead of only - storing the id and all other properties as null). This is not normally needed, as the data is - present in the last-but-one revision. Sometimes, however, it is easier and more efficient to - access it in the last revision (then the data that the entity contained before deletion is - stored twice). - - - - - org.hibernate.envers.default_schema - - - null (same schema as table being audited) - - - The default schema name that should be used for audit tables. Can be overridden using the - @AuditTable(schema="...") annotation. If not present, the schema will - be the same as the schema of the table being audited. - - - - - org.hibernate.envers.default_catalog - - - null (same catalog as table being audited) - - - The default catalog name that should be used for audit tables. Can be overridden using the - @AuditTable(catalog="...") annotation. If not present, the catalog will - be the same as the catalog of the normal tables. - - - - - org.hibernate.envers.audit_strategy - - - org.hibernate.envers.strategy.DefaultAuditStrategy - - - The audit strategy that should be used when persisting audit data. The default stores only - the revision, at which an entity was modified. An alternative, the - org.hibernate.envers.strategy.ValidityAuditStrategy stores both the - start revision and the end revision. Together these define when an audit row was valid, - hence the name ValidityAuditStrategy. - - - - - org.hibernate.envers.audit_strategy_validity_end_rev_field_name - - - REVEND - - - The column name that will hold the end revision number in audit entities. This property is - only valid if the validity audit strategy is used. - - - - - org.hibernate.envers.audit_strategy_validity_store_revend_timestamp - - - false - - - Should the timestamp of the end revision be stored, until which the data was valid, in - addition to the end revision itself. This is useful to be able to purge old Audit records - out of a relational database by using table partitioning. Partitioning requires a column - that exists within the table. This property is only evaluated if the ValidityAuditStrategy - is used. - - - - - org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name - - - REVEND_TSTMP - - - Column name of the timestamp of the end revision until which the data was valid. Only used - if the ValidityAuditStrategy is used, and - org.hibernate.envers.audit_strategy_validity_store_revend_timestamp - evaluates to true - - - - - org.hibernate.envers.use_revision_entity_with_native_id - - - true - - - Boolean flag that determines the strategy of revision number generation. Default - implementation of revision entity uses native identifier generator. If current database - engine does not support identity columns, users are advised to set this property to false. - In this case revision numbers are created by preconfigured - org.hibernate.id.enhanced.SequenceStyleGenerator. See: - - org.hibernate.envers.DefaultRevisionEntity - org.hibernate.envers.enhanced.SequenceIdRevisionEntity - - - - - - org.hibernate.envers.track_entities_changed_in_revision - - - false - - - Should entity types, that have been modified during each revision, be tracked. The default - implementation creates REVCHANGES table that stores entity names - of modified persistent objects. Single record encapsulates the revision identifier - (foreign key to REVINFO table) and a string value. For more - information refer to - and . - - - - - org.hibernate.envers.global_with_modified_flag - - - false, can be individually overriden with @Audited(withModifiedFlag=true) - - - Should property modification flags be stored for all audited entities and all properties. - When set to true, for all properties an additional boolean column in the audit tables will - be created, filled with information if the given property changed in the given revision. - When set to false, such column can be added to selected entities or properties using the - @Audited annotation. - For more information refer to - and . - - - - - org.hibernate.envers.modified_flag_suffix - - - _MOD - - - The suffix for columns storing "Modified Flags". - For example: a property called "age", will by default get modified flag with column name "age_MOD". - - - - - org.hibernate.envers.embeddable_set_ordinal_field_name - - - SETORDINAL - - - Name of column used for storing ordinal of the change in sets of embeddable elements. - - - - - org.hibernate.envers.cascade_delete_revision - - - false - - - While deleting revision entry, remove data of associated audited entities. - Requires database support for cascade row removal. - - - - - org.hibernate.envers.allow_identifier_reuse - - - false - - - Guarantees proper validity audit strategy behavior when application reuses identifiers - of deleted entities. Exactly one row with null end date exists - for each identifier. - - - - -
- - - - The following configuration options have been added recently and should be regarded as experimental: - - - org.hibernate.envers.track_entities_changed_in_revision - - - org.hibernate.envers.using_modified_flag - - - org.hibernate.envers.modified_flag_suffix - - - - -
- -
- Additional mapping annotations - - - The name of the audit table can be set on a per-entity basis, using the - @AuditTable annotation. It may be tedious to add this - annotation to every audited entity, so if possible, it's better to use a prefix/suffix. - - - - If you have a mapping with secondary tables, audit tables for them will be generated in - the same way (by adding the prefix and suffix). If you wish to overwrite this behaviour, - you can use the @SecondaryAuditTable and - @SecondaryAuditTables annotations. - - - - If you'd like to override auditing behaviour of some fields/properties inherited from - @Mappedsuperclass or in an embedded component, you can - apply the @AuditOverride(s) annotation on the subtype or usage site - of the component. - - - - If you want to audit a relation mapped with @OneToMany+@JoinColumn, - please see for a description of the additional - @AuditJoinTable annotation that you'll probably want to use. - - - - If you want to audit a relation, where the target entity is not audited (that is the case for example with - dictionary-like entities, which don't change and don't have to be audited), just annotate it with - @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED). Then, while reading historic - versions of your entity, the relation will always point to the "current" related entity. By default Envers - throws javax.persistence.EntityNotFoundException when "current" entity does not - exist in the database. Apply @NotFound(action = NotFoundAction.IGNORE) annotation - to silence the exception and assign null value instead. Hereby solution causes implicit eager loading - of to-one relations. - - - - If you'd like to audit properties of a superclass of an entity, which are not explicitly audited (which - don't have the @Audited annotation on any properties or on the class), you can list the - superclasses in the auditParents attribute of the @Audited - annotation. Please note that auditParents feature has been deprecated. Use - @AuditOverride(forClass = SomeEntity.class, isAudited = true/false) instead. - -
- -
- Choosing an audit strategy - - After the basic configuration it is important to choose the audit strategy that will be used to persist - and retrieve audit information. There is a trade-off between the performance of persisting and the - performance of querying the audit information. Currently there two audit strategies. - - - - - The default audit strategy persists the audit data together with a start revision. For each row - inserted, updated or deleted in an audited table, one or more rows are inserted in the audit - tables, together with the start revision of its validity. Rows in the audit tables are never - updated after insertion. Queries of audit information use subqueries to select the applicable - rows in the audit tables. These subqueries are notoriously slow and difficult to index. - - - - - The alternative is a validity audit strategy. This strategy stores the start-revision and the - end-revision of audit information. For each row inserted, updated or deleted in an audited table, - one or more rows are inserted in the audit tables, together with the start revision of its - validity. But at the same time the end-revision field of the previous audit rows (if available) - are set to this revision. Queries on the audit information can then use 'between start and end - revision' instead of subqueries as used by the default audit strategy. - - - The consequence of this strategy is that persisting audit information will be a bit slower, - because of the extra updates involved, but retrieving audit information will be a lot faster. - This can be improved by adding extra indexes. - - - -
- -
- Revision Log - Logging data for revisions - - - When Envers starts a new revision, it creates a new revision entity which stores - information about the revision. By default, that includes just - - - - - revision number - An integral value (int/Integer or - long/Long). Essentially the primary key of the revision - - - - - revision timestamp - either a long/Long or - java.util.Date value representing the instant at which the revision was made. - When using a java.util.Date, instead of a long/Long for - the revision timestamp, take care not to store it to a column data type which will loose precision. - - - - - - Envers handles this information as an entity. By default it uses its own internal class to act as the - entity, mapped to the REVINFO table. - You can, however, supply your own approach to collecting this information which might be useful to - capture additional details such as who made a change or the ip address from which the request came. There - are 2 things you need to make this work. - - - - - First, you will need to tell Envers about the entity you wish to use. Your entity must use the - @org.hibernate.envers.RevisionEntity annotation. It must - define the 2 attributes described above annotated with - @org.hibernate.envers.RevisionNumber and - @org.hibernate.envers.RevisionTimestamp, respectively. You can extend - from org.hibernate.envers.DefaultRevisionEntity, if you wish, to inherit all - these required behaviors. - - - Simply add the custom revision entity as you do your normal entities. Envers will "find it". Note - that it is an error for there to be multiple entities marked as - @org.hibernate.envers.RevisionEntity - - - - - Second, you need to tell Envers how to create instances of your revision entity which is handled - by the newRevision method of the - org.jboss.envers.RevisionListener interface. - - - You tell Envers your custom org.hibernate.envers.RevisionListener - implementation to use by specifying it on the - @org.hibernate.envers.RevisionEntity annotation, using the - value attribute. If your RevisionListener - class is inaccessible from @RevisionEntity (e.g. exists in a different - module), set org.hibernate.envers.revision_listener property to it's fully - qualified name. Class name defined by the configuration parameter overrides revision entity's - value attribute. - - - - - - - An alternative method to using the org.hibernate.envers.RevisionListener - is to instead call the getCurrentRevision method of the - org.hibernate.envers.AuditReader interface to obtain the current revision, - and fill it with desired information. The method accepts a persist parameter indicating - whether the revision entity should be persisted prior to returning from this method. true - ensures that the returned entity has access to its identifier value (revision number), but the revision - entity will be persisted regardless of whether there are any audited entities changed. false - means that the revision number will be null, but the revision entity will be persisted - only if some audited entities have changed. - - - - - Example of storing username with revision - - - ExampleRevEntity.java - - - ExampleListener.java - - - -
- Tracking entity names modified during revisions - - By default entity types that have been changed in each revision are not being tracked. This implies the - necessity to query all tables storing audited data in order to retrieve changes made during - specified revision. Envers provides a simple mechanism that creates REVCHANGES - table which stores entity names of modified persistent objects. Single record encapsulates the revision - identifier (foreign key to REVINFO table) and a string value. - - - Tracking of modified entity names can be enabled in three different ways: - - - - - Set org.hibernate.envers.track_entities_changed_in_revision parameter to - true. In this case - org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity will - be implicitly used as the revision log entity. - - - - - Create a custom revision entity that extends - org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity class. - - - - - - - Mark an appropriate field of a custom revision entity with - @org.hibernate.envers.ModifiedEntityNames annotation. The property is - required to be of ]]> type. - - - modifiedEntityNames; - - ... -}]]> - - - - Users, that have chosen one of the approaches listed above, can retrieve all entities modified in a - specified revision by utilizing API described in . - - - Users are also allowed to implement custom mechanism of tracking modified entity types. In this case, they - shall pass their own implementation of - org.hibernate.envers.EntityTrackingRevisionListener interface as the value - of @org.hibernate.envers.RevisionEntity annotation. - EntityTrackingRevisionListener interface exposes one method that notifies - whenever audited entity instance has been added, modified or removed within current revision boundaries. - - - - Custom implementation of tracking entity classes modified during revisions - - CustomEntityTrackingRevisionListener.java - - - CustomTrackingRevisionEntity.java - modifiedEntityTypes = - new HashSet(); - - public void addModifiedEntityType(String entityClassName) { - modifiedEntityTypes.add(new ModifiedEntityTypeEntity(this, entityClassName)); - } - - ... -} -]]> - - ModifiedEntityTypeEntity.java - - modifiedEntityTypes = revEntity.getModifiedEntityTypes()]]> - -
- -
- -
- Tracking entity changes at property level - - By default the only information stored by Envers are revisions of modified entities. - This approach lets user create audit queries based on historical values of entity's properties. - - Sometimes it is useful to store additional metadata for each revision, when you are interested also in - the type of changes, not only about the resulting values. The feature described in - - makes it possible to tell which entities were modified in given revision. - - Feature described here takes it one step further. "Modification Flags" enable Envers to track which - properties of audited entities were modified in a given revision. - - - Tracking entity changes at property level can be enabled by: - - - - - setting org.hibernate.envers.global_with_modified_flag configuration - property to true. This global switch will cause adding modification flags - for all audited properties in all audited entities. - - - - - using @Audited(withModifiedFlag=true) on a property or on an entity. - - - - - The trade-off coming with this functionality is an increased size of - audit tables and a very little, almost negligible, performance drop - during audit writes. This is due to the fact that every tracked - property has to have an accompanying boolean column in the - schema that stores information about the property's modifications. Of - course it is Envers' job to fill these columns accordingly - no additional work by the - developer is required. Because of costs mentioned, it is recommended - to enable the feature selectively, when needed with use of the - granular configuration means described above. - - - To see how "Modified Flags" can be utilized, check out the very - simple query API that uses them: . - -
- -
- - Queries - - - You can think of historic data as having two dimension. The first - horizontal - - is the state of the database at a given revision. Thus, you can - query for entities as they were at revision N. The second - vertical - are the - revisions, at which entities changed. Hence, you can query for revisions, - in which a given entity changed. - - - - The queries in Envers are similar to Hibernate Criteria queries, so if you are common with them, - using Envers queries will be much easier. - - - - The main limitation of the current queries implementation is that you cannot - traverse relations. You can only specify constraints on the ids of the - related entities, and only on the "owning" side of the relation. This however - will be changed in future releases. - - - - Please note, that queries on the audited data will be in many cases much slower - than corresponding queries on "live" data, as they involve correlated subselects. - - - - In the future, queries will be improved both in terms of speed and possibilities, when using the valid-time - audit strategy, that is when storing both start and end revisions for entities. See - . - - -
- - Querying for entities of a class at a given revision - - - The entry point for this type of queries is: - - - - - - You can then specify constraints, which should be met by the entities returned, by - adding restrictions, which can be obtained using the AuditEntity - factory class. For example, to select only entities, where the "name" property - is equal to "John": - - - - - - And to select only entites that are related to a given entity: - - - - - - You can limit the number of results, order them, and set aggregations and projections - (except grouping) in the usual way. - When your query is complete, you can obtain the results by calling the - getSingleResult() or getResultList() methods. - - - - A full query, can look for example like this: - - - - -
- -
- - Querying for revisions, at which entities of a given class changed - - - The entry point for this type of queries is: - - - - - - You can add constraints to this query in the same way as to the previous one. - There are some additional possibilities: - - - - - - using AuditEntity.revisionNumber() you can specify constraints, projections - and order on the revision number, in which the audited entity was modified - - - - - similarly, using AuditEntity.revisionProperty(propertyName) you can specify constraints, - projections and order on a property of the revision entity, corresponding to the revision - in which the audited entity was modified - - - - - AuditEntity.revisionType() gives you access as above to the type of - the revision (ADD, MOD, DEL). - - - - - - Using these methods, - you can order the query results by revision number, set projection or constraint - the revision number to be greater or less than a specified value, etc. For example, the - following query will select the smallest revision number, at which entity of class - MyEntity with id entityId has changed, after revision - number 42: - - - - - - The second additional feature you can use in queries for revisions is the ability - to maximalize/minimize a property. For example, if you want to select the - revision, at which the value of the actualDate for a given entity - was larger then a given value, but as small as possible: - - - - - - The minimize() and maximize() methods return a criteria, - to which you can add constraints, which must be met by the entities with the - maximized/minimized properties. AggregatedAuditExpression#computeAggregationInInstanceContext() - enables the possibility to compute aggregated expression in the context of each entity instance - separately. It turns out useful when querying for latest revisions of all entities of a particular type. - - - - You probably also noticed that there are two boolean parameters, passed when - creating the query. The first one, selectEntitiesOnly, is only valid when - you don't set an explicit projection. If true, the result of the query will be - a list of entities (which changed at revisions satisfying the specified - constraints). - - - - If false, the result will be a list of three element arrays. The - first element will be the changed entity instance. The second will be an entity - containing revision data (if no custom entity is used, this will be an instance - of DefaultRevisionEntity). The third will be the type of the - revision (one of the values of the RevisionType enumeration: - ADD, MOD, DEL). - - - - The second parameter, selectDeletedEntities, specifies if revisions, - in which the entity was deleted should be included in the results. If yes, such entities - will have the revision type DEL and all fields, except the id, - null. - - -
- -
- - Querying for revisions of entity that modified given property - - - For the two types of queries described above it's possible to use - special Audit criteria called - hasChanged() - and - hasNotChanged() - that makes use of the functionality - described in . - They're best suited for vertical queries, - however existing API doesn't restrict their usage for horizontal - ones. - - Let's have a look at following examples: - - - - - - - This query will return all revisions of MyEntity with given id, - where the - actualDate - property has been changed. - Using this query we won't get all other revisions in which - actualDate - wasn't touched. Of course nothing prevents user from combining - hasChanged condition with some additional criteria - add method - can be used here in a normal way. - - - - - - - This query will return horizontal slice for MyEntity at the time - revisionNumber was generated. It will be limited to revisions - that modified - prop1 - but not prop2. - Note that the result set will usually also contain revisions - with numbers lower than the revisionNumber, so we cannot read - this query as "Give me all MyEntities changed in revisionNumber - with - prop1 - modified and - prop2 - untouched". To get such result we have to use the - forEntitiesModifiedAtRevision query: - - - - - -
- - -
- Querying for entities modified in a given revision - - The basic query allows retrieving entity names and corresponding Java classes changed in a specified revision: - - > modifiedEntityTypes = getAuditReader() - .getCrossTypeRevisionChangesReader().findEntityTypes(revisionNumber);]]> - - Other queries (also accessible from org.hibernate.envers.CrossTypeRevisionChangesReader): - - - - - List]]> findEntities(Number) - - Returns snapshots of all audited entities changed (added, updated and removed) in a given revision. - Executes n+1 SQL queries, where n is a number of different entity - classes modified within specified revision. - - - - - List]]> findEntities(Number, RevisionType) - - Returns snapshots of all audited entities changed (added, updated or removed) in a given revision - filtered by modification type. Executes n+1 SQL queries, where n - is a number of different entity classes modified within specified revision. - - - - - >]]> findEntitiesGroupByRevisionType(Number) - - Returns a map containing lists of entity snapshots grouped by modification operation (e.g. - addition, update and removal). Executes 3n+1 SQL queries, where n - is a number of different entity classes modified within specified revision. - - - - - Note that methods described above can be legally used only when default mechanism of - tracking changed entity names is enabled (see ). - -
- -
- -
- Conditional auditing - - Envers persists audit data in reaction to various Hibernate events (e.g. post update, post insert, and - so on), using a series of even listeners from the org.hibernate.envers.event.spi - package. By default, if the Envers jar is in the classpath, the event listeners are auto-registered with - Hibernate. - - - Conditional auditing can be implemented by overriding some of the Envers event listeners. - To use customized Envers event listeners, the following steps are needed: - - - - Turn off automatic Envers event listeners registration by setting the - hibernate.listeners.envers.autoRegister Hibernate property to - false. - - - - - Create subclasses for appropriate event listeners. For example, if you want to - conditionally audit entity insertions, extend the - org.hibernate.envers.event.spi.EnversPostInsertEventListenerImpl - class. Place the conditional-auditing logic in the subclasses, call the super method if - auditing should be performed. - - - - - Create your own implementation of org.hibernate.integrator.spi.Integrator, - similar to org.hibernate.envers.boot.internal.EnversIntegrator. Use your event - listener classes instead of the default ones. - - - - - For the integrator to be automatically used when Hibernate starts up, you will need to add a - META-INF/services/org.hibernate.integrator.spi.Integrator file to your jar. - The file should contain the fully qualified name of the class implementing the interface. - - - - -
- -
- Understanding the Envers Schema - - - For each audited entity (that is, for each entity containing at least one audited field), an audit table is - created. By default, the audit table's name is created by adding a "_AUD" suffix to the original table name, - but this can be overridden by specifying a different suffix/prefix in the configuration or per-entity using - the @org.hibernate.envers.AuditTable annotation. - - - - Audit table columns - - - id of the original entity (this can be more then one column in the case of composite primary keys) - - - - - revision number - an integer. Matches to the revision number in the revision entity table. - - - - - revision type - a small integer - - - - - audited fields from the original entity - - - - - - The primary key of the audit table is the combination of the original id of the entity and the revision - number - there can be at most one historic entry for a given entity instance at a given revision. - - - - The current entity data is stored in the original table and in the audit table. This is a duplication of - data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully - this won't be a major drawback for the users. A row in the audit table with entity id ID, revision N and - data D means: entity with id ID has data D from revision N upwards. Hence, if we want to find an entity at - revision M, we have to search for a row in the audit table, which has the revision number smaller or equal - to M, but as large as possible. If no such row is found, or a row with a "deleted" marker is found, it means - that the entity didn't exist at that revision. - - - - The "revision type" field can currently have three values: 0, 1, 2, which means ADD, MOD and DEL, - respectively. A row with a revision of type DEL will only contain the id of the entity and no data (all - fields NULL), as it only serves as a marker saying "this entity was deleted at that revision". - - - - Additionally, there is a revision entity table which contains the information about the - global revision. By default the generated table is named REVINFO and - contains just 2 columns: ID and TIMESTAMP. - A row is inserted into this table on each new revision, that is, on each commit of a transaction, which - changes audited data. The name of this table can be configured, the name of its columns as well as adding - additional columns can be achieved as discussed in . - - - - While global revisions are a good way to provide correct auditing of relations, some people have pointed out - that this may be a bottleneck in systems, where data is very often modified. One viable solution is to - introduce an option to have an entity "locally revisioned", that is revisions would be created for it - independently. This wouldn't enable correct versioning of relations, but wouldn't also require the - REVINFO table. Another possibility is to introduce a notion of - "revisioning groups": groups of entities which share revision numbering. Each such group would have to - consist of one or more strongly connected component of the graph induced by relations between entities. - Your opinions on the subject are very welcome on the forum! :) - - -
- -
- Generating schema with Ant - - - If you'd like to generate the database schema file with the Hibernate Tools Ant task, - you'll probably notice that the generated file doesn't contain definitions of audit - tables. To generate also the audit tables, you simply need to use - org.hibernate.tool.ant.EnversHibernateToolTask instead of the usual - org.hibernate.tool.ant.HibernateToolTask. The former class extends - the latter, and only adds generation of the version entities. So you can use the task - just as you used to. - - - - For example: - - - - - - - - - - - - - - -]]> - - - Will generate the following schema: - - - -
- - -
- Mapping exceptions - -
- - What isn't and will not be supported - - - Bags, as they can contain non-unique elements. - The reason is that persisting, for example a bag of String-s, violates a principle - of relational databases: that each table is a set of tuples. In case of bags, - however (which require a join table), if there is a duplicate element, the two - tuples corresponding to the elements will be the same. Hibernate allows this, - however Envers (or more precisely: the database connector) will throw an exception - when trying to persist two identical elements, because of a unique constraint violation. - - - - There are at least two ways out if you need bag semantics: - - - - - - use an indexed collection, with the @IndexColumn annotation, or - - - - - provide a unique id for your elements with the @CollectionId annotation. - - - - -
- -
- - What isn't and <emphasis>will</emphasis> be supported - - - - - Bag style collection which identifier column has been defined using - @CollectionId annotation (JIRA ticket HHH-3950). - - - - -
- -
- - <literal>@OneToMany</literal>+<literal>@JoinColumn</literal> - - - When a collection is mapped using these two annotations, Hibernate doesn't - generate a join table. Envers, however, has to do this, so that when you read the - revisions in which the related entity has changed, you don't get false results. - - - To be able to name the additional join table, there is a special annotation: - @AuditJoinTable, which has similar semantics to JPA's - @JoinTable. - - - - One special case are relations mapped with @OneToMany+@JoinColumn on - the one side, and @ManyToOne+@JoinColumn(insertable=false, updatable=false) - on the many side. Such relations are in fact bidirectional, but the owning side is the collection. - - - To properly audit such relations with Envers, you can use the @AuditMappedBy annotation. - It enables you to specify the reverse property (using the mappedBy element). In case - of indexed collections, the index column must also be mapped in the referenced entity (using - @Column(insertable=false, updatable=false), and specified using - positionMappedBy. This annotation will affect only the way - Envers works. Please note that the annotation is experimental and may change in the future. - - -
-
- -
- Advanced: Audit table partitioning - -
- - Benefits of audit table partitioning - - - Because audit tables tend to grow indefinitely they can quickly become really large. When the audit tables have grown - to a certain limit (varying per RDBMS and/or operating system) it makes sense to start using table partitioning. - SQL table partitioning offers a lot of advantages including, but certainly not limited to: - - - - Improved query performance by selectively moving rows to various partitions (or even purging old rows) - - - - - Faster data loads, index creation, etc. - - - - - -
- -
- - Suitable columns for audit table partitioning - - Generally SQL tables must be partitioned on a column that exists within the table. As a rule it makes sense to use - either the end revision or the end revision timestamp column for - partioning of audit tables. - - - End revision information is not available for the default AuditStrategy. - - - - Therefore the following Envers configuration options are required: - - - org.hibernate.envers.audit_strategy = - org.hibernate.envers.strategy.ValidityAuditStrategy - - - org.hibernate.envers.audit_strategy_validity_store_revend_timestamp = - true - - - - Optionally, you can also override the default values using following properties: - - - org.hibernate.envers.audit_strategy_validity_end_rev_field_name - - - org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name - - - - For more information, see . - - - - - - The reason why the end revision information should be used for audit table partioning is based on the assumption that - audit tables should be partionioned on an 'increasing level of interestingness', like so: - - - - - - - A couple of partitions with audit data that is not very (or no longer) interesting. - This can be stored on slow media, and perhaps even be purged eventually. - - - - - Some partitions for audit data that is potentially interesting. - - - - - One partition for audit data that is most likely to be interesting. - This should be stored on the fastest media, both for reading and writing. - - - - - - -
- -
- - Audit table partitioning example - - In order to determine a suitable column for the 'increasing level of interestingness', - consider a simplified example of a salary registration for an unnamed agency. - - - - Currently, the salary table contains the following rows for a certain person X: - - - Salaries table - - - - - - Year - Salary (USD) - - - - - 2006 - 3300 - - - 2007 - 3500 - - - 2008 - 4000 - - - 2009 - 4500 - - - -
-
- - - The salary for the current fiscal year (2010) is unknown. The agency requires that all changes in registered - salaries for a fiscal year are recorded (i.e. an audit trail). The rationale behind this is that decisions - made at a certain date are based on the registered salary at that time. And at any time it must be possible - reproduce the reason why a certain decision was made at a certain date. - - - - The following audit information is available, sorted on in order of occurrence: - - - Salaries - audit table - - - - - - - - - Year - Revision type - Revision timestamp - Salary (USD) - End revision timestamp - - - - - 2006 - ADD - 2007-04-01 - 3300 - null - - - 2007 - ADD - 2008-04-01 - 35 - 2008-04-02 - - - 2007 - MOD - 2008-04-02 - 3500 - null - - - 2008 - ADD - 2009-04-01 - 3700 - 2009-07-01 - - - 2008 - MOD - 2009-07-01 - 4100 - 2010-02-01 - - - 2008 - MOD - 2010-02-01 - 4000 - null - - - 2009 - ADD - 2010-04-01 - 4500 - null - - - -
-
- -
- - Determining a suitable partitioning column - - To partition this data, the 'level of interestingness' must be defined. - Consider the following: - - - - For fiscal year 2006 there is only one revision. It has the oldest revision timestamp - of all audit rows, but should still be regarded as interesting because it is the latest modification - for this fiscal year in the salary table; its end revision timestamp is null. - - - Also note that it would be very unfortunate if in 2011 there would be an update of the salary for fiscal - year 2006 (which is possible in until at least 10 years after the fiscal year) and the audit - information would have been moved to a slow disk (based on the age of the - revision timestamp). Remember that in this case Envers will have to update - the end revision timestamp of the most recent audit row. - - - - - There are two revisions in the salary of fiscal year 2007 which both have nearly the same - revision timestamp and a different end revision timestamp. - On first sight it is evident that the first revision was a mistake and probably uninteresting. - The only interesting revision for 2007 is the one with end revision timestamp null. - - - - - Based on the above, it is evident that only the end revision timestamp is suitable for - audit table partitioning. The revision timestamp is not suitable. - - -
- -
- - Determining a suitable partitioning scheme - - A possible partitioning scheme for the salary table would be as follows: - - - - end revision timestamp year = 2008 - - - This partition contains audit data that is not very (or no longer) interesting. - - - - - end revision timestamp year = 2009 - - - This partition contains audit data that is potentially interesting. - - - - - end revision timestamp year >= 2010 or null - - - This partition contains the most interesting audit data. - - - - - - - This partitioning scheme also covers the potential problem of the update of the - end revision timestamp, which occurs if a row in the audited table is modified. - Even though Envers will update the end revision timestamp of the audit row to - the system date at the instant of modification, the audit row will remain in the same partition - (the 'extension bucket'). - - - - And sometime in 2011, the last partition (or 'extension bucket') is split into two new partitions: - - - - end revision timestamp year = 2010 - - - This partition contains audit data that is potentially interesting (in 2011). - - - - - end revision timestamp year >= 2011 or null - - - This partition contains the most interesting audit data and is the new 'extension bucket'. - - - - - -
- -
-
- -
- Envers links - - - - - Hibernate main page - - - - - Forum - - - - - JIRA issue tracker - (when adding issues concerning Envers, be sure to select the "envers" component!) - - - - - IRC channel - - - - - Envers Blog - - - - - FAQ - - - - -
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/events/Events.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/events/Events.xml deleted file mode 100644 index faefdf73c845..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/events/Events.xml +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - Interceptors and events - - - It is useful for the application to react to certain events that occur inside Hibernate. This allows for the - implementation of generic functionality and the extension of Hibernate functionality. - - -
- Interceptors - - - The org.hibernate.Interceptor interface provides callbacks from the session - to the application, allowing the application to inspect and/or manipulate properties of a persistent object - before it is saved, updated, deleted or loaded. One possible use for this is to track auditing information. - For example, the following example shows an Interceptor implementation - that automatically sets the createTimestamp property when an - Auditable entity is created and updates the - lastUpdateTimestamp property when an Auditable entity is - updated. - - - - - You can either implement Interceptor directly or extend - org.hibernate.EmptyInterceptor. - - - - - An Interceptor can be either Session-scoped or SessionFactory-scoped. - - - - A Session-scoped interceptor is specified when a session is opened. - - - - - - A SessionFactory-scoped interceptor is registered with the Configuration object - prior to building the SessionFactory. Unless a session is opened explicitly specifying the interceptor to - use, the SessionFactory-scoped interceptor will be applied to all sessions opened from that SessionFactory. - SessionFactory-scoped interceptors must be thread safe. Ensure that you do not store session-specific - states, since multiple sessions will use this interceptor potentially concurrently. - - - - -
- -
- Native Event system - - - If you have to react to particular events in the persistence layer, you can also use the Hibernate - event architecture. The event system can be used in place of or in addition to - interceptors. - - - - Many methods of the Session interface correlate to an event type. The - full range of defined event types is declared as enum values on - org.hibernate.event.spi.EventType. When a request is made of one of these methods, - the Session generates an appropriate event and passes it to the configured event listener(s) for that type. - Applications are free to implement a customization of one of the listener interfaces - (i.e., the LoadEvent is processed by the registered implementation - of the LoadEventListener interface), in which case their - implementation would be responsible for processing any load() requests - made of the Session. - - - - - See for information on registering custom event - listeners. - - - - - The listeners should be considered stateless; they are shared between requests, and should not save any - state as instance variables. - - - - A custom listener implements the appropriate interface for the event it wants to process and/or extend one - of the convenience base classes (or even the default event listeners used by Hibernate out-of-the-box as - these are declared non-final for this purpose). Here is an example of a custom load event listener: - - - - - Custom LoadListener example - - - - -
- Hibernate declarative security - - Usually, declarative security in Hibernate applications is managed in a session facade - layer. Hibernate allows certain actions to be permissioned via JACC, and authorized - via JAAS. This is an optional functionality that is built on top of the event architecture. - - - - First, you must configure the appropriate event listeners, to enable the use of JACC authorization. - Again, see for the details. Below is an example of an - appropriate org.hibernate.integrator.spi.Integrator implementation for - this purpose. - - - - - JACC listener registration example - - - - - - You must also decide how to configure your JACC provider. Consult your JACC provider documentation. - -
-
- -
- JPA Callbacks - - JPA also defines a more limited set of callbacks through annotations. - - - - Callback annotations - - - - - - Type - Description - - - - - - @PrePersist - - - Executed before the entity manager persist operation is actually executed or cascaded. - This call is synchronous with the persist operation. - - - - - @PreRemove - - - Executed before the entity manager remove operation is actually executed or cascaded. - This call is synchronous with the remove operation. - - - - - @PostPersist - - - Executed after the entity manager persist operation is actually executed or cascaded. - This call is invoked after the database INSERT is executed. - - - - - @PostRemove - - - Executed after the entity manager remove operation is actually executed or cascaded. - This call is synchronous with the remove operation. - - - - - @PreUpdate - - - Executed before the database UPDATE operation. - - - - - @PostUpdate - - - Executed after the database UPDATE operation. - - - - - @PostLoad - - - Executed after an entity has been loaded into the current persistence context or an entity - has been refreshed. - - - - -
- - - There are 2 available approaches defined for specifying callback handling: - - - - - The first approach is to annotate methods on the entity itself to receive notification of - particular entity life cycle event(s). - - - - - The second is to use a separate entity listener class. An entity listener is a stateless class - with a no-arg constructor. The callback annotations are placed on a method of this class instead - of the entity class. The entity listener class is then associated with the entity using the - javax.persistence.EntityListeners annotation - - - - - - Example of specifying JPA callbacks - - - - - These approaches can be mixed, meaning you can use both together. - - - Regardless of whether the callback method is defined on the entity or on an entity listener, it must have - a void-return signature. The name of the method is irrelevant as it is the placement of the callback - annotations that makes the method a callback. In the case of callback methods defined on the - entity class, the method must additionally have a no-argument signature. For callback methods defined on - an entity listener class, the method must have a single argument signature; the type of that argument can - be either java.lang.Object (to facilitate attachment to multiple entities) or the - specific entity type. - - - A callback method can throw a RuntimeException. If the callback method does - throw a RuntimeException, then the current transaction, if any, must be rolled back. - - - A callback method must not invoke EntityManager or - Query methods! - - - It is possible that multiple callback methods are defined for a particular lifecycle event. When that - is the case, the defined order of execution is well defined by the JPA spec (specifically section 3.5.4): - - - - - Any default listeners associated with the entity are invoked first, in the order they were - specified in the XML. See the javax.persistence.ExcludeDefaultListeners - annotation. - - - - - Next, entity listener class callbacks associated with the entity hierarchy are invoked, in the order - they are defined in the EntityListeners. If multiple classes in the - entity hierarchy define entity listeners, the listeners defined for a superclass are invoked before - the listeners defined for its subclasses. See the - javax.persistence.ExcludeSuperclassListeners annotation. - - - - - Lastly, callback methods defined on the entity hierarchy are invoked. If a callback type is - annotated on both an entity and one or more of its superclasses without method overriding, both - would be called, the most general superclass first. An entity class is also allowed to override - a callback method defined in a superclass in which case the super callback would not get invoked; - the overriding method would get invoked provided it is annotated. - - - -
- -
- diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/events/extras/AuditInterceptor.java b/documentation/src/main/docbook/userGuide/en-US/chapters/events/extras/AuditInterceptor.java deleted file mode 100644 index 76e8b5bde224..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/events/extras/AuditInterceptor.java +++ /dev/null @@ -1,79 +0,0 @@ -import java.io.Serializable; -import java.util.Date; - -import org.hibernate.EmptyInterceptor; -import org.hibernate.Transaction; -import org.hibernate.type.Type; - -public class AuditInterceptor extends EmptyInterceptor { - - private int updates; - private int creates; - private int loads; - - public void onDelete(Object entity, - Serializable id, - Object[] state, - String[] propertyNames, - Type[] types) { - // do nothing - } - - public boolean onFlushDirty(Object entity, - Serializable id, - Object[] currentState, - Object[] previousState, - String[] propertyNames, - Type[] types) { - - if ( entity instanceof Auditable ) { - updates++; - for ( int i=0; i < propertyNames.length; i++ ) { - if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) { - currentState[i] = new Date(); - return true; - } - } - } - return false; - } - - public boolean onLoad(Object entity, - Serializable id, - Object[] state, - String[] propertyNames, - Type[] types) { - if ( entity instanceof Auditable ) { - loads++; - } - return false; - } - - public boolean onSave(Object entity, - Serializable id, - Object[] state, - String[] propertyNames, - Type[] types) { - - if ( entity instanceof Auditable ) { - creates++; - for ( int i=0; i - - - - Fetching - - - - - - - Fetching, essentially, is the process of grabbing data from the database and making it available to the - application. Tuning how an application does fetching is one of the biggest factors in determining how an - application will perform. Fetching too much data, in terms of width (values/columns) and/or - depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. - Fetching too little data causes additional fetches to be needed. Tuning how an application - fetches data presents a great opportunity to influence the application's overall performance. - - -
- The basics - - - The concept of fetching breaks down into two different questions. - - - - When should the data be fetched? Now? Later? - - - - - How should the data be fetched? - - - - - - - - "now" is generally termed eager or immediate. "later" is - generally termed lazy or delayed. - - - - - There are a number of scopes for defining fetching: - - - - static - Static definition of fetching strategies is done in the - mappings. The statically-defined fetch strategies is used in the absence of any dynamically - defined strategies Except in the case of HQL/JPQL; see xyz. - - - - - dynamic (sometimes referred to as runtime) - Dynamic definition is - really use-case centric. There are 2 main ways to define dynamic fetching: - - - - - fetch profiles - defined in mappings, but can be - enabled/disabled on the Session. - - - - - HQL/JPQL and both Hibernate and JPA Criteria queries have the ability to specify - fetching, specific to said query. - - - - - - - Starting in Hibernate 4.2 (JPA 2.1) you can also use JPA EntityGraphs. - - - - - - - The strategies - - SELECT - - - Performs a separate SQL select to load the data. This can either be EAGER (the second select - is issued immediately) or LAZY (the second select is delayed until the data is needed). This - is the strategy generally termed N+1. - - - - - JOIN - - - Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of - an SQL join. - - - - - BATCH - - - Performs a separate SQL select to load a number of related data items using an - IN-restriction as part of the SQL WHERE-clause based on a batch size. Again, this can either - be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until - the data is needed). - - - - - SUBSELECT - - - Performs a separate SQL select to load associated data based on the SQL restriction used to - load the owner. Again, this can either be EAGER (the second select is issued immediately) - or LAZY (the second select is delayed until the data is needed). - - - - -
- -
- Applying fetch strategies - - - Let's consider these topics as it relates to an simple domain model and a few use cases. - - - - Sample domain model - - - - - - - - The Hibernate recommendation is to statically mark all associations lazy and to use dynamic fetching - strategies for eagerness. This is unfortunately at odds with the JPA specification which defines that - all one-to-one and many-to-one associations should be eagerly fetched by default. Hibernate, as a JPA - provider, honors that default. - - - -
- No fetching - The login use-case - - For the first use case, consider the application's login process for an Employee. Lets assume that - login only requires access to the Employee information, not Project nor Department information. - - - - No fetching example - - - - - In this example, the application gets the Employee data. However, because all associations from - Employee are declared as LAZY (JPA defines the default for collections as LAZY) no other data is - fetched. - - - - If the login process does not need access to the Employee information specifically, another - fetching optimization here would be to limit the width of the query results. - - - - No fetching (scalar) example - - -
- -
- Dynamic fetching via queries - The projects for an employee use-case - - - For the second use case, consider a screen displaying the Projects for an Employee. Certainly access - to the Employee is needed, as is the collection of Projects for that Employee. Information - about Departments, other Employees or other Projects is not needed. - - - - Dynamic query fetching example - - - - - - In this example we have an Employee and their Projects loaded in a single query shown both as an HQL - query and a JPA Criteria query. In both cases, this resolves to exactly one database query to get all - that information. - -
- -
- Dynamic fetching via profiles - The projects for an employee use-case using natural-id - - - Suppose we wanted to leverage loading by natural-id to obtain the Employee information in the - "projects for and employee" use-case. Loading by natural-id uses the statically defined fetching - strategies, but does not expose a means to define load-specific fetching. So we would leverage a - fetch profile. - - - - Fetch profile example - - - - - - Here the Employee is obtained by natural-id lookup and the Employee's Project data is fetched eagerly. - If the Employee data is resolved from cache, the Project data is resolved on its own. However, - if the Employee data is not resolved in cache, the Employee and Project data is resolved in one - SQL query via join as we saw above. - -
-
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Department.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Department.java deleted file mode 100644 index 06eab06a51b9..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Department.java +++ /dev/null @@ -1,10 +0,0 @@ -@Entity -public class Department { - @Id - private Long id; - - @OneToMany(mappedBy="department") - private List employees; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Employee.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Employee.java deleted file mode 100644 index 62ed3e54e06b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Employee.java +++ /dev/null @@ -1,24 +0,0 @@ -@Entity -public class Employee { - @Id - private Long id; - - @NaturalId - private String userid; - - @Column( name="pswd" ) - @ColumnTransformer( read="decrypt(pswd)" write="encrypt(?)" ) - private String password; - - private int accessLevel; - - @ManyToOne( fetch=LAZY ) - @JoinColumn - private Department department; - - @ManyToMany(mappedBy="employees") - @JoinColumn - private Set projects; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/FetchOverrides.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/FetchOverrides.java deleted file mode 100644 index 4144ea27dcfb..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/FetchOverrides.java +++ /dev/null @@ -1,10 +0,0 @@ -@FetchProfile( - name="employee.projects", - fetchOverrides={ - @FetchOverride( - entity=Employee.class, - association="projects", - mode=JOIN - ) - } -) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Login.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Login.java deleted file mode 100644 index f916bb847a86..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Login.java +++ /dev/null @@ -1,4 +0,0 @@ -String loginHql = "select e from Employee e where e.userid = :userid and e.password = :password"; -Employee employee = (Employee) session.createQuery( loginHql ) - ... - .uniqueResult(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/LoginScalar.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/LoginScalar.java deleted file mode 100644 index 8905b0ce4ad0..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/LoginScalar.java +++ /dev/null @@ -1,4 +0,0 @@ -String loginHql = "select e.accessLevel from Employee e where e.userid = :userid and e.password = :password"; -Employee employee = (Employee) session.createQuery( loginHql ) - ... - .uniqueResult(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Project.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Project.java deleted file mode 100644 index 94fe42c0d54b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/Project.java +++ /dev/null @@ -1,10 +0,0 @@ -@Entity -public class Project { - @Id - private Long id; - - @ManyToMany - private Set employees; - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeCriteria.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeCriteria.java deleted file mode 100644 index 384d964e076d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeCriteria.java +++ /dev/null @@ -1,10 +0,0 @@ -String userid = ...; -CriteriaBuilder cb = entityManager.getCriteriaBuilder(); -CriteriaQuery criteria = cb.createQuery( Employee.class ); -Root root = criteria.from( Employee.class ); -root.fetch( Employee_.projects ); -criteria.select( root ); -criteria.where( - cb.equal( root.get( Employee_.userid ), cb.literal( userid ) ) -); -Employee e = entityManager.createQuery( criteria ).getSingleResult(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeFetchProfile.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeFetchProfile.java deleted file mode 100644 index 297cb8cfc631..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeFetchProfile.java +++ /dev/null @@ -1,4 +0,0 @@ -String userid = ...; -session.enableFetchProfile( "employee.projects" ); -Employee e = (Employee) session.bySimpleNaturalId( Employee.class ) - .load( userid ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeHql.java b/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeHql.java deleted file mode 100644 index 11235281d064..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/fetching/extras/ProjectsForAnEmployeeHql.java +++ /dev/null @@ -1,5 +0,0 @@ -String userid = ...; -String hql = "select e from Employee e join fetch e.projects where e.userid = :userid"; -Employee e = (Employee) session.createQuery( hql ) - .setParameter( "userid", userid ) - .uniqueResult(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/flushing/Flushing.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/flushing/Flushing.xml deleted file mode 100644 index 4ac5f07df9f8..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/flushing/Flushing.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - Flushing - - - Discuss flushing of the persistence context. - - - - - - Related Topics - - - - - - - - - - - - - - - - - - - - - Flushing is the process of synchronizing the state of the persistence context to the database. TBC... - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/jdbc/Database_Access.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/jdbc/Database_Access.xml deleted file mode 100644 index f5333fec915d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/jdbc/Database_Access.xml +++ /dev/null @@ -1,699 +0,0 @@ - - - - - - Database access - - -
- ConnectionProvider - - - As an ORM tool, probably the single most important thing you need to tell Hibernate is how to connect to - your database so that it may connect on behalf of your application. This is ultimately the function of - the org.hibernate.engine.jdbc.connections.spi.ConnectionProvider - interface. Hibernate provides some out of the box implementations of this interface. ConnectionProvider - is also an extension point, so you can also use custom implementations from third parties or written yourself. - The ConnectionProvider to use is defined by the hibernate.connection.provider_class setting. - See the org.hibernate.cfg.AvailableSettings#CONNECTION_PROVIDER - - - - Generally speaking applications should not have to configure a ConnectionProvider explicitly if using - one of the Hibernate-provided implementations. Hibernate will internally determine which ConnectionProvider - to use based on the following algorithm: - - - - If hibernate.connection.provider_class is set, it takes precedence - - - - - else if hibernate.connection.datasource is set -> - - - - - else if any setting prefixed by hibernate.c3p0. is set -> - - - - - else if any setting prefixed by hibernate.proxool. is set -> - - - - - else if any setting prefixed by hibernate.hikari. is set -> - - - - - else if hibernate.connection.url is set -> - - - - - else -> - - - - - - -
- Using DataSources - - - Hibernate can integrate with a javax.sql.DataSource for obtaining - JDBC Connections. Applications would tell Hibernate about the DataSource via the (required) - hibernate.connection.datasource setting which can either specify a JNDI name - or would reference the actual DataSource instance. For cases where a JNDI name is given, be sure - to read - - - - - For JPA applications, note that hibernate.connection.datasource corresponds to - either javax.persistence.jtaDataSource or javax.persistence.nonJtaDataSource. - - - - - The DataSource ConnectionProvider also (optionally) accepts the hibernate.connection.username - and hibernate.connection.password. If specified, the form of DataSource#getConnection - accepting username and password will be used. Otherwise the no-arg form is used. - -
- -
- Using c3p0 - - - - To use this integration, the application must include the hibernate-c3p0 - module jar (as well as its dependencies) on the classpath. - - - - - Hibernate also provides support for applications to use c3p0 - connection pooling. When using this c3p0 support, a number of additional configuration settings - are recognized. - - - - Transaction isolation of the Connections is managed by the ConnectionProvider itself. See - . - - - - Additional settings - - hibernate.connection.driver_class - - - The name of the JDBC Driver class to use - - - - - hibernate.connection.url - - - The JDBC connection url. - - - - - Any settings prefixed with hibernate.connection. (other than the "special ones") - - - These all have the hibernate.connection. prefix stripped and the - rest will be passed as JDBC connection properties - - - - - hibernate.c3p0.min_size or c3p0.minPoolSize - - - The minimum size of the c3p0 pool. See - - - - - hibernate.c3p0.max_size or c3p0.maxPoolSize - - - The maximum size of the c3p0 pool. See - - - - - hibernate.c3p0.timeout or c3p0.maxIdleTime - - - The Connection idle time. See - - - - - hibernate.c3p0.max_statements or c3p0.maxStatements - - - Controls the c3p0 PreparedStatement cache size (if using). See - - - - - hibernate.c3p0.acquire_increment or c3p0.acquireIncrement - - - Number of connections c3p0 should acquire at a time when pool is exhauted. See - - - - - hibernate.c3p0.idle_test_period or c3p0.idleConnectionTestPeriod - - - Idle time before a c3p0 pooled connection is validated. See - - - - - c3p0.initialPoolSize - - - The initial c3p0 pool size. If not specified, default is to use the min pool size. - See - - - - - Any other settings prefixed with hibernate.c3p0. - - - Will have the hibernate. portion stripped and be passed to c3p0. - - - - - Any other settings prefixed with c3p0. - - - Get passed to c3p0 as is. See - - - - -
- -
- Using Proxool - - - - To use this integration, the application must include the hibernate-proxool - module jar (as well as its dependencies) on the classpath. - - - - - Hibernate also provides support for applications to use Proxool - connection pooling. - - - - Transaction isolation of the Connections is managed by the ConnectionProvider itself. See - . - - -
- Using existing Proxool pools - - - Controlled by the hibernate.proxool.existing_pool setting. If set to true, - this ConnectionProvider will use an already existing Proxool pool by alias as indicated by - the hibernate.proxool.pool_alias setting - -
- -
- Configuring Proxool via XML - - - The hibernate.proxool.xml setting names a Proxool configuration XML - file to be loaded as a classpath resource and loaded by Proxool's JAXPConfigurator. - See . - hibernate.proxool.pool_alias must be set to indicate which pool to use. - -
- -
- Configuring Proxool via Properties - - - The hibernate.proxool.properties setting names a Proxool configuration Properties - file to be loaded as a classpath resource and loaded by Proxool's PropertyConfigurator. - See . - hibernate.proxool.pool_alias must be set to indicate which pool to use. - -
-
- -
- Using Hikari - - - - To use this integration, the application must include the hibernate-hikari - module jar (as well as its dependencies) on the classpath. - - - - - Hibernate also provides support for applications to use - Hikari connection pool. - - - - Set all of your Hikari settings in Hibernate prefixed by hibernate.hikari. - and this ConnectionProvider will pick them up and pass them along to Hikari. Additionally, this - ConnectionProvider will pick up the following Hibernate-specific properties and map - them to the corresponding Hikari ones (any hibernate.hikari. prefixed ones have precedence): - - - - Hibernate-specific properties recognized by Hikari ConnectionProvider - - hibernate.connection.driver_class - - Mapped to Hikari's driverClassName setting - - - - hibernate.connection.url - - Mapped to Hikari's jdbcUrl setting - - - - hibernate.connection.username - - Mapped to Hikari's username setting - - - - hibernate.connection.password - - Mapped to Hikari's password setting - - - - hibernate.connection.isolation - - - Mapped to Hikari's transactionIsolation setting. See - . Note that - Hikari only supports JDBC standard isolation levels (apparently). - - - - - hibernate.connection.autocommit - - Mapped to Hikari's autoCommit setting - - - -
- -
- Using Hibernate's built-in (and unsupported) pooling - - - - The built-in connection pool is not supported supported for use. - - - - - This section is here just for completeness. - -
- -
- User-provided Connections - - - It is possible to use Hibernate by simply passing a Connection to use to the Session when the Session - is opened. This usage is discouraged and not discussed here. - -
- -
- ConnectionProvider support for transaction isolation setting - - All of the provided ConnectionProvider implementations, other than DataSourceConnectionProvider, - support consistent setting of transaction isolation for all Connections obtained from the underlying - pool. The value for hibernate.connection.isolation can be specified in - one of 3 formats: - - - - the integer value accepted at the JDBC level - - - - - the name of the java.sql.Connection constant field - representing the isolation you would like to use. For example, - TRANSACTION_REPEATABLE_READ for - java.sql.Connection#TRANSACTION_REPEATABLE_READ. Not that this is - only supported for JDBC standard isolations, not for isolation levels specific to - a particular JDBC driver. - - - - - a short-name version of the java.sql.Connection constant field - without the TRANSACTION_ prefix. For example, - REPEATABLE_READ for - java.sql.Connection#TRANSACTION_REPEATABLE_READ. Again, this is - only supported for JDBC standard isolations, not for isolation levels specific to - a particular JDBC driver. - - - - -
-
- - - -
- Database Dialect - - - Although SQL is relatively standardized, each database vendor uses a subset and superset of - ANSI SQL defined syntax. This is referred to as the database's dialect. - Hibernate handles variations across these dialects through its - org.hibernate.dialect.Dialect class and the various subclasses for each database - vendor. - - - - In most cases Hibernate will be able to determine the proper Dialect to use by asking some questions - of the JDBC Connection during bootstrap. For information on Hibernate's ability to determine the - proper Dialect to use (and your ability to influence that resolution), see - - - - If for some reason it is not able to determine the proper one - or you want to use a custom Dialect, you will need to set the hibernate.dialect setting. - - - - Provided Dialects - - - - - - - Dialect (short name) - Remarks - - - - - Cache71 - - Support for the CachÉ database, version 2007.1 - - - - CUBRID - - Support for the CUBRID database, version 8.3. May work with later versions. - - - - DB2 - - Support for the DB2 database - - - - DB2390 - - Support for DB2 Universal Database for OS/390, also known as DB2/390. - - - - DB2400 - - Support for DB2 Universal Database for iSeries, also known as DB2/400. - - - - DerbyTenFive - - Support for the Derby database, version 10.5 - - - - DerbyTenSix - - Support for the Derby database, version 10.6 - - - - DerbyTenSeven - - Support for the Derby database, version 10.7 - - - - Firebird - - Support for the Firebird database - - - - FrontBase - - Support for the Frontbase database - - - - H2 - - Support for the H2 database - - - - HSQL - - Support for the HSQL (HyperSQL) database - - - - Informix - - Support for the Informix database - - - - Ingres - - Support for the Ingres database, version 9.2 - - - - Ingres9 - - Support for the Ingres database, version 9.3. May work with newer versions - - - - Ingres10 - - Support for the Ingres database, version 10. May work with newer versions - - - - Interbase - - Support for the Interbase database. - - - - JDataStore - - Support for the JDataStore database - - - - McKoi - - Support for the McKoi database - - - - Mimer - - Support for the Mimer database, version 9.2.1. May work with newer versions - - - - MySQL5 - - Support for the MySQL database, version 5.x - - - - MySQL5InnoDB - - Support for the MySQL database, version 5.x preferring the InnoDB storage engine - when exporting tables. - - - - MySQL57InnoDB - - Support for the MySQL database, version 5.7 preferring the InnoDB storage engine - when exporting tables. May work with newer versions - - - - Oracle8i - - Support for the Oracle database, version 8i - - - - Oracle9i - - Support for the Oracle database, version 9i - - - - Oracle10g - - Support for the Oracle database, version 10g - - - - Pointbase - - Support for the Pointbase database - - - - PostgresPlus - - Support for the Postgres Plus database - - - - PostgreSQL81 - - Support for the PostgrSQL database, version 8.1 - - - - PostgreSQL82 - - Support for the PostgreSQL database, version 8.2 - - - - PostgreSQL9 - - Support for the PostgreSQL database, version 9. May work with later versions. - - - - Progress - - Support for the Progress database, version 9.1C. May work with newer versions. - - - - SAPDB - - Support for the SAPDB/MAXDB database. - - - - SQLServer - - Support for the SQL Server 2000 database - - - - SQLServer2005 - - Support for the SQL Server 2005 database - - - - SQLServer2008 - - Support for the SQL Server 2008 database - - - - Sybase11 - - Support for the Sybase database, up to version 11.9.2 - - - - SybaseAnywhere - - Support for the Sybase Anywhere database - - - - SybaseASE15 - - Support for the Sybase Adaptive Server Enterprise database, version 15 - - - - SybaseASE157 - - Support for the Sybase Adaptive Server Enterprise database, version 15.7. May work with newer versions. - - - - Teradata - - Support for the Teradata database - - - - TimesTen - - Support for the TimesTen database, version 5.1. May work with newer versions - - - - -
-
-
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/jndi/JNDI.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/jndi/JNDI.xml deleted file mode 100644 index 56b77d4462be..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/jndi/JNDI.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - JNDI - - - Hibernate does optionally interact with JNDI on the applications behalf. Generally - it does this when the application: - - - has asked the SessionFactory be bound to JNDI - - - has specified a DataSource to use by JNDI name - - - is using JTA transactions and the JtaPlatform needs to do JNDI lookups for TM, UT, etc - - - - - - All of these JNDI calls route through a single service whose role is - org.hibernate.engine.jndi.spi.JndiService. The standard JndiService - accepts a number of configuration settings - - - - hibernate.jndi.class - names the - javax.naming.InitialContext implementation class to use. See - javax.naming.Context#INITIAL_CONTEXT_FACTORY - - - - - hibernate.jndi.url - names the JNDI InitialContext connection url. See - javax.naming.Context.PROVIDER_URL - - - - - Any other settings prefixed with hibernate.jndi. will be collected - and passed along to the JNDI provider. - - - - - - - - The standard JndiService assumes that all JNDI calls are relative to the same InitialContext. If your - application uses multiple naming servers for whatever reason, you will need a custom JndiService - implementation to handle those details. - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/Locking.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/locking/Locking.xml deleted file mode 100644 index b8bcfa7e6627..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/Locking.xml +++ /dev/null @@ -1,332 +0,0 @@ - - - - - Locking - - Locking refers to actions taken to prevent data in a relational database from changing between the time it is read - and the time that it is used. - - - Your locking strategy can be either optimistic or pessimistic. - - - Locking strategies - - Optimistic - - - Optimistic locking assumes that multiple transactions can complete without affecting each other, and that - therefore transactions can proceed without locking the data resources that they affect. Before committing, - each transaction verifies that no other transaction has modified its data. If the check reveals conflicting - modifications, the committing transaction rolls back. - - - - - Pessimistic - - - Pessimistic locking assumes that concurrent transactions will conflict with each other, and requires resources - to be locked after they are read and only unlocked after the application has finished using the data. - - - - - - Hibernate provides mechanisms for implementing both types of locking in your applications. - -
- Optimistic - - When your application uses long transactions or conversations that span several database transactions, you can - store versioning data, so that if the same entity is updated by two conversations, the last to commit changes is - informed of the conflict, and does not override the other conversation's work. This approach guarantees some - isolation, but scales well and works particularly well in Read-Often Write-Sometimes - situations. - - - Hibernate provides two different mechanisms for storing versioning information, a dedicated version number or a - timestamp. - - - - Version number - - - - - - - - Timestamp - - - - - - - - - - A version or timestamp property can never be null for a detached instance. Hibernate detects any instance with a - null version or timestamp as transient, regardless of other unsaved-value strategies that you specify. Declaring - a nullable version or timestamp property is an easy way to avoid problems with transitive reattachment in - Hibernate, especially useful if you use assigned identifiers or composite keys. - - - -
- Dedicated version number - - The version number mechanism for optimistic locking is provided through a @Version - annotation. - - - The @Version annotation - - - Here, the version property is mapped to the OPTLOCK column, and the entity manager uses it - to detect conflicting updates, and prevent the loss of updates that would be overwritten by a - last-commit-wins strategy. - - - - The version column can be any kind of type, as long as you define and implement the appropriate - UserVersionType. - - - Your application is forbidden from altering the version number set by Hibernate. To artificially increase the - version number, see the documentation for properties - LockModeType.OPTIMISTIC_FORCE_INCREMENT or - LockModeType.PESSIMISTIC_FORCE_INCREMENTcheck in the Hibernate Entity Manager reference - documentation. - - - Database-generated version numbers - - If the version number is generated by the database, such as a trigger, use the annotation - @org.hibernate.annotations.Generated(GenerationTime.ALWAYS). - - - - Declaring a version property in <filename>hbm.xml</filename> - - - - - - column - The name of the column holding the version number. Optional, defaults to the property - name. - - - name - The name of a property of the persistent class. - - - type - The type of the version number. Optional, defaults to - integer. - - - access - Hibernate's strategy for accessing the property value. Optional, defaults to - property. - - - unsaved-value - Indicates that an instance is newly instantiated and thus unsaved. This distinguishes it - from detached instances that were saved or loaded in a previous session. The default value, - undefined, indicates that the identifier property value should be - used. Optional. - - - generated - Indicates that the version property value is generated by the database. Optional, defaults - to never. - - - insert - Whether or not to include the version column in SQL insert - statements. Defaults to true, but you can set it to false if the - database column is defined with a default value of 0. - - - - - -
- -
- Timestamp - - Timestamps are a less reliable way of optimistic locking than version numbers, but can be used by applications - for other purposes as well. Timestamping is automatically used if you the @Version annotation on a - Date or Calendar. - - - Using timestamps for optimistic locking - - - - Hibernate can retrieve the timestamp value from the database or the JVM, by reading the value you specify for - the @org.hibernate.annotations.Source annotation. The value can be either - org.hibernate.annotations.SourceType.DB or - org.hibernate.annotations.SourceType.VM. The default behavior is to use the database, and is - also used if you don't specify the annotation at all. - - - The timestamp can also be generated by the database instead of Hibernate, if you use the - @org.hibernate.annotations.Generated(GenerationTime.ALWAYS) annotation. - - - The timestamp element in <filename>hbm.xml</filename> - - - - - - column - The name of the column which holds the timestamp. Optional, defaults to the property - namel - - - name - The name of a JavaBeans style property of Java type Date or Timestamp of the persistent - class. - - - access - The strategy Hibernate uses to access the property value. Optional, defaults to - property. - - - unsaved-value A version property which indicates than instance is newly - instantiated, and unsaved. This distinguishes it from detached instances that were saved or loaded in a - previous session. The default value of undefined indicates that Hibernate uses the - identifier property value. - - - source - Whether Hibernate retrieves the timestamp from the database or the current - JVM. Database-based timestamps incur an overhead because Hibernate needs to query the database each time - to determine the incremental next value. However, database-derived timestamps are safer to use in a - clustered environment. Not all database dialects are known to support the retrieval of the database's - current timestamp. Others may also be unsafe for locking, because of lack of precision. - - - generated - Whether the timestamp property value is generated by the database. Optional, defaults to - never. - - - - - -
- -
- -
- Pessimistic - - Typically, you only need to specify an isolation level for the JDBC connections and let the database handle - locking issues. If you do need to obtain exclusive pessimistic locks or re-obtain locks at the start of a new - transaction, Hibernate gives you the tools you need. - - - - Hibernate always uses the locking mechanism of the database, and never lock objects in memory. - - -
- The <classname>LockMode</classname> class - - The LockMode class defines the different lock levels that Hibernate can acquire. - - - - - - LockMode.WRITE - acquired automatically when Hibernate updates or inserts a row. - - - LockMode.UPGRADE - acquired upon explicit user request using SELECT ... FOR UPDATE on databases - which support that syntax. - - - LockMode.UPGRADE_NOWAIT - acquired upon explicit user request using a SELECT ... FOR UPDATE NOWAIT in - Oracle. - - - LockMode.UPGRADE_SKIPLOCKED - acquired upon explicit user request using a SELECT ... FOR UPDATE SKIP LOCKED in - Oracle, or SELECT ... with (rowlock,updlock,readpast) in SQL Server. - - - LockMode.READ - acquired automatically when Hibernate reads data under Repeatable Read or - Serializable isolation level. It can be re-acquired by explicit user - request. - - - LockMode.NONE - The absence of a lock. All objects switch to this lock mode at the end of a - Transaction. Objects associated with the session via a call to update() or - saveOrUpdate() also start out in this lock mode. - - - - - - The explicit user request mentioned above occurs as a consequence of any of the following actions: - - - - - A call to Session.load(), specifying a LockMode. - - - - - A call to Session.lock(). - - - - - A call to Query.setLockMode(). - - - - - If you call Session.load() with option , - or , and the requested object is not already - loaded by the session, the object is loaded using SELECT ... FOR UPDATE. If you call - load() for an object that is already loaded with a less restrictive lock than the one - you request, Hibernate calls lock() for that object. - - - Session.lock() performs a version number check if the specified lock mode is - READ, UPGRADE, UPGRADE_NOWAIT or - UPGRADE_SKIPLOCKED. In the case of UPGRADE, - UPGRADE_NOWAIT or UPGRADE_SKIPLOCKED, SELECT ... FOR UPDATE - syntax is used. - - - If the requested lock mode is not supported by the database, Hibernate uses an appropriate alternate mode - instead of throwing an exception. This ensures that applications are portable. - -
-
-
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/timestamp_version.java b/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/timestamp_version.java deleted file mode 100644 index 20724336cfe4..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/timestamp_version.java +++ /dev/null @@ -1,6 +0,0 @@ -@Entity -public class Flight implements Serializable { -... - @Version - public Date getLastUpdate() { ... } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/timestamp_version.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/timestamp_version.xml deleted file mode 100644 index 51ed52a0550d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/timestamp_version.xml +++ /dev/null @@ -1,15 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/updating_version.java b/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/updating_version.java deleted file mode 100644 index b68c41c370ba..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/updating_version.java +++ /dev/null @@ -1,9 +0,0 @@ -Session session = sessionFactory.openSession(); -Transaction tx = session.beginTransaction(); -String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName"; -int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); -tx.commit(); -session.close(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/version_annotation.java b/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/version_annotation.java deleted file mode 100644 index 416ab76e4cfb..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/version_annotation.java +++ /dev/null @@ -1,7 +0,0 @@ -@Entity -public class Flight implements Serializable { -... - @Version - @Column(name="OPTLOCK") - public Integer getVersion() { ... } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/version_property.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/version_property.xml deleted file mode 100644 index 517dc7e7670e..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/locking/extras/version_property.xml +++ /dev/null @@ -1,16 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/Multi_Tenancy.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/Multi_Tenancy.xml deleted file mode 100644 index 94ee9ae1b941..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/Multi_Tenancy.xml +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - Multi-tenancy - -
- What is multi-tenancy? - - The term multi-tenancy in general is applied to software development to indicate an architecture in which - a single running instance of an application simultaneously serves multiple clients (tenants). This is - highly common in SaaS solutions. Isolating information (data, customizations, etc) pertaining to the - various tenants is a particular challenge in these systems. This includes the data owned by each tenant - stored in the database. It is this last piece, sometimes called multi-tenant data, on which we will focus. - -
- -
- Multi-tenant data approaches - - There are 3 main approaches to isolating information in these multi-tenant systems which goes hand-in-hand - with different database schema definitions and JDBC setups. - - - - - Each approach has pros and cons as well as specific techniques and considerations. Such - topics are beyond the scope of this documentation. Many resources exist which delve into these - other topics. One example is - which does a great job of covering these topics. - - - -
- Separate database - - - - - - - - - - - - Each tenant's data is kept in a physically separate database instance. JDBC Connections would point - specifically to each database, so any pooling would be per-tenant. A general application approach - here would be to define a JDBC Connection pool per-tenant and to select the pool to use based on the - tenant identifier associated with the currently logged in user. - -
- -
- Separate schema - - - - - - - - - - - - Each tenant's data is kept in a distinct database schema on a single database instance. There are 2 - different ways to define JDBC Connections here: - - - - Connections could point specifically to each schema, as we saw with the - Separate database approach. This is an option provided that - the driver supports naming the default schema in the connection URL or if the - pooling mechanism supports naming a schema to use for its Connections. Using this - approach, we would have a distinct JDBC Connection pool per-tenant where the pool to use - would be selected based on the tenant identifier associated with the - currently logged in user. - - - - - Connections could point to the database itself (using some default schema) but - the Connections would be altered using the SQL SET SCHEMA (or similar) - command. Using this approach, we would have a single JDBC Connection pool for use to - service all tenants, but before using the Connection it would be altered to reference - the schema named by the tenant identifier associated with the currently - logged in user. - - - - -
- -
- Partitioned (discriminator) data - - - - - - - - - - - - All data is kept in a single database schema. The data for each tenant is partitioned by the use of - partition value or discriminator. The complexity of this discriminator might range from a simple - column value to a complex SQL formula. Again, this approach would use a single Connection pool - to service all tenants. However, in this approach the application needs to alter each and every - SQL statement sent to the database to reference the tenant identifier discriminator. - -
-
- -
- Multi-tenancy in Hibernate - - Using Hibernate with multi-tenant data comes down to both an API and then integration piece(s). As - usual Hibernate strives to keep the API simple and isolated from any underlying integration complexities. - The API is really just defined by passing the tenant identifier as part of opening any session. - - - Specifying tenant identifier from <interfacename>SessionFactory</interfacename> - - - - Additionally, when specifying configuration, a org.hibernate.MultiTenancyStrategy - should be named using the hibernate.multiTenancy setting. Hibernate will perform - validations based on the type of strategy you specify. The strategy here correlates to the isolation - approach discussed above. - - - - NONE - - - (the default) No multi-tenancy is expected. In fact, it is considered an error if a tenant - identifier is specified when opening a session using this strategy. - - - - - SCHEMA - - - Correlates to the separate schema approach. It is an error to attempt to open a session without - a tenant identifier using this strategy. Additionally, a - MultiTenantConnectionProvider - must be specified. - - - - - DATABASE - - - Correlates to the separate database approach. It is an error to attempt to open a session without - a tenant identifier using this strategy. Additionally, a - MultiTenantConnectionProvider - must be specified. - - - - - DISCRIMINATOR - - - Correlates to the partitioned (discriminator) approach. It is an error to attempt to open a - session without a tenant identifier using this strategy. This strategy is not yet implemented - in Hibernate as of 4.0 and 4.1. Its support is planned for 5.0. - - - - - -
- <interfacename>MultiTenantConnectionProvider</interfacename> - - When using either the DATABASE or SCHEMA approach, Hibernate needs to be able to obtain Connections - in a tenant specific manner. That is the role of the - MultiTenantConnectionProvider - contract. Application developers will need to provide an implementation of this - contract. Most of its methods are extremely self-explanatory. The only ones which might not be are - getAnyConnection and releaseAnyConnection. It is - important to note also that these methods do not accept the tenant identifier. Hibernate uses these - methods during startup to perform various configuration, mainly via the - java.sql.DatabaseMetaData object. - - - The MultiTenantConnectionProvider to use can be specified in a number of - ways: - - - - - Use the hibernate.multi_tenant_connection_provider setting. It could - name a MultiTenantConnectionProvider instance, a - MultiTenantConnectionProvider implementation class reference or - a MultiTenantConnectionProvider implementation class name. - - - - - Passed directly to the org.hibernate.boot.registry.StandardServiceRegistryBuilder. - - - - - If none of the above options match, but the settings do specify a - hibernate.connection.datasource value, Hibernate will assume it should - use the specific - DataSourceBasedMultiTenantConnectionProviderImpl - implementation which works on a number of pretty reasonable assumptions when running inside of - an app server and using one javax.sql.DataSource per tenant. - See its javadocs for more details. - - - -
- -
- <interfacename>CurrentTenantIdentifierResolver</interfacename> - - org.hibernate.context.spi.CurrentTenantIdentifierResolver is a contract - for Hibernate to be able to resolve what the application considers the current tenant identifier. - The implementation to use is either passed directly to Configuration via its - setCurrentTenantIdentifierResolver method. It can also be specified via - the hibernate.tenant_identifier_resolver setting. - - - There are 2 situations where CurrentTenantIdentifierResolver is used: - - - - - The first situation is when the application is using the - org.hibernate.context.spi.CurrentSessionContext feature in - conjunction with multi-tenancy. In the case of the current-session feature, Hibernate will - need to open a session if it cannot find an existing one in scope. However, when a session - is opened in a multi-tenant environment the tenant identifier has to be specified. This is - where the CurrentTenantIdentifierResolver comes into play; - Hibernate will consult the implementation you provide to determine the tenant identifier to use - when opening the session. In this case, it is required that a - CurrentTenantIdentifierResolver be supplied. - - - - - The other situation is when you do not want to have to explicitly specify the tenant - identifier all the time as we saw in . If a - CurrentTenantIdentifierResolver has been specified, Hibernate - will use it to determine the default tenant identifier to use when opening the session. - - - - - Additionally, if the CurrentTenantIdentifierResolver implementation - returns true for its validateExistingCurrentSessions - method, Hibernate will make sure any existing sessions that are found in scope have a matching - tenant identifier. This capability is only pertinent when the - CurrentTenantIdentifierResolver is used in current-session settings. - -
- -
- Caching - - Multi-tenancy support in Hibernate works seamlessly with the Hibernate second level cache. The key - used to cache data encodes the tenant identifier. - -
- -
- Odds and ends - - Currently schema export will not really work with multi-tenancy. That may not change. - - - The JPA expert group is in the process of defining multi-tenancy support for the upcoming 2.1 - version of the specification. - -
-
- -
- Strategies for <interfacename>MultiTenantConnectionProvider</interfacename> implementors - - Implementing MultiTenantConnectionProvider using different connection pools - - - - The approach above is valid for the DATABASE approach. It is also valid for the SCHEMA approach - provided the underlying database allows naming the schema to which to connect in the connection URL. - - - Implementing MultiTenantConnectionProvider using single connection pool - - - - This approach is only relevant to the SCHEMA approach. - -
-
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-multi-cp.java b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-multi-cp.java deleted file mode 100644 index 56dcb7a9cda7..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-multi-cp.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Simplisitc implementation for illustration purposes supporting 2 hard coded providers (pools) and leveraging - * the support class {@link org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider} - */ -public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider { - private final ConnectionProvider acmeProvider = ConnectionProviderUtils.buildConnectionProvider( "acme" ); - private final ConnectionProvider jbossProvider = ConnectionProviderUtils.buildConnectionProvider( "jboss" ); - - @Override - protected ConnectionProvider getAnyConnectionProvider() { - return acmeProvider; - } - - @Override - protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) { - if ( "acme".equals( tenantIdentifier ) ) { - return acmeProvider; - } - else if ( "jboss".equals( tenantIdentifier ) ) { - return jbossProvider; - } - throw new HibernateException( "Unknown tenant identifier" ); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-single-cp.java b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-single-cp.java deleted file mode 100644 index 6d41cfd2fd92..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/MultiTenantConnectionProviderImpl-single-cp.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Simplisitc implementation for illustration purposes showing a single connection pool used to serve - * multiple schemas using "connection altering". Here we use the T-SQL specific USE command; Oracle - * users might use the ALTER SESSION SET SCHEMA command; etc. - */ -public class MultiTenantConnectionProviderImpl - implements MultiTenantConnectionProvider, Stoppable { - private final ConnectionProvider connectionProvider = ConnectionProviderUtils.buildConnectionProvider( "master" ); - - @Override - public Connection getAnyConnection() throws SQLException { - return connectionProvider.getConnection(); - } - - @Override - public void releaseAnyConnection(Connection connection) throws SQLException { - connectionProvider.closeConnection( connection ); - } - - @Override - public Connection getConnection(String tenantIdentifier) throws SQLException { - final Connection connection = getAnyConnection(); - try { - connection.createStatement().execute( "USE " + tenanantIdentifier ); - } - catch ( SQLException e ) { - throw new HibernateException( - "Could not alter JDBC connection to specified schema [" + - tenantIdentifier + "]", - e - ); - } - return connection; - } - - @Override - public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { - try { - connection.createStatement().execute( "USE master" ); - } - catch ( SQLException e ) { - // on error, throw an exception to make sure the connection is not returned to the pool. - // your requirements may differ - throw new HibernateException( - "Could not alter JDBC connection to specified schema [" + - tenantIdentifier + "]", - e - ); - } - connectionProvider.closeConnection( connection ); - } - - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/tenant-identifier-from-SessionFactory.java b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/tenant-identifier-from-SessionFactory.java deleted file mode 100644 index bbc859be3f0e..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/extras/tenant-identifier-from-SessionFactory.java +++ /dev/null @@ -1,4 +0,0 @@ -Session session = sessionFactory.withOptions() - .tenantIdentifier( yourTenantIdentifier ) - ... - .openSession(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_database.png b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_database.png deleted file mode 100644 index 84822ec4e5b0..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_database.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_database.svg b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_database.svg deleted file mode 100644 index f0d9e7baa864..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_database.svg +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -Application - - - - - - - - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_discriminator.png b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_discriminator.png deleted file mode 100644 index dcc3ad6ed9a1..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_discriminator.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_discriminator.svg b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_discriminator.svg deleted file mode 100644 index afb291332695..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_discriminator.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -TENANT_ID VARCHAR -) -Application - - - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_schema.png b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_schema.png deleted file mode 100644 index 2756ba2ed605..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_schema.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_schema.svg b/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_schema.svg deleted file mode 100644 index 6fbb7a40e8d0..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/multitenancy/images/multitenacy_schema.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -CUSTOMER ( -ID BIGINT, -NAME VARCHAR, -... -) -Application - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/OSGi.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/OSGi.xml deleted file mode 100644 index 1281f2ea6ddf..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/OSGi.xml +++ /dev/null @@ -1,428 +0,0 @@ - - - - - - OSGi - - - The Open Services Gateway initiative (OSGi) specification describes a dynamic, modularized system. "Bundles" - (components) can be installed, activated, deactivated, and uninstalled during runtime, without requiring - a system restart. OSGi frameworks manage bundles' dependencies, packages, and classes. The framework - is also in charge of ClassLoading, managing visibility of packages between bundles. Further, service - registry and discovery is provided through a "whiteboard" pattern. - - - - OSGi environments present numerous, unique challenges. Most notably, the dynamic nature of available - bundles during runtime can require significant architectural considerations. Also, - architectures must allow the OSGi-specific ClassLoading and service registration/discovery. - - - - -
- OSGi Specification and Environment - - - Hibernate targets the OSGi 4.3 spec or later. It was necessary to start with 4.3, over 4.2, due to our - dependency on OSGi's BundleWiring for entity/mapping scanning. - - - - Hibernate supports three types of configurations within OSGi. - - - - Container-Managed JPA: - - - Unmanaged JPA: - - - Unmanaged Native: - - - -
- -
- hibernate-osgi - - Rather than embed OSGi capabilities into hibernate-core, hibernate-entitymanager, and sub-modules, - hibernate-osgi was created. It's purposefully separated, isolating all OSGi dependencies. It provides an - OSGi-specific ClassLoader (aggregates the container's CL with core and entitymanager CLs), JPA persistence - provider, SF/EMF bootstrapping, entities/mappings scanner, and service management. - -
- -
- features.xml - - Apache Karaf environments tend to make heavy use of its "features" concept, where a feature is a set of order-specific - bundles focused on a concise capability. These features are typically defined in a features.xml file. - Hibernate produces and releases its own features.xml that defines a core hibernate-orm, - as well as additional features for optional functionality (caching, Envers, etc.). - This is included in the binary distribution, as well as deployed to the JBoss Nexus repository - (using the org.hibernate groupId and hibernate-osgi with the karaf.xml classifier). - - - - Note that our features are versioned using the same ORM artifact versions they wrap. Also note that the features are - heavily tested against Karaf 3.0.3 as a part of our PaxExam-based integration tests. However, they'll likely work - on other versions as well. - - - - hibernate-osgi, theoretically, supports a variety of OSGi containers, such as Equinox. In that case, - please use features.xml as a reference for necessary bundles to activate and their correct ordering. However, - note that Karaf starts a number of bundles automatically, several of which would need to be installed manually - on alternatives. - -
- -
- QuickStarts/Demos - - All three configurations have a QuickStart/Demo available in the - hibernate-demos project: - -
- -
- Container-Managed JPA - - - The Enterprise OSGi specification includes container-managed JPA. The container is responsible for - discovering persistence units in bundles and automatically creating the EntityManagerFactory (one EMF per PU). - It uses the JPA provider (hibernate-osgi) that has registered itself with the OSGi - PersistenceProvider service. - - -
- Enterprise OSGi JPA Container - - In order to utilize container-managed JPA, an Enterprise OSGi JPA container must be active in the runtime. - In Karaf, this means Aries JPA, which is included out-of-the-box (simply activate the jpa - and transaction features). Originally, we intended to include those dependencies within our own - features.xml. However, after guidance from the Karaf and Aries teams, it was pulled out. - This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead having - the user choose which to use. - - - That being said, the QuickStart/Demo projects include a sample - features.xml - showing which features need activated in Karaf in order to support this environment. As mentioned, use this - purely as a reference! - -
- -
- persistence.xml - - Similar to any other JPA setup, your bundle must include a persistence.xml file. - This is typically located in META-INF. - -
- -
- DataSource - - Typical Enterprise OSGi JPA usage includes a DataSource installed in the container. Your - bundle's persistence.xml calls out the DataSource through JNDI. For example, you could - install the following H2 DS. You can deploy the DS manually (Karaf has a deploy dir), or - through a "blueprint bundle" (blueprint:file:/[PATH]/datasource-h2.xml). - - - - datasource-h2.xml - - - - - That DS is then used by your persistence.xml persistence-unit. The following works - in Karaf, but the names may need tweaked in alternative containers. - - - - META-INF/persistence.xml - <jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/h2ds)</jta-data-source> - -
- -
- Bundle Package Imports - - Your bundle's manifest will need to import, at a minimum, - - - javax.persistence - - - - org.hibernate.proxy and javassist.util.proxy, due to - Hibernate's ability to return proxies for lazy initialization (Javassist enhancement - occurs on the entity's ClassLoader during runtime). - - - - -
- -
- Obtaining an EntityManger - - The easiest, and most supported, method of obtaining an EntityManager utilizes OSGi's - OSGI-INF/blueprint/blueprint.xml in your bundle. The container takes the name of your - persistence unit, then automatically injects - an EntityManager instance into your given bean attribute. - - - - OSGI-INF/blueprint/blueprint.xml - - -
-
- -
- Unmanaged JPA - - - Hibernate also supports the use of JPA through hibernate-entitymanager, unmanaged by the OSGi - container. The client bundle is responsible for managing the EntityManagerFactory and EntityManagers. - - -
- persistence.xml - - Similar to any other JPA setup, your bundle must include a persistence.xml file. - This is typically located in META-INF. - -
- -
- Bundle Package Imports - - Your bundle's manifest will need to import, at a minimum, - - - javax.persistence - - - - org.hibernate.proxy and javassist.util.proxy, due to - Hibernate's ability to return proxies for lazy initialization (Javassist enhancement - occurs on the entity's ClassLoader during runtime) - - - - - JDBC driver package (example: org.h2) - - - - - org.osgi.framework, necessary to discover the EMF (described below) - - - - -
- -
- Obtaining an EntityMangerFactory - - hibernate-osgi registers an OSGi service, using the JPA PersistenceProvider interface - name, that bootstraps and creates an EntityManagerFactory specific for OSGi - environments. It is VITAL that your EMF be obtained through the service, rather than creating it - manually. The service handles the OSGi ClassLoader, discovered extension points, scanning, etc. Manually - creating an EntityManagerFactory is guaranteed to NOT work during runtime! - - - Discover/Use EntityManagerFactory - - -
-
- -
- Unmanaged Native - - - Native Hibernate use is also supported. The client bundle is responsible for managing the - SessionFactory and Sessions. - - -
- Bundle Package Imports - - Your bundle's manifest will need to import, at a minimum, - - - javax.persistence - - - - org.hibernate.proxy and javassist.util.proxy, due to - Hibernate's ability to return proxies for lazy initialization (Javassist enhancement - occurs on the entity's ClassLoader during runtime) - - - - - JDBC driver package (example: org.h2) - - - - - org.osgi.framework, necessary to discover the SF (described below) - - - - - org.hibernate.* packages, as necessary (ex: cfg, criterion, service, etc.) - - - - -
- -
- Obtaining an SessionFactory - - hibernate-osgi registers an OSGi service, using the SessionFactory interface - name, that bootstraps and creates an SessionFactory specific for OSGi - environments. It is VITAL that your SF be obtained through the service, rather than creating it - manually. The service handles the OSGi ClassLoader, discovered extension points, scanning, etc. Manually - creating an SessionFactory is guaranteed to NOT work during runtime! - - - Discover/Use EntityManagerFactory - - -
-
- -
- Optional Modules - - - The unmanaged-native - demo project displays the use of optional Hibernate modules. Each module adds additional - dependency bundles that must first be activated, either manually or through an additional feature. - As of ORM 4.2, Envers is fully supported. Support for C3P0, Proxool, EhCache, and Infinispan were added in - 4.3, however none of their 3rd party libraries currently work in OSGi (lots of ClassLoader problems, etc.). - We're tracking the issues in JIRA. - -
- -
- Extension Points - - - Multiple contracts exist to allow applications to integrate with and extend Hibernate capabilities. Most - apps utilize JDK services to provide their implementations. hibernate-osgi supports the same - extensions through OSGi services. Implement and register them in any of the three configurations. - hibernate-osgi will discover and integrate them during EMF/SF bootstrapping. Supported extension points - are as follows. The specified interface should be used during service registration. - - - - org.hibernate.integrator.spi.Integrator (as of 4.2) - - - org.hibernate.boot.registry.selector.StrategyRegistrationProvider (as of 4.3) - - - org.hibernate.boot.model.TypeContributor (as of 4.3) - - - JTA's javax.transaction.TransactionManager and - javax.transaction.UserTransaction (as of 4.2), however these are typically - provided by the OSGi container. - - - - - - The easiest way to register extension point implementations is through a blueprint.xml - file. Add OSGI-INF/blueprint/blueprint.xml to your classpath. Envers' blueprint - is a great example: - - - - Example extension point registrations in blueprint.xml - - - - - Extension points can also be registered programmatically with - BundleContext#registerService, typically within your - BundleActivator#start. - -
- -
- Caveats - - - - - Technically, multiple persistence units are supported by Enterprise OSGi JPA and unmanaged - Hibernate JPA use. However, we cannot currently support this in OSGi. In Hibernate 4, only one - instance of the OSGi-specific ClassLoader is used per Hibernate bundle, mainly due to heavy use of - static TCCL utilities. We hope to support one OSGi ClassLoader per persistence unit in - Hibernate 5. - - - - - Scanning is supported to find non-explicitly listed entities and mappings. However, they MUST be - in the same bundle as your persistence unit (fairly typical anyway). Our OSGi ClassLoader only - considers the "requesting bundle" (hence the requirement on using services to create EMF/SF), - rather than attempting to scan all available bundles. This is primarily for versioning - considerations, collision protections, etc. - - - - - Some containers (ex: Aries) always return true for - PersistenceUnitInfo#excludeUnlistedClasses, - even if your persistence.xml explicitly has exclude-unlisted-classes set - to false. They claim it's to protect JPA providers from having to implement - scanning ("we handle it for you"), even though we still want to support it in many cases. The work - around is to set hibernate.archive.autodetection to, for example, - hbm,class. This tells hibernate to ignore the excludeUnlistedClasses value and - scan for *.hbm.xml and entities regardless. - - - - - Scanning does not currently support annotated packages on package-info.java. - - - - - Currently, Hibernate OSGi is primarily tested using Apache Karaf and Apache Aries JPA. Additional - testing is needed with Equinox, Gemini, and other container providers. - - - - - Hibernate ORM has many dependencies that do not currently provide OSGi manifests. - The QuickStart tutorials make heavy use of 3rd party bundles (SpringSource, ServiceMix) or the - wrap:... operator. - - - -
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/NativeHibernateUtil.java b/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/NativeHibernateUtil.java deleted file mode 100644 index b68ff1b9b131..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/NativeHibernateUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -public class HibernateUtil { - - private SessionFactory sf; - - public Session getSession() { - return getSessionFactory().openSession(); - } - - private SessionFactory getSessionFactory() { - if ( sf == null ) { - Bundle thisBundle = FrameworkUtil.getBundle( HibernateUtil.class ); - BundleContext context = thisBundle.getBundleContext(); - - ServiceReference sr = context.getServiceReference( SessionFactory.class.getName() ); - sf = (SessionFactory) context.getService( sr ); - } - return sf; - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/UnmanagedJPAHibernateUtil.java b/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/UnmanagedJPAHibernateUtil.java deleted file mode 100644 index 7aa88d5994eb..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/UnmanagedJPAHibernateUtil.java +++ /dev/null @@ -1,21 +0,0 @@ -public class HibernateUtil { - - private EntityManagerFactory emf; - - public EntityManager getEntityManager() { - return getEntityManagerFactory().createEntityManager(); - } - - private EntityManagerFactory getEntityManagerFactory() { - if ( emf == null ) { - Bundle thisBundle = FrameworkUtil.getBundle( HibernateUtil.class ); - BundleContext context = thisBundle.getBundleContext(); - - ServiceReference serviceReference = context.getServiceReference( PersistenceProvider.class.getName() ); - PersistenceProvider persistenceProvider = (PersistenceProvider) context.getService( serviceReference ); - - emf = persistenceProvider.createEntityManagerFactory( "YourPersistenceUnitName", null ); - } - return emf; - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/blueprint.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/blueprint.xml deleted file mode 100644 index 92822a2e6f06..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/blueprint.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/datasource-h2.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/datasource-h2.xml deleted file mode 100644 index 951eccfb44cd..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/datasource-h2.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/extension_point_blueprint.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/extension_point_blueprint.xml deleted file mode 100644 index cdef1308bd98..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/osgi/extras/extension_point_blueprint.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/PersistenceContext.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/PersistenceContext.xml deleted file mode 100644 index 31c5a59f6eef..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/PersistenceContext.xml +++ /dev/null @@ -1,357 +0,0 @@ - - - - - - Persistence Contexts - - - - Both the org.hibernate.Session API and - javax.persistence.EntityManager API represent a context for dealing with - persistent data. This concept is called a persistence context. Persistent data has a - state in relation to both a persistence context and the underlying database. - - - - Entity states - - - transient - the entity has just been instantiated and is - not associated with a persistence context. It has no persistent representation in the database and - typically no identifier value has been assigned. - - - - - managed, or persistent - the entity has an associated identifier - and is associated with a persistence context. It may or may not physically exist in the database - yet. - - - - - detached - the entity has an associated identifier, but is no longer associated with - a persistence context (usually because the persistence context was closed or the instance was evicted - from the context) - - - - - removed - the entity has an associated identifier and is associated with a persistence - context, however it is scheduled for removal from the database. - - - - - - Much of the org.hibernate.Session and - javax.persistence.EntityManager methods deal with moving entities between these - states. - - - -
- Making entities persistent - - - Once you've created a new entity instance (using the standard new operator) it is in - new state. You can make it persistent by associating it to either a - org.hibernate.Session or - javax.persistence.EntityManager - - - - Example of making an entity persistent - - - - - - org.hibernate.Session also has a method named persist - which follows the exact semantic defined in the JPA specification for the persist - method. It is this method on org.hibernate.Session to which the - Hibernate javax.persistence.EntityManager implementation delegates. - - - - If the DomesticCat entity type has a generated identifier, the value is associated - to the instance when the save or persist is called. If the - identifier is not automatically generated, the application-assigned (usually natural) key value has to be - set on the instance before save or persist is called. - -
- -
- Deleting entities - - Entities can also be deleted. - - - Example of deleting an entity - - - - - It is important to note that Hibernate itself can handle deleting detached state. JPA, however, disallows - it. The implication here is that the entity instance passed to the - org.hibernate.Session delete method can be either - in managed or detached state, while the entity instance passed to remove on - javax.persistence.EntityManager must be in managed state. - -
- -
- Obtain an entity reference without initializing its data - - Sometimes referred to as lazy loading, the ability to obtain a reference to an entity without having to - load its data is hugely important. The most common case being the need to create an association between - an entity and another, existing entity. - - - Example of obtaining an entity reference without initializing its data - - - - - The above works on the assumption that the entity is defined to allow lazy loading, generally through - use of runtime proxies. For more information see . In both - cases an exception will be thrown later if the given entity does not refer to actual database state if and - when the application attempts to use the returned proxy in any way that requires access to its data. - -
- -
- Obtain an entity with its data initialized - - - It is also quite common to want to obtain an entity along with with its data, for display for example. - - - Example of obtaining an entity reference with its data initialized - - - - - In both cases null is returned if no matching database row was found. - -
- -
- Obtain an entity by natural-id - - - In addition to allowing to load by identifier, Hibernate allows applications to load by declared - natural identifier. - - - Example of simple natural-id access - - - - Example of natural-id access - - - - Just like we saw above, access entity data by natural id allows both the load - and getReference forms, with the same semantics. - - - - Accessing persistent data by identifier and by natural-id is consistent in the Hibernate API. Each defines - the same 2 data access methods: - - - - getReference - - - Should be used in cases where the identifier is assumed to exist, where non-existence would be - an actual error. Should never be used to test existence. That is because this method will - prefer to create and return a proxy if the data is not already associated with the Session - rather than hit the database. The quintessential use-case for using this method is to create - foreign-key based associations. - - - - - load - - - Will return the persistent data associated with the given identifier value or null if that - identifier does not exist. - - - - - - In addition to those 2 methods, each also defines the method with accepting - a org.hibernate.LockOptions argument. Locking is discussed in a separate - chapter. - -
- -
- Refresh entity state - - - You can reload an entity instance and it's collections at any time. - - - - Example of refreshing entity state - - - - - - One case where this is useful is when it is known that the database state has changed since the data was - read. Refreshing allows the current database state to be pulled into the entity instance and the - persistence context. - - - - Another case where this might be useful is when database triggers are used to initialize some of the - properties of the entity. Note that only the entity instance and its collections are refreshed unless you - specify REFRESH as a cascade style of any associations. However, please note that - Hibernate has the capability to handle this automatically through its notion of generated properties. - See the discussion of non-identifier generated attributes in the - Hibernate User Guide - -
- -
- Modifying managed/persistent state - - - Entities in managed/persistent state may be manipulated by the application and any changes will be - automatically detected and persisted when the persistence context is flushed. There is no need to call a - particular method to make your modifications persistent. - - - - Example of modifying managed state - - - -
- -
- Working with detached data - - - Detachment is the process of working with data outside the scope of any persistence context. Data becomes - detached in a number of ways. Once the persistence context is closed, all data that was associated with it - becomes detached. Clearing the persistence context has the same effect. Evicting a particular entity - from the persistence context makes it detached. And finally, serialization will make the deserialized form - be detached (the original instance is still managed). - - - - Detached data can still be manipulated, however the persistence context will no longer automatically know - about these modification and the application will need to intervene to make the changes persistent. - - -
- Reattaching detached data - - Reattachment is the process of taking an incoming entity instance that is in detached state - and re-associating it with the current persistence context. - - - - JPA does not provide for this model. This is only available through Hibernate - org.hibernate.Session. - - - - Example of reattaching a detached entity - - - - - The method name update is a bit misleading here. It does not mean that an - SQL UPDATE is immediately performed. It does, however, mean that - an SQL UPDATE will be performed when the persistence context is - flushed since Hibernate does not know its previous state against which to compare for changes. Unless - the entity is mapped with select-before-update, in which case Hibernate will - pull the current state from the database and see if an update is needed. - - - Provided the entity is detached, update and saveOrUpdate - operate exactly the same. - -
- -
- Merging detached data - - Merging is the process of taking an incoming entity instance that is in detached state and copying its - data over onto a new instance that is in managed state. - - - Visualizing merge - - - - That is not exactly what happens, but its a good visualization. - - - Example of merging a detached entity - - - -
- -
- - -
- Checking persistent state - - - An application can verify the state of entities and collections in relation to the persistence context. - - - Examples of verifying managed state - - - - - Examples of verifying laziness - - - - - In JPA there is an alternative means to check laziness using the following - javax.persistence.PersistenceUtil pattern. However, the - javax.persistence.PersistenceUnitUtil is recommended where ever possible - - - Alternative JPA means to verify laziness - - - -
- -
- Accessing Hibernate APIs from JPA - - JPA defines an incredibly useful method to allow applications access to the APIs of the underlying provider. - - - Usage of EntityManager.unwrap - - -
- - - -
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithHibernate.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithHibernate.java deleted file mode 100644 index 1c166cdca096..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithHibernate.java +++ /dev/null @@ -1,9 +0,0 @@ -if ( Hibernate.isInitialized( customer.getAddress() ) { - //display address if loaded -} -if ( Hibernate.isInitialized( customer.getOrders()) ) ) { - //display orders if loaded -} -if (Hibernate.isPropertyInitialized( customer, "detailedBio" ) ) { - //display property detailedBio if loaded -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithJPA.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithJPA.java deleted file mode 100644 index 50751d25d3f0..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithJPA.java +++ /dev/null @@ -1,10 +0,0 @@ -javax.persistence.PersistenceUnitUtil jpaUtil = entityManager.getEntityManagerFactory().getPersistenceUnitUtil(); -if ( jpaUtil.isLoaded( customer.getAddress() ) { - //display address if loaded -} -if ( jpaUtil.isLoaded( customer.getOrders()) ) ) { - //display orders if loaded -} -if (jpaUtil.isLoaded( customer, "detailedBio" ) ) { - //display property detailedBio if loaded -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithJPA2.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithJPA2.java deleted file mode 100644 index 9581b5d0e266..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/CheckingLazinessWithJPA2.java +++ /dev/null @@ -1,10 +0,0 @@ -javax.persistence.PersistenceUtil jpaUtil = javax.persistence.Persistence.getPersistenceUtil(); -if ( jpaUtil.isLoaded( customer.getAddress() ) { - //display address if loaded -} -if ( jpaUtil.isLoaded( customer.getOrders()) ) ) { - //display orders if loaded -} -if (jpaUtil.isLoaded(customer, "detailedBio") ) { - //display property detailedBio if loaded -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ContainsWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ContainsWithEM.java deleted file mode 100644 index 12aaaff765e4..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ContainsWithEM.java +++ /dev/null @@ -1 +0,0 @@ -assert entityManager.contains( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ContainsWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ContainsWithSession.java deleted file mode 100644 index acb40fed3ef7..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ContainsWithSession.java +++ /dev/null @@ -1 +0,0 @@ -assert session.contains( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/DeletingWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/DeletingWithEM.java deleted file mode 100644 index 0938f5a3bf28..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/DeletingWithEM.java +++ /dev/null @@ -1 +0,0 @@ -entityManager.remove( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/DeletingWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/DeletingWithSession.java deleted file mode 100644 index 25831ee34690..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/DeletingWithSession.java +++ /dev/null @@ -1 +0,0 @@ -session.delete( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/GetReferenceWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/GetReferenceWithEM.java deleted file mode 100644 index e45609a42f0d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/GetReferenceWithEM.java +++ /dev/null @@ -1,2 +0,0 @@ -Book book = new Book(); -book.setAuthor( entityManager.getReference( Author.class, authorId ) ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/GetReferenceWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/GetReferenceWithSession.java deleted file mode 100644 index d789191dc953..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/GetReferenceWithSession.java +++ /dev/null @@ -1,2 +0,0 @@ -Book book = new Book(); -book.setAuthor( session.byId( Author.class ).getReference( authorId ) ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/LoadWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/LoadWithEM.java deleted file mode 100644 index 3c3e56f9ff1f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/LoadWithEM.java +++ /dev/null @@ -1 +0,0 @@ -entityManager.find( Author.class, authorId ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/LoadWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/LoadWithSession.java deleted file mode 100644 index d9801ef3e694..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/LoadWithSession.java +++ /dev/null @@ -1 +0,0 @@ -session.byId( Author.class ).load( authorId ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MakingPersistentWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MakingPersistentWithEM.java deleted file mode 100644 index 67397e5ea32f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MakingPersistentWithEM.java +++ /dev/null @@ -1,6 +0,0 @@ -// Using the JPA EntityManager -DomesticCat fritz = new DomesticCat(); -fritz.setColor( Color.GINGER ); -fritz.setSex( 'M' ); -fritz.setName( "Fritz" ); -entityManager.persist( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MakingPersistentWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MakingPersistentWithSession.java deleted file mode 100644 index 2ad336bafc7b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MakingPersistentWithSession.java +++ /dev/null @@ -1,6 +0,0 @@ -// Using the Hibernate Session -DomesticCat fritz = new DomesticCat(); -fritz.setColor( Color.GINGER ); -fritz.setSex( 'M' ); -fritz.setName( "Fritz" ); -session.save( fritz ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ManagedUpdateWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ManagedUpdateWithEM.java deleted file mode 100644 index 49544b6502e6..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ManagedUpdateWithEM.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = entityManager.find( Cat.class, catId ); -cat.setName( "Garfield" ); -entityManager.flush(); // generally this is not explicitly needed \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ManagedUpdateWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ManagedUpdateWithSession.java deleted file mode 100644 index 5e707244c238..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ManagedUpdateWithSession.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = session.get( Cat.class, catId ); -cat.setName( "Garfield" ); -session.flush(); // generally this is not explicitly needed \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MergeWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MergeWithEM.java deleted file mode 100644 index 340af5cddfaf..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MergeWithEM.java +++ /dev/null @@ -1 +0,0 @@ -Cat theManagedInstance = entityManager.merge( someDetachedCat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MergeWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MergeWithSession.java deleted file mode 100644 index adfad9d69e76..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/MergeWithSession.java +++ /dev/null @@ -1 +0,0 @@ -Cat theManagedInstance = session.merge( someDetachedCat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/NaturalIdLoading.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/NaturalIdLoading.java deleted file mode 100644 index cd5cd431253b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/NaturalIdLoading.java +++ /dev/null @@ -1,31 +0,0 @@ -import java.lang.String; - -@Entity -public class User { - @Id - @GeneratedValue - Long id; - - @NaturalId - String system; - - @NaturalId - String userName; - - ... -} - -// use getReference() to create associations... -Resource aResource = (Resource) session.byId( Resource.class ).getReference( 123 ); -User aUser = (User) session.byNaturalId( User.class ) - .using( "system", "prod" ) - .using( "userName", "steve" ) - .getReference(); -aResource.assignTo( user ); - - -// use load() to pull initialzed data -return session.byNaturalId( User.class ) - .using( "system", "prod" ) - .using( "userName", "steve" ) - .load(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ReattachingWithSession1.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ReattachingWithSession1.java deleted file mode 100644 index 939fc5f93f3d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ReattachingWithSession1.java +++ /dev/null @@ -1 +0,0 @@ -session.lock( someDetachedCat, LockMode.NONE ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ReattachingWithSession2.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ReattachingWithSession2.java deleted file mode 100644 index a26c2c98b073..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/ReattachingWithSession2.java +++ /dev/null @@ -1 +0,0 @@ -session.saveOrUpdate( someDetachedCat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/RefreshWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/RefreshWithEM.java deleted file mode 100644 index 8258af31e674..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/RefreshWithEM.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = entityManager.find( Cat.class, catId ); -... -entityManager.refresh( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/RefreshWithSession.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/RefreshWithSession.java deleted file mode 100644 index 3436fe3f881f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/RefreshWithSession.java +++ /dev/null @@ -1,3 +0,0 @@ -Cat cat = session.get( Cat.class, catId ); -... -session.refresh( cat ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/SimpleNaturalIdLoading.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/SimpleNaturalIdLoading.java deleted file mode 100644 index a07ecf789bb0..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/SimpleNaturalIdLoading.java +++ /dev/null @@ -1,20 +0,0 @@ -@Entity -public class User { - @Id - @GeneratedValue - Long id; - - @NaturalId - String userName; - - ... -} - -// use getReference() to create associations... -Resource aResource = (Resource) session.byId( Resource.class ).getReference( 123 ); -User aUser = (User) session.bySimpleNaturalId( User.class ).getReference( "steve" ); -aResource.assignTo( user ); - - -// use load() to pull initialzed data -return session.bySimpleNaturalId( User.class ).load( "steve" ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/UnwrapWithEM.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/UnwrapWithEM.java deleted file mode 100644 index d0c4fac2aa17..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/UnwrapWithEM.java +++ /dev/null @@ -1,2 +0,0 @@ -Session session = entityManager.unwrap( Session.class ); -SessionImplementor sessionImplementor = entityManager.unwrap( SessionImplementor.class ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/VisualizingMerge.java b/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/VisualizingMerge.java deleted file mode 100644 index 3f8697a4aef1..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/pc/extras/VisualizingMerge.java +++ /dev/null @@ -1,5 +0,0 @@ -Object detached = ...; -Object managed = entityManager.find( detached.getClass(), detached.getId() ); -managed.setXyz( detached.getXyz() ); -... -return managed; \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/portability/Portability.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/portability/Portability.xml deleted file mode 100644 index 3f99af70ccde..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/portability/Portability.xml +++ /dev/null @@ -1,201 +0,0 @@ - - - - - Database Portability Considerations - -
- Portability Basics - - - One of the selling points of Hibernate (and really Object/Relational Mapping as a whole) is - the notion of database portability. This could mean an internal IT user migrating from one - database vendor to another, or it could mean a framework or deployable application consuming - Hibernate to simultaneously target multiple database products by their users. Regardless of - the exact scenario, the basic idea is that you want Hibernate to help you run against any number - of databases without changes to your code, and ideally without any changes to the mapping metadata. - -
- -
- Dialect - - - The first line of portability for Hibernate is the dialect, which is a specialization of the - org.hibernate.dialect.Dialect contract. A dialect encapsulates all - the differences in how Hibernate must communicate with a particular database to accomplish some - task like getting a sequence value or structuring a SELECT query. Hibernate bundles a wide range - of dialects for many of the most popular databases. If you find that your particular database is - not among them, it is not terribly difficult to write your own. - -
- -
- Dialect resolution - - - Originally, Hibernate would always require that users specify which dialect to use. In the case - of users looking to simultaneously target multiple databases with their build that was problematic. - Generally this required their users to configure the Hibernate dialect or defining their own method - of setting that value. - - - - Starting with version 3.2, Hibernate introduced the notion of automatically detecting the dialect - to use based on the java.sql.DatabaseMetaData obtained from a - java.sql.Connection to that database. This was much better, expect - that this resolution was limited to databases Hibernate know about ahead of time and was in no way - configurable or overrideable. - - - - Starting with version 3.3, Hibernate has a fare more powerful way to automatically determine - which dialect to should be used by relying on a series of delegates which implement the - org.hibernate.dialect.resolver.DialectResolver which defines only a - single method: - - - - The basic contract here is that if the resolver 'understands' the given database metadata then - it returns the corresponding Dialect; if not it returns null and the process continues to the next - resolver. The signature also identifies org.hibernate.exception.JDBCConnectionException - as possibly being thrown. A JDBCConnectionException here is interpreted to imply a "non transient" - (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution - attempts. All other exceptions result in a warning and continuing on to the next resolver. - - - - The cool part about these resolvers is that users can also register their own custom resolvers - which will be processed ahead of the built-in Hibernate ones. This might be useful in a number of - different situations: it allows easy integration for auto-detection of dialects beyond those - shipped with HIbernate itself; it allows you to specify to use a custom dialect when a particular - database is recognized; etc. To register one or more resolvers, simply specify them (seperated by - commas, tabs or spaces) using the 'hibernate.dialect_resolvers' configuration setting (see the - DIALECT_RESOLVERS constant on - org.hibernate.cfg.Environment). - -
- -
- Identifier generation - - - When considering portability between databases, another important decision is selecting the - identifier generation stratagy you want to use. Originally Hibernate provided the - native generator for this purpose, which was intended to select between - a sequence, identity, or table - strategy depending on the capability of the underlying database. However, an insidious implication - of this approach comes about when targtetting some databases which support identity - generation and some which do not. identity generation relies on the SQL - definition of an IDENTITY (or auto-increment) column to manage the identifier value; it is what is - known as a post-insert generation strategy becauase the insert must actually happen before we can - know the identifier value. Because Hibernate relies on this identifier value to uniquely reference - entities within a persistence context it must then issue the insert - immediately when the users requests the entitiy be associated with the session (like via - save() e.g.) regardless of current transactional semantics. - - - - Hibernate was changed slightly once the implication of this was better understood so that - the insert is delayed in cases where that is feasible. - - - - The underlying issue is that the actual semanctics of the application itself changes in these cases. - - - - Starting with version 3.2.3, Hibernate comes with a set of - enhanced - identifier generators targetting - portability in a much different way. - - - There are specifically 2 bundled enhancedgenerators: - - - - org.hibernate.id.enhanced.SequenceStyleGenerator - - - - - org.hibernate.id.enhanced.TableGenerator - - - - - - The idea behind these generators is to port the actual semantics of the identifer value - generation to the different databases. For example, the - org.hibernate.id.enhanced.SequenceStyleGenerator mimics the behavior of - a sequence on databases which do not support sequences by using a table. - -
- -
- Database functions - - - - This is an area in Hibernate in need of improvement. In terms of portability concerns, - this function handling currently works pretty well from HQL; however, it is quite lacking - in all other aspects. - - - - - SQL functions can be referenced in many ways by users. However, not all databases - support the same set of functions. Hibernate, provides a means of mapping a - logical function name to a delegate which knows how to render - that particular function, perhaps even using a totally different physical function call. - - - Technically this function registration is handled through the - org.hibernate.dialect.function.SQLFunctionRegistry class - which is intended to allow users to provide custom function definitions without - having to provide a custom dialect. This specific behavior is not fully completed - as of yet. - - - It is sort of implemented such that users can programatically register functions - with the org.hibernate.cfg.Configuration and those functions - will be recognized for HQL. - - - -
- -
- Type mappings - - - This section scheduled for completion at a later date... - - - -
- - JPA portability - * HQL/JPQL differences - * naming strategies - * basic types - * simple id types - * generated id types - * composite ids and many-to-one - * "embedded composite identifiers" -
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/Criteria.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/Criteria.xml deleted file mode 100644 index 775decf7418e..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/Criteria.xml +++ /dev/null @@ -1,414 +0,0 @@ - - - - - - Criteria - - - Criteria queries offer a type-safe alternative to HQL, JPQL and native-sql queries. - - - - - Hibernate offers an older, legacy org.hibernate.Criteria API which should be - considered deprecated. No feature development will target those APIs. Eventually, Hibernate-specific - criteria features will be ported as extensions to the JPA - javax.persistence.criteria.CriteriaQuery. For details on the - org.hibernate.Criteria API, see . - - - This chapter will focus on the JPA APIs for declaring type-safe criteria queries. - - - - - - Criteria queries are a programmatic, type-safe way to express a query. They are type-safe in terms of - using interfaces and classes to represent various structural parts of a query such as the query itself, - or the select clause, or an order-by, etc. They can also be type-safe in terms of referencing attributes - as we will see in a bit. Users of the older Hibernate org.hibernate.Criteria - query API will recognize the general approach, though we believe the JPA API to be superior - as it represents a clean look at the lessons learned from that API. - - - - Criteria queries are essentially an object graph, where each part of the graph represents an increasing - (as we navigate down this graph) more atomic part of query. The first step in performing a criteria query - is building this graph. The javax.persistence.criteria.CriteriaBuilder - interface is the first thing with which you need to become acquainted to begin using criteria queries. Its - role is that of a factory for all the individual pieces of the criteria. You obtain a - javax.persistence.criteria.CriteriaBuilder instance by calling the - getCriteriaBuilder method of either - javax.persistence.EntityManagerFactory or - javax.persistence.EntityManager. - - - - The next step is to obtain a javax.persistence.criteria.CriteriaQuery. This - is accomplished using one of the 3 methods on - javax.persistence.criteria.CriteriaBuilder for this purpose: - - - - - - Each serves a different purpose depending on the expected type of the query results. - - - - - Chapter 6 Criteria API of the JPA Specification - already contains a decent amount of reference material pertaining to the various parts of a - criteria query. So rather than duplicate all that content here, lets instead look at some of - the more widely anticipated usages of the API. - - - -
- Typed criteria queries - - - The type of the criteria query (aka the ]]>) indicates the expected types in the query - result. This might be an entity, an Integer, or any other object. - - -
- Selecting an entity - - - This is probably the most common form of query. The application wants to select entity instances. - - - - Selecting the root entity - - - - - The example uses createQuery passing in the Person - class reference as the results of the query will be Person objects. - - - - - The call to the CriteriaQuery.select method in this example is - unnecessary because personRoot will be the implied selection since we - have only a single query root. It was done here only for completeness of an example. - - - The Person_.eyeColor reference is an example of the static form of JPA - metamodel reference. We will use that form exclusively in this chapter. See - the documentation for the Hibernate JPA Metamodel Generator for additional details on - the JPA static metamodel. - - -
- -
- Selecting an expression - - - The simplest form of selecting an expression is selecting a particular attribute from an entity. - But this expression might also represent an aggregation, a mathematical operation, etc. - - - - Selecting an attribute - - - - - In this example, the query is typed as java.lang.Integer because that - is the anticipated type of the results (the type of the Person#age attribute - is java.lang.Integer). Because a query might contain multiple references to - the Person entity, attribute references always need to be qualified. This is accomplished by the - Root#get method call. - -
- - -
- Selecting multiple values - - - There are actually a few different ways to select multiple values using criteria queries. We - will explore 2 options here, but an alternative recommended approach is to use tuples as described in - . Or consider a wrapper query; see - for details. - - - - Selecting an array - - - - - Technically this is classified as a typed query, but you can see from handling the results that - this is sort of misleading. Anyway, the expected result type here is an array. - - - - The example then uses the array method of - javax.persistence.criteria.CriteriaBuilder which explicitly - combines individual selections into a - javax.persistence.criteria.CompoundSelection. - - - - Selecting an array (2) - - - - - Just as we saw in we have a typed criteria - query returning an Object array. Both queries are functionally equivalent. This second example - uses the multiselect method which behaves slightly differently based on - the type given when the criteria query was first built, but in this case it says to select and - return an Object[]. - -
- -
- Selecting a wrapper - - Another alternative to is to instead - select an object that will wrap the multiple values. Going back to the example - query there, rather than returning an array of [Person#id, Person#age] - instead declare a class that holds these values and instead return that. - - - - Selecting an wrapper - - - - - First we see the simple definition of the wrapper object we will be using to wrap our result - values. Specifically notice the constructor and its argument types. Since we will be returning - PersonWrapper objects, we use PersonWrapper as the - type of our criteria query. - - - - This example illustrates the use of the - javax.persistence.criteria.CriteriaBuilder method - construct which is used to build a wrapper expression. For every row in the - result we are saying we would like a PersonWrapper instantiated with - the remaining arguments by the matching constructor. This wrapper expression is then passed as - the select. - -
-
- -
- Tuple criteria queries - - - A better approach to is to use either a - wrapper (which we just saw in ) or using the - javax.persistence.Tuple contract. - - - - Selecting a tuple - - - - - This example illustrates accessing the query results through the - javax.persistence.Tuple interface. The example uses the explicit - createTupleQuery of - javax.persistence.criteria.CriteriaBuilder. An alternate approach - is to use createQuery passing Tuple.class. - - - - Again we see the use of the multiselect method, just like in - . The difference here is that the type of the - javax.persistence.criteria.CriteriaQuery was defined as - javax.persistence.Tuple so the compound selections in this case are - interpreted to be the tuple elements. - - - - The javax.persistence.Tuple contract provides 3 forms of access to - the underlying elements: - - - - - typed - - - The example illustrates this form of access - in the tuple.get( idPath ) and tuple.get( agePath ) calls. - This allows typed access to the underlying tuple values based on the - javax.persistence.TupleElement expressions used to build - the criteria. - - - - - positional - - - Allows access to the underlying tuple values based on the position. The simple - Object get(int position) form is very similar to the access - illustrated in and - . The - X get(int position, Class type]]> form - allows typed positional access, but based on the explicitly supplied type which the tuple - value must be type-assignable to. - - - - - aliased - - - Allows access to the underlying tuple values based an (optionally) assigned alias. The - example query did not apply an alias. An alias would be applied via the - alias method on - javax.persistence.criteria.Selection. Just like - positional access, there is both a typed - (Object get(String alias)) and an untyped - ( X get(String alias, Class type]]> form. - - - - -
- -
- FROM clause - -
- JPA Specification, section 6.5.2 Query Roots, pg 262 - - - A CriteriaQuery object defines a query over one or more entity, embeddable, or basic abstract - schema types. The root objects of the query are entities, from which the other types are reached - by navigation. - -
- - - - All the individual parts of the FROM clause (roots, joins, paths) implement the - javax.persistence.criteria.From interface. - - - -
- Roots - - - Roots define the basis from which all joins, paths and attributes are available in the query. - A root is always an entity type. Roots are defined and added to the criteria by the overloaded - from methods on - javax.persistence.criteria.CriteriaQuery: - - - - - - Adding a root - - - - - Criteria queries may define multiple roots, the effect of which is to create a cartesian - product between the newly added root and the others. Here is an example matching all single - men and all single women: - - - - Adding multiple roots - - -
- -
- Joins - - - Joins allow navigation from other javax.persistence.criteria.From - to either association or embedded attributes. Joins are created by the numerous overloaded - join methods of the - javax.persistence.criteria.From interface - - - - Example with Embedded and ManyToOne - - - - - Example with Collections - - -
- -
- Fetches - - - Just like in HQL and JPQL, criteria queries can specify that associated data be fetched along - with the owner. Fetches are created by the numerous overloaded fetch - methods of the javax.persistence.criteria.From interface. - - - - Example with Embedded and ManyToOne - - - - - - Technically speaking, embedded attributes are always fetched with their owner. However in - order to define the fetching of Address#country we needed a - javax.persistence.criteria.Fetch for its parent path. - - - - - Example with Collections - - -
-
- -
- Path expressions - - - Roots, joins and fetches are themselves paths as well. - - -
- -
- Using parameters - - - Using parameters - - - - - Use the parameter method of - javax.persistence.criteria.CriteriaBuilder to obtain a parameter - reference. Then use the parameter reference to bind the parameter value to the - javax.persistence.Query - -
- -
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/CriteriaBuilder_query_creation_snippet.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/CriteriaBuilder_query_creation_snippet.java deleted file mode 100644 index a4dc3ef01bc5..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/CriteriaBuilder_query_creation_snippet.java +++ /dev/null @@ -1,3 +0,0 @@ - CriteriaQuery createQuery(Class resultClass); -CriteriaQuery createTupleQuery(); -CriteriaQuery createQuery(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_fetch_example_embedded_and_many2one.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_fetch_example_embedded_and_many2one.java deleted file mode 100644 index fe68976760d3..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_fetch_example_embedded_and_many2one.java +++ /dev/null @@ -1,6 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -// Person.address is an embedded attribute -Fetch personAddress = personRoot.fetch( Person_.address ); -// Address.country is a ManyToOne -Fetch addressCountry = personAddress.fetch( Address_.country ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_fetch_example_plural.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_fetch_example_plural.java deleted file mode 100644 index 5a216e28ef8d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_fetch_example_plural.java +++ /dev/null @@ -1,4 +0,0 @@ -CriteriaQuery<Person> personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -Fetch orders = personRoot.fetch( Person_.orders ); -Fetch orderLines = orders.fetch( Order_.lineItems ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_join_example_embedded_and_many2one.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_join_example_embedded_and_many2one.java deleted file mode 100644 index 478b649cacaa..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_join_example_embedded_and_many2one.java +++ /dev/null @@ -1,6 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -// Person.address is an embedded attribute -Join personAddress = personRoot.join( Person_.address ); -// Address.country is a ManyToOne -Join addressCountry = personAddress.join( Address_.country ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_join_example_plural.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_join_example_plural.java deleted file mode 100644 index 1840bad318f6..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_join_example_plural.java +++ /dev/null @@ -1,4 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -Root personRoot = person.from( Person.class ); -Join orders = personRoot.join( Person_.orders ); -Join orderLines = orders.join( Order_.lineItems ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_example.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_example.java deleted file mode 100644 index 0c73ad04ffa8..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_example.java +++ /dev/null @@ -1,3 +0,0 @@ -CriteriaQuery personCriteria = builder.createQuery( Person.class ); -// create and add the root -person.from( Person.class ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_example_multiple.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_example_multiple.java deleted file mode 100644 index e37cbc17a468..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_example_multiple.java +++ /dev/null @@ -1,12 +0,0 @@ -CriteriaQuery query = builder.createQuery(); -Root men = query.from( Person.class ); -Root women = query.from( Person.class ); -Predicate menRestriction = builder.and( - builder.equal( men.get( Person_.gender ), Gender.MALE ), - builder.equal( men.get( Person_.relationshipStatus ), RelationshipStatus.SINGLE ) -); -Predicate womenRestriction = builder.and( - builder.equal( women.get( Person_.gender ), Gender.FEMALE ), - builder.equal( women.get( Person_.relationshipStatus ), RelationshipStatus.SINGLE ) -); -query.where( builder.and( menRestriction, womenRestriction ) ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_methods.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_methods.java deleted file mode 100644 index 4a9c915904b3..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/from_root_methods.java +++ /dev/null @@ -1,3 +0,0 @@ - Root from(Class); - - Root from(EntityType) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/parameter_example.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/parameter_example.java deleted file mode 100644 index bcb72ff2b4d4..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/parameter_example.java +++ /dev/null @@ -1,9 +0,0 @@ -CriteriaQuery criteria = build.createQuery( Person.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( personRoot ); -ParameterExpression eyeColorParam = builder.parameter( String.class ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), eyeColorParam ) ); - -TypedQuery query = em.createQuery( criteria ); -query.setParameter( eyeColorParam, "brown" ); -List people = query.getResultList(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_attribute_example.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_attribute_example.java deleted file mode 100644 index 0667a435e916..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_attribute_example.java +++ /dev/null @@ -1,9 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Integer.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( personRoot.get( Person_.age ) ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List ages = em.createQuery( criteria ).getResultList(); -for ( Integer age : ages ) { - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_multiple_values_array.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_multiple_values_array.java deleted file mode 100644 index 7e3fefaa18e1..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_multiple_values_array.java +++ /dev/null @@ -1,13 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Object[].class ); -Root personRoot = criteria.from( Person.class ); -Path idPath = personRoot.get( Person_.id ); -Path agePath = personRoot.get( Person_.age ); -criteria.select( builder.array( idPath, agePath ) ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List valueArray = em.createQuery( criteria ).getResultList(); -for ( Object[] values : valueArray ) { - final Long id = (Long) values[0]; - final Integer age = (Integer) values[1]; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_multiple_values_array2.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_multiple_values_array2.java deleted file mode 100644 index 75c8feeaa38d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_multiple_values_array2.java +++ /dev/null @@ -1,13 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Object[].class ); -Root personRoot = criteria.from( Person.class ); -Path idPath = personRoot.get( Person_.id ); -Path agePath = personRoot.get( Person_.age ); -criteria.multiselect( idPath, agePath ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List valueArray = em.createQuery( criteria ).getResultList(); -for ( Object[] values : valueArray ) { - final Long id = (Long) values[0]; - final Integer age = (Integer) values[1]; - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_root_entity_example.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_root_entity_example.java deleted file mode 100644 index c60921d11618..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_root_entity_example.java +++ /dev/null @@ -1,9 +0,0 @@ -CriteriaQuery criteria = builder.createQuery( Person.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( personRoot ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List people = em.createQuery( criteria ).getResultList(); -for ( Person person : people ) { - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_tuple.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_tuple.java deleted file mode 100644 index dc651c1cf05f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_tuple.java +++ /dev/null @@ -1,13 +0,0 @@ -CriteriaQuery criteria = builder.createTupleQuery(); -Root personRoot = criteria.from( Person.class ); -Path idPath = personRoot.get( Person_.id ); -Path agePath = personRoot.get( Person_.age ); -criteria.multiselect( idPath, agePath ); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List tuples = em.createQuery( criteria ).getResultList(); -for ( Tuple tuple : valueArray ) { - assert tuple.get( 0 ) == tuple.get( idPath ); - assert tuple.get( 1 ) == tuple.get( agePath ); - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_wrapper.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_wrapper.java deleted file mode 100644 index f82398829fd5..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-criteria/extras/select_wrapper.java +++ /dev/null @@ -1,27 +0,0 @@ -public class PersonWrapper { - private final Long id; - private final Integer age; - public PersonWrapper(Long id, Integer age) { - this.id = id; - this.age = age; - } - ... -} - -... - -CriteriaQuery criteria = builder.createQuery( PersonWrapper.class ); -Root personRoot = criteria.from( Person.class ); -criteria.select( - builder.construct( - PersonWrapper.class, - personRoot.get( Person_.id ), - personRoot.get( Person_.age ) - ) -); -criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); - -List people = em.createQuery( criteria ).getResultList(); -for ( PersonWrapper person : people ) { - ... -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/HQL.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/HQL.xml deleted file mode 100644 index 8afb714a5db1..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/HQL.xml +++ /dev/null @@ -1,1764 +0,0 @@ - - - - - - - HQL and JPQL - - Discuss syntax and execution of both HQL and JPQL - - - - - Related topics - - - - - - - - - - - - - - - The Hibernate Query Language (HQL) and Java Persistence Query Language (JPQL) are both object model - focused query languages similar in nature to SQL. JPQL is a heavily-inspired-by subset of HQL. A JPQL - query is always a valid HQL query, the reverse is not true however. - - - - Both HQL and JPQL are non-type-safe ways to perform query operations. Criteria queries offer a - type-safe approach to querying. See for more information. - - -
- Case Sensitivity - - - With the exception of names of Java classes and properties, queries are case-insensitive. - So SeLeCT is the same as sELEct is the same as - SELECT, but - org.hibernate.eg.FOO and org.hibernate.eg.Foo are different, as are - foo.barSet and foo.BARSET. - - - - - This documentation uses lowercase keywords as convention in examples. - - -
- -
- Statement types - - Both HQL and JPQL allow SELECT, UPDATE and DELETE - statements to be performed. HQL additionally allows INSERT statements, in a form - similar to a SQL INSERT-SELECT. - - - - - Care should be taken as to when a UPDATE or DELETE statement is - executed. - -
- Section 4.10 of the JPA 2.0 Specification - - Caution should be used when executing bulk update or delete operations because they may result in - inconsistencies between the database and the entities in the active persistence context. In general, bulk - update and delete operations should only be performed within a transaction in a new persistence con- - text or before fetching or accessing entities whose state might be affected by such operations. - -
-
- -
- Select statements - - The BNF for SELECT statements in HQL is: - - - - The simplest possible HQL SELECT statement is of the form: - - from com.acme.Cat - - The select statement in JPQL is exactly the same as for HQL except that JPQL requires a - select_clause, whereas HQL does not. Even though HQL does not require the presence - of a select_clause, it is generally good practice to include one. For simple queries - the intent is clear and so the intended result of the select_clause is east to - infer. But on more complex queries that is not always the case. It is usually better to explicitly - specify intent. Hibernate does not actually enforce that a select_clause be present - even when parsing JPQL queries, however applications interested in JPA portability should take heed of - this. - - -
- -
- Update statements - - The BNF for UPDATE statements is the same in HQL and JPQL: - - - - UPDATE statements, by default, do not effect the version - or the timestamp attribute values for the affected entities. However, - you can force Hibernate to set the version or timestamp attribute - values through the use of a versioned update. This is achieved by adding the - VERSIONED keyword after the UPDATE keyword. Note, however, that - this is a Hibernate specific feature and will not work in a portable manner. Custom version types, - org.hibernate.usertype.UserVersionType, are not allowed in conjunction - with a update versioned statement. - - - An UPDATE statement is executed using the executeUpdate - of either org.hibernate.Query or - javax.persistence.Query. The method is named for those familiar with - the JDBC executeUpdate on java.sql.PreparedStatement. - The int value returned by the executeUpdate() method - indicates the number of entities effected by the operation. This may or may not correlate to the number - of rows effected in the database. An HQL bulk operation might result in multiple actual SQL statements - being executed (for joined-subclass, for example). The returned number indicates the number of actual - entities affected by the statement. Using a JOINED inheritance hierarchy, a delete against one of the - subclasses may actually result in deletes against not just the table to which that subclass is mapped, - but also the "root" table and tables in between - - - Example UPDATE query statements - - - - -
- - - - Neither UPDATE nor DELETE statements are allowed to - result in what is called an implicit join. Their form already disallows explicit joins. - - - -
- Delete statements - - The BNF for DELETE statements is the same in HQL and JPQL: - - - - A DELETE statement is also executed using the executeUpdate - method of either org.hibernate.Query or - javax.persistence.Query. - -
- -
- Insert statements - - HQL adds the ability to define INSERT statements as well. There is no JPQL - equivalent to this. The BNF for an HQL INSERT statement is: - - - - The attribute_list is analogous to the column specification in the - SQL INSERT statement. For entities involved in mapped inheritance, only attributes - directly defined on the named entity can be used in the attribute_list. Superclass - properties are not allowed and subclass properties do not make sense. In other words, - INSERT statements are inherently non-polymorphic. - - - select_statement can be any valid HQL select query, with the caveat that the return - types must match the types expected by the insert. Currently, this is checked during query - compilation rather than allowing the check to relegate to the database. This may cause problems - between Hibernate Types which are equivalent as opposed to - equal. For example, this might cause lead to issues with mismatches between an - attribute mapped as a org.hibernate.type.DateType and an attribute defined as - a org.hibernate.type.TimestampType, even though the database might not make a - distinction or might be able to handle the conversion. - - - For the id attribute, the insert statement gives you two options. You can either explicitly specify - the id property in the attribute_list, in which case its value is taken from the - corresponding select expression, or omit it from the attribute_list in which case a - generated value is used. This latter option is only available when using id generators that operate - in the database; attempting to use this option with any in memory type - generators will cause an exception during parsing. - - - For optimistic locking attributes, the insert statement again gives you two options. You can either - specify the attribute in the attribute_list in which case its value is taken from - the corresponding select expressions, or omit it from the attribute_list in which - case the seed value defined by the corresponding - org.hibernate.type.VersionType is used. - - - Example INSERT query statements - - -
-
- -
- The <literal>FROM</literal> clause - - The FROM clause is responsible defining the scope of object model types available to - the rest of the query. It also is responsible for defining all the identification variables - available to the rest of the query. - -
- Identification variables - - Identification variables are often referred to as aliases. References to object model classes - in the FROM clause can be associated with an identification variable that can then be used to - refer to that type thoughout the rest of the query. - - - In most cases declaring an identification variable is optional, though it is usually good practice to - declare them. - - - An identification variable must follow the rules for Java identifier validity. - - - According to JPQL, identification variables must be treated as case insensitive. Good practice - says you should use the same case throughout a query to refer to a given identification variable. In - other words, JPQL says they can be case insensitive and so Hibernate must - be able to treat them as such, but this does not make it good practice. - -
-
- Root entity references - - A root entity reference, or what JPA calls a range variable declaration, is - specifically a reference to a mapped entity type from the application. It cannot name component/ - embeddable types. And associations, including collections, are handled in a different manner - discussed later. - - - The BNF for a root entity reference is: - - - - Simple query example - - - - We see that the query is defining a root entity reference to the com.acme.Cat - object model type. Additionally, it declares an alias of c to that - com.acme.Cat reference; this is the identification variable. - - - Usually the root entity reference just names the entity name rather than the - entity class FQN. By default the entity name is the unqualified entity class name, - here Cat - - - Simple query using entity name for root entity reference - - - - Multiple root entity references can also be specified. Even naming the same entity! - - - Simple query using multiple root entity references - - - -
-
- Explicit joins - - The FROM clause can also contain explicit relationship joins using the - join keyword. These joins can be either inner - or left outer style joins. - - - Explicit inner join examples - - - - Explicit left (outer) join examples - - - - An important use case for explicit joins is to define FETCH JOINS which override - the laziness of the joined association. As an example, given an entity named Customer - with a collection-valued association named orders - - - Fetch join example - - - - As you can see from the example, a fetch join is specified by injecting the keyword fetch - after the keyword join. In the example, we used a left outer join because we want - to return customers who have no orders also. Inner joins can also be fetched. But inner joins still - filter. In the example, using an inner join instead would have resulted in customers without any orders - being filtered out of the result. - - - - Fetch joins are not valid in sub-queries. - - - Care should be taken when fetch joining a collection-valued association which is in any way further - restricted; the fetched collection will be restricted too! For this reason it is usually considered - best practice to not assign an identification variable to fetched joins except for the purpose - of specifying nested fetch joins. - - - Fetch joins should not be used in paged queries (aka, setFirstResult/ - setMaxResults). Nor should they be used with the HQL - scroll or iterate features. - - - - HQL also defines a WITH clause to qualify the join conditions. Again, this is - specific to HQL; JPQL does not define this feature. - - - with-clause join example - - - - The important distinction is that in the generated SQL the conditions of the - with clause are made part of the on clause in the generated SQL - as opposed to the other queries in this section where the HQL/JPQL conditions are made part of the - where clause in the generated SQL. The distinction in this specific example is - probably not that significant. The with clause is sometimes necessary in more - complicated queries. - - - Explicit joins may reference association or component/embedded attributes. For further information - about collection-valued association references, see . - In the case of component/embedded attributes, the join is simply logical and does not correlate to a - physical (SQL) join. - -
-
- Implicit joins (path expressions) - - Another means of adding to the scope of object model types available to the query is through the - use of implicit joins, or path expressions. - - - Simple implicit join example - - - - An implicit join always starts from an identification variable, followed by - the navigation operator (.), followed by an attribute for the object model type referenced by the - initial identification variable. In the example, the initial - identification variable is c which refers to the - Customer entity. The c.chiefExecutive reference then refers - to the chiefExecutive attribute of the Customer entity. - chiefExecutive is an association type so we further navigate to its - age attribute. - - - - If the attribute represents an entity association (non-collection) or a component/embedded, that - reference can be further navigated. Basic values and collection-valued associations cannot be - further navigated. - - - - As shown in the example, implicit joins can appear outside the FROM clause. However, - they affect the FROM clause. Implicit joins are always treated as inner joins. - Multiple references to the same implicit join always refer to the same logical and physical (SQL) join. - - - Reused implicit join - - - - Just as with explicit joins, implicit joins may reference association or component/embedded attributes. - For further information about collection-valued association references, see - . In the case of component/embedded attributes, - the join is simply logical and does not correlate to a physical (SQL) join. Unlike explicit joins, - however, implicit joins may also reference basic state fields as long as the path expression ends - there. - -
-
- Collection member references - - References to collection-valued associations actually refer to the values of - that collection. - - - Collection references example - - - - In the example, the identification variable o actually refers to the object model - type Order which is the type of the elements of the - Customer#orders association. - - - The example also shows the alternate syntax for specifying collection association joins using the - IN syntax. Both forms are equivalent. Which form an application chooses to use is - simply a matter of taste. - -
- Special case - qualified path expressions - - We said earlier that collection-valued associations actually refer to the values - of that collection. Based on the type of collection, there are also available a set of - explicit qualification expressions. - - - Qualified collection references example - - - - - VALUE - - - Refers to the collection value. Same as not specifying a qualifier. Useful to - explicitly show intent. Valid for any type of collection-valued reference. - - - - - INDEX - - - According to HQL rules, this is valid for both Maps and Lists which specify a - javax.persistence.OrderColumn annotation to refer to - the Map key or the List position (aka the OrderColumn value). JPQL however, reserves - this for use in the List case and adds KEY for the MAP case. - Applications interested in JPA provider portability should be aware of this - distinction. - - - - - KEY - - - Valid only for Maps. Refers to the map's key. If the key is itself an entity, - can be further navigated. - - - - - ENTRY - - - Only valid only for Maps. Refers to the Map's logical - java.util.Map.Entry tuple (the combination of its key - and value). ENTRY is only valid as a terminal path and only valid - in the select clause. - - - - - - See for additional details on collection related - expressions. - -
-
-
- Polymorphism - - HQL and JPQL queries are inherently polymorphic. - - select p from Payment p - - This query names the Payment entity explicitly. However, all subclasses of - Payment are also available to the query. So if the - CreditCardPayment entity and WireTransferPayment entity - each extend from Payment all three types would be available to the query. And - the query would return instances of all three. - - - The logical extreme - - The HQL query from java.lang.Object is totally valid! It returns every - object of every type defined in your application. - - - - This can be altered by using either the - org.hibernate.annotations.Polymorphism annotation (global, and - Hibernate-specific) or limiting them using in the query itself using an entity type expression. - -
-
- -
- Expressions - - - Essentially expressions are references that resolve to basic or tuple values. - - -
- Identification variable - - See . - -
- -
- Path expressions - - Again, see . - -
- -
- Literals - - String literals are enclosed in single-quotes. To escape a single-quote within a string literal, use - double single-quotes. - - - String literal examples - - - - - Numeric literals are allowed in a few different forms. - - - Numeric literal examples - - - - In the scientific notation form, the E is case insensitive. - - - Specific typing can be achieved through the use of the same suffix approach specified by Java. So, - L denotes a long; D denotes a double; F - denotes a float. The actual suffix is case insensitive. - - - - The boolean literals are TRUE and FALSE, again case-insensitive. - - - - Enums can even be referenced as literals. The fully-qualified enum class name must be used. HQL - can also handle constants in the same manner, though JPQL does not define that as supported. - - - - Entity names can also be used as literal. See . - - - - Date/time literals can be specified using the JDBC escape syntax: {d 'yyyy-mm-dd'} - for dates, {t 'hh:mm:ss'} for times and - {ts 'yyyy-mm-dd hh:mm:ss[.millis]'} (millis optional) for timestamps. These - literals only work if you JDBC drivers supports them. - -
- -
- Parameters - - HQL supports all 3 of the following forms. JPQL does not support the HQL-specific positional - parameters notion. It is good practice to not mix forms in a given query. - -
- Named parameters - - Named parameters are declared using a colon followed by an identifier - - :aNamedParameter. The same named parameter can appear multiple times in a query. - - - Named parameter examples - - -
-
- Positional (JPQL) parameters - - JPQL-style positional parameters are declared using a question mark followed by an ordinal - - ?1, ?2. The ordinals start with 1. Just like with - named parameters, positional parameters can also appear multiple times in a query. - - - Positional (JPQL) parameter examples - - -
-
- Positional (HQL) parameters - - HQL-style positional parameters follow JDBC positional parameter syntax. They are declared using - ? without a following ordinal. There is no way to relate two such - positional parameters as being "the same" aside from binding the same value to each. - - - This form should be considered deprecated and may be removed in the near future. - -
-
- -
- Arithmetic - - Arithmetic operations also represent valid expressions. - - - Numeric arithmetic examples - - - - The following rules apply to the result of arithmetic operations: - - - - - If either of the operands is Double/double, the result is a Double; - - - - - else, if either of the operands is Float/float, the result is a Float; - - - - - else, if either operand is BigDecimal, the result is BigDecimal; - - - - - else, if either operand is BigInteger, the result is BigInteger (except for division, in - which case the result type is not further defined); - - - - - else, if either operand is Long/long, the result is Long (except for division, in - which case the result type is not further defined); - - - - - else, (the assumption being that both operands are of integral type) the result is Integer - (except for division, in which case the result type is not further defined); - - - - - - Date arithmetic is also supported, albeit in a more limited fashion. This is due partially to - differences in database support and partially to the lack of support for INTERVAL - definition in the query language itself. - -
- -
- Concatenation (operation) - - HQL defines a concatenation operator in addition to supporting the concatenation - (CONCAT) function. This is not defined by JPQL, so portable applications - should avoid it use. The concatenation operator is taken from the SQL concatenation operator - - ||. - - - Concatenation operation example - - - - See for details on the concat() function - -
- -
- Aggregate functions - - Aggregate functions are also valid expressions in HQL and JPQL. The semantic is the same as their - SQL counterpart. The supported aggregate functions are: - - - - - COUNT (including distinct/all qualifiers) - The result type is always Long. - - - - - AVG - The result type is always Double. - - - - - MIN - The result type is the same as the argument type. - - - - - MAX - The result type is the same as the argument type. - - - - - SUM - The result type of the avg() function depends on - the type of the values being averaged. For integral values (other than BigInteger), the result - type is Long. For floating point values (other than BigDecimal) the result type is Double. For - BigInteger values, the result type is BigInteger. For BigDecimal values, the result type is - BigDecimal. - - - - - Aggregate function examples - - - - Aggregations often appear with grouping. For information on grouping see - -
- -
- Scalar functions - - Both HQL and JPQL define some standard functions that are available regardless of the underlying - database in use. HQL can also understand additional functions defined by the Dialect as well as the - application. - - -
- Standardized functions - JPQL - - Here are the list of functions defined as supported by JPQL. Applications interested in remaining - portable between JPA providers should stick to these functions. - - - - CONCAT - - - String concatenation function. Variable argument length of 2 or more string values - to be concatenated together. - - - - - SUBSTRING - - - Extracts a portion of a string value. - - - - The second argument denotes the starting position. The third (optional) argument - denotes the length. - - - - - UPPER - - - Upper cases the specified string - - - - - LOWER - - - Lower cases the specified string - - - - - TRIM - - - Follows the semantics of the SQL trim function. - - - - - LENGTH - - - Returns the length of a string. - - - - - LOCATE - - - Locates a string within another string. - - - - The third argument (optional) is used to denote a position from which to start looking. - - - - - ABS - - - Calculates the mathematical absolute value of a numeric value. - - - - - MOD - - - Calculates the remainder of dividing the first argument by the second. - - - - - SQRT - - - Calculates the mathematical square root of a numeric value. - - - - - CURRENT_DATE - - - Returns the database current date. - - - - - CURRENT_TIME - - - Returns the database current time. - - - - - CURRENT_TIMESTAMP - - - Returns the database current timestamp. - - - - -
-
- Standardized functions - HQL - - Beyond the JPQL standardized functions, HQL makes some additional functions available regardless - of the underlying database in use. - - - - BIT_LENGTH - - - Returns the length of binary data. - - - - - CAST - - - Performs a SQL cast. The cast target should name the Hibernate mapping type to use. - See the chapter on data types for more information. - - - - - EXTRACT - - - Performs a SQL extraction on datetime values. An extraction extracts parts of - the datetime (the year, for example). See the abbreviated forms below. - - - - - SECOND - - - Abbreviated extract form for extracting the second. - - - - - MINUTE - - - Abbreviated extract form for extracting the minute. - - - - - HOUR - - - Abbreviated extract form for extracting the hour. - - - - - DAY - - - Abbreviated extract form for extracting the day. - - - - - MONTH - - - Abbreviated extract form for extracting the month. - - - - - YEAR - - - Abbreviated extract form for extracting the year. - - - - - STR - - - Abbreviated form for casting a value as character data. - - - - -
- -
- Non-standardized functions - - Hibernate Dialects can register additional functions known to be available for that particular - database product. These functions are also available in HQL (and JPQL, though only when using - Hibernate as the JPA provider obviously). However, they would only be available when using that - database/Dialect. Applications that aim for database portability should avoid using functions - in this category. - - - Application developers can also supply their own set of functions. This would usually represent - either custom SQL functions or aliases for snippets of SQL. Such function declarations are - made by using the addSqlFunction method of - org.hibernate.cfg.Configuration - -
-
- -
- Collection-related expressions - - There are a few specialized expressions for working with collection-valued associations. Generally - these are just abbreviated forms or other expressions for the sake of conciseness. - - - - SIZE - - - Calculate the size of a collection. Equates to a subquery! - - - - - MAXELEMENT - - - Available for use on collections of basic type. Refers to the maximum value as determined - by applying the max SQL aggregation. - - - - - MAXINDEX - - - Available for use on indexed collections. Refers to the maximum index (key/position) as - determined by applying the max SQL aggregation. - - - - - MINELEMENT - - - Available for use on collections of basic type. Refers to the minimum value as determined - by applying the min SQL aggregation. - - - - - MININDEX - - - Available for use on indexed collections. Refers to the minimum index (key/position) as - determined by applying the min SQL aggregation. - - - - - ELEMENTS - - - Used to refer to the elements of a collection as a whole. Only allowed in the where clause. - Often used in conjunction with ALL, ANY or - SOME restrictions. - - - - - INDICES - - - Similar to elements except that indices refers to - the collections indices (keys/positions) as a whole. - - - - - - Collection-related expressions examples - - - - Elements of indexed collections (arrays, lists, and maps) can be referred to by index operator. - - - Index operator examples - - - - See also as there is a good deal of overlap. - -
- -
- Entity type - - We can also refer to the type of an entity as an expression. This is mainly useful when dealing - with entity inheritance hierarchies. The type can expressed using a TYPE function - used to refer to the type of an identification variable representing an entity. The name of the - entity also serves as a way to refer to an entity type. Additionally the entity type can be - parametrized, in which case the entity's Java Class reference would be bound as the parameter - value. - - - Entity type expression examples - - - - HQL also has a legacy form of referring to an entity type, though that legacy form is considered - deprecated in favor of TYPE. The legacy form would have used p.class - in the examples rather than type(p). It is mentioned only for completeness. - -
- -
- CASE expressions - - Both the simple and searched forms are supported, as well as the 2 SQL defined abbreviated forms - (NULLIF and COALESCE) - -
- Simple CASE expressions - - The simple form has the following syntax: - - - - Simple case expression example - - -
-
- Searched CASE expressions - - The searched form has the following syntax: - - - - Searched case expression example - - -
-
- NULLIF expressions - - NULLIF is an abbreviated CASE expression that returns NULL if its operands are considered equal. - - - NULLIF example - - -
-
- COALESCE expressions - - COALESCE is an abbreviated CASE expression that returns the first non-null operand. We have seen a - number of COALESCE examples above. - -
-
-
- -
- The <literal>SELECT</literal> clause - - The SELECT clause identifies which objects and values to return as the query results. - The expressions discussed in are all valid select expressions, except - where otherwise noted. See the section for information on handling the results - depending on the types of values specified in the SELECT clause. - - - - There is a particular expression type that is only valid in the select clause. Hibernate calls this - dynamic instantiation. JPQL supports some of that feature and calls it - a constructor expression - - - - Dynamic instantiation example - constructor - - - - - So rather than dealing with the Object[] (again, see ) here we are wrapping - the values in a type-safe java object that will be returned as the results of the query. The class - reference must be fully qualified and it must have a matching constructor. - - - The class here need not be mapped. If it does represent an entity, the resulting instances are - returned in the NEW state (not managed!). - - - - That is the part JPQL supports as well. HQL supports additional dynamic instantiation - features. First, the query can specify to return a List rather than an Object[] for scalar results: - - - Dynamic instantiation example - list - - - - The results from this query will be a ]]> as opposed to a ]]> - - - - HQL also supports wrapping the scalar results in a Map. - - - Dynamic instantiation example - map - - - - The results from this query will be a >]]> as opposed to a - ]]>. The keys of the map are defined by the aliases given to the select - expressions. - -
- -
- Predicates - - Predicates form the basis of the where clause, the having clause and searched case expressions. - They are expressions which resolve to a truth value, generally TRUE or - FALSE, although boolean comparisons involving NULLs generally resolve to - UNKNOWN. - - -
- Relational comparisons - - Comparisons involve one of the comparison operators - , >=, <, <=, <>]>. HQL also defines - as a comparison operator synonymous with ]]>. The operands should be - of the same type. - - - Relational comparison examples - - - - Comparisons can also involve subquery qualifiers - ALL, ANY, - SOME. SOME and ANY are synonymous. - - - The ALL qualifier resolves to true if the comparison is true for all of the values in the result of - the subquery. It resolves to false if the subquery result is empty. - - - ALL subquery comparison qualifier example - - - - The ANY/SOME qualifier resolves to true if the comparison is true for some of (at least one of) the - values in the result of the subquery. It resolves to false if the subquery result is empty. - -
- -
- Nullness predicate - - Check a value for nullness. Can be applied to basic attribute references, entity references and - parameters. HQL additionally allows it to be applied to component/embeddable types. - - - Nullness checking examples - - -
- -
- Like predicate - - Performs a like comparison on string values. The syntax is: - - - - The semantics follow that of the SQL like expression. The pattern_value is the - pattern to attempt to match in the string_expression. Just like SQL, - pattern_value can use _ and % as wildcards. The - meanings are the same. _ matches any single character. % matches - any number of characters. - - - The optional escape_character is used to specify an escape character used to - escape the special meaning of _ and % in the - pattern_value. THis is useful when needing to search on patterns including either - _ or % - - - Like predicate examples - - -
- -
- Between predicate - - Analogous to the SQL between expression. Perform a evaluation that a value is within the range - of 2 other values. All the operands should have comparable types. - - - Between predicate examples - - -
- -
- In predicate - - IN predicates performs a check that a particular value is in a list of values. - Its syntax is: - - - - The types of the single_valued_expression and the individual values in the - single_valued_list must be consistent. JPQL limits the valid types here - to string, numeric, date, time, timestamp, and enum types. In JPQL, - single_valued_expression can only refer to: - - - - - state fields, which is its term for simple attributes. Specifically this - excludes association and component/embedded attributes. - - - - - entity type expressions. See - - - - - In HQL, single_valued_expression can refer to a far more broad set of expression - types. Single-valued association are allowed. So are component/embedded attributes, although that - feature depends on the level of support for tuple or row value constructor syntax in - the underlying database. Additionally, HQL does not limit the value type in any way, though - application developers should be aware that different types may incur limited support based on - the underlying database vendor. This is largely the reason for the JPQL limitations. - - - The list of values can come from a number of different sources. In the - constructor_expression and collection_valued_input_parameter, the - list of values must not be empty; it must contain at least one value. - - - In predicate examples - - -
- -
- Exists predicate - - Exists expressions test the existence of results from a subquery. The affirmative form returns true - if the subquery result contains values. The negated form returns true if the subquery - result is empty. - -
- -
- Empty collection predicate - - The IS [NOT] EMPTY expression applies to collection-valued path expressions. It - checks whether the particular collection has any associated values. - - - Empty collection expression examples - - -
- -
- Member-of collection predicate - - The [NOT] MEMBER [OF] expression applies to collection-valued path expressions. It - checks whether a value is a member of the specified collection. - - - Member-of collection expression examples - - -
- -
- NOT predicate operator - - The NOT operator is used to negate the predicate that follows it. If that - following predicate is true, the NOT resolves to false. If the predicate is true, NOT resolves to - false. If the predicate is unknown, the NOT resolves to unknown as well. - -
- -
- AND predicate operator - - The AND operator is used to combine 2 predicate expressions. The result of the - AND expression is true if and only if both predicates resolve to true. If either predicate resolves - to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. - -
- -
- OR predicate operator - - The OR operator is used to combine 2 predicate expressions. The result of the - OR expression is true if either predicate resolves to true. If both predicates resolve to unknown, the - OR expression resolves to unknown. Otherwise, the result is false. - -
-
- -
- The <literal>WHERE</literal> clause - - The WHERE clause of a query is made up of predicates which assert whether values in - each potential row match the predicated checks. Thus, the where clause restricts the results returned - from a select query and limits the scope of update and delete queries. - -
- -
- Grouping - - The GROUP BY clause allows building aggregated results for various value groups. As an - example, consider the following queries: - - - Group-by illustration - - - - The first query retrieves the complete total of all orders. The second retrieves the total for each - customer; grouped by each customer. - - - In a grouped query, the where clause applies to the non aggregated values (essentially it determines whether - rows will make it into the aggregation). The HAVING clause also restricts results, - but it operates on the aggregated values. In the example, - we retrieved order totals for all customers. If that ended up being too much data to deal with, - we might want to restrict the results to focus only on customers with a summed order total of more than - $10,000.00: - - - Having illustration - - - - The HAVING clause follows the same rules as the WHERE clause and is also made up of predicates. HAVING is - applied after the groupings and aggregations have been done; WHERE is applied before. - -
- -
- Ordering - - The results of the query can also be ordered. The ORDER BY clause is used to specify - the selected values to be used to order the result. The types of expressions considered valid as part - of the order-by clause include: - - - - - state fields - - - - - component/embeddable attributes - - - - - scalar expressions such as arithmetic operations, functions, etc. - - - - - identification variable declared in the select clause for any of the previous expression types - - - - - Additionally, JPQL says that all values referenced in the order-by clause must be named in the select - clause. HQL does not mandate that restriction, but applications desiring database portability should be - aware that not all databases support referencing values in the order-by clause that are not referenced - in the select clause. - - - Individual expressions in the order-by can be qualified with either ASC (ascending) or - DESC (descending) to indicated the desired ordering direction. Null values can be placed - in front or at the end of sorted set using NULLS FIRST or NULLS LAST - clause respectively. - - - Order-by examples - - -
- -
- Query API - -
- Hibernate Query API - - - In Hibernate the HQL/JPQL query is represented as org.hibernate.Query which - is obtained from the Session. If the HQL/JPQL is a named query, Session#getNamedQuery - would be used; otherwise Session#createQuery would be used. - - - - Obtaining a Query reference - Hibernate - - - - - - The Query interface can then be used to control the execution of the query. For example, we - may want to specify an execution timeout or control caching. - - - - Basic Query usage - Hibernate - - - - - For complete details, see the Query javadocs. - - - - - Query hints here are database query hints. They are added directly to the generated SQL - according to Dialect#getQueryHintString. The JPA notion of query - hints, on the other hand, refer to hints that target the provider (Hibernate). So even though - they are called the same, be aware they have a very different purpose. Also be aware that - Hibernate query hints generally make the application non-portable across databases unless the code - adding them first checks the Dialect. - - - - - Flushing is covered in detail in . Locking is covered in detail in - . The concept of read-only state is covered in . - - - - Hibernate also allows an application to hook into the process of building the query results via the - org.hibernate.transform.ResultTransformer contract. See its javadocs - as well as the Hibernate-provided implementations for additional details. - - - - The last thing that needs to happen before we can execute the query is to bind the values for any - parameters defined in the query. Query defines many overloaded methods for this purpose. The most - generic form takes the value as well as the Hibernate Type. - - - - Parameter binding - Hibernate - - - - - Hibernate generally understands the expected type of the parameter given its context in the query. - In the previous example, since we are using the parameter in a LIKE comparison against a String-typed - attribute Hibernate would automatically infer the type; so the above could be simplified. - - - - Parameter binding (inferred type) - Hibernate - - - - - There are also short hand forms for binding common types such as strings, booleans, integers, etc. - - - - Parameter binding (short forms) - Hibernate - - - - - In terms of execution, Hibernate offers 4 different methods. The 2 most commonly used are - - - - Query#list - executes the select query and returns back the list - of results. - - - - - Query#uniqueResult - executes the select query and returns the - single result. If there were more than one result an exception is thrown. - - - - - - - list() and uniqueResult() - - - - - - - If the unique result is used often and the attributes upon which it is based are unique, you may - want to consider mapping a natural-id and using the natural-id loading API. See the - Hibernate Domain Mapping Guide for more information on natural-ids. - - - - - Hibernate offers 2 additional, specialized methods for performing the query and handling results. - Query#scroll works in tandem with the JDBC notion of a scrollable - ResultSet. The scroll method is overloaded. Then main form accepts a single - argument of type org.hibernate.ScrollMode which indicates the type of - scrolling to be used. See the javadocs for ScrollMode for the details on each. The second form accepts - no argument and will use the ScrollMode indicated by - Dialect#defaultScrollMode. Query#scroll returns - a org.hibernate.ScrollableResults which wraps the underlying JDBC - (scrollable) ResultSet and provides access to the results. Since this form holds the JDBC ResultSet - open, the application should indicate when it is done with the ScrollableResults by calling - its close method (as inherited from java.io.Closeable, - so that ScrollableResults will work with try-with-resources blocks!). If left unclosed by the - application, Hibernate will automatically close the ScrollableResults when the current transaction - completes. - - - - - If you plan to use Query#scroll with collection fetches it is important - that your query explicitly order the results so that the JDBC results contain the the related - rows sequentially. - - - - - The last is Query#iterate, which is intended for loading entities which the - the application feels certain will be in the second-level cache. The idea behind iterate is that just - the matching identifiers will be obtained in the SQL query. From these the identifiers are resolved - by second-level cache lookup. If these second-level cache lookups fail, additional queries will - need to be issued against the database. This operation can perform significantly better for loading - large numbers of entities that for certain already exist in the second-level cache. In cases where - many of the entities do not exist in the second-level cache, this operation will almost definitely - perform worse. The Iterator returned from Query#iterate is actually a - specially typed Iterator: org.hibernate.engine.HibernateIterator. It - is specialized to expose a close method (again, - inherited from java.io.Closeable). When you are done with this Iterator - you should close it, either by casting to HibernateIterator or Closeable, or by calling - org.hibernate.Hibernate#close - -
- -
- JPA Query API - - - In JPA the query is represented by javax.persistence.Query or - javax.persistence.TypedQuery as obtained from the EntityManager. For named - queries EntityManager#createNamedQuery is used; otherwise - EntityManager#createQuery is used. - - - - Obtaining a Query reference - JPA - - - - - - - This will all sound very familiar. Not only was the JPQL syntax heavily inspired by HQL, but many - of the JPA APIs were heavily inspired by Hibernate. The 2 Query contracts are very similar. - - - - - The Query interface can then be used to control the execution of the query. For example, we - may want to specify an execution timeout or control caching. - - - - Basic Query usage - JPA - - - - - For complete details, see the Query javadocs. Many of the settings controlling the execution of the - query are defined as hints. JPA defines some standard hints (like timeout in the example), but most - are provider specific. Relying on provider specific hints limits your applications portability to some - degree. - - - - JPA standardized Query hints - - - javax.persistence.query.timeout - Defines the query timeout, in milliseconds. - - - - - javax.persistence.fetchgraph - Defines a "fetchgraph" EntityGraph. - Attributes explicitly specified as AttributeNodes are treated as FetchType.EAGER (via join fetch - or subsequent select). For details, see the EntityGraph discussions in . - - - - - javax.persistence.loadgraph - Defines a "loadgraph" EntityGraph. Attributes - explicitly specified as AttributeNodes are treated as FetchType.EAGER (via join fetch or - subsequent select). Attributes that are not specified are treated as FetchType.LAZY or - FetchType.EAGER depending on the attribute's definition in metadata. For details, see the - EntityGraph discussions in . - - - - - - Hibernate specific JPA Query hints - - - org.hibernate.cacheMode - Defines the CacheMode to use. See - org.hibernate.Query#setCacheMode. - - - - - org.hibernate.cacheable - Defines whether the query is cacheable. - true/false. See org.hibernate.Query#setCacheable. - - - - - org.hibernate.cacheRegion For queries that are cacheable, defines a specific - cache region to use. See org.hibernate.Query#setCacheRegion. - - - - - org.hibernate.comment - Defines the comment to apply to the generated SQL. - See org.hibernate.Query#setComment. - - - - - org.hibernate.fetchSize - Defines the JDBC fetch-size to use. See - org.hibernate.Query#setFetchSize - - - - - org.hibernate.flushMode - Defines the Hibernate-specific FlushMode to use. - See org.hibernate.Query#setFlushMode. If possible, prefer using - javax.persistence.Query#setFlushMode instead. - - - - - org.hibernate.readOnly - Defines that entities and collections loaded by - this query should be marked as read-only. See org.hibernate.Query#setReadOnly - - - - - - Just as seen in the Hibernate API, the final thing that needs to happen before the query can be - executed is to bind the values for any defined parameters. JPA defines a simplified set of parameter - binding methods. Essentially it supports setting the parameter value (by name/position) and - a specialized form for Calendar/Date types additionally accepting a TemporalType. - - - - Parameter binding - JPA - - - - - Additionally, JPA allows access to read some information about parameters as well. - - - - As far as execution, JPA supports the two main methods discussed above for the Hibernate API. It calls - these methods Query#getResultList and Query#getSingleResult. - They behave exactly as described for org.hibernate.Query#list and - org.hibernate.Query#uniqueResult. - -
-
- -
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/agg_func_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/agg_func_example.txt deleted file mode 100644 index fffa0f6acccd..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/agg_func_example.txt +++ /dev/null @@ -1,10 +0,0 @@ -select count(*), sum( o.total ), avg( o.total ), min( o.total ), max( o.total ) -from Order o - -select count( distinct c.name ) -from Customer c - -select c.id, c.name, sum( o.total ) -from Customer c - left join c.orders o -group by c.id, c.name \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/BasicQueryUsage.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/BasicQueryUsage.java deleted file mode 100644 index 0caf813b4524..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/BasicQueryUsage.java +++ /dev/null @@ -1,9 +0,0 @@ -Query query = ...; -// in seconds -query.setTimeout( 2 ); -// write to L2 caches, but do not read from them -query.setCacheMode( CacheMode.REFRESH ); -// assuming query cache was enabled for the SessionFactory -query.setCacheable( true ); -// add a comment to the generated SQL if enabled with the SF -query.setComment( "e pluribus unum" ) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/CreateQuery.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/CreateQuery.java deleted file mode 100644 index b3618a6c6296..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/CreateQuery.java +++ /dev/null @@ -1,3 +0,0 @@ -Query query = session.createQuery( - "select e.id, e.name from MyEntity e" -); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/GetNamedQuery.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/GetNamedQuery.java deleted file mode 100644 index 38e6435bc82e..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/GetNamedQuery.java +++ /dev/null @@ -1 +0,0 @@ -Query query = session.getNamedQuery( "my-predefined-named-query" ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/List.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/List.java deleted file mode 100644 index b56dd7624011..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/List.java +++ /dev/null @@ -1,3 +0,0 @@ -List results = - session.createQuery( "select e from MyEntity e" ) - .list(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameter.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameter.java deleted file mode 100644 index 984c4d1086b6..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameter.java +++ /dev/null @@ -1,4 +0,0 @@ -Query query = session.createQuery( - "select e from MyEntity e where e.name like :filter" -); -query.setParameter( "filter", "D%", StringType.INSTANCE ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameterInferredType.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameterInferredType.java deleted file mode 100644 index d8aacc9dc9c4..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameterInferredType.java +++ /dev/null @@ -1,4 +0,0 @@ -Query query = session.createQuery( - "select e from MyEntity e where e.name like :filter" -); -query.setParameter( "filter", "D%" ); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameterShortForms.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameterShortForms.java deleted file mode 100644 index c0105ac95ad6..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/SetParameterShortForms.java +++ /dev/null @@ -1,9 +0,0 @@ -Query query = session.createQuery( - "select e from MyEntity e where e.name like :filter" -); -query.setString( "filter", "D%" ); - -query = session.createQuery( - "select e from MyEntity e where e.active = :active" -); -query.setBoolean( "active", true ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/UniqueResult.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/UniqueResult.java deleted file mode 100644 index 4bb245a50b9b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/hibernate/UniqueResult.java +++ /dev/null @@ -1,5 +0,0 @@ -String qry = "select e from MyEntity e " + - " where e.code = :code" -MyEntity result = (MyEntity) session.createQuery( qry ) - .setParameter( "code", 123 ) - .uniqueResult(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/BasicQueryUsage.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/BasicQueryUsage.java deleted file mode 100644 index 32a4e911f6e1..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/BasicQueryUsage.java +++ /dev/null @@ -1,5 +0,0 @@ -Query query = ...; -// timeout - in milliseconds -query.setHint( "javax.persistence.query.timeout", 2000 ) -// Do not peform (AUTO) implicit flushing -query.setFlushMode( COMMIT ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/CreateNamedQuery.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/CreateNamedQuery.java deleted file mode 100644 index 37ecdab198d6..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/CreateNamedQuery.java +++ /dev/null @@ -1,5 +0,0 @@ -Query query = em.createNamedQuery( "my-predefined-named-query" ); -TypedQuery query2 = em.createNamedQuery( - "my-predefined-named-query", - MyEntity.class -); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/CreateQuery.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/CreateQuery.java deleted file mode 100644 index 742cd286d0de..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/CreateQuery.java +++ /dev/null @@ -1,7 +0,0 @@ -Query query = em.createQuery( - "select e from MyEntity e where name like :filter" -); -TypedQuery query2 = em.createQuery( - "select e from MyEntity e where name like :filter" - MyEntity.class -); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/SetParameter.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/SetParameter.java deleted file mode 100644 index ab524d1b7f6e..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/api/jpa/SetParameter.java +++ /dev/null @@ -1,9 +0,0 @@ -Query query = em.createQuery( - "select e from MyEntity e where e.name like :filter" -); -query.setParameter( "filter", "D%" ); - -Query q2 = em.createQuery( - "select e from MyEntity e where e.activeDate > :activeDate" -); -q2.setParameter( "activeDate", new Date(), TemporalType.DATE ); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/arithmetic_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/arithmetic_example.txt deleted file mode 100644 index 19c8b5c2fb0a..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/arithmetic_example.txt +++ /dev/null @@ -1,9 +0,0 @@ -select year( current_date() ) - year( c.dateOfBirth ) -from Customer c - -select c -from Customer c -where year( current_date() ) - year( c.dateOfBirth ) < 30 - -select o.customer, o.total + ( o.total * :salesTax ) -from Order o \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/collection_expression_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/collection_expression_example.txt deleted file mode 100644 index a625cf5066f1..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/collection_expression_example.txt +++ /dev/null @@ -1,36 +0,0 @@ -select cal -from Calendar cal -where maxelement(cal.holidays) > current_date() - -select o -from Order o -where maxindex(o.items) > 100 - -select o -from Order o -where minelement(o.items) > 10000 - -select m -from Cat as m, Cat as kit -where kit in elements(m.kittens) - -// the above query can be re-written in jpql standard way: -select m -from Cat as m, Cat as kit -where kit member of m.kittens - -select p -from NameList l, Person p -where p.name = some elements(l.names) - -select cat -from Cat cat -where exists elements(cat.kittens) - -select p -from Player p -where 3 > all elements(p.scores) - -select show -from Show show -where 'fizard' in indices(show.acts) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/collection_reference_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/collection_reference_example.txt deleted file mode 100644 index d9f1eaceb33f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/collection_reference_example.txt +++ /dev/null @@ -1,16 +0,0 @@ -select c -from Customer c - join c.orders o - join o.lineItems l - join l.product p -where o.status = 'pending' - and p.status = 'backorder' - -// alternate syntax -select c -from Customer c, - in(c.orders) o, - in(o.lineItems) l - join l.product p -where o.status = 'pending' - and p.status = 'backorder' \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/concat_op_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/concat_op_example.txt deleted file mode 100644 index 645f51dca823..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/concat_op_example.txt +++ /dev/null @@ -1,3 +0,0 @@ -select 'Mr. ' || c.name.first || ' ' || c.name.last -from Customer c -where c.gender = Gender.MALE \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/ctor_dynamic_instantiation_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/ctor_dynamic_instantiation_example.txt deleted file mode 100644 index c96df422fe87..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/ctor_dynamic_instantiation_example.txt +++ /dev/null @@ -1,4 +0,0 @@ -select new Family( mother, mate, offspr ) -from DomesticCat as mother - join mother.mate as mate - left join mother.kittens as offspr \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/empty_collection_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/empty_collection_example.txt deleted file mode 100644 index f6af597a87f3..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/empty_collection_example.txt +++ /dev/null @@ -1,7 +0,0 @@ -select o -from Order o -where o.lineItems is empty - -select c -from Customer c -where c.pastDueBills is not empty \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/entity_type_exp_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/entity_type_exp_example.txt deleted file mode 100644 index 1f6c58e49cda..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/entity_type_exp_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -select p -from Payment p -where type(p) = CreditCardPayment - -select p -from Payment p -where type(p) = :aType - diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/group_by_illustration.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/group_by_illustration.txt deleted file mode 100644 index 598d20c5f8bf..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/group_by_illustration.txt +++ /dev/null @@ -1,10 +0,0 @@ -// retrieve the total for all orders -select sum( o.total ) -from Order o - -// retrieve the total of all orders -// *grouped by* customer -select c.id, sum( o.total ) -from Order o - inner join o.customer c -group by c.id \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/having_illustration.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/having_illustration.txt deleted file mode 100644 index 60f39b3c4a5d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/having_illustration.txt +++ /dev/null @@ -1,5 +0,0 @@ -select c.id, sum( o.total ) -from Order o - inner join o.customer c -group by c.id -having sum( o.total ) > 10000.00 \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/index_operator_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/index_operator_example.txt deleted file mode 100644 index 06f3326c03ed..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/index_operator_example.txt +++ /dev/null @@ -1,22 +0,0 @@ -select o -from Order o -where o.items[0].id = 1234 - -select p -from Person p, Calendar c -where c.holidays['national day'] = p.birthDay - and p.nationality.calendar = c - -select i -from Item i, Order o -where o.items[ o.deliveredItemIndices[0] ] = i - and o.id = 11 - -select i -from Item i, Order o -where o.items[ maxindex(o.items) ] = i - and o.id = 11 - -select i -from Item i, Order o -where o.items[ size(o.items) - 1 ] = i \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_explicit_inner.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_explicit_inner.txt deleted file mode 100644 index a809d3fec960..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_explicit_inner.txt +++ /dev/null @@ -1,10 +0,0 @@ -select c -from Customer c - join c.chiefExecutive ceo -where ceo.age < 25 - -// same query but specifying join type as 'inner' explicitly -select c -from Customer c - inner join c.chiefExecutive ceo -where ceo.age < 25 diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_explicit_outer.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_explicit_outer.txt deleted file mode 100644 index 2e8ed3385451..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_explicit_outer.txt +++ /dev/null @@ -1,15 +0,0 @@ -// get customers who have orders worth more than $5000 -// or who are in "preferred" status -select distinct c -from Customer c - left join c.orders o -where o.value > 5000.00 - or c.status = 'preferred' - -// functionally the same query but using the -// 'left outer' phrase -select distinct c -from Customer c - left outer join c.orders o -where o.value > 5000.00 - or c.status = 'preferred' diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_fetch.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_fetch.txt deleted file mode 100644 index a882f288167b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_fetch.txt +++ /dev/null @@ -1,3 +0,0 @@ -select c -from Customer c - left join fetch c.orders o \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_implicit.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_implicit.txt deleted file mode 100644 index 4185a64f7271..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_implicit.txt +++ /dev/null @@ -1,9 +0,0 @@ -select c -from Customer c -where c.chiefExecutive.age < 25 - -// same as -select c -from Customer c - inner join c.chiefExecutive ceo -where ceo.age < 25 diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_implicit_reused.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_implicit_reused.txt deleted file mode 100644 index 18db817e5ec1..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_implicit_reused.txt +++ /dev/null @@ -1,19 +0,0 @@ -select c -from Customer c -where c.chiefExecutive.age < 25 - and c.chiefExecutive.address.state = 'TX' - -// same as -select c -from Customer c - inner join c.chiefExecutive ceo -where ceo.age < 25 - and ceo.address.state = 'TX' - -// same as -select c -from Customer c - inner join c.chiefExecutive ceo - inner join ceo.address a -where ceo.age < 25 - and a.state = 'TX' diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_with.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_with.txt deleted file mode 100644 index b0441e621687..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/join_example_with.txt +++ /dev/null @@ -1,4 +0,0 @@ -select distinct c -from Customer c - left join c.orders o - with o.value > 5000.00 \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/jpql_positional_parameter_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/jpql_positional_parameter_example.txt deleted file mode 100644 index ca276e93c575..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/jpql_positional_parameter_example.txt +++ /dev/null @@ -1,16 +0,0 @@ -String queryString = - "select c " + - "from Customer c " + - "where c.name = ?1 " + - " or c.nickName = ?1"; - -// HQL - as you can see, handled just like named parameters -// in terms of API -List customers = session.createQuery( queryString ) - .setParameter( "1", theNameOfInterest ) - .list(); - -// JPQL -List customers = entityManager.createQuery( queryString, Customer.class ) - .setParameter( 1, theNameOfInterest ) - .getResultList(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/list_dynamic_instantiation_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/list_dynamic_instantiation_example.txt deleted file mode 100644 index 1678b5ad2d23..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/list_dynamic_instantiation_example.txt +++ /dev/null @@ -1,4 +0,0 @@ -select new list(mother, offspr, mate.name) -from DomesticCat as mother - inner join mother.mate as mate - left outer join mother.kittens as offspr \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/locate_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/locate_bnf.txt deleted file mode 100644 index c8e0f02d8ffa..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/locate_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -locate( string_expression, string_expression[, numeric_expression] ) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/map_dynamic_instantiation_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/map_dynamic_instantiation_example.txt deleted file mode 100644 index ed9bcfc972b0..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/map_dynamic_instantiation_example.txt +++ /dev/null @@ -1,7 +0,0 @@ -select new map( mother as mother, offspr as offspr, mate as mate ) -from DomesticCat as mother - inner join mother.mate as mate - left outer join mother.kittens as offspr - -select new map( max(c.bodyWeight) as max, min(c.bodyWeight) as min, count(*) as n ) -from Cat c \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/member_of_collection_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/member_of_collection_example.txt deleted file mode 100644 index e0969a3d0bd5..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/member_of_collection_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -select p -from Person p -where 'John' member of p.nickNames - -select p -from Person p -where p.name.first = 'Joseph' - and 'Joey' not member of p.nickNames diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/multiple_root_entity_ref_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/multiple_root_entity_ref_example.txt deleted file mode 100644 index 173c8b38791f..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/multiple_root_entity_ref_example.txt +++ /dev/null @@ -1,5 +0,0 @@ -// build a product between customers and active mailing campaigns so we can spam! -select distinct cust, camp -from Customer cust, Campaign camp -where camp.type = 'mail' - and current_timestamp() between camp.activeRange.start and camp.activeRange.end \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/multiple_root_entity_ref_example2.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/multiple_root_entity_ref_example2.txt deleted file mode 100644 index 5e2231e433db..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/multiple_root_entity_ref_example2.txt +++ /dev/null @@ -1,5 +0,0 @@ -// retrieve all customers with headquarters in the same state as Acme's headquarters -select distinct c1 -from Customer c1, Customer c2 -where c1.address.state = c2.address.state - and c2.name = 'Acme' \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/named_parameter_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/named_parameter_example.txt deleted file mode 100644 index 0ca77a503d9c..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/named_parameter_example.txt +++ /dev/null @@ -1,15 +0,0 @@ -String queryString = - "select c " + - "from Customer c " + - "where c.name = :name " + - " or c.nickName = :name"; - -// HQL -List customers = session.createQuery( queryString ) - .setParameter( "name", theNameOfInterest ) - .list(); - -// JPQL -List customers = entityManager.createQuery( queryString, Customer.class ) - .setParameter( "name", theNameOfInterest ) - .getResultList(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/nullif_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/nullif_example.txt deleted file mode 100644 index 6610b5764c55..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/nullif_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -// return customers who have changed their last name -select nullif( c.previousName.last, c.name.last ) -from Customer c - -// equivalent CASE expression -select case when c.previousName.last = c.name.last then null - else c.previousName.last end -from Customer c diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/numeric_literals_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/numeric_literals_example.txt deleted file mode 100644 index 1732edfec295..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/numeric_literals_example.txt +++ /dev/null @@ -1,29 +0,0 @@ -// simple integer literal -select o -from Order o -where o.referenceNumber = 123 - -// simple integer literal, typed as a long -select o -from Order o -where o.referenceNumber = 123L - -// decimal notation -select o -from Order o -where o.total > 5000.00 - -// decimal notation, typed as a float -select o -from Order o -where o.total > 5000.00F - -// scientific notation -select o -from Order o -where o.total > 5e+3 - -// scientific notation, typed as a float -select o -from Order o -where o.total > 5e+3F \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/order_by_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/order_by_example.txt deleted file mode 100644 index 71b6024feeb8..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/order_by_example.txt +++ /dev/null @@ -1,10 +0,0 @@ -// legal because p.name is implicitly part of p -select p -from Person p -order by p.name - -select c.id, sum( o.total ) as t -from Order o - inner join o.customer c -group by c.id -order by t diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_between_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_between_example.txt deleted file mode 100644 index f445b48d4658..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_between_example.txt +++ /dev/null @@ -1,19 +0,0 @@ -select p -from Customer c - join c.paymentHistory p -where c.id = 123 - and index(p) between 0 and 9 - -select c -from Customer c -where c.president.dateOfBirth - between {d '1945-01-01'} - and {d '1965-01-01'} - -select o -from Order o -where o.total between 500 and 5000 - -select p -from Person p -where p.name between 'A' and 'E' \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_comparison_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_comparison_example.txt deleted file mode 100644 index ff52fa793269..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_comparison_example.txt +++ /dev/null @@ -1,34 +0,0 @@ -// numeric comparison -select c -from Customer c -where c.chiefExecutive.age < 30 - -// string comparison -select c -from Customer c -where c.name = 'Acme' - -// datetime comparison -select c -from Customer c -where c.inceptionDate < {d '2000-01-01'} - -// enum comparison -select c -from Customer c -where c.chiefExecutive.gender = com.acme.Gender.MALE - -// boolean comparison -select c -from Customer c -where c.sendEmail = true - -// entity type comparison -select p -from Payment p -where type(p) = WireTransferPayment - -// entity value comparison -select c -from Customer c -where c.chiefExecutive = c.chiefTechnologist \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_comparison_example_using_all.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_comparison_example_using_all.txt deleted file mode 100644 index 2c9aa10ed3ad..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_comparison_example_using_all.txt +++ /dev/null @@ -1,9 +0,0 @@ -// select all players that scored at least 3 points -// in every game. -select p -from Player p -where 3 > all ( - select spg.points - from StatsPerGame spg - where spg.player = p -) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_in_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_in_bnf.txt deleted file mode 100644 index a8f3daec416b..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_in_bnf.txt +++ /dev/null @@ -1,8 +0,0 @@ -in_expression ::= single_valued_expression - [NOT] IN single_valued_list - -single_valued_list ::= constructor_expression | - (subquery) | - collection_valued_input_parameter - -constructor_expression ::= (expression[, expression]*) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_in_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_in_example.txt deleted file mode 100644 index 8e0f3b97f5ff..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_in_example.txt +++ /dev/null @@ -1,36 +0,0 @@ -select p -from Payment p -where type(p) in (CreditCardPayment, WireTransferPayment) - -select c -from Customer c -where c.hqAddress.state in ('TX', 'OK', 'LA', 'NM') - -select c -from Customer c -where c.hqAddress.state in ? - -select c -from Customer c -where c.hqAddress.state in ( - select dm.state - from DeliveryMetadata dm - where dm.salesTax is not null -) - -// Not JPQL compliant! -select c -from Customer c -where c.name in ( - ('John','Doe'), - ('Jane','Doe') -) - -// Not JPQL compliant! -select c -from Customer c -where c.chiefExecutive in ( - select p - from Person p - where ... -) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_like_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_like_bnf.txt deleted file mode 100644 index abac134a49cf..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_like_bnf.txt +++ /dev/null @@ -1,4 +0,0 @@ -like_expression ::= - string_expression - [NOT] LIKE pattern_value - [ESCAPE escape_character] diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_like_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_like_example.txt deleted file mode 100644 index 348c21bae391..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_like_example.txt +++ /dev/null @@ -1,13 +0,0 @@ -select p -from Person p -where p.name like '%Schmidt' - -select p -from Person p -where p.name not like 'Jingleheimmer%' - -// find any with name starting with "sp_" -select sp -from StoredProcedureMetadata sp -where sp.name like 'sp|_%' escape '|' - diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_nullness_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_nullness_example.txt deleted file mode 100644 index bf36fe023035..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/predicate_nullness_example.txt +++ /dev/null @@ -1,9 +0,0 @@ -// select everyone with an associated address -select p -from Person p -where p.address is not null - -// select everyone without an associated address -select p -from Person p -where p.address is null \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/qualified_path_expressions_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/qualified_path_expressions_example.txt deleted file mode 100644 index 5bb7eab416ea..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/qualified_path_expressions_example.txt +++ /dev/null @@ -1,33 +0,0 @@ -// Product.images is a Map : key = a name, value = file path - -// select all the image file paths (the map value) for Product#123 -select i -from Product p - join p.images i -where p.id = 123 - -// same as above -select value(i) -from Product p - join p.images i -where p.id = 123 - -// select all the image names (the map key) for Product#123 -select key(i) -from Product p - join p.images i -where p.id = 123 - -// select all the image names and file paths (the 'Map.Entry') for Product#123 -select entry(i) -from Product p - join p.images i -where p.id = 123 - -// total the value of the initial line items for all orders for a customer -select sum( li.amount ) -from Customer c - join c.orders o - join o.lineItems li -where c.id = 123 - and index(li) = 1 \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/root_entity_ref_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/root_entity_ref_bnf.txt deleted file mode 100644 index 9e5b71cca098..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/root_entity_ref_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -root_entity_reference ::= entity_name [AS] identification_variable \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/searched_case_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/searched_case_bnf.txt deleted file mode 100644 index 211fa3be089d..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/searched_case_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -CASE [ WHEN {test_conditional} THEN {match_result} ]* ELSE {miss_result} END \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/searched_case_exp_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/searched_case_exp_example.txt deleted file mode 100644 index 34d80d9cc1fd..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/searched_case_exp_example.txt +++ /dev/null @@ -1,9 +0,0 @@ -select case when c.name.first is not null then c.name.first - when c.nickName is not null then c.nickName - else '' end -from Customer c - -// Again, the abbreviated form coalesce can handle this a -// little more succinctly -select coalesce( c.name.first, c.nickName, '' ) -from Customer c diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simple_case_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simple_case_bnf.txt deleted file mode 100644 index f4b03b45fa73..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simple_case_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -CASE {operand} WHEN {test_value} THEN {match_result} ELSE {miss_result} END \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simple_case_exp_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simple_case_exp_example.txt deleted file mode 100644 index f09820d50618..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simple_case_exp_example.txt +++ /dev/null @@ -1,16 +0,0 @@ -select case c.nickName when null then '' else c.nickName end -from Customer c - -// This NULL checking is such a common case that most dbs -// define an abbreviated CASE form. For example: -select nvl( c.nickName, '' ) -from Customer c - -// or: -select isnull( c.nickName, '' ) -from Customer c - -// the standard coalesce abbreviated form can be used -// to achieve the same result: -select coalesce( c.nickName, '' ) -from Customer c diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simplest_query.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simplest_query.java deleted file mode 100644 index 10b1a0b4bf6c..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simplest_query.java +++ /dev/null @@ -1 +0,0 @@ -select c from com.acme.Cat c \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simplest_query2.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simplest_query2.java deleted file mode 100644 index 4b56392fadb9..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/simplest_query2.java +++ /dev/null @@ -1 +0,0 @@ -select c from Cat c \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_delete_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_delete_bnf.txt deleted file mode 100644 index 087d1ed4da4a..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_delete_bnf.txt +++ /dev/null @@ -1,3 +0,0 @@ -delete_statement ::= delete_clause [where_clause] - -delete_clause ::= DELETE FROM entity_name [[AS] identification_variable] diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_insert_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_insert_bnf.txt deleted file mode 100644 index 95bb029cceb3..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_insert_bnf.txt +++ /dev/null @@ -1,5 +0,0 @@ -insert_statement ::= insert_clause select_statement - -insert_clause ::= INSERT INTO entity_name (attribute_list) - -attribute_list ::= state_field[, state_field ]* \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_insert_example_named_id.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_insert_example_named_id.java deleted file mode 100644 index 1a27846de607..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_insert_example_named_id.java +++ /dev/null @@ -1,2 +0,0 @@ -String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ..."; -int createdEntities = s.createQuery( hqlInsert ).executeUpdate(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_select_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_select_bnf.txt deleted file mode 100644 index e4aac0d1c3bb..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_select_bnf.txt +++ /dev/null @@ -1,7 +0,0 @@ -select_statement :: = - [select_clause] - from_clause - [where_clause] - [groupby_clause] - [having_clause] - [orderby_clause] \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_bnf.txt deleted file mode 100644 index 6ffbb48ed2d3..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_bnf.txt +++ /dev/null @@ -1,11 +0,0 @@ -update_statement ::= update_clause [where_clause] - -update_clause ::= UPDATE entity_name [[AS] identification_variable] - SET update_item {, update_item}* - -update_item ::= [identification_variable.]{state_field | single_valued_object_field} - = new_value - -new_value ::= scalar_expression | - simple_entity_expression | - NULL diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_hql.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_hql.java deleted file mode 100644 index 779ea5121fbc..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_hql.java +++ /dev/null @@ -1,8 +0,0 @@ -String hqlUpdate = - "update Customer c " + - "set c.name = :newName " + - "where c.name = :oldName"; -int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_hql_versioned.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_hql_versioned.java deleted file mode 100644 index aece8f859511..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_hql_versioned.java +++ /dev/null @@ -1,8 +0,0 @@ -String hqlVersionedUpdate = - "update versioned Customer c " + - "set c.name = :newName " + - "where c.name = :oldName"; -int updatedEntities = s.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_jpql.java b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_jpql.java deleted file mode 100644 index 99a501254853..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/statement_update_example_jpql.java +++ /dev/null @@ -1,8 +0,0 @@ -String jpqlUpdate = - "update Customer c " + - "set c.name = :newName " + - "where c.name = :oldName"; -int updatedEntities = entityManager.createQuery( jpqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/string_literals_example.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/string_literals_example.txt deleted file mode 100644 index d2f95cb50f49..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/string_literals_example.txt +++ /dev/null @@ -1,8 +0,0 @@ -select c -from Customer c -where c.name = 'Acme' - -select c -from Customer c -where c.name = 'Acme''s Pretzel Logic' - diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/substring_bnf.txt b/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/substring_bnf.txt deleted file mode 100644 index 81302e9a4b19..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-hql/extras/substring_bnf.txt +++ /dev/null @@ -1 +0,0 @@ -substring( string_expression, numeric_expression [, numeric_expression] ) \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/query-native/Native.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/query-native/Native.xml deleted file mode 100644 index a5f0e8f1071e..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/query-native/Native.xml +++ /dev/null @@ -1,1126 +0,0 @@ - - - - - - Native SQL Queries - - - You may also express queries in the native SQL dialect of your database. This is useful if you - want to utilize database specific features such as query hints or the CONNECT BY option in Oracle. - It also provides a clean migration path from a direct SQL/JDBC based application to Hibernate/JPA. - Hibernate also allows you to specify handwritten SQL (including stored procedures) for all - create, update, delete, and load operations. - - -
- Using a <literal>SQLQuery</literal> - - Execution of native SQL queries is controlled via the - SQLQuery interface, which is obtained by calling - Session.createSQLQuery(). The following sections - describe how to use this API for querying. - -
- Scalar queries - - The most basic SQL query is to get a list of scalars - (values). - - sess.createSQLQuery("SELECT * FROM CATS").list(); -sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list(); - - - These will return a List of Object arrays (Object[]) with scalar - values for each column in the CATS table. Hibernate will use - ResultSetMetadata to deduce the actual order and types of the returned - scalar values. - - To avoid the overhead of using - ResultSetMetadata, or simply to be more explicit in - what is returned, one can use addScalar(): - - sess.createSQLQuery("SELECT * FROM CATS") - .addScalar("ID", Hibernate.LONG) - .addScalar("NAME", Hibernate.STRING) - .addScalar("BIRTHDATE", Hibernate.DATE) - - - This query specified: - - - - the SQL query string - - - - the columns and types to return - - - - This will return Object arrays, but now it will not use - ResultSetMetadata but will instead explicitly get the - ID, NAME and BIRTHDATE column as respectively a Long, String and a Short - from the underlying resultset. This also means that only these three - columns will be returned, even though the query is using - * and could return more than the three listed - columns. - - It is possible to leave out the type information for all or some - of the scalars. - - sess.createSQLQuery("SELECT * FROM CATS") - .addScalar("ID", Hibernate.LONG) - .addScalar("NAME") - .addScalar("BIRTHDATE") - - - This is essentially the same query as before, but now - ResultSetMetaData is used to determine the type of - NAME and BIRTHDATE, where as the type of ID is explicitly - specified. - - How the java.sql.Types returned from ResultSetMetaData is mapped - to Hibernate types is controlled by the Dialect. If a specific type is - not mapped, or does not result in the expected type, it is possible to - customize it via calls to registerHibernateType in - the Dialect. -
- -
- Entity queries - - The above queries were all about returning scalar values, - basically returning the "raw" values from the resultset. The following - shows how to get entity objects from a native sql query via - addEntity(). - - sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class); -sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class); - - - This query specified: - - - - the SQL query string - - - - the entity returned by the query - - - - Assuming that Cat is mapped as a class with the columns ID, NAME - and BIRTHDATE the above queries will both return a List where each - element is a Cat entity. - - If the entity is mapped with a many-to-one to - another entity it is required to also return this when performing the - native query, otherwise a database specific "column not found" error - will occur. The additional columns will automatically be returned when - using the * notation, but we prefer to be explicit as in the following - example for a many-to-one to a - Dog: - - sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class); - - - This will allow cat.getDog() to function properly. -
- -
- Handling associations and collections - - It is possible to eagerly join in the Dog to - avoid the possible extra roundtrip for initializing the proxy. This is - done via the addJoin() method, which allows you to - join in an association or collection. - - sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID") - .addEntity("cat", Cat.class) - .addJoin("cat.dog"); - - - In this example, the returned Cat's will have - their dog property fully initialized without any - extra roundtrip to the database. Notice that you added an alias name - ("cat") to be able to specify the target property path of the join. It - is possible to do the same eager joining for collections, e.g. if the - Cat had a one-to-many to Dog - instead. - - sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID") - .addEntity("cat", Cat.class) - .addJoin("cat.dogs"); - - - At this stage you are reaching the limits of what is possible with - native queries, without starting to enhance the sql queries to make them - usable in Hibernate. Problems can arise when returning multiple entities - of the same type or when the default alias/column names are not - enough. -
- -
- Returning multiple entities - - Until now, the result set column names are assumed to be the same - as the column names specified in the mapping document. This can be - problematic for SQL queries that join multiple tables, since the same - column names can appear in more than one table. - - Column alias injection is needed in the following query (which - most likely will fail): - - sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID") - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class) - - - The query was intended to return two Cat instances per row: a cat - and its mother. The query will, however, fail because there is a - conflict of names; the instances are mapped to the same column names. - Also, on some databases the returned column aliases will most likely be - on the form "c.ID", "c.NAME", etc. which are not equal to the columns - specified in the mappings ("ID" and "NAME"). - - The following form is not vulnerable to column name - duplication: - - sess.createSQLQuery("SELECT {cat.*}, {m.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = m.ID") - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class) - - - This query specified: - - - - the SQL query string, with placeholders for Hibernate to - inject column aliases - - - - the entities returned by the query - - - - The {cat.*} and {mother.*} notation used above is a shorthand for - "all properties". Alternatively, you can list the columns explicitly, - but even in this case Hibernate injects the SQL column aliases for each - property. The placeholder for a column alias is just the property name - qualified by the table alias. In the following example, you retrieve - Cats and their mothers from a different table (cat_log) to the one - declared in the mapping metadata. You can even use the property aliases - in the where clause. - - String sql = "SELECT ID as {c.id}, NAME as {c.name}, " + - "BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " + - "FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID"; - -List loggedCats = sess.createSQLQuery(sql) - .addEntity("cat", Cat.class) - .addEntity("mother", Cat.class).list() - - -
- Alias and property references - - In most cases the above alias injection is needed. For queries - relating to more complex mappings, like composite properties, - inheritance discriminators, collections etc., you can use specific - aliases that allow Hibernate to inject the proper aliases. - - The following table shows the different ways you can use the - alias injection. Please note that the alias names in the result are - simply examples; each alias will have a unique and probably different - name when used. - - - Alias injection names - - - - - - - - - - - Description - - Syntax - - Example - - - - - - A simple property - - {[aliasname].[propertyname] - - A_NAME as {item.name} - - - - A composite property - - {[aliasname].[componentname].[propertyname]} - - CURRENCY as {item.amount.currency}, VALUE as - {item.amount.value} - - - - Discriminator of an entity - - {[aliasname].class} - - DISC as {item.class} - - - - All properties of an entity - - {[aliasname].*} - - {item.*} - - - - A collection key - - {[aliasname].key} - - ORGID as {coll.key} - - - - The id of an collection - - {[aliasname].id} - - EMPID as {coll.id} - - - - The element of an collection - - {[aliasname].element} - - XID as {coll.element} - - - - property of the element in the collection - - {[aliasname].element.[propertyname]} - - NAME as {coll.element.name} - - - - All properties of the element in the collection - - {[aliasname].element.*} - - {coll.element.*} - - - - All properties of the collection - - {[aliasname].*} - - {coll.*} - - - -
-
-
- -
- Returning non-managed entities - - It is possible to apply a ResultTransformer to native SQL queries, - allowing it to return non-managed entities. - - sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") - .setResultTransformer(Transformers.aliasToBean(CatDTO.class)) - - This query specified: - - - - the SQL query string - - - - a result transformer - - - - The above query will return a list of CatDTO - which has been instantiated and injected the values of NAME and - BIRTHNAME into its corresponding properties or fields. -
- -
- Handling inheritance - - Native SQL queries which query for entities that are mapped as - part of an inheritance must include all properties for the baseclass and - all its subclasses. -
- -
- Parameters - - Native SQL queries support positional as well as named - parameters: - - Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class); -List pusList = query.setString(0, "Pus%").list(); - -query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class); -List pusList = query.setString("name", "Pus%").list(); -
-
- -
- Named SQL queries - - Named SQL queries can also be defined in the mapping document and - called in exactly the same way as a named HQL query. In this case, you do - not need to call - addEntity(). - - - Named sql query using the <sql-query> maping - element - - <sql-query name="persons"> - <return alias="person" class="eg.Person"/> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex} - FROM PERSON person - WHERE person.NAME LIKE :namePattern -</sql-query> - - - - Execution of a named query - - List people = sess.getNamedQuery("persons") - .setString("namePattern", namePattern) - .setMaxResults(50) - .list(); - - - The <return-join> element is use to join - associations and the <load-collection> element is - used to define queries which initialize collections, - - - Named sql query with association - - <sql-query name="personsWith"> - <return alias="person" class="eg.Person"/> - <return-join alias="address" property="person.mailingAddress"/> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex}, - address.STREET AS {address.street}, - address.CITY AS {address.city}, - address.STATE AS {address.state}, - address.ZIP AS {address.zip} - FROM PERSON person - JOIN ADDRESS address - ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' - WHERE person.NAME LIKE :namePattern -</sql-query> - - - A named SQL query may return a scalar value. You must declare the - column alias and Hibernate type using the - <return-scalar> element: - - - Named query returning a scalar - - <sql-query name="mySqlQuery"> - <return-scalar column="name" type="string"/> - <return-scalar column="age" type="long"/> - SELECT p.NAME AS name, - p.AGE AS age, - FROM PERSON p WHERE p.NAME LIKE 'Hiber%' -</sql-query> - - - You can externalize the resultset mapping information in a - <resultset> element which will allow you to - either reuse them across several named queries or through the - setResultSetMapping() API. - - - <resultset> mapping used to externalize mapping - information - - <resultset name="personAddress"> - <return alias="person" class="eg.Person"/> - <return-join alias="address" property="person.mailingAddress"/> -</resultset> - -<sql-query name="personsWith" resultset-ref="personAddress"> - SELECT person.NAME AS {person.name}, - person.AGE AS {person.age}, - person.SEX AS {person.sex}, - address.STREET AS {address.street}, - address.CITY AS {address.city}, - address.STATE AS {address.state}, - address.ZIP AS {address.zip} - FROM PERSON person - JOIN ADDRESS address - ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' - WHERE person.NAME LIKE :namePattern -</sql-query> - - - You can, alternatively, use the resultset mapping information in - your hbm files directly in java code. - - - Programmatically specifying the result mapping information - - - List cats = sess.createSQLQuery( - "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id" - ) - .setResultSetMapping("catAndKitten") - .list(); - - - So far we have only looked at externalizing SQL queries using - Hibernate mapping files. The same concept is also available with - anntations and is called named native queries. You can use - @NamedNativeQuery - (@NamedNativeQueries) in conjunction with - @SqlResultSetMapping - (@SqlResultSetMappings). Like - @NamedQuery, @NamedNativeQuery - and @SqlResultSetMapping can be defined at class level, - but their scope is global to the application. Lets look at a view - examples. - - - shows how a resultSetMapping parameter is defined in - @NamedNativeQuery. It represents the name of a defined - @SqlResultSetMapping. The resultset mapping declares - the entities retrieved by this native query. Each field of the entity is - bound to an SQL alias (or column name). All fields of the entity including - the ones of subclasses and the foreign key columns of related entities - have to be present in the SQL query. Field definitions are optional - provided that they map to the same column name as the one declared on the - class property. In the example 2 entities, Night and - Area, are returned and each property is declared and - associated to a column name, actually the column name retrieved by the - query. - - In the result - set mapping is implicit. We only describe the entity class of the result - set mapping. The property / column mappings is done using the entity - mapping values. In this case the model property is bound to the model_txt - column. - - Finally, if the association to a related entity involve a composite - primary key, a @FieldResult element should be used for - each foreign key column. The @FieldResult name is - composed of the property name for the relationship, followed by a dot - ("."), followed by the name or the field or property of the primary key. - This can be seen in . - - - Named SQL query using <classname>@NamedNativeQuery</classname> - together with <classname>@SqlResultSetMapping</classname> - - @NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, " - + " night.night_date, area.id aid, night.area_id, area.name " - + "from Night night, Area area where night.area_id = area.id", - resultSetMapping="joinMapping") -@SqlResultSetMapping(name="joinMapping", entities={ - @EntityResult(entityClass=Night.class, fields = { - @FieldResult(name="id", column="nid"), - @FieldResult(name="duration", column="night_duration"), - @FieldResult(name="date", column="night_date"), - @FieldResult(name="area", column="area_id"), - discriminatorColumn="disc" - }), - @EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = { - @FieldResult(name="id", column="aid"), - @FieldResult(name="name", column="name") - }) - } -) - - - - Implicit result set mapping - - @Entity -@SqlResultSetMapping(name="implicit", - entities=@EntityResult(entityClass=SpaceShip.class)) -@NamedNativeQuery(name="implicitSample", - query="select * from SpaceShip", - resultSetMapping="implicit") -public class SpaceShip { - private String name; - private String model; - private double speed; - - @Id - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Column(name="model_txt") - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } -} - - - - Using dot notation in @FieldResult for specifying associations - - - @Entity -@SqlResultSetMapping(name="compositekey", - entities=@EntityResult(entityClass=SpaceShip.class, - fields = { - @FieldResult(name="name", column = "name"), - @FieldResult(name="model", column = "model"), - @FieldResult(name="speed", column = "speed"), - @FieldResult(name="captain.firstname", column = "firstn"), - @FieldResult(name="captain.lastname", column = "lastn"), - @FieldResult(name="dimensions.length", column = "length"), - @FieldResult(name="dimensions.width", column = "width") - }), - columns = { @ColumnResult(name = "surface"), - @ColumnResult(name = "volume") } ) - -@NamedNativeQuery(name="compositekey", - query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip", - resultSetMapping="compositekey") -} ) -public class SpaceShip { - private String name; - private String model; - private double speed; - private Captain captain; - private Dimensions dimensions; - - @Id - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @ManyToOne(fetch= FetchType.LAZY) - @JoinColumns( { - @JoinColumn(name="fname", referencedColumnName = "firstname"), - @JoinColumn(name="lname", referencedColumnName = "lastname") - } ) - public Captain getCaptain() { - return captain; - } - - public void setCaptain(Captain captain) { - this.captain = captain; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } - - public Dimensions getDimensions() { - return dimensions; - } - - public void setDimensions(Dimensions dimensions) { - this.dimensions = dimensions; - } -} - -@Entity -@IdClass(Identity.class) -public class Captain implements Serializable { - private String firstname; - private String lastname; - - @Id - public String getFirstname() { - return firstname; - } - - public void setFirstname(String firstname) { - this.firstname = firstname; - } - - @Id - public String getLastname() { - return lastname; - } - - public void setLastname(String lastname) { - this.lastname = lastname; - } -} - - - - - If you retrieve a single entity using the default mapping, you can - specify the resultClass attribute instead of - resultSetMapping: - - @NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultClass=SpaceShip.class) -public class SpaceShip { - - - In some of your native queries, you'll have to return scalar values, - for example when building report queries. You can map them in the - @SqlResultsetMapping through - @ColumnResult. You actually can even mix, entities and - scalar returns in the same native query (this is probably not that common - though). - - - Scalar values via <classname>@ColumnResult</classname> - - @SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension")) -@NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar") - - - An other query hint specific to native queries has been introduced: - org.hibernate.callable which can be true or false - depending on whether the query is a stored procedure or not. - -
- Using return-property to explicitly specify column/alias - names - - You can explicitly tell Hibernate what column aliases to use with - <return-property>, instead of using the - {}-syntax to let Hibernate inject its own aliases.For - example: - - <sql-query name="mySqlQuery"> - <return alias="person" class="eg.Person"> - <return-property name="name" column="myName"/> - <return-property name="age" column="myAge"/> - <return-property name="sex" column="mySex"/> - </return> - SELECT person.NAME AS myName, - person.AGE AS myAge, - person.SEX AS mySex, - FROM PERSON person WHERE person.NAME LIKE :name -</sql-query> - - - <return-property> also works with - multiple columns. This solves a limitation with the - {}-syntax which cannot allow fine grained control of - multi-column properties. - - <sql-query name="organizationCurrentEmployments"> - <return alias="emp" class="Employment"> - <return-property name="salary"> - <return-column name="VALUE"/> - <return-column name="CURRENCY"/> - </return-property> - <return-property name="endDate" column="myEndDate"/> - </return> - SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer}, - STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate}, - REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY - FROM EMPLOYMENT - WHERE EMPLOYER = :id AND ENDDATE IS NULL - ORDER BY STARTDATE ASC -</sql-query> - - In this example <return-property> was - used in combination with the {}-syntax for injection. - This allows users to choose how they want to refer column and - properties. - - If your mapping has a discriminator you must use - <return-discriminator> to specify the - discriminator column. -
- -
- Using stored procedures for querying - - Hibernate provides support for queries via stored procedures and - functions. Most of the following documentation is equivalent for both. - The stored procedure/function must return a resultset as the first - out-parameter to be able to work with Hibernate. An example of such a - stored function in Oracle 9 and higher is as follows: - - CREATE OR REPLACE FUNCTION selectAllEmployments - RETURN SYS_REFCURSOR -AS - st_cursor SYS_REFCURSOR; -BEGIN - OPEN st_cursor FOR - SELECT EMPLOYEE, EMPLOYER, - STARTDATE, ENDDATE, - REGIONCODE, EID, VALUE, CURRENCY - FROM EMPLOYMENT; - RETURN st_cursor; - END; - - To use this query in Hibernate you need to map it via a named - query. - - <sql-query name="selectAllEmployees_SP" callable="true"> - <return alias="emp" class="Employment"> - <return-property name="employee" column="EMPLOYEE"/> - <return-property name="employer" column="EMPLOYER"/> - <return-property name="startDate" column="STARTDATE"/> - <return-property name="endDate" column="ENDDATE"/> - <return-property name="regionCode" column="REGIONCODE"/> - <return-property name="id" column="EID"/> - <return-property name="salary"> - <return-column name="VALUE"/> - <return-column name="CURRENCY"/> - </return-property> - </return> - { ? = call selectAllEmployments() } -</sql-query> - - Stored procedures currently only return scalars and entities. - <return-join> and - <load-collection> are not supported. - -
- Rules/limitations for using stored procedures - - You cannot use stored procedures with Hibernate unless you - follow some procedure/function rules. If they do not follow those - rules they are not usable with Hibernate. If you still want to use - these procedures you have to execute them via - session.connection(). The rules are different for - each database, since database vendors have different stored procedure - semantics/syntax. - - Stored procedure queries cannot be paged with - setFirstResult()/setMaxResults(). - - The recommended call form is standard SQL92: { ? = call - functionName(<parameters>) } or { ? = call - procedureName(<parameters>}. Native call syntax is not - supported. - - For Oracle the following rules apply: - - - - A function must return a result set. The first parameter of - a procedure must be an OUT that returns a - result set. This is done by using a - SYS_REFCURSOR type in Oracle 9 or 10. In Oracle - you need to define a REF CURSOR type. See - Oracle literature for further information. - - - - For Sybase or MS SQL server the following rules apply: - - - - The procedure must return a result set. Note that since - these servers can return multiple result sets and update counts, - Hibernate will iterate the results and take the first result that - is a result set as its return value. Everything else will be - discarded. - - - - If you can enable SET NOCOUNT ON in your - procedure it will probably be more efficient, but this is not a - requirement. - - -
-
-
- -
- Custom SQL for create, update and delete - - Hibernate can use custom SQL for create, update, and delete - operations. The SQL can be overridden at the statement level or - inidividual column level. This section describes statement overrides. For - columns, see . shows how to define - custom SQL operatons using annotations. - - - Custom CRUD via annotations - - @Entity -@Table(name="CHAOS") -@SQLInsert( sql="INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)") -@SQLUpdate( sql="UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?") -@SQLDelete( sql="DELETE CHAOS WHERE id = ?") -@SQLDeleteAll( sql="DELETE CHAOS") -@Loader(namedQuery = "chaos") -@NamedNativeQuery(name="chaos", query="select id, size, name, lower( nickname ) as nickname from CHAOS where id= ?", resultClass = Chaos.class) -public class Chaos { - @Id - private Long id; - private Long size; - private String name; - private String nickname; - - - @SQLInsert, @SQLUpdate, - @SQLDelete, @SQLDeleteAll - respectively override the INSERT, UPDATE, DELETE, and DELETE all - statement. The same can be achieved using Hibernate mapping files and the - <sql-insert>, - <sql-update> and - <sql-delete> nodes. This can be seen in . - - - Custom CRUD XML - - <class name="Person"> - <id name="id"> - <generator class="increment"/> - </id> - <property name="name" not-null="true"/> - <sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert> - <sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update> - <sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete> -</class> - - - If you expect to call a store procedure, be sure to set the - callable attribute to true. In - annotations as well as in xml. - - To check that the execution happens correctly, Hibernate allows you - to define one of those three strategies: - - - - none: no check is performed: the store procedure is expected to - fail upon issues - - - - count: use of rowcount to check that the update is - successful - - - - param: like COUNT but using an output parameter rather that the - standard mechanism - - - - To define the result check style, use the check - parameter which is again available in annoations as well as in xml. - - You can use the exact same set of annotations respectively xml nodes - to override the collection related statements -see . - - - Overriding SQL statements for collections using - annotations - - @OneToMany -@JoinColumn(name="chaos_fk") -@SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?") -@SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?") -private Set<CasimirParticle> particles = new HashSet<CasimirParticle>(); - - - - The parameter order is important and is defined by the order - Hibernate handles properties. You can see the expected order by enabling - debug logging for the org.hibernate.persister.entity - level. With this level enabled Hibernate will print out the static SQL - that is used to create, update, delete etc. entities. (To see the - expected sequence, remember to not include your custom SQL through - annotations or mapping files as that will override the Hibernate - generated static sql) - - - Overriding SQL statements for secondary tables is also possible - using @org.hibernate.annotations.Table and either (or - all) attributes sqlInsert, - sqlUpdate, sqlDelete: - - - Overriding SQL statements for secondary tables - - @Entity -@SecondaryTables({ - @SecondaryTable(name = "`Cat nbr1`"), - @SecondaryTable(name = "Cat2"}) -@org.hibernate.annotations.Tables( { - @Table(appliesTo = "Cat", comment = "My cat table" ), - @Table(appliesTo = "Cat2", foreignKey = @ForeignKey(name="FK_CAT2_CAT"), fetch = FetchMode.SELECT, - sqlInsert=@SQLInsert(sql="insert into Cat2(storyPart2, id) values(upper(?), ?)") ) -} ) -public class Cat implements Serializable { - - - The previous example also shows that you can give a comment to a - given table (primary or secondary): This comment will be used for DDL - generation. - - - The SQL is directly executed in your database, so you can use any - dialect you like. This will, however, reduce the portability of your - mapping if you use database specific SQL. - - - Last but not least, stored procedures are in most cases required to - return the number of rows inserted, updated and deleted. Hibernate always - registers the first statement parameter as a numeric output parameter for - the CUD operations: - - - Stored procedures and their return value - - CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2) - RETURN NUMBER IS -BEGIN - - update PERSON - set - NAME = uname, - where - ID = uid; - - return SQL%ROWCOUNT; - -END updatePerson; - -
- -
- Custom SQL for loading - - You can also declare your own SQL (or HQL) queries for entity - loading. As with inserts, updates, and deletes, this can be done at the - individual column level as described in or at the statement level. Here - is an example of a statement level override: - - <sql-query name="person"> - <return alias="pers" class="Person" lock-mode="upgrade"/> - SELECT NAME AS {pers.name}, ID AS {pers.id} - FROM PERSON - WHERE ID=? - FOR UPDATE -</sql-query> - - This is just a named query declaration, as discussed earlier. You - can reference this named query in a class mapping: - - <class name="Person"> - <id name="id"> - <generator class="increment"/> - </id> - <property name="name" not-null="true"/> - <loader query-ref="person"/> -</class> - - This even works with stored procedures. - - You can even define a query for collection loading: - - <set name="employments" inverse="true"> - <key/> - <one-to-many class="Employment"/> - <loader query-ref="employments"/> -</set> - - <sql-query name="employments"> - <load-collection alias="emp" role="Person.employments"/> - SELECT {emp.*} - FROM EMPLOYMENT emp - WHERE EMPLOYER = :id - ORDER BY STARTDATE ASC, EMPLOYEE ASC -</sql-query> - - You can also define an entity loader that loads a collection by join - fetching: - - <sql-query name="person"> - <return alias="pers" class="Person"/> - <return-join alias="emp" property="pers.employments"/> - SELECT NAME AS {pers.*}, {emp.*} - FROM PERSON pers - LEFT OUTER JOIN EMPLOYMENT emp - ON pers.ID = emp.PERSON_ID - WHERE ID=? -</sql-query> - - The annotation equivalent <loader> is the - @Loader annotation as seen in . -
- -
diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/Transactions.xml b/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/Transactions.xml deleted file mode 100644 index 363b693e3a99..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/Transactions.xml +++ /dev/null @@ -1,546 +0,0 @@ - - - - - - Transactions and concurrency control - - - It is important to understand that the term transaction has many different yet related meanings in regards - to persistence and Object/Relational Mapping. In most use-cases these definitions align, but that is not - always the case. - - - - - Might refer to the physical transaction with the database. - - - - - Might refer to the logical notion of a transaction as related to a persistence context. - - - - - Might refer to the application notion of a Unit-of-Work, as defined by the archetypal pattern. - - - - - - - This documentation largely treats the physical and logic notions of transaction as one-in-the-same. - - - - -
- Physical Transactions - - Hibernate uses the JDBC API for persistence. In the world of Java there are 2 well defined mechanism - for dealing with transactions in JDBC: JDBC itself and JTA. Hibernate supports both mechanisms for - integrating with transactions and allowing applications to manage physical transactions. - - - - Transaction handling per Session is handled by the - org.hibernate.resource.transaction.spi.TransactionCoordinator contract, which - are built by the org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder - service. TransactionCoordinatorBuilder represents a strategy for dealing with transactions whereas - TransactionCoordinator represents one instance of that strategy related to a Session. Which - TransactionCoordinatorBuilder implementation to use is defined by the - hibernate.transaction.coordinator_class setting. - - - - Hibernate-provided TransactionCoordinatorBuilder implementations - - - jdbc (the default) - Manages transactions via calls to java.sql.Connection - - - - - jta - Manages transactions via JTA. See - - - - - - - For details on implementing a custom TransactionCoordinatorBuilder, or simply better understanding - how it works, see the Integrations Guide - - - - - Hibernate uses JDBC connections and JTA resources directly, without adding any additional locking behavior. - Hibernate does not lock objects in memory. The behavior defined by the isolation level of your database - transactions does not change when you use Hibernate. The Hibernate Session acts as a transaction-scoped - cache providing repeatable reads for lookup by identifier and queries that result in loading entities. - - - - - To reduce lock contention in the database, the physical database transaction needs to be as short as - possible. Long database transactions prevent your application from scaling to a highly-concurrent load. - Do not hold a database transaction open during end-user-level work, but open it after the end-user-level - work is finished. This is concept is referred to as transactional write-behind. - - - -
- JTA configuration - - - Interaction with a JTA system is consolidated behind a single contract named - org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform which - exposes access to the javax.transaction.TransactionManager and - javax.transaction.UserTransaction for that system as well as exposing - the ability to register javax.transaction.Synchronization instances, - check transaction status, etc. - - - - - Generally JtaPlatform will need access to JNDI to resolve the JTA TransactionManager, - UserTransaction, etc. See for details on configuring access - to JNDI - - - - - Hibernate tries to discover the JtaPlatform it should use through the use of another service named - org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformResolver. - If that resolution does not work, or if you wish to provide a custom implementation you will need to - specify the hibernate.transaction.jta.platform setting. Hibernate provides many - implementations of the JtaPlatform contract, all with short-names: - - - - Built-in JtaPlatform implementations by short name - - - Borland - JtaPlatform for the Borland Enterprise Server. - - - - - Bitronix - JtaPlatform for Bitronix. - - - - - JBossAS - JtaPlatform for Arjuna/JBossTransactions/Narnya when used within the JBoss/WildFly Application Server. - - - - - JBossTS - JtaPlatform for Arjuna/JBossTransactions/Narnya when used standalone. - - - - - JOnAS - JtaPlatform for JOTM when used within JOnAS. - - - - - JOTM - JtaPlatform for JOTM when used standalone. - - - - - JRun4 - JtaPlatform for the JRun 4 Application Server. - - - - - OC4J - JtaPlatform for Oracle's OC4J container. - - - - - Orion - JtaPlatform for the Orion Application Server. - - - - - Resin - JtaPlatform for the Resin Application Server. - - - - - SunOne - JtaPlatform for the SunOne Application Server. - - - - - Weblogic - JtaPlatform for the Weblogic Application Server. - - - - - WebSphere - JtaPlatform for older versions of the WebSphere Application Server. - - - - - WebSphereExtended - JtaPlatform for newer versions of the WebSphere Application Server. - - - -
-
- -
- Hibernate Transaction API - - - Hibernate provides an API for helping to isolate applications from the differences in the underlying - physical transaction system in use. Based on the configured TransactionCoordinatorBuilder, Hibernate - will simply do the right thing when this transaction API is used by the application. This allows your - applications and components to be more portable move around into different environments. - - - - To use this API, you would obtain the org.hibernate.Transaction - from the Session. - - - - Transaction allows for all the normal operations you'd expect. begin, - commit and rollback. And these calls noop if they - should. - - - - It even exposes some cool methods like: - - - markRollbackOnly that works in both JTA and JDBC! - - - getTimeout and setTimeout that again work - in both JTA and JDBC! - - - registerSynchronization that allows you to register JTA Synchronizations - even in non-JTA environments. In fact in both JTA and JDBC environments, these Synchronizations - are kept locally by Hibernate. In JTA environments Hibernate will only ever register one single - Synchronization with the TransactionManager to avoid ordering problems. - - - - - - Additionally it exposes a getStatus method that returns an - org.hibernate.resource.transaction.spi.TransactionStatus enum. This method - checks with the underling transaction system if needed, so care should be taken to minimize its use; - it can have a big performance impact in certain JTA set ups. - - - - Lets take a look at using the Transaction API in the various environments. - - - - Using Transaction API in JDBC - - - - - Using Transaction API in JTA (CMT) - - - - - Using Transaction API in JTA (BMT) - - - - - In the CMT case we really could have omitted all of the Transaction calls. But the point of - the examples was to show that the Transaction API really does insulate your code from the underlying - transaction mechanism. In fact if you strip away the comments and the single configruation - setting supplied at bootstrap, the code is exactly the same in all 3 examples. In other words, - we could develop that code and drop it, as-is, in any of the 3 transaction environments. - - - - The Transaction API tries hard to make the experience consistent across all environments. To that end, - it generally defers to the JTA specification when there are differences (for example automatically trying - rollback on a failed commit). - -
- -
- Transactional patterns (and anti-patterns) - -
- Session-per-operation anti-pattern - - This is an anti-pattern of opening and closing a Session for each database call - in a single thread. It is also an anti-pattern in terms of database transactions. Group your database - calls into a planned sequence. In the same way, do not auto-commit after every SQL statement in your - application. Hibernate disables, or expects the application server to disable, auto-commit mode - immediately. Database transactions are never optional. All communication with a database must - be encapsulated by a transaction. Avoid auto-commit behavior for reading data, because many small - transactions are unlikely to perform better than one clearly-defined unit of work, and are more - difficult to maintain and extend. - - - - Using auto-commit does not circumvent database transactions. Instead, when in auto-commit mode, - JDBC drivers simply perform each call in an implicit transaction call. It is as if your application - called commit after each and every JDBC call. - - -
- -
- Session-per-request pattern - - This is the most common transaction pattern. The term request here relates to the concept of a system - that reacts to a series of requests from a client/user. Web applications are a prime example of this - type of system, though certainly not the only one. At the beginning of handling such a request, the - application opens a Hibernate Session, starts a transaction, performs - all data related work, ends the transaction and closes the Session. - The crux of the pattern is the one-to-one relationship between the transaction and the - Session. - - - - Within this pattern there is a common technique of defining a current session to - simplify the need of passing this Session around to all the application - components that may need access to it. Hibernate provides support for this technique through the - getCurrentSession method of the SessionFactory. - The concept of a "current" session has to have a scope that defines the bounds in which the notion - of "current" is valid. This is purpose of the - org.hibernate.context.spi.CurrentSessionContext contract. There are 2 - reliable defining scopes: - - - - - First is a JTA transaction because it allows a callback hook to know when it is ending which - gives Hibernate a chance to close the Session and clean up. - This is represented by the - org.hibernate.context.internal.JTASessionContext implementation of - the org.hibernate.context.spi.CurrentSessionContext contract. - Using this implementation, a Session will be opened the first - time getCurrentSession is called within that transaction. - - - - - Secondly is this application request cycle itself. This is best represented with the - org.hibernate.context.internal.ManagedSessionContext implementation of - the org.hibernate.context.spi.CurrentSessionContext contract. - Here an external component is responsible for managing the lifecycle and scoping of a "current" - session. At the start of such a scope, ManagedSessionContext's - bind method is called passing in the - Session. At the end, its unbind - method is called. - - - Some common examples of such "external components" include: - - - - - javax.servlet.Filter implementation - - - - - AOP interceptor with a pointcut on the service methods - - - - - A proxy/interception container - - - - - - - - The getCurrentSession() method has one downside in a JTA environment. If - you use it, after_statement connection release mode is also used by default. Due to a limitation of - the JTA specification, Hibernate cannot automatically clean up any unclosed - ScrollableResults or Iterator - instances returned by scroll() or iterate(). - Release the underlying database cursor by calling ScrollableResults.close() - or Hibernate.close(Iterator) explicitly from a - finally block. - - -
- -
- Conversations - - The session-per-request pattern is not the only valid way of designing units of work. - Many business processes require a whole series of interactions with the user that are interleaved with - database accesses. In web and enterprise applications, it is not acceptable for a database transaction - to span a user interaction. Consider the following example: - - - An example of a long-running conversation - - - The first screen of a dialog opens. The data seen by the user is loaded in a particular - Session and database transaction. The user is free to modify the objects. - - - - - The user uses a UI element to save their work after five minutes of editing. The modifications - are made persistent. The user also expects to have exclusive access to the data during the edit - session. - - - - - - Even though we have multiple databases access here, from the point of view of the user, this series of - steps represents a single unit of work. There are many ways to implement this in your application. - - - - A first naive implementation might keep the Session and database transaction open - while the user is editing, using database-level locks to prevent other users from modifying the same - data and to guarantee isolation and atomicity. This is an anti-pattern, because lock contention is a - bottleneck which will prevent scalability in the future. - - - Several database transactions are used to implement the conversation. In this case, maintaining - isolation of business processes becomes the partial responsibility of the application tier. A single - conversation usually spans several database transactions. These multiple database accesses can only - be atomic as a whole if only one of these database transactions (typically the last one) stores the - updated data. All others only read data. A common way to receive this data is through a wizard-style - dialog spanning several request/response cycles. Hibernate includes some features which make this easy - to implement. - - - - - - - - - Automatic Versioning - - - - - Hibernate can perform automatic optimistic concurrency control for you. It can - automatically detect if a concurrent modification occurred during user think time. - Check for this at the end of the conversation. - - - - - - - Detached Objects - - - - - If you decide to use the session-per-request pattern, all loaded instances will be - in the detached state during user think time. Hibernate allows you to reattach the - objects and persist the modifications. The pattern is called - session-per-request-with-detached-objects. Automatic versioning is used to isolate - concurrent modifications. - - - - - - - Extended Session - - - - - The Hibernate Session can be disconnected from the - underlying JDBC connection after the database transaction has been committed and - reconnected when a new client request occurs. This pattern is known as - session-per-conversation and makes even reattachment unnecessary. Automatic - versioning is used to isolate concurrent modifications and the - Session will not be allowed to flush automatically, - only explicitly. - - - - - - - - - Session-per-request-with-detached-objects and session-per-conversation - each have advantages and disadvantages. - -
- -
- Session-per-application - - Discussion coming soon.. - -
-
- - -
- Common issues - - - - - A Session is not thread-safe. Things that work concurrently, like HTTP requests, session beans, - or Swing workers, will cause race conditions if a Session instance is shared. If you keep your - Hibernate Session in your javax.servlet.http.HttpSession you should - consider synchronizing access to your HttpSession; otherwise, a user that clicks reload fast enough - can use the same Session in two concurrently running threads. - - - - - An exception thrown by Hibernate means you have to rollback your database transaction - and close the Session immediately. If your Session is bound to the application, - you have to stop the application. Rolling back the database transaction does not put your business - objects back into the state they were at the start of the transaction. This means that the - database state and the business objects will be out of sync. Usually this is not a - problem, because exceptions are not recoverable and you will have to start over after - rollback anyway. - - - - - The Session caches every object that is in a persistent state (watched and checked for changes - by Hibernate). If you keep it open for a long time or simply load too much data, it will grow - endlessly until you get an OutOfMemoryException. One solution is to call - clear() and evict() to manage the - Session cache, but you should consider an alternate means of dealing with large amounts of data - such as a Stored Procedure. Java is simply not the right tool for these kind of operations. - Some solutions are shown in . Keeping a Session open for the duration of - a user session also means a higher probability of stale data. - - - - -
- -
\ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/bmt.java b/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/bmt.java deleted file mode 100644 index 9b3c84d6b0fe..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/bmt.java +++ /dev/null @@ -1,41 +0,0 @@ -public void doSomeWork() { - StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() - .applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" ) - ...; - - // Note: depending on the JtaPlatform used and some optional settings, - // the underlying transactions here will be controlled through either - // the JTA TransactionManager or UserTransaction - - SessionFactory = ...; - - Session session = sessionFactory.openSession(); - try { - // Assuming a JTA transaction is not already active, - // this call the TM/UT begin method. If a JTA - // transaction is already active, we remember that - // the Transaction associated with the Session did - // not "initiate" the JTA transaction and will later - // nop-op the commit and rollback calls... - session.getTransaction().begin(); - - doTheWork(); - - // calls TM/UT commit method, assuming we are initiator. - session.getTransaction().commit(); - } - catch (Exception e) { - // we may need to rollback depending on - // where the exception happened - if ( session.getTransaction().getStatus() == ACTIVE - || session.getTransaction().getStatus() == MARKED_ROLLBACK ) { - // calls TM/UT commit method, assuming we are initiator; - // otherwise marks the JTA trsnaction for rollback only - session.getTransaction().rollback(); - } - // handle the underlying error - } - finally { - session.close(); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/cmt.java b/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/cmt.java deleted file mode 100644 index 9a144dec84e6..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/cmt.java +++ /dev/null @@ -1,38 +0,0 @@ -public void doSomeWork() { - StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() - .applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" ) - ...; - - // Note: depending on the JtaPlatform used and some optional settings, - // the underlying transactions here will be controlled through either - // the JTA TransactionManager or UserTransaction - - SessionFactory = ...; - - Session session = sessionFactory.openSession(); - try { - // Since we are in CMT, a JTA transaction would - // already have been started. This call essentially - // no-ops - session.getTransaction().begin(); - - doTheWork(); - - // Since we did not start the transaction (CMT), - // we also will not end it. This call essentially - // no-ops in terms of transaction handling. - session.getTransaction().commit(); - } - catch (Exception e) { - // again, the rollback call here would no-op (aside from - // marking the underlying CMT transaction for rollback only). - if ( session.getTransaction().getStatus() == ACTIVE - || session.getTransaction().getStatus() == MARKED_ROLLBACK ) { - session.getTransaction().rollback(); - } - // handle the underlying error - } - finally { - session.close(); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/jdbc.java b/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/jdbc.java deleted file mode 100644 index 9cf59f174e0a..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/chapters/transactions/extras/jdbc.java +++ /dev/null @@ -1,33 +0,0 @@ -public void doSomeWork() { - StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() - // "jdbc" is the default, but for explicitness - .applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jdbc" ) - ...; - - SessionFactory = ...; - - Session session = sessionFactory.openSession(); - try { - // calls Connection#setAutoCommit(false) to - // signal start of transaction - session.getTransaction().begin(); - - doTheWork(); - - // calls Connection#commit(), if an error - // happens we attempt a rollback - session.getTransaction().commit(); - } - catch (Exception e) { - // we may need to rollback depending on - // where the exception happened - if ( session.getTransaction().getStatus() == ACTIVE - || session.getTransaction().getStatus() == MARKED_ROLLBACK ) { - session.getTransaction().rollback(); - } - // handle the underlying error - } - finally { - session.close(); - } -} \ No newline at end of file diff --git a/documentation/src/main/docbook/userGuide/en-US/images/AuthorWork.png b/documentation/src/main/docbook/userGuide/en-US/images/AuthorWork.png deleted file mode 100644 index ef4ab7227ae9..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/AuthorWork.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/AuthorWork.zargo b/documentation/src/main/docbook/userGuide/en-US/images/AuthorWork.zargo deleted file mode 100644 index f249b229518e..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/AuthorWork.zargo and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/CustomerOrderProduct.png b/documentation/src/main/docbook/userGuide/en-US/images/CustomerOrderProduct.png deleted file mode 100644 index 7034cbe8cd54..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/CustomerOrderProduct.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/CustomerOrderProduct.zargo b/documentation/src/main/docbook/userGuide/en-US/images/CustomerOrderProduct.zargo deleted file mode 100644 index 016c559eee0c..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/CustomerOrderProduct.zargo and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/EmployerEmployee.png b/documentation/src/main/docbook/userGuide/en-US/images/EmployerEmployee.png deleted file mode 100644 index a7ecff483fe4..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/EmployerEmployee.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/EmployerEmployee.zargo b/documentation/src/main/docbook/userGuide/en-US/images/EmployerEmployee.zargo deleted file mode 100644 index 487368e8c7fc..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/EmployerEmployee.zargo and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/hibernate_logo_a.png b/documentation/src/main/docbook/userGuide/en-US/images/hibernate_logo_a.png deleted file mode 100644 index 0a343c4bca60..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/hibernate_logo_a.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/overview.png b/documentation/src/main/docbook/userGuide/en-US/images/overview.png deleted file mode 100644 index 1593a9522d53..000000000000 Binary files a/documentation/src/main/docbook/userGuide/en-US/images/overview.png and /dev/null differ diff --git a/documentation/src/main/docbook/userGuide/en-US/images/overview.svg b/documentation/src/main/docbook/userGuide/en-US/images/overview.svg deleted file mode 100644 index 1b58a483c4e4..000000000000 --- a/documentation/src/main/docbook/userGuide/en-US/images/overview.svg +++ /dev/null @@ -1,250 +0,0 @@ - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Application - - - -Persistent Objects - - - -Database - - - -HIBERNATE - - - - - - - - - - - - - - -hibernate. - -properties - - - -XML Mapping - - - diff --git a/documentation/src/main/java/org/hibernate/userguide/model/Person.java b/documentation/src/main/java/org/hibernate/userguide/model/Person.java index 51d477f4e519..0a236c51e177 100644 --- a/documentation/src/main/java/org/hibernate/userguide/model/Person.java +++ b/documentation/src/main/java/org/hibernate/userguide/model/Person.java @@ -26,10 +26,15 @@ import javax.persistence.NamedNativeQuery; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.NamedStoredProcedureQueries; +import javax.persistence.NamedStoredProcedureQuery; import javax.persistence.OneToMany; import javax.persistence.OrderColumn; +import javax.persistence.ParameterMode; +import javax.persistence.QueryHint; import javax.persistence.SqlResultSetMapping; import javax.persistence.SqlResultSetMappings; +import javax.persistence.StoredProcedureParameter; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Version; @@ -147,14 +152,47 @@ //end::sql-multiple-scalar-values-dto-NamedNativeQuery-example[] }) //tag::hql-examples-domain-model-example[] -//tag::jpql-api-named-query-example[] -@NamedQueries( +@NamedQueries({ + //tag::jpql-api-named-query-example[] @NamedQuery( name = "get_person_by_name", query = "select p from Person p where name = :name" ) + //end::jpql-api-named-query-example[] + , + // tag::jpa-read-only-entities-native-example[] + @NamedQuery( + name = "get_read_only_person_by_name", + query = "select p from Person p where name = :name", + hints = { + @QueryHint( + name = "org.hibernate.readOnly", + value = "true" + ) + } + ) + //end::jpa-read-only-entities-native-example[] +}) +//tag::sql-sp-ref-cursor-oracle-named-query-example[] +@NamedStoredProcedureQueries( + @NamedStoredProcedureQuery( + name = "sp_person_phones", + procedureName = "sp_person_phones", + parameters = { + @StoredProcedureParameter( + name = "personId", + type = Long.class, + mode = ParameterMode.IN + ), + @StoredProcedureParameter( + name = "personPhones", + type = Class.class, + mode = ParameterMode.REF_CURSOR + ) + } + ) ) -//end::jpql-api-named-query-example[] +//end::sql-sp-ref-cursor-oracle-named-query-example[] @Entity public class Person { diff --git a/documentation/src/main/java/org/hibernate/userguide/model/PersonPhoneCount.java b/documentation/src/main/java/org/hibernate/userguide/model/PersonPhoneCount.java new file mode 100644 index 000000000000..e8cbe902c6c8 --- /dev/null +++ b/documentation/src/main/java/org/hibernate/userguide/model/PersonPhoneCount.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.model; + +/** + * @author Vlad Mihalcea + */ +public class PersonPhoneCount { + + private final String name; + + private final Number phoneCount; + + public PersonPhoneCount(String name, Number phoneCount) { + this.name = name; + this.phoneCount = phoneCount; + } + + public String getName() { + return name; + } + + public Number getPhoneCount() { + return phoneCount; + } +} diff --git a/documentation/src/main/java/org/hibernate/userguide/model/Phone.java b/documentation/src/main/java/org/hibernate/userguide/model/Phone.java index 29ad69a98572..75a24c4c52b1 100644 --- a/documentation/src/main/java/org/hibernate/userguide/model/Phone.java +++ b/documentation/src/main/java/org/hibernate/userguide/model/Phone.java @@ -13,21 +13,67 @@ import java.util.Map; import javax.persistence.CascadeType; import javax.persistence.Column; +import javax.persistence.ColumnResult; +import javax.persistence.ConstructorResult; import javax.persistence.ElementCollection; import javax.persistence.Entity; +import javax.persistence.EntityResult; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; +import javax.persistence.FieldResult; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.MapKey; import javax.persistence.MapKeyTemporal; import javax.persistence.OneToMany; +import javax.persistence.SqlResultSetMapping; import javax.persistence.TemporalType; +import org.hibernate.annotations.NamedNativeQueries; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.NamedQueries; +import org.hibernate.annotations.NamedQuery; + /** * @author Vlad Mihalcea */ +//tag::jpql-api-hibernate-named-query-example[] +@NamedQueries({ + @NamedQuery( + name = "get_phone_by_number", + query = "select p " + + "from Phone p " + + "where p.number = :number", + timeout = 1, + readOnly = true + ) +}) +//end::jpql-api-hibernate-named-query-example[] +//tag::sql-multiple-scalar-values-dto-NamedNativeQuery-hibernate-example[] +@NamedNativeQueries({ + @NamedNativeQuery( + name = "get_person_phone_count", + query = "SELECT pr.name AS name, count(*) AS phoneCount " + + "FROM Phone p " + + "JOIN Person pr ON pr.id = p.person_id " + + "GROUP BY pr.name", + resultSetMapping = "person_phone_count", + timeout = 1, + readOnly = true + ), +}) +@SqlResultSetMapping( + name = "person_phone_count", + classes = @ConstructorResult( + targetClass = PersonPhoneCount.class, + columns = { + @ColumnResult(name = "name"), + @ColumnResult(name = "phoneCount") + } + ) +) +//end::sql-multiple-scalar-values-dto-NamedNativeQuery-hibernate-example[] //tag::hql-examples-domain-model-example[] @Entity public class Phone { diff --git a/documentation/src/main/javadoc/overview.html b/documentation/src/main/javadoc/overview.html new file mode 100644 index 000000000000..dcd92f879aa1 --- /dev/null +++ b/documentation/src/main/javadoc/overview.html @@ -0,0 +1,67 @@ + + + +

Hibernate O/RM Aggregated JavaDocs

+ +Hibernate provides both
    +
  • + a native API comprised centrally around {@link org.hibernate.SessionFactory} and {@link org.hibernate.Session} +
  • +
  • + an implementation of the Java Persistence API (JPA). + See the latest JPA JSR for details. +
  • +
+

+ +

Native API

+In addition to SessionFactory and Session, applications using the native API will often utilize the following +interfaces:
    +
  • {@link org.hibernate.cfg.Configuration}
  • +
  • {@link org.hibernate.Transaction}
  • +
  • {@link org.hibernate.Query}
  • +
  • {@link org.hibernate.Criteria}
  • +
  • {@link org.hibernate.criterion.Projection}
  • +
  • {@link org.hibernate.criterion.Projections}
  • +
  • {@link org.hibernate.criterion.Criterion}
  • +
  • {@link org.hibernate.criterion.Restrictions}
  • +
  • {@link org.hibernate.criterion.Order}
  • +
  • {@link org.hibernate.criterion.Example}
  • +
+These interfaces are fully intended to be exposed to application code. +

+ +

JPA

+The JPA interfaces are all defined by the JPA specification. For details see {@link javax.persistence}. +Not that since 5.2 Hibernate extends JPA (e.g. SessionFactory extends EntityManagerFactory) rather +than wrapping it. +

+ + +

Note about package categories

+Hibernate categorizes packages into a number of groups based on intended consumers:
    +
  • + API - classes to which application code will generally bind directly. These + are generally classes which do not have "spi" nor "internal" in their package path and are + not under the "org.hibernate.testing" package +
  • +
  • + SPI - classes to which integrator developers will commonly bind directly in + order to develop extensions to Hibernate, or to alter its behavior in some way. These are + generally under packages with "spi" in the package path. +
  • +
  • + Testing Support - classes from the hibernate-testing artifact used in building + Hibernate test cases. These are classes under the "org.hibernate.testing" package +
  • +
+

+ +Complete Hibernate documentation may be found online at http://hibernate.org/orm/documentation/ + + \ No newline at end of file diff --git a/documentation/src/main/resources/docbook2asciidoc.sh b/documentation/src/main/resources/docbook2asciidoc.sh deleted file mode 100644 index 196e9a2fab1c..000000000000 --- a/documentation/src/main/resources/docbook2asciidoc.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -xmls=`find ../docbook/mappingGuide/en-US/chapters/basic -name '*.xml'` - -for xml in ../docbook/devguide-old/en-US/Batch_Processing.xml -do - adoc="${xml%.*}.adoc" - echo 'Converting' $xml to $adoc - cp $xml $adoc - sed -i -r 's_<(programlisting.*?)>_<\!--\1-->_g' $adoc - pandoc -s -r docbook "$adoc" -t asciidoc --no-wrap -o "$adoc" -done \ No newline at end of file diff --git a/documentation/src/main/style/asciidoctor/css/asciidoctor.css b/documentation/src/main/style/asciidoctor/css/asciidoctor.css index 0dcb799a2c47..d430512f5c82 100644 --- a/documentation/src/main/style/asciidoctor/css/asciidoctor.css +++ b/documentation/src/main/style/asciidoctor/css/asciidoctor.css @@ -7,7 +7,6 @@ audio:not([controls]){display:none;height:0} [hidden],template{display:none} script{display:none!important} html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} -body{margin:0} a{background:transparent} a:focus{outline:thin dotted} a:active,a:hover{outline:0} @@ -42,7 +41,7 @@ textarea{overflow:auto;vertical-align:top} table{border-collapse:collapse;border-spacing:0} *,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} html,body{font-size:100%} -body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin-top:30px;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto} a:hover{cursor:pointer} img,object,embed{max-width:100%;height:auto} object,embed{height:100%} diff --git a/documentation/src/main/style/asciidoctor/css/hibernate-layout.css b/documentation/src/main/style/asciidoctor/css/hibernate-layout.css index 2c73f78324fe..1934f722b453 100644 --- a/documentation/src/main/style/asciidoctor/css/hibernate-layout.css +++ b/documentation/src/main/style/asciidoctor/css/hibernate-layout.css @@ -5,8 +5,6 @@ body{ background-repeat: repeat-x !important; background-size: 300px 300px; color:#333 !important; - padding:25px !important; - margin:0 !important; font-family:"Noto Serif","DejaVu Serif",serif !important; font-weight:400 !important; font-style:normal !important; @@ -16,6 +14,7 @@ body{ } body:before { content: url(../images/org/hibernate/logo_smaller.png); + padding:25px; } #header { width: 1000px !important; @@ -41,6 +40,9 @@ body:before { font-weight: bold !important; font-size: 1.3em !important; } +#toc>#tocsearch { + font-family: "FontAwesome"; +} .subheader .title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{ color:darkslategray; } @@ -97,7 +99,6 @@ h4,h5,h6{ border-radius: 1em !important; width: 100% !important; padding: 2em 0em 2em 0em !important; - margin: 2em 2em 2em 2em !important; } td.icon { display:block !important; @@ -110,7 +111,7 @@ td.content { .admonitionblock .icon [class^="fa icon-tip"]{ background-image: url(../images/org/hibernate/docbook/tip.png) !important; background-repeat: no-repeat !important; - margin: 1em 2em 1em 1em !important; + margin: 0.5em !important; background-position: center !important; width: 50px !important; height: 50px !important; @@ -119,7 +120,7 @@ td.content { .admonitionblock td.icon [class^="fa icon-note"]{ background-image: url(../images/org/hibernate/docbook/note.png)!important; background-repeat: no-repeat !important; - margin: 1em 2em 1em 1em !important; + margin: 0.5em !important; background-position: center !important; width: 50px; height: 50px; @@ -128,7 +129,7 @@ td.content { .admonitionblock td.icon [class^="fa icon-warning"]{ background-image: url(../images/org/hibernate/docbook/warning.png) !important; background-repeat: no-repeat !important; - margin: 1em 2em 1em 1em !important; + margin: 0.5em !important; background-position: center !important; width: 50px !important; height: 50px !important; @@ -146,7 +147,7 @@ td.content { .admonitionblock td.icon [class^="fa icon-important"]{ background-image: url(../images/org/hibernate/docbook/important.png) !important; background-repeat: no-repeat !important; - margin: 1em 2em 1em 1em !important; + margin: 0.5em !important; background-position: center !important; width: 50px !important; height: 50px !important; @@ -181,21 +182,6 @@ ul { padding-top:.25em !important; padding-bottom:.25em !important; } -.sectlevel1{ - font-weight: bolder !important; - margin-left: 1.5em !important; -} -.sectlevel2{ - margin-left: 3em !important; - padding-top: .5em; - padding-bottom: .5em; - font-weight: normal; -} -.sectlevel3{ - margin-left: 3em !important; - padding-top: .5em !important; - padding-bottom: .5em!important; -} .exampleblock>.title{ text-rendering:optimizeLegibility !important; text-align:left !important; @@ -231,4 +217,4 @@ code{ font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace !important; font-weight:400 !important; color:rgba(0,0,0,.9) !important -} \ No newline at end of file +} diff --git a/documentation/src/main/style/asciidoctor/css/hibernate.css b/documentation/src/main/style/asciidoctor/css/hibernate.css index a4ab2ac5c47b..cd00e6c6988a 100644 --- a/documentation/src/main/style/asciidoctor/css/hibernate.css +++ b/documentation/src/main/style/asciidoctor/css/hibernate.css @@ -1,3 +1,3 @@ @import url("hibernate-fonts.css"); @import url("hibernate-layout.css"); -@import url("asciidoctor.css"); \ No newline at end of file +@import url("asciidoctor.css"); diff --git a/documentation/src/main/style/asciidoctor/css/jstree-style.css b/documentation/src/main/style/asciidoctor/css/jstree-style.css new file mode 100644 index 000000000000..b356b0423592 --- /dev/null +++ b/documentation/src/main/style/asciidoctor/css/jstree-style.css @@ -0,0 +1,1111 @@ +/* jsTree default theme */ +.jstree-node, +.jstree-children, +.jstree-container-ul { + display: block; + margin: 0; + padding: 0; + list-style-type: none; + list-style-image: none; +} +.jstree-node { + white-space: nowrap; +} +.jstree-anchor { + display: inline-block; + color: #2156a5; !important; + white-space: nowrap; + padding: 0 4px 0 1px; + margin: 0; + vertical-align: top; +} +.jstree-anchor:focus { + outline: 0; +} +.jstree-anchor, +.jstree-anchor:link, +.jstree-anchor:visited, +.jstree-anchor:hover, +.jstree-anchor:active { + text-decoration: none; + color: inherit; +} +.jstree-icon { + display: inline-block; + text-decoration: none; + margin: 0; + padding: 0; + vertical-align: top; + text-align: center; +} +.jstree-icon:empty { + display: inline-block; + text-decoration: none; + margin: 0; + padding: 0; + vertical-align: top; + text-align: center; +} +.jstree-ocl { + cursor: pointer; +} +.jstree-leaf > .jstree-ocl { + cursor: default; +} +.jstree .jstree-open > .jstree-children { + display: block; +} +.jstree .jstree-closed > .jstree-children, +.jstree .jstree-leaf > .jstree-children { + display: none; +} +.jstree-anchor > .jstree-themeicon { + margin-right: 2px; +} +.jstree-no-icons .jstree-themeicon, +.jstree-anchor > .jstree-themeicon-hidden { + display: none; +} +.jstree-hidden, +.jstree-node.jstree-hidden { + display: none; +} +.jstree-rtl .jstree-anchor { + padding: 0 1px 0 4px; +} +.jstree-rtl .jstree-anchor > .jstree-themeicon { + margin-left: 2px; + margin-right: 0; +} +.jstree-rtl .jstree-node { + margin-left: 0; +} +.jstree-rtl .jstree-container-ul > .jstree-node { + margin-right: 0; +} +.jstree-wholerow-ul { + position: relative; + display: inline-block; + min-width: 100%; +} +.jstree-wholerow-ul .jstree-leaf > .jstree-ocl { + cursor: pointer; +} +.jstree-wholerow-ul .jstree-anchor, +.jstree-wholerow-ul .jstree-icon { + position: relative; +} +.jstree-wholerow-ul .jstree-wholerow { + width: 100%; + cursor: pointer; + position: absolute; + left: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.jstree-contextmenu .jstree-anchor { + -webkit-user-select: none; + /* disable selection/Copy of UIWebView */ + -webkit-touch-callout: none; + /* disable the IOS popup when long-press on a link */ +} +.vakata-context { + display: none; +} +.vakata-context, +.vakata-context ul { + margin: 0; + padding: 2px; + position: absolute; + background: #f5f5f5; + border: 1px solid #979797; + box-shadow: 2px 2px 2px #999999; +} +.vakata-context ul { + list-style: none; + left: 100%; + margin-top: -2.7em; + margin-left: -4px; +} +.vakata-context .vakata-context-right ul { + left: auto; + right: 100%; + margin-left: auto; + margin-right: -4px; +} +.vakata-context li { + list-style: none; +} +.vakata-context li > a { + display: block; + padding: 0 2em 0 2em; + text-decoration: none; + width: auto; + color: black; + white-space: nowrap; + line-height: 2.4em; + text-shadow: 1px 1px 0 white; + border-radius: 1px; +} +.vakata-context li > a:hover { + position: relative; + background-color: #e8eff7; + box-shadow: 0 0 2px #0a6aa1; +} +.vakata-context li > a.vakata-context-parent { + background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw=="); + background-position: right center; + background-repeat: no-repeat; +} +.vakata-context li > a:focus { + outline: 0; +} +.vakata-context .vakata-context-hover > a { + position: relative; + background-color: #e8eff7; + box-shadow: 0 0 2px #0a6aa1; +} +.vakata-context .vakata-context-separator > a, +.vakata-context .vakata-context-separator > a:hover { + background: white; + border: 0; + border-top: 1px solid #e2e3e3; + height: 1px; + min-height: 1px; + max-height: 1px; + padding: 0; + margin: 0 0 0 2.4em; + border-left: 1px solid #e0e0e0; + text-shadow: 0 0 0 transparent; + box-shadow: 0 0 0 transparent; + border-radius: 0; +} +.vakata-context .vakata-contextmenu-disabled a, +.vakata-context .vakata-contextmenu-disabled a:hover { + color: silver; + background-color: transparent; + border: 0; + box-shadow: 0 0 0; +} +.vakata-context li > a > i { + text-decoration: none; + display: inline-block; + width: 2.4em; + height: 2.4em; + background: transparent; + margin: 0 0 0 -2em; + vertical-align: top; + text-align: center; + line-height: 2.4em; +} +.vakata-context li > a > i:empty { + width: 2.4em; + line-height: 2.4em; +} +.vakata-context li > a .vakata-contextmenu-sep { + display: inline-block; + width: 1px; + height: 2.4em; + background: white; + margin: 0 0.5em 0 0; + border-left: 1px solid #e2e3e3; +} +.vakata-context .vakata-contextmenu-shortcut { + font-size: 0.8em; + color: silver; + opacity: 0.5; + display: none; +} +.vakata-context-rtl ul { + left: auto; + right: 100%; + margin-left: auto; + margin-right: -4px; +} +.vakata-context-rtl li > a.vakata-context-parent { + background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7"); + background-position: left center; + background-repeat: no-repeat; +} +.vakata-context-rtl .vakata-context-separator > a { + margin: 0 2.4em 0 0; + border-left: 0; + border-right: 1px solid #e2e3e3; +} +.vakata-context-rtl .vakata-context-left ul { + right: auto; + left: 100%; + margin-left: -4px; + margin-right: auto; +} +.vakata-context-rtl li > a > i { + margin: 0 -2em 0 0; +} +.vakata-context-rtl li > a .vakata-contextmenu-sep { + margin: 0 0 0 0.5em; + border-left-color: white; + background: #e2e3e3; +} +#jstree-marker { + position: absolute; + top: 0; + left: 0; + margin: -5px 0 0 0; + padding: 0; + border-right: 0; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid; + width: 0; + height: 0; + font-size: 0; + line-height: 0; +} +#jstree-dnd { + line-height: 16px; + margin: 0; + padding: 4px; +} +#jstree-dnd .jstree-icon, +#jstree-dnd .jstree-copy { + display: inline-block; + text-decoration: none; + margin: 0 2px 0 0; + padding: 0; + width: 16px; + height: 16px; +} +#jstree-dnd .jstree-ok { + background: green; +} +#jstree-dnd .jstree-er { + background: red; +} +#jstree-dnd .jstree-copy { + margin: 0 2px 0 2px; +} +.jstree-default .jstree-node, +.jstree-default .jstree-icon { + background-repeat: no-repeat; + background-color: transparent; +} +.jstree-default .jstree-anchor, +.jstree-default .jstree-animated, +.jstree-default .jstree-wholerow { + transition: background-color 0.15s, box-shadow 0.15s; +} +.jstree-default .jstree-hovered { + background: #e7f4f9; + border-radius: 2px; + box-shadow: inset 0 0 1px #cccccc; +} +.jstree-default .jstree-context { + background: #e7f4f9; + border-radius: 2px; + box-shadow: inset 0 0 1px #cccccc; +} +.jstree-default .jstree-clicked { + background: #beebff; + border-radius: 2px; + box-shadow: inset 0 0 1px #999999; +} +.jstree-default .jstree-no-icons .jstree-anchor > .jstree-themeicon { + display: none; +} +.jstree-default .jstree-disabled { + background: transparent; + color: #666666; +} +.jstree-default .jstree-disabled.jstree-hovered { + background: transparent; + box-shadow: none; +} +.jstree-default .jstree-disabled.jstree-clicked { + background: #efefef; +} +.jstree-default .jstree-disabled > .jstree-icon { + opacity: 0.8; + filter: url("data:image/svg+xml;utf8,#jstree-grayscale"); + /* Firefox 10+ */ + filter: gray; + /* IE6-9 */ + -webkit-filter: grayscale(100%); + /* Chrome 19+ & Safari 6+ */ +} +.jstree-default .jstree-search { + font-style: italic; + color: #8b0000; + font-weight: bold; +} +.jstree-default .jstree-no-checkboxes .jstree-checkbox { + display: none !important; +} +.jstree-default.jstree-checkbox-no-clicked .jstree-clicked { + background: transparent; + box-shadow: none; +} +.jstree-default.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered { + background: #e7f4f9; +} +.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked { + background: transparent; +} +.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered { + background: #e7f4f9; +} +.jstree-default > .jstree-striped { + min-width: 100%; + display: inline-block; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat; +} +.jstree-default > .jstree-wholerow-ul .jstree-hovered, +.jstree-default > .jstree-wholerow-ul .jstree-clicked { + background: transparent; + box-shadow: none; + border-radius: 0; +} +.jstree-default .jstree-wholerow { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.jstree-default .jstree-wholerow-hovered { + background: #e7f4f9; +} +.jstree-default .jstree-wholerow-clicked { + background: #beebff; + background: -webkit-linear-gradient(top, #beebff 0%, #a8e4ff 100%); + background: linear-gradient(to bottom, #beebff 0%, #a8e4ff 100%); +} +.jstree-default .jstree-node { + min-height: 24px; + line-height: 24px; + margin-left: 24px; + min-width: 24px; +} +.jstree-default .jstree-anchor { + line-height: 24px; + height: 24px; +} +.jstree-default .jstree-icon { + width: 24px; + height: 24px; + line-height: 24px; +} +.jstree-default .jstree-icon:empty { + width: 24px; + height: 24px; + line-height: 24px; +} +.jstree-default.jstree-rtl .jstree-node { + margin-right: 24px; +} +.jstree-default .jstree-wholerow { + height: 24px; +} +.jstree-default .jstree-node, +.jstree-default .jstree-icon { + background-image: url("../images/jstree/32px.png"); +} +.jstree-default .jstree-node { + background-position: -292px -4px; + background-repeat: repeat-y; +} +.jstree-default .jstree-last { + background: transparent; +} +.jstree-default .jstree-open > .jstree-ocl { + background-position: -132px -4px; +} +.jstree-default .jstree-closed > .jstree-ocl { + background-position: -100px -4px; +} +.jstree-default .jstree-leaf > .jstree-ocl { + background-position: -68px -4px; +} +.jstree-default .jstree-themeicon { + background-position: -260px -4px; +} +.jstree-default > .jstree-no-dots .jstree-node, +.jstree-default > .jstree-no-dots .jstree-leaf > .jstree-ocl { + background: transparent; +} +.jstree-default > .jstree-no-dots .jstree-open > .jstree-ocl { + background-position: -36px -4px; +} +.jstree-default > .jstree-no-dots .jstree-closed > .jstree-ocl { + background-position: -4px -4px; +} +.jstree-default .jstree-disabled { + background: transparent; +} +.jstree-default .jstree-disabled.jstree-hovered { + background: transparent; +} +.jstree-default .jstree-disabled.jstree-clicked { + background: #efefef; +} +.jstree-default .jstree-checkbox { + background-position: -164px -4px; +} +.jstree-default .jstree-checkbox:hover { + background-position: -164px -36px; +} +.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox, +.jstree-default .jstree-checked > .jstree-checkbox { + background-position: -228px -4px; +} +.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover, +.jstree-default .jstree-checked > .jstree-checkbox:hover { + background-position: -228px -36px; +} +.jstree-default .jstree-anchor > .jstree-undetermined { + background-position: -196px -4px; +} +.jstree-default .jstree-anchor > .jstree-undetermined:hover { + background-position: -196px -36px; +} +.jstree-default .jstree-checkbox-disabled { + opacity: 0.8; + filter: url("data:image/svg+xml;utf8,#jstree-grayscale"); + /* Firefox 10+ */ + filter: gray; + /* IE6-9 */ + -webkit-filter: grayscale(100%); + /* Chrome 19+ & Safari 6+ */ +} +.jstree-default > .jstree-striped { + background-size: auto 48px; +} +.jstree-default.jstree-rtl .jstree-node { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); + background-position: 100% 1px; + background-repeat: repeat-y; +} +.jstree-default.jstree-rtl .jstree-last { + background: transparent; +} +.jstree-default.jstree-rtl .jstree-open > .jstree-ocl { + background-position: -132px -36px; +} +.jstree-default.jstree-rtl .jstree-closed > .jstree-ocl { + background-position: -100px -36px; +} +.jstree-default.jstree-rtl .jstree-leaf > .jstree-ocl { + background-position: -68px -36px; +} +.jstree-default.jstree-rtl > .jstree-no-dots .jstree-node, +.jstree-default.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl { + background: transparent; +} +.jstree-default.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl { + background-position: -36px -36px; +} +.jstree-default.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl { + background-position: -4px -36px; +} +.jstree-default .jstree-themeicon-custom { + background-color: transparent; + background-image: none; + background-position: 0 0; +} +.jstree-default > .jstree-container-ul .jstree-loading > .jstree-ocl { + background: url("../images/jstree/throbber.gif") center center no-repeat; +} +.jstree-default .jstree-file { + background: url("../images/jstree/32px.png") -100px -68px no-repeat; +} +.jstree-default .jstree-folder { + background: url("../images/jstree/32px.png") -260px -4px no-repeat; +} +.jstree-default > .jstree-container-ul > .jstree-node { + margin-left: 0; + margin-right: 0; +} +#jstree-dnd.jstree-default { + line-height: 24px; + padding: 0 4px; +} +#jstree-dnd.jstree-default .jstree-ok, +#jstree-dnd.jstree-default .jstree-er { + background-image: url("../images/jstree/32px.png"); + background-repeat: no-repeat; + background-color: transparent; +} +#jstree-dnd.jstree-default i { + background: transparent; + width: 24px; + height: 24px; + line-height: 24px; +} +#jstree-dnd.jstree-default .jstree-ok { + background-position: -4px -68px; +} +#jstree-dnd.jstree-default .jstree-er { + background-position: -36px -68px; +} +.jstree-default .jstree-ellipsis { + overflow: hidden; +} +.jstree-default .jstree-ellipsis .jstree-anchor { + width: calc(100% - 29px); + text-overflow: ellipsis; + overflow: hidden; +} +.jstree-default .jstree-ellipsis.jstree-no-icons .jstree-anchor { + width: calc(100% - 5px); +} +.jstree-default.jstree-rtl .jstree-node { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); +} +.jstree-default.jstree-rtl .jstree-last { + background: transparent; +} +.jstree-default-small .jstree-node { + min-height: 18px; + line-height: 18px; + margin-left: 18px; + min-width: 18px; +} +.jstree-default-small .jstree-anchor { + line-height: 18px; + height: 18px; +} +.jstree-default-small .jstree-icon { + width: 18px; + height: 18px; + line-height: 18px; +} +.jstree-default-small .jstree-icon:empty { + width: 18px; + height: 18px; + line-height: 18px; +} +.jstree-default-small.jstree-rtl .jstree-node { + margin-right: 18px; +} +.jstree-default-small .jstree-wholerow { + height: 18px; +} +.jstree-default-small .jstree-node, +.jstree-default-small .jstree-icon { + background-image: url("../images/jstree/32px.png"); +} +.jstree-default-small .jstree-node { + background-position: -295px -7px; + background-repeat: repeat-y; +} +.jstree-default-small .jstree-last { + background: transparent; +} +.jstree-default-small .jstree-open > .jstree-ocl { + background-position: -135px -7px; +} +.jstree-default-small .jstree-closed > .jstree-ocl { + background-position: -103px -7px; +} +.jstree-default-small .jstree-leaf > .jstree-ocl { + background-position: -71px -7px; +} +.jstree-default-small .jstree-themeicon { + background-position: -263px -7px; +} +.jstree-default-small > .jstree-no-dots .jstree-node, +.jstree-default-small > .jstree-no-dots .jstree-leaf > .jstree-ocl { + background: transparent; +} +.jstree-default-small > .jstree-no-dots .jstree-open > .jstree-ocl { + background-position: -39px -7px; +} +.jstree-default-small > .jstree-no-dots .jstree-closed > .jstree-ocl { + background-position: -7px -7px; +} +.jstree-default-small .jstree-disabled { + background: transparent; +} +.jstree-default-small .jstree-disabled.jstree-hovered { + background: transparent; +} +.jstree-default-small .jstree-disabled.jstree-clicked { + background: #efefef; +} +.jstree-default-small .jstree-checkbox { + background-position: -167px -7px; +} +.jstree-default-small .jstree-checkbox:hover { + background-position: -167px -39px; +} +.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox, +.jstree-default-small .jstree-checked > .jstree-checkbox { + background-position: -231px -7px; +} +.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover, +.jstree-default-small .jstree-checked > .jstree-checkbox:hover { + background-position: -231px -39px; +} +.jstree-default-small .jstree-anchor > .jstree-undetermined { + background-position: -199px -7px; +} +.jstree-default-small .jstree-anchor > .jstree-undetermined:hover { + background-position: -199px -39px; +} +.jstree-default-small .jstree-checkbox-disabled { + opacity: 0.8; + filter: url("data:image/svg+xml;utf8,#jstree-grayscale"); + /* Firefox 10+ */ + filter: gray; + /* IE6-9 */ + -webkit-filter: grayscale(100%); + /* Chrome 19+ & Safari 6+ */ +} +.jstree-default-small > .jstree-striped { + background-size: auto 36px; +} +.jstree-default-small.jstree-rtl .jstree-node { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); + background-position: 100% 1px; + background-repeat: repeat-y; +} +.jstree-default-small.jstree-rtl .jstree-last { + background: transparent; +} +.jstree-default-small.jstree-rtl .jstree-open > .jstree-ocl { + background-position: -135px -39px; +} +.jstree-default-small.jstree-rtl .jstree-closed > .jstree-ocl { + background-position: -103px -39px; +} +.jstree-default-small.jstree-rtl .jstree-leaf > .jstree-ocl { + background-position: -71px -39px; +} +.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-node, +.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl { + background: transparent; +} +.jstree-node { + color: #2156a5; !important; +} +.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl { + background-position: -39px -39px; +} +.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl { + background-position: -7px -39px; +} +.jstree-default-small .jstree-themeicon-custom { + background-color: transparent; + background-image: none; + background-position: 0 0; +} +.jstree-default-small > .jstree-container-ul .jstree-loading > .jstree-ocl { + background: url("../images/jstree/throbber.gif") center center no-repeat; +} +.jstree-default-small .jstree-file { + background: url("../images/jstree/32px.png") -103px -71px no-repeat; +} +.jstree-default-small .jstree-folder { + background: url("../images/jstree/32px.png") -263px -7px no-repeat; +} +.jstree-default-small > .jstree-container-ul > .jstree-node { + margin-left: 0; + margin-right: 0; +} +#jstree-dnd.jstree-default-small { + line-height: 18px; + padding: 0 4px; +} +#jstree-dnd.jstree-default-small .jstree-ok, +#jstree-dnd.jstree-default-small .jstree-er { + background-image: url("../images/jstree/32px.png"); + background-repeat: no-repeat; + background-color: transparent; +} +#jstree-dnd.jstree-default-small i { + background: transparent; + width: 18px; + height: 18px; + line-height: 18px; +} +#jstree-dnd.jstree-default-small .jstree-ok { + background-position: -7px -71px; +} +#jstree-dnd.jstree-default-small .jstree-er { + background-position: -39px -71px; +} +.jstree-default-small .jstree-ellipsis { + overflow: hidden; +} +.jstree-default-small .jstree-ellipsis .jstree-anchor { + width: calc(100% - 23px); + text-overflow: ellipsis; + overflow: hidden; +} +.jstree-default-small .jstree-ellipsis.jstree-no-icons .jstree-anchor { + width: calc(100% - 5px); +} +.jstree-default-small.jstree-rtl .jstree-node { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg=="); +} +.jstree-default-small.jstree-rtl .jstree-last { + background: transparent; +} +.jstree-default-large .jstree-node { + min-height: 32px; + line-height: 32px; + margin-left: 32px; + min-width: 32px; +} +.jstree-default-large .jstree-anchor { + line-height: 32px; + height: 32px; +} +.jstree-default-large .jstree-icon { + width: 32px; + height: 32px; + line-height: 32px; +} +.jstree-default-large .jstree-icon:empty { + width: 32px; + height: 32px; + line-height: 32px; +} +.jstree-default-large.jstree-rtl .jstree-node { + margin-right: 32px; +} +.jstree-default-large .jstree-wholerow { + height: 32px; +} +.jstree-default-large .jstree-node, +.jstree-default-large .jstree-icon { + background-image: url("../images/jstree/32px.png"); +} +.jstree-default-large .jstree-node { + background-position: -288px 0px; + background-repeat: repeat-y; +} +.jstree-default-large .jstree-last { + background: transparent; +} +.jstree-default-large .jstree-open > .jstree-ocl { + background-position: -128px 0px; +} +.jstree-default-large .jstree-closed > .jstree-ocl { + background-position: -96px 0px; +} +.jstree-default-large .jstree-leaf > .jstree-ocl { + background-position: -64px 0px; +} +.jstree-default-large .jstree-themeicon { + background-position: -256px 0px; +} +.jstree-default-large > .jstree-no-dots .jstree-node, +.jstree-default-large > .jstree-no-dots .jstree-leaf > .jstree-ocl { + background: transparent; +} +.jstree-default-large > .jstree-no-dots .jstree-open > .jstree-ocl { + background-position: -32px 0px; +} +.jstree-default-large > .jstree-no-dots .jstree-closed > .jstree-ocl { + background-position: 0px 0px; +} +.jstree-default-large .jstree-disabled { + background: transparent; +} +.jstree-default-large .jstree-disabled.jstree-hovered { + background: transparent; +} +.jstree-default-large .jstree-disabled.jstree-clicked { + background: #efefef; +} +.jstree-default-large .jstree-checkbox { + background-position: -160px 0px; +} +.jstree-default-large .jstree-checkbox:hover { + background-position: -160px -32px; +} +.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox, +.jstree-default-large .jstree-checked > .jstree-checkbox { + background-position: -224px 0px; +} +.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover, +.jstree-default-large .jstree-checked > .jstree-checkbox:hover { + background-position: -224px -32px; +} +.jstree-default-large .jstree-anchor > .jstree-undetermined { + background-position: -192px 0px; +} +.jstree-default-large .jstree-anchor > .jstree-undetermined:hover { + background-position: -192px -32px; +} +.jstree-default-large .jstree-checkbox-disabled { + opacity: 0.8; + filter: url("data:image/svg+xml;utf8,#jstree-grayscale"); + /* Firefox 10+ */ + filter: gray; + /* IE6-9 */ + -webkit-filter: grayscale(100%); + /* Chrome 19+ & Safari 6+ */ +} +.jstree-default-large > .jstree-striped { + background-size: auto 64px; +} +.jstree-default-large.jstree-rtl .jstree-node { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); + background-position: 100% 1px; + background-repeat: repeat-y; +} +.jstree-default-large.jstree-rtl .jstree-last { + background: transparent; +} +.jstree-default-large.jstree-rtl .jstree-open > .jstree-ocl { + background-position: -128px -32px; +} +.jstree-default-large.jstree-rtl .jstree-closed > .jstree-ocl { + background-position: -96px -32px; +} +.jstree-default-large.jstree-rtl .jstree-leaf > .jstree-ocl { + background-position: -64px -32px; +} +.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-node, +.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl { + background: transparent; +} +.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl { + background-position: -32px -32px; +} +.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl { + background-position: 0px -32px; +} +.jstree-default-large .jstree-themeicon-custom { + background-color: transparent; + background-image: none; + background-position: 0 0; +} +.jstree-default-large > .jstree-container-ul .jstree-loading > .jstree-ocl { + background: url("../images/jstree/throbber.gif") center center no-repeat; +} +.jstree-default-large .jstree-file { + background: url("../images/jstree/32px.png") -96px -64px no-repeat; +} +.jstree-default-large .jstree-folder { + background: url("../images/jstree/32px.png") -256px 0px no-repeat; +} +.jstree-default-large > .jstree-container-ul > .jstree-node { + margin-left: 0; + margin-right: 0; +} +#jstree-dnd.jstree-default-large { + line-height: 32px; + padding: 0 4px; +} +#jstree-dnd.jstree-default-large .jstree-ok, +#jstree-dnd.jstree-default-large .jstree-er { + background-image: url("../images/jstree/32px.png"); + background-repeat: no-repeat; + background-color: transparent; +} +#jstree-dnd.jstree-default-large i { + background: transparent; + width: 32px; + height: 32px; + line-height: 32px; +} +#jstree-dnd.jstree-default-large .jstree-ok { + background-position: 0px -64px; +} +#jstree-dnd.jstree-default-large .jstree-er { + background-position: -32px -64px; +} +.jstree-default-large .jstree-ellipsis { + overflow: hidden; +} +.jstree-default-large .jstree-ellipsis .jstree-anchor { + width: calc(100% - 37px); + text-overflow: ellipsis; + overflow: hidden; +} +.jstree-default-large .jstree-ellipsis.jstree-no-icons .jstree-anchor { + width: calc(100% - 5px); +} +.jstree-default-large.jstree-rtl .jstree-node { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg=="); +} +.jstree-default-large.jstree-rtl .jstree-last { + background: transparent; +} +@media (max-width: 768px) { + #jstree-dnd.jstree-dnd-responsive { + line-height: 40px; + font-weight: bold; + font-size: 1.1em; + text-shadow: 1px 1px white; + } + #jstree-dnd.jstree-dnd-responsive > i { + background: transparent; + width: 40px; + height: 40px; + } + #jstree-dnd.jstree-dnd-responsive > .jstree-ok { + background-image: url("../images/jstree/40px.png"); + background-position: 0 -200px; + background-size: 120px 240px; + } + #jstree-dnd.jstree-dnd-responsive > .jstree-er { + background-image: url("../images/jstree/40px.png"); + background-position: -40px -200px; + background-size: 120px 240px; + } + #jstree-marker.jstree-dnd-responsive { + border-left-width: 10px; + border-top-width: 10px; + border-bottom-width: 10px; + margin-top: -10px; + } +} +@media (max-width: 768px) { + .jstree-default-responsive { + /* + .jstree-open > .jstree-ocl, + .jstree-closed > .jstree-ocl { border-radius:20px; background-color:white; } + */ + } + .jstree-default-responsive .jstree-icon { + background-image: url("../images/jstree/40px.png"); + } + .jstree-default-responsive .jstree-node, + .jstree-default-responsive .jstree-leaf > .jstree-ocl { + background: transparent; + } + .jstree-default-responsive .jstree-node { + min-height: 40px; + line-height: 40px; + margin-left: 40px; + min-width: 40px; + white-space: nowrap; + } + .jstree-default-responsive .jstree-anchor { + line-height: 40px; + height: 40px; + } + .jstree-default-responsive .jstree-icon, + .jstree-default-responsive .jstree-icon:empty { + width: 40px; + height: 40px; + line-height: 40px; + } + .jstree-default-responsive > .jstree-container-ul > .jstree-node { + margin-left: 0; + } + .jstree-default-responsive.jstree-rtl .jstree-node { + margin-left: 0; + margin-right: 40px; + background: transparent; + } + .jstree-default-responsive.jstree-rtl .jstree-container-ul > .jstree-node { + margin-right: 0; + } + .jstree-default-responsive .jstree-ocl, + .jstree-default-responsive .jstree-themeicon, + .jstree-default-responsive .jstree-checkbox { + background-size: 120px 240px; + } + .jstree-default-responsive .jstree-leaf > .jstree-ocl, + .jstree-default-responsive.jstree-rtl .jstree-leaf > .jstree-ocl { + background: transparent; + } + .jstree-default-responsive .jstree-open > .jstree-ocl { + background-position: 0 0px !important; + } + .jstree-default-responsive .jstree-closed > .jstree-ocl { + background-position: 0 -40px !important; + } + .jstree-default-responsive.jstree-rtl .jstree-closed > .jstree-ocl { + background-position: -40px 0px !important; + } + .jstree-default-responsive .jstree-themeicon { + background-position: -40px -40px; + } + .jstree-default-responsive .jstree-checkbox, + .jstree-default-responsive .jstree-checkbox:hover { + background-position: -40px -80px; + } + .jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox, + .jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover, + .jstree-default-responsive .jstree-checked > .jstree-checkbox, + .jstree-default-responsive .jstree-checked > .jstree-checkbox:hover { + background-position: 0 -80px; + } + .jstree-default-responsive .jstree-anchor > .jstree-undetermined, + .jstree-default-responsive .jstree-anchor > .jstree-undetermined:hover { + background-position: 0 -120px; + } + .jstree-default-responsive .jstree-anchor { + font-weight: bold; + font-size: 1.1em; + text-shadow: 1px 1px white; + } + .jstree-default-responsive > .jstree-striped { + background: transparent; + } + .jstree-default-responsive .jstree-wholerow { + border-top: 1px solid rgba(255, 255, 255, 0.7); + border-bottom: 1px solid rgba(64, 64, 64, 0.2); + background: #ebebeb; + height: 40px; + } + .jstree-default-responsive .jstree-wholerow-hovered { + background: #e7f4f9; + } + .jstree-default-responsive .jstree-wholerow-clicked { + background: #beebff; + } + .jstree-default-responsive .jstree-children .jstree-last > .jstree-wholerow { + box-shadow: inset 0 -6px 3px -5px #666666; + } + .jstree-default-responsive .jstree-children .jstree-open > .jstree-wholerow { + box-shadow: inset 0 6px 3px -5px #666666; + border-top: 0; + } + .jstree-default-responsive .jstree-children .jstree-open + .jstree-open { + box-shadow: none; + } + .jstree-default-responsive .jstree-node, + .jstree-default-responsive .jstree-icon, + .jstree-default-responsive .jstree-node > .jstree-ocl, + .jstree-default-responsive .jstree-themeicon, + .jstree-default-responsive .jstree-checkbox { + background-image: url("../images/jstree/40px.png"); + background-size: 120px 240px; + } + .jstree-default-responsive .jstree-node { + background-position: -80px 0; + background-repeat: repeat-y; + } + .jstree-default-responsive .jstree-last { + background: transparent; + } + .jstree-default-responsive .jstree-leaf > .jstree-ocl { + background-position: -40px -120px; + } + .jstree-default-responsive .jstree-last > .jstree-ocl { + background-position: -40px -160px; + } + .jstree-default-responsive .jstree-themeicon-custom { + background-color: transparent; + background-image: none; + background-position: 0 0; + } + .jstree-default-responsive .jstree-file { + background: url("../images/jstree/40px.png") 0 -160px no-repeat; + background-size: 120px 240px; + } + .jstree-default-responsive .jstree-folder { + background: url("../images/jstree/40px.png") -40px -40px no-repeat; + background-size: 120px 240px; + } + .jstree-default-responsive > .jstree-container-ul > .jstree-node { + margin-left: 0; + margin-right: 0; + } +} diff --git a/documentation/src/main/style/asciidoctor/css/jstree-toc.css b/documentation/src/main/style/asciidoctor/css/jstree-toc.css new file mode 100644 index 000000000000..2d5840ab1ff8 --- /dev/null +++ b/documentation/src/main/style/asciidoctor/css/jstree-toc.css @@ -0,0 +1,9 @@ +.sectlevel1{ + display: none; +} +.sectlevel2{ + display: none; +} +.sectlevel3{ + display: none; +} diff --git a/documentation/src/main/style/asciidoctor/images/jstree/32px.png b/documentation/src/main/style/asciidoctor/images/jstree/32px.png new file mode 100644 index 000000000000..153271524817 Binary files /dev/null and b/documentation/src/main/style/asciidoctor/images/jstree/32px.png differ diff --git a/documentation/src/main/style/asciidoctor/images/jstree/40px.png b/documentation/src/main/style/asciidoctor/images/jstree/40px.png new file mode 100644 index 000000000000..1959347aea04 Binary files /dev/null and b/documentation/src/main/style/asciidoctor/images/jstree/40px.png differ diff --git a/documentation/src/main/style/asciidoctor/images/jstree/throbber.gif b/documentation/src/main/style/asciidoctor/images/jstree/throbber.gif new file mode 100644 index 000000000000..1b5b2fde42f8 Binary files /dev/null and b/documentation/src/main/style/asciidoctor/images/jstree/throbber.gif differ diff --git a/documentation/src/main/style/asciidoctor/js/toc.js b/documentation/src/main/style/asciidoctor/js/toc.js new file mode 100644 index 000000000000..5b9445d70790 --- /dev/null +++ b/documentation/src/main/style/asciidoctor/js/toc.js @@ -0,0 +1,52 @@ +var versions = { + 'current' : '/current/userguide/html_single/Hibernate_User_Guide.html', + '5.2' : '/5.2/userguide/html_single/Hibernate_User_Guide.html', + '5.1' : '/5.1/userguide/html_single/Hibernate_User_Guide.html', + '5.0' : '/5.0/userguide/html_single/Hibernate_User_Guide.html', + '4.3' : '/4.3/manual/en-US/html_single/', + '4.2' : '/4.2/manual/en-US/html_single/', + '4.1' : '/4.1/manual/en-US/html_single/', + '4.0' : '/4.0/manual/en-US/html_single/', + '3.6' : '/3.6/reference/en-US/html_single/', + '3.5' : '/3.5/reference/en-US/html_single/', + '3.3' : '/3.3/reference/en-US/html_single/', + '3.2' : '/3.2/reference/en/html_single/' +}; + +$(document).ready(function() { + $('#toctitle').before(''); + $('#vchooser').append(''); + + for(var version in versions) { + var path = 'http://docs.jboss.org/hibernate/orm' + versions[version]; + $('#vchooser').append(''); + }; + + $('#vchooser').change(function(e) { + if (this.value !== '') + window.location.href = this.value; + }); + + $('ul.sectlevel1').wrap('
'); + + $('#toctree').jstree({ + "core" : { + "themes" : {"variant" : "small", "icons" : false} + }, + "plugins" : [ "search", "state", "wholerow" ] + }) + .on("activate_node.jstree", function (e, data) { location.href = data.node.a_attr.href; }); + + $('#toctree').before(''); + var searchTimeout = false; + $('#tocsearch').keyup(function () { + if(searchTimeout) { clearTimeout(searchTimeout); } + searchTimeout = setTimeout(function () { + var v = $('#tocsearch').val(); + $('#toctree').jstree(true).search(v); + }, 250); + }); + $('#tocsearch').after(''); + $('#toctreeexpand').click(function() { $('#toctree').jstree('open_all'); }); + $('#toctreecollapse').click(function() { $('#toctree').jstree('close_all'); }); +}); \ No newline at end of file diff --git a/documentation/src/main/style/css/css/hibernate-eclipse.css b/documentation/src/main/style/css/css/hibernate-eclipse.css deleted file mode 100644 index 25a5b9aae9e9..000000000000 --- a/documentation/src/main/style/css/css/hibernate-eclipse.css +++ /dev/null @@ -1,3 +0,0 @@ -@import url("hibernate.css"); - -body {background-image:none;} diff --git a/documentation/src/main/style/css/css/hibernate-single.css b/documentation/src/main/style/css/css/hibernate-single.css deleted file mode 100644 index 7b24266b9930..000000000000 --- a/documentation/src/main/style/css/css/hibernate-single.css +++ /dev/null @@ -1,3 +0,0 @@ -@import url("hibernate.css"); - -.title {margin-top:30px;} diff --git a/documentation/src/main/style/css/css/hibernate.css b/documentation/src/main/style/css/css/hibernate.css deleted file mode 100644 index 2048fbff75fe..000000000000 --- a/documentation/src/main/style/css/css/hibernate.css +++ /dev/null @@ -1,113 +0,0 @@ -@import url("jbossorg.css"); - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -/* work around problems in the jboss.org styles wrt html & jHighLight */ -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -pre { - font-family:Monaco,monospace; - line-height: 1.29em; -} - -pre br { - display:none; -} - -pre.JAVA { - line-height: 1.29em; -} - -pre.XML { - line-height: 1.29em; -} - -pre.JSP { - line-height: 1.29em; -} - -pre.XHTML { - line-height: 1.29em; -} -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -body{ - background-image:url(../images/org/hibernate/bkg_gradient.png); - font-family:'Lucida Grande', Geneva, Verdana, Arial, sans-serif; -} - -a{ - text-decoration:none; -} - -h1{ - background-image:url(../images/org/hibernate/title_hdr.png); - color:#182737; -} - -h2,h3,h4,h5,h6{ - color:#4a5d75; -} - -#title a.site_href { - display:block; - height:100px; - width:362px; - float:left; - background:url(../images/org/hibernate/hibernatelogo.png) top left no-repeat; -} - -#title a.doc_href { - display:block; - height:100px; - background:transparent url(../images/org/hibernate/community_doc.png) top right no-repeat; -} - -.releaseinfo { - color:#4a5d75; - font-size:150%; -} - -div.note { - background-color:#849092; - color:white; - background-image: url(../images/org/hibernate/docbook/note.png); -} - -div.note h2 { - color:white; -} - -div.tip { - background-image:url(../images/org/hibernate/docbook/tip.png); -} - -div.important { - background-image:url(../images/org/hibernate/docbook/important.png); -} - -div.caution { - background-image:url(../images/org/hibernate/docbook/caution.png); -} - -div.warning { - background-image:url(../images/org/hibernate/docbook/warning.png); -} - -div.note a:visited, div.tip a:visited, div.important a:visited, div.caution a:visited, div.warning a:visited, div.note a:link, div.tip a:link, div.important a:link, div.caution a:link, div.warning a:link { - color: #f7f2d0; -} - -.docnav li.next a strong {background-image:url(../images/org/hibernate/docbook/next.png);} -.docnav li.previous a strong {background-image:url(../images/org/hibernate/docbook/prev.png);} -.docnav li.home a strong {background-image:url(../images/org/hibernate/docbook/home.png);} -.docnav li.up a strong {background-image:url(../images/org/hibernate/docbook/up.png);} - -/* Eclipse Help Navigation */ -.navheader td.next a {background-image:url(../images/org/hibernate/docbook/next.png);} -.navheader td.previous a {background-image:url(../images/org/hibernate/docbook/prev.png);} - -.navfooter td.next a {background-image:url(../images/org/hibernate/docbook/next.png);} -.navfooter td.previous a {background-image:url(../images/org/hibernate/docbook/prev.png);} -.navfooter td.home a {background-image:url(../images/org/hibernate/docbook/home.png);} -.navfooter td.up a {background-image:url(../images/org/hibernate/docbook/up.png);} - - diff --git a/documentation/src/main/style/images/images/org/hibernate/bkg_gradient.png b/documentation/src/main/style/images/images/org/hibernate/bkg_gradient.png deleted file mode 100644 index 48365edf0603..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/bkg_gradient.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/community_doc.png b/documentation/src/main/style/images/images/org/hibernate/community_doc.png deleted file mode 100644 index d416cc361825..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/community_doc.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/1.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/1.png deleted file mode 100644 index 352500a772b1..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/1.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/1.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/1.svg deleted file mode 100644 index 6258b69a5014..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/1.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/10.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/10.png deleted file mode 100644 index c11a095d493d..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/10.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/10.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/10.svg deleted file mode 100644 index 6642fa899c41..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/10.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - -]> - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/11.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/11.png deleted file mode 100644 index d550daed5956..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/11.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/11.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/11.svg deleted file mode 100644 index 664171e03313..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/11.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - -]> - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/12.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/12.png deleted file mode 100644 index 05dd6d4165b5..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/12.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/12.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/12.svg deleted file mode 100644 index 4c4df2e6a769..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/12.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - -]> - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/13.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/13.png deleted file mode 100644 index 11d816b6a495..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/13.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/13.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/13.svg deleted file mode 100644 index 50aa7b7d46d6..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/13.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - -]> - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/14.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/14.png deleted file mode 100644 index bd24e1b59e80..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/14.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/14.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/14.svg deleted file mode 100644 index b5f1f48456fb..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/14.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - -]> - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/15.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/15.png deleted file mode 100644 index 771a7db7bfc6..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/15.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/15.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/15.svg deleted file mode 100644 index 7fe3899e6770..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/15.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - -]> - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/2.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/2.png deleted file mode 100644 index 3949673eb9e1..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/2.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/2.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/2.svg deleted file mode 100644 index 6a5c501bac6e..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/2.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/3.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/3.png deleted file mode 100644 index 23523d977294..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/3.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/3.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/3.svg deleted file mode 100644 index 006ea01ccb2f..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/3.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/4.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/4.png deleted file mode 100644 index ced622a16009..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/4.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/4.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/4.svg deleted file mode 100644 index a532a0d1e807..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/4.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/5.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/5.png deleted file mode 100644 index c117955328f0..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/5.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/5.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/5.svg deleted file mode 100644 index b4bd44c187c7..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/5.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/6.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/6.png deleted file mode 100644 index da45f300d07f..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/6.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/6.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/6.svg deleted file mode 100644 index 8a44c6a437c0..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/6.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/7.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/7.png deleted file mode 100644 index 5b2d43b0d883..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/7.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/7.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/7.svg deleted file mode 100644 index 0652ed9c879e..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/7.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/8.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/8.png deleted file mode 100644 index 067e861a63b6..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/8.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/8.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/8.svg deleted file mode 100644 index dd384e171105..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/8.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/9.png b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/9.png deleted file mode 100644 index fc1c09faaad6..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/9.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/9.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/9.svg deleted file mode 100644 index 40f61aed18e0..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/callouts/9.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - -]> - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/caution.png b/documentation/src/main/style/images/images/org/hibernate/docbook/caution.png deleted file mode 100644 index f6431e7af729..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/caution.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/caution.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/caution.svg deleted file mode 100644 index 1a72aaf6dc37..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/caution.svg +++ /dev/null @@ -1,142 +0,0 @@ - - - -]> - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/home.png b/documentation/src/main/style/images/images/org/hibernate/docbook/home.png deleted file mode 100644 index 50c39f3ed8fb..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/home.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/important.png b/documentation/src/main/style/images/images/org/hibernate/docbook/important.png deleted file mode 100644 index 76b90e072e1a..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/important.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/important.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/important.svg deleted file mode 100644 index 7c727b948afd..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/important.svg +++ /dev/null @@ -1,137 +0,0 @@ - - - -]> - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/next.png b/documentation/src/main/style/images/images/org/hibernate/docbook/next.png deleted file mode 100644 index 3957e327a37f..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/next.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/note.png b/documentation/src/main/style/images/images/org/hibernate/docbook/note.png deleted file mode 100644 index 310b1da19e72..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/note.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/note.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/note.svg deleted file mode 100644 index 28acab9be55e..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/note.svg +++ /dev/null @@ -1,137 +0,0 @@ - - - -]> - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/prev.png b/documentation/src/main/style/images/images/org/hibernate/docbook/prev.png deleted file mode 100644 index 8ee9dedc0fe4..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/prev.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/tip.png b/documentation/src/main/style/images/images/org/hibernate/docbook/tip.png deleted file mode 100644 index 3b377637332b..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/tip.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/tip.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/tip.svg deleted file mode 100644 index 4c876bbf041a..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/tip.svg +++ /dev/null @@ -1,143 +0,0 @@ - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/up.png b/documentation/src/main/style/images/images/org/hibernate/docbook/up.png deleted file mode 100644 index 0612e13cd657..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/up.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/warning.png b/documentation/src/main/style/images/images/org/hibernate/docbook/warning.png deleted file mode 100644 index be2b415a6e68..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/docbook/warning.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/docbook/warning.svg b/documentation/src/main/style/images/images/org/hibernate/docbook/warning.svg deleted file mode 100644 index 894b95850873..000000000000 --- a/documentation/src/main/style/images/images/org/hibernate/docbook/warning.svg +++ /dev/null @@ -1,231 +0,0 @@ - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/style/images/images/org/hibernate/dot.png b/documentation/src/main/style/images/images/org/hibernate/dot.png deleted file mode 100755 index 079add95ded9..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/dot.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/dot2.png b/documentation/src/main/style/images/images/org/hibernate/dot2.png deleted file mode 100755 index 8348fcd054a5..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/dot2.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/hibernatelogo.png b/documentation/src/main/style/images/images/org/hibernate/hibernatelogo.png deleted file mode 100644 index 47c4dbe840b1..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/hibernatelogo.png and /dev/null differ diff --git a/documentation/src/main/style/images/images/org/hibernate/title_hdr.png b/documentation/src/main/style/images/images/org/hibernate/title_hdr.png deleted file mode 100644 index 0c801672ce51..000000000000 Binary files a/documentation/src/main/style/images/images/org/hibernate/title_hdr.png and /dev/null differ diff --git a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/common-base.xsl b/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/common-base.xsl deleted file mode 100644 index 0928948da4ef..000000000000 --- a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/common-base.xsl +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - images/org/hibernate/docbook/ - - - - diff --git a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/common-xhtml.xsl b/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/common-xhtml.xsl deleted file mode 100644 index 9954b745c394..000000000000 --- a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/common-xhtml.xsl +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - legalnotice.html - - - - - - -
- - - - - - - - - - - - - -
- -
diff --git a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/fop1.xsl b/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/fop1.xsl deleted file mode 100644 index e25c126255ea..000000000000 --- a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/fop1.xsl +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/pdf.xsl b/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/pdf.xsl deleted file mode 100644 index 9d4410192316..000000000000 --- a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/pdf.xsl +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - Setting 'title.font.family' param = - - - - - - - - - - Setting 'body.font.family' param = - - - - - - - - - - Setting 'monospace.font.family' param = - - - - - - - - - - Setting 'sans.font.family' param = - - - - - - #4a5d75 - #4a5d75 - #4a5d75 - #4a5d75 - - - - - - - bold - #EDE8DB - black - - - - - - - - - - - - - - - - - - - #EDE8DB - - - - - diff --git a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/xhtml-single.xsl b/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/xhtml-single.xsl deleted file mode 100644 index 0885e3e2866c..000000000000 --- a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/xhtml-single.xsl +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - diff --git a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/xhtml.xsl b/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/xhtml.xsl deleted file mode 100644 index f047df1c785a..000000000000 --- a/documentation/src/main/style/xslt/org/hibernate/jdocbook/xslt/xhtml.xsl +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java index e239f3b0b21a..81fb51ead265 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java @@ -71,9 +71,14 @@ public static class Person { @NaturalId private String registrationNumber; + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List
addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-example[] + public Person() { } @@ -85,6 +90,7 @@ public List
getAddresses() { return addresses; } + //tag::associations-many-to-many-bidirectional-example[] public void addAddress(Address address) { addresses.add( address ); address.getOwners().add( this ); @@ -130,6 +136,10 @@ public static class Address { @ManyToMany(mappedBy = "addresses") private List owners = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-example[] + public Address() { } @@ -159,6 +169,7 @@ public List getOwners() { return owners; } + //tag::associations-many-to-many-bidirectional-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java index 36ed0c678075..4f4d64594597 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java @@ -32,8 +32,6 @@ */ public class ManyToManyBidirectionalWithLinkEntityTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( ManyToManyBidirectionalWithLinkEntityTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -83,9 +81,17 @@ public static class Person implements Serializable { @NaturalId private String registrationNumber; - @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany( + mappedBy = "person", + cascade = CascadeType.ALL, + orphanRemoval = true + ) private List addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public Person() { } @@ -101,6 +107,7 @@ public List getAddresses() { return addresses; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] public void addAddress(Address address) { PersonAddress personAddress = new PersonAddress( this, address ); addresses.add( personAddress ); @@ -144,6 +151,10 @@ public static class PersonAddress implements Serializable { @ManyToOne private Address address; + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public PersonAddress() { } @@ -168,6 +179,7 @@ public void setAddress(Address address) { this.address = address; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] @Override public boolean equals(Object o) { if ( this == o ) { @@ -201,9 +213,17 @@ public static class Address implements Serializable { private String postalCode; - @OneToMany(mappedBy = "address", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany( + mappedBy = "address", + cascade = CascadeType.ALL, + orphanRemoval = true + ) private List owners = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public Address() { } @@ -233,6 +253,7 @@ public List getOwners() { return owners; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java index d869f4c786c3..99c5ba5c75f3 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java @@ -28,8 +28,6 @@ */ public class ManyToManyUnidirectionalTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( ManyToManyUnidirectionalTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -98,15 +96,21 @@ public static class Person { @Id @GeneratedValue private Long id; + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List
addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-unidirectional-example[] + public Person() { } public List
getAddresses() { return addresses; } + //tag::associations-many-to-many-unidirectional-example[] } @Entity(name = "Address") @@ -121,6 +125,10 @@ public static class Address { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-unidirectional-example[] + public Address() { } @@ -140,6 +148,7 @@ public String getStreet() { public String getNumber() { return number; } + //tag::associations-many-to-many-unidirectional-example[] } //end::associations-many-to-many-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java index eb99f055582a..93c98facf337 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java @@ -58,8 +58,8 @@ public static class Person { @GeneratedValue private Long id; - public Person() { - } + //Getters and setters are omitted for brevity + } @Entity(name = "Phone") @@ -78,6 +78,10 @@ public static class Phone { ) private Person person; + //Getters and setters are omitted for brevity + + //end::associations-many-to-one-example[] + public Phone() { } @@ -100,6 +104,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::associations-many-to-one-example[] } //end::associations-many-to-one-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java new file mode 100644 index 000000000000..82b9442fceda --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java @@ -0,0 +1,162 @@ +package org.hibernate.userguide.associations; + +import java.io.Serializable; +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.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * @author Fábio Ueno + */ +public class NotFoundTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + City.class + }; + } + + @Test + public void test() { + doInJPA( this::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" ); + 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[] + + //tag::associations-not-found-non-existing-persist-example[] + person.setCityName( "Atlantis" ); + //end::associations-not-found-non-existing-persist-example[] + + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::associations-not-found-non-existing-find-example[] + Person person = entityManager.find( Person.class, 1L ); + + assertEquals( "Atlantis", person.getCityName() ); + assertNull( null, person.getCity() ); + //end::associations-not-found-non-existing-find-example[] + } ); + } + + //tag::associations-not-found-domain-model-example[] + @Entity + @Table( name = "Person" ) + public static class Person { + + @Id + private Long id; + + private String name; + + private String cityName; + + @ManyToOne( fetch = FetchType.LAZY ) + @NotFound ( action = NotFoundAction.IGNORE ) + @JoinColumn( + name = "cityName", + referencedColumnName = "name", + insertable = false, + updatable = false + ) + private City city; + + //Getters and setters are omitted for brevity + + //end::associations-not-found-domain-model-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public 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[] + } + + @Entity + @Table( name = "City" ) + public static class City implements Serializable { + + @Id + @GeneratedValue + private Long id; + + private String name; + + //Getters and setters are omitted for brevity + + //end::associations-not-found-domain-model-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::associations-not-found-domain-model-example[] + } + //end::associations-not-found-domain-model-example[] +} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java index 2a21d8191824..2bd1ad812350 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java @@ -62,9 +62,14 @@ public static class Person { @Id @GeneratedValue private Long id; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-bidirectional-example[] + public Person() { } @@ -76,6 +81,7 @@ public List getPhones() { return phones; } + //tag::associations-one-to-many-bidirectional-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -101,6 +107,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-bidirectional-example[] + public Phone() { } @@ -124,6 +134,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::associations-one-to-many-bidirectional-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java index aa12bba7b990..7e9406421008 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java @@ -26,7 +26,6 @@ */ public class OneToManyUnidirectionalTest extends BaseEntityManagerFunctionalTestCase { - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -60,15 +59,22 @@ public static class Person { @Id @GeneratedValue private Long id; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-unidirectional-example[] + public Person() { } public List getPhones() { return phones; } + + //tag::associations-one-to-many-unidirectional-example[] } @Entity(name = "Phone") @@ -81,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-unidirectional-example[] + public Phone() { } @@ -95,6 +105,7 @@ public Long getId() { public String getNumber() { return number; } + //tag::associations-one-to-many-unidirectional-example[] } //end::associations-one-to-many-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java new file mode 100644 index 000000000000..fcdd7722533e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java @@ -0,0 +1,154 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.associations; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Assert; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class OneToOneBidirectionalLazyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Phone.class, + PhoneDetails.class, + }; + } + + @Test + public void testLifecycle() { + + } + + //tag::associations-one-to-one-bidirectional-lazy-example[] + @Entity(name = "Phone") + public static class Phone { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "`number`") + private String number; + + @OneToOne( + mappedBy = "phone", + cascade = CascadeType.ALL, + orphanRemoval = true, + fetch = FetchType.LAZY + ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + private PhoneDetails details; + + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-lazy-example[] + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public Long getId() { + return id; + } + + public String getNumber() { + return number; + } + + public PhoneDetails getDetails() { + return details; + } + + //tag::associations-one-to-one-bidirectional-lazy-example[] + public void addDetails(PhoneDetails details) { + details.setPhone( this ); + this.details = details; + } + + public void removeDetails() { + if ( details != null ) { + details.setPhone( null ); + this.details = null; + } + } + } + + @Entity(name = "PhoneDetails") + public static class PhoneDetails { + + @Id + @GeneratedValue + private Long id; + + private String provider; + + private String technology; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "phone_id") + private Phone phone; + + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-lazy-example[] + + public PhoneDetails() { + } + + public PhoneDetails(String provider, String technology) { + this.provider = provider; + this.technology = technology; + } + //Getters and setters are omitted for brevity + + public String getProvider() { + return provider; + } + + public String getTechnology() { + return technology; + } + + public void setTechnology(String technology) { + this.technology = technology; + } + + public Phone getPhone() { + return phone; + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + //tag::associations-one-to-one-bidirectional-lazy-example[] + } + //end::associations-one-to-one-bidirectional-lazy-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java index d01607f30672..bb6a4ba800be 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java @@ -29,8 +29,6 @@ */ public class OneToOneBidirectionalTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( OneToOneBidirectionalTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -96,9 +94,18 @@ public static class Phone { @Column(name = "`number`") private String number; - @OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToOne( + mappedBy = "phone", + cascade = CascadeType.ALL, + orphanRemoval = true, + fetch = FetchType.LAZY + ) private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-example[] + public Phone() { } @@ -118,6 +125,7 @@ public PhoneDetails getDetails() { return details; } + //tag::associations-one-to-one-bidirectional-example[] public void addDetails(PhoneDetails details) { details.setPhone( this ); this.details = details; @@ -146,6 +154,10 @@ public static class PhoneDetails { @JoinColumn(name = "phone_id") private Phone phone; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-example[] + public PhoneDetails() { } @@ -173,6 +185,7 @@ public Phone getPhone() { public void setPhone(Phone phone) { this.phone = phone; } + //tag::associations-one-to-one-bidirectional-example[] } //end::associations-one-to-one-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneMapsIdTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneMapsIdTest.java new file mode 100644 index 000000000000..8b7003a22a8b --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneMapsIdTest.java @@ -0,0 +1,126 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.associations; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class OneToOneMapsIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + PersonDetails.class + }; + } + + @Test + public void testLifecycle() { + //tag::identifiers-derived-mapsid-persist-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( "ABC-123" ); + person.setId( 1L ); + entityManager.persist( person ); + + PersonDetails personDetails = new PersonDetails(); + personDetails.setNickName( "John Doe" ); + personDetails.setPerson( person ); + + entityManager.persist( personDetails ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + PersonDetails personDetails = entityManager.find( PersonDetails.class, 1L ); + + assertEquals("John Doe", personDetails.getNickName()); + } ); + //end::identifiers-derived-mapsid-persist-example[] + } + + //tag::identifiers-derived-mapsid[] + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + @NaturalId + private String registrationNumber; + + public Person() {} + + public Person(String registrationNumber) { + this.registrationNumber = registrationNumber; + } + + //Getters and setters are omitted for brevity + //end::identifiers-derived-mapsid[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getRegistrationNumber() { + return registrationNumber; + } + //tag::identifiers-derived-mapsid[] + } + + @Entity(name = "PersonDetails") + public static class PersonDetails { + + @Id + private Long id; + + private String nickName; + + @OneToOne + @MapsId + private Person person; + + //Getters and setters are omitted for brevity + //end::identifiers-derived-mapsid[] + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + //tag::identifiers-derived-mapsid[] + } + //end::identifiers-derived-mapsid[] + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOnePrimaryKeyJoinColumnTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOnePrimaryKeyJoinColumnTest.java new file mode 100644 index 000000000000..f28a1100b848 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOnePrimaryKeyJoinColumnTest.java @@ -0,0 +1,136 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.associations; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class OneToOnePrimaryKeyJoinColumnTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + PersonDetails.class + }; + } + + @Test + public void testLifecycle() { + //tag::identifiers-derived-primarykeyjoincolumn-persist-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( "ABC-123" ); + person.setId( 1L ); + entityManager.persist( person ); + + PersonDetails personDetails = new PersonDetails(); + personDetails.setNickName( "John Doe" ); + personDetails.setPerson( person ); + + entityManager.persist( personDetails ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + PersonDetails personDetails = entityManager.find( PersonDetails.class, 1L ); + + assertEquals("John Doe", personDetails.getNickName()); + } ); + //end::identifiers-derived-primarykeyjoincolumn-persist-example[] + } + + //tag::identifiers-derived-primarykeyjoincolumn[] + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + @NaturalId + private String registrationNumber; + + public Person() {} + + public Person(String registrationNumber) { + this.registrationNumber = registrationNumber; + } + + //Getters and setters are omitted for brevity + //end::identifiers-derived-primarykeyjoincolumn[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getRegistrationNumber() { + return registrationNumber; + } + //tag::identifiers-derived-primarykeyjoincolumn[] + } + + @Entity(name = "PersonDetails") + public static class PersonDetails { + + @Id + private Long id; + + private String nickName; + + @OneToOne + @PrimaryKeyJoinColumn + private Person person; + + public void setPerson(Person person) { + this.person = person; + this.id = person.getId(); + } + + //Other getters and setters are omitted for brevity + //end::identifiers-derived-primarykeyjoincolumn[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public Person getPerson() { + return person; + } + + //tag::identifiers-derived-primarykeyjoincolumn[] + } + //end::identifiers-derived-primarykeyjoincolumn[] + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java index 11136ed7415c..b596d540dac7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java @@ -59,6 +59,10 @@ public static class Phone { @JoinColumn(name = "details_id") private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-unidirectional-example[] + public Phone() { } @@ -81,6 +85,7 @@ public PhoneDetails getDetails() { public void setDetails(PhoneDetails details) { this.details = details; } + //tag::associations-one-to-one-unidirectional-example[] } @Entity(name = "PhoneDetails") @@ -94,6 +99,10 @@ public static class PhoneDetails { private String technology; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-unidirectional-example[] + public PhoneDetails() { } @@ -113,6 +122,7 @@ public String getTechnology() { public void setTechnology(String technology) { this.technology = technology; } + //tag::associations-one-to-one-unidirectional-example[] } //end::associations-one-to-one-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/UnidirectionalManyToManyRemoveTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/UnidirectionalManyToManyRemoveTest.java index 83ef795a167b..401423e3946b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/UnidirectionalManyToManyRemoveTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/UnidirectionalManyToManyRemoveTest.java @@ -28,8 +28,6 @@ */ public class UnidirectionalManyToManyRemoveTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( UnidirectionalManyToManyRemoveTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/batch/BatchTest.java b/documentation/src/test/java/org/hibernate/userguide/batch/BatchTest.java index f26fcf949961..bb508114a1eb 100644 --- a/documentation/src/test/java/org/hibernate/userguide/batch/BatchTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/batch/BatchTest.java @@ -35,8 +35,6 @@ */ public class BatchTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( BatchTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -202,15 +200,15 @@ private void withBatch() { int batchSize = 25; - for ( int i = 0; i < entityCount; ++i ) { - Person Person = new Person( String.format( "Person %d", i ) ); - entityManager.persist( Person ); - + for ( int i = 0; i < entityCount; i++ ) { if ( i > 0 && i % batchSize == 0 ) { //flush a batch of inserts and release memory entityManager.flush(); entityManager.clear(); } + + Person Person = new Person( String.format( "Person %d", i ) ); + entityManager.persist( Person ); } txn.commit(); diff --git a/documentation/src/test/java/org/hibernate/userguide/bootstrap/BootstrapTest.java b/documentation/src/test/java/org/hibernate/userguide/bootstrap/BootstrapTest.java index f20bbf912fe1..b6fc2cf11c74 100644 --- a/documentation/src/test/java/org/hibernate/userguide/bootstrap/BootstrapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/bootstrap/BootstrapTest.java @@ -16,9 +16,12 @@ import java.util.Properties; import javax.persistence.AttributeConverter; import javax.persistence.Entity; +import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Id; import javax.persistence.Persistence; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceProperty; import javax.persistence.PersistenceUnit; import javax.persistence.SharedCacheMode; import javax.persistence.ValidationMode; @@ -28,6 +31,7 @@ import javax.sql.DataSource; import org.hibernate.EmptyInterceptor; +import org.hibernate.FlushMode; import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.boot.Metadata; @@ -64,510 +68,535 @@ */ public class BootstrapTest { - @Test - public void test_bootstrap_bootstrap_native_registry_BootstrapServiceRegistry_example() { - - ClassLoader customClassLoader = Thread.currentThread().getContextClassLoader(); - Integrator customIntegrator = new BeanValidationIntegrator(); - - //tag::bootstrap-bootstrap-native-registry-BootstrapServiceRegistry-example[] - BootstrapServiceRegistryBuilder bootstrapRegistryBuilder = - new BootstrapServiceRegistryBuilder(); - // add a custom ClassLoader - bootstrapRegistryBuilder.applyClassLoader( customClassLoader ); - // manually add an Integrator - bootstrapRegistryBuilder.applyIntegrator( customIntegrator ); - - BootstrapServiceRegistry bootstrapRegistry = bootstrapRegistryBuilder.build(); - //end::bootstrap-bootstrap-native-registry-BootstrapServiceRegistry-example[] - } - - @Test - public void test_bootstrap_bootstrap_native_registry_StandardServiceRegistryBuilder_example_1() { - //tag::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] - // An example using an implicitly built BootstrapServiceRegistry - StandardServiceRegistryBuilder standardRegistryBuilder = - new StandardServiceRegistryBuilder(); - //end::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] - } - - @Test - public void test_bootstrap_bootstrap_native_registry_StandardServiceRegistryBuilder_example_2() { - //tag::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] - - // An example using an explicitly built BootstrapServiceRegistry - BootstrapServiceRegistry bootstrapRegistry = - new BootstrapServiceRegistryBuilder().build(); - - StandardServiceRegistryBuilder standardRegistryBuilder = - new StandardServiceRegistryBuilder( bootstrapRegistry ); - //end::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] - } - - @Test - public void test_bootstrap_bootstrap_native_registry_MetadataSources_example() { - - try { - //tag::bootstrap-bootstrap-native-registry-MetadataSources-example[] - ServiceRegistry standardRegistry = - new StandardServiceRegistryBuilder().build(); - - MetadataSources sources = new MetadataSources( standardRegistry ); - - // alternatively, we can build the MetadataSources without passing - // a service registry, in which case it will build a default - // BootstrapServiceRegistry to use. But the approach shown - // above is preferred - // MetadataSources sources = new MetadataSources(); - - // add a class using JPA/Hibernate annotations for mapping - sources.addAnnotatedClass( MyEntity.class ); - - // add the name of a class using JPA/Hibernate annotations for mapping. - // differs from above in that accessing the Class is deferred which is - // important if using runtime bytecode-enhancement - sources.addAnnotatedClassName( "org.hibernate.example.Customer" ); - - // Read package-level metadata. - sources.addPackage( "hibernate.example" ); - - // Read package-level metadata. - sources.addPackage( MyEntity.class.getPackage() ); - - // Adds the named hbm.xml resource as a source: which performs the - // classpath lookup and parses the XML - sources.addResource( "org/hibernate/example/Order.hbm.xml" ); - - // Adds the named JPA orm.xml resource as a source: which performs the - // classpath lookup and parses the XML - sources.addResource( "org/hibernate/example/Product.orm.xml" ); - - // Read all mapping documents from a directory tree. - // Assumes that any file named *.hbm.xml is a mapping document. - sources.addDirectory( new File( ".") ); - - // Read mappings from a particular XML file - sources.addFile( new File( "./mapping.xml") ); - - // Read all mappings from a jar file. - // Assumes that any file named *.hbm.xml is a mapping document. - sources.addJar( new File( "./entities.jar") ); - - // Read a mapping as an application resource using the convention that a class named foo.bar.MyEntity is - // mapped by a file named foo/bar/MyEntity.hbm.xml which can be resolved as a classpath resource. - sources.addClass( MyEntity.class ); - //end::bootstrap-bootstrap-native-registry-MetadataSources-example[] - } - catch (Exception ignore) { - - } - } - - - @Test - public void test_bootstrap_bootstrap_native_metadata_source_example() { - try { - { - //tag::bootstrap-native-metadata-source-example[] - ServiceRegistry standardRegistry = - new StandardServiceRegistryBuilder().build(); - - MetadataSources sources = new MetadataSources( standardRegistry ) - .addAnnotatedClass( MyEntity.class ) - .addAnnotatedClassName( "org.hibernate.example.Customer" ) - .addResource( "org/hibernate/example/Order.hbm.xml" ) - .addResource( "org/hibernate/example/Product.orm.xml" ); - //end::bootstrap-native-metadata-source-example[] - } - - { - AttributeConverter myAttributeConverter = new AttributeConverter() { - @Override - public Object convertToDatabaseColumn(Object attribute) { - return null; - } - - @Override - public Object convertToEntityAttribute(Object dbData) { - return null; - } - } ; - //tag::bootstrap-native-metadata-builder-example[] - ServiceRegistry standardRegistry = - new StandardServiceRegistryBuilder().build(); - - MetadataSources sources = new MetadataSources( standardRegistry ); - - MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); - - // Use the JPA-compliant implicit naming strategy - metadataBuilder.applyImplicitNamingStrategy( - ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ); - - // specify the schema name to use for tables, etc when none is explicitly specified - metadataBuilder.applyImplicitSchemaName( "my_default_schema" ); - - // specify a custom Attribute Converter - metadataBuilder.applyAttributeConverter( myAttributeConverter ); - - Metadata metadata = metadataBuilder.build(); - //end::bootstrap-native-metadata-builder-example[] - } - } - catch (Exception ignore) { - - } - } - - @Test - public void test_bootstrap_bootstrap_native_SessionFactory_example() { - try { - { - //tag::bootstrap-native-SessionFactory-example[] - StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder() - .configure( "org/hibernate/example/hibernate.cfg.xml" ) - .build(); - - Metadata metadata = new MetadataSources( standardRegistry ) - .addAnnotatedClass( MyEntity.class ) - .addAnnotatedClassName( "org.hibernate.example.Customer" ) - .addResource( "org/hibernate/example/Order.hbm.xml" ) - .addResource( "org/hibernate/example/Product.orm.xml" ) - .getMetadataBuilder() - .applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ) - .build(); - - SessionFactory sessionFactory = metadata.getSessionFactoryBuilder() - .applyBeanManager( getBeanManager() ) - .build(); - //end::bootstrap-native-SessionFactory-example[] - } - { - //tag::bootstrap-native-SessionFactoryBuilder-example[] - StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder() - .configure( "org/hibernate/example/hibernate.cfg.xml" ) - .build(); - - Metadata metadata = new MetadataSources( standardRegistry ) - .addAnnotatedClass( MyEntity.class ) - .addAnnotatedClassName( "org.hibernate.example.Customer" ) - .addResource( "org/hibernate/example/Order.hbm.xml" ) - .addResource( "org/hibernate/example/Product.orm.xml" ) - .getMetadataBuilder() - .applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ) - .build(); - - SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder(); - - // Supply an SessionFactory-level Interceptor - sessionFactoryBuilder.applyInterceptor( new CustomSessionFactoryInterceptor() ); - - // Add a custom observer - sessionFactoryBuilder.addSessionFactoryObservers( new CustomSessionFactoryObserver() ); - - // Apply a CDI BeanManager ( for JPA event listeners ) - sessionFactoryBuilder.applyBeanManager( getBeanManager() ); - - SessionFactory sessionFactory = sessionFactoryBuilder.build(); - //end::bootstrap-native-SessionFactoryBuilder-example[] - } - } - catch (Exception ignore) { - - } - } - - @Test - public void test_bootstrap_bootstrap_jpa_compliant_EntityManagerFactory_example() { - try { - //tag::bootstrap-jpa-compliant-EntityManagerFactory-example[] - // Create an EMF for our CRM persistence-unit. - EntityManagerFactory emf = Persistence.createEntityManagerFactory( "CRM" ); - //end::bootstrap-jpa-compliant-EntityManagerFactory-example[] - } catch (Exception ignore) {} - } - - @Test - public void test_bootstrap_bootstrap_native_EntityManagerFactory_example() { - - try { - //tag::bootstrap-native-EntityManagerFactory-example[] - String persistenceUnitName = "CRM"; - List entityClassNames = new ArrayList<>( ); - Properties properties = new Properties( ); - - PersistenceUnitInfoImpl persistenceUnitInfo = new PersistenceUnitInfoImpl( - persistenceUnitName, - entityClassNames, - properties - ); - - Map integrationSettings = new HashMap<>(); - integrationSettings.put( - AvailableSettings.INTERCEPTOR, - new CustomSessionFactoryInterceptor() - ); - - EntityManagerFactoryBuilderImpl entityManagerFactoryBuilder = - new EntityManagerFactoryBuilderImpl( - new PersistenceUnitInfoDescriptor( persistenceUnitInfo ), - integrationSettings - ); - - EntityManagerFactory emf = entityManagerFactoryBuilder.build(); - //end::bootstrap-native-EntityManagerFactory-example[] - } - catch (Exception ignore) { - } - } - - public Object getBeanManager() { - return null; - } - - @Entity - public static class MyEntity { - @Id - private Long id; - } - - //tag::bootstrap-event-listener-registration-example[] - public class MyIntegrator implements org.hibernate.integrator.spi.Integrator { - - @Override - public void integrate( - Metadata metadata, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - - // As you might expect, an EventListenerRegistry is the thing with which event - // listeners are registered - // It is a service so we look it up using the service registry - final EventListenerRegistry eventListenerRegistry = - serviceRegistry.getService( EventListenerRegistry.class ); - - // If you wish to have custom determination and handling of "duplicate" listeners, - // you would have to add an implementation of the - // org.hibernate.event.service.spi.DuplicationStrategy contract like this - eventListenerRegistry.addDuplicationStrategy( new CustomDuplicationStrategy() ); - - // EventListenerRegistry defines 3 ways to register listeners: - - // 1) This form overrides any existing registrations with - eventListenerRegistry.setListeners( EventType.AUTO_FLUSH, - DefaultAutoFlushEventListener.class ); - - // 2) This form adds the specified listener(s) to the beginning of the listener chain - eventListenerRegistry.prependListeners( EventType.PERSIST, - DefaultPersistEventListener.class ); - - // 3) This form adds the specified listener(s) to the end of the listener chain - eventListenerRegistry.appendListeners( EventType.MERGE, - DefaultMergeEventListener.class ); - } - - @Override - public void disintegrate( - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - - } - } - //end::bootstrap-event-listener-registration-example[] - - public class CustomDuplicationStrategy implements DuplicationStrategy { - - @Override - public boolean areMatch(Object listener, Object original) { - return false; - } - - @Override - public Action getAction() { - return null; - } - } - - public class CustomSessionFactoryInterceptor extends EmptyInterceptor {} - - public class CustomSessionFactoryObserver implements SessionFactoryObserver { - - @Override - public void sessionFactoryCreated(SessionFactory factory) { - - } - - @Override - public void sessionFactoryClosed(SessionFactory factory) { - - } - } - - //tag::bootstrap-jpa-compliant-PersistenceUnit-example[] - @PersistenceUnit - private EntityManagerFactory emf; - //end::bootstrap-jpa-compliant-PersistenceUnit-example[] - - //tag::bootstrap-native-PersistenceUnitInfoImpl-example[] - public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { - - private final String persistenceUnitName; - - private PersistenceUnitTransactionType transactionType = - PersistenceUnitTransactionType.RESOURCE_LOCAL; - - private final List managedClassNames; - - private final Properties properties; - - private DataSource jtaDataSource; - - private DataSource nonJtaDataSource; - - public PersistenceUnitInfoImpl( - String persistenceUnitName, - List managedClassNames, - Properties properties) { - this.persistenceUnitName = persistenceUnitName; - this.managedClassNames = managedClassNames; - this.properties = properties; - } - - @Override - public String getPersistenceUnitName() { - return persistenceUnitName; - } - - @Override - public String getPersistenceProviderClassName() { - return HibernatePersistenceProvider.class.getName(); - } - - @Override - public PersistenceUnitTransactionType getTransactionType() { - return transactionType; - } - - @Override - public DataSource getJtaDataSource() { - return jtaDataSource; - } - - public PersistenceUnitInfoImpl setJtaDataSource(DataSource jtaDataSource) { - this.jtaDataSource = jtaDataSource; - this.nonJtaDataSource = null; - transactionType = PersistenceUnitTransactionType.JTA; - return this; - } - - @Override - public DataSource getNonJtaDataSource() { - return nonJtaDataSource; - } - - public PersistenceUnitInfoImpl setNonJtaDataSource(DataSource nonJtaDataSource) { - this.nonJtaDataSource = nonJtaDataSource; - this.jtaDataSource = null; - transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; - return this; - } - - @Override - public List getMappingFileNames() { - return null; - } - - @Override - public List getJarFileUrls() { - return Collections.emptyList(); - } - - @Override - public URL getPersistenceUnitRootUrl() { - return null; - } - - @Override - public List getManagedClassNames() { - return managedClassNames; - } - - @Override - public boolean excludeUnlistedClasses() { - return false; - } - - @Override - public SharedCacheMode getSharedCacheMode() { - return SharedCacheMode.UNSPECIFIED; - } - - @Override - public ValidationMode getValidationMode() { - return ValidationMode.AUTO; - } - - public Properties getProperties() { - return properties; - } - - @Override - public String getPersistenceXMLSchemaVersion() { - return "2.1"; - } - - @Override - public ClassLoader getClassLoader() { - return Thread.currentThread().getContextClassLoader(); - } - - @Override - public void addTransformer(ClassTransformer transformer) { - - } - - @Override - public ClassLoader getNewTempClassLoader() { - return null; - } - } - //end::bootstrap-native-PersistenceUnitInfoImpl-example[] - - @Test - public void test_basic_custom_type_register_BasicType_example() { - try { - //tag::basic-custom-type-register-BasicType-example[] - ServiceRegistry standardRegistry = - new StandardServiceRegistryBuilder().build(); - - MetadataSources sources = new MetadataSources( standardRegistry ); - - MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); - - metadataBuilder.applyBasicType( BitSetType.INSTANCE ); - //end::basic-custom-type-register-BasicType-example[] - } - catch (Exception ignore) { - - } - } - - @Test - public void test_basic_custom_type_register_UserType_example() { - try { - //tag::basic-custom-type-register-UserType-example[] - ServiceRegistry standardRegistry = - new StandardServiceRegistryBuilder().build(); - - MetadataSources sources = new MetadataSources( standardRegistry ); - - MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); - - metadataBuilder.applyBasicType( BitSetUserType.INSTANCE, "bitset" ); - //end::basic-custom-type-register-UserType-example[] - } - catch (Exception ignore) { - - } - } + @Test + public void test_bootstrap_bootstrap_native_registry_BootstrapServiceRegistry_example() { + + ClassLoader customClassLoader = Thread.currentThread().getContextClassLoader(); + Integrator customIntegrator = new BeanValidationIntegrator(); + + //tag::bootstrap-bootstrap-native-registry-BootstrapServiceRegistry-example[] + BootstrapServiceRegistryBuilder bootstrapRegistryBuilder = + new BootstrapServiceRegistryBuilder(); + // add a custom ClassLoader + bootstrapRegistryBuilder.applyClassLoader( customClassLoader ); + // manually add an Integrator + bootstrapRegistryBuilder.applyIntegrator( customIntegrator ); + + BootstrapServiceRegistry bootstrapRegistry = bootstrapRegistryBuilder.build(); + //end::bootstrap-bootstrap-native-registry-BootstrapServiceRegistry-example[] + } + + @Test + public void test_bootstrap_bootstrap_native_registry_StandardServiceRegistryBuilder_example_1() { + //tag::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] + // An example using an implicitly built BootstrapServiceRegistry + StandardServiceRegistryBuilder standardRegistryBuilder = + new StandardServiceRegistryBuilder(); + //end::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] + } + + @Test + public void test_bootstrap_bootstrap_native_registry_StandardServiceRegistryBuilder_example_2() { + //tag::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] + + // An example using an explicitly built BootstrapServiceRegistry + BootstrapServiceRegistry bootstrapRegistry = + new BootstrapServiceRegistryBuilder().build(); + + StandardServiceRegistryBuilder standardRegistryBuilder = + new StandardServiceRegistryBuilder( bootstrapRegistry ); + //end::bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example[] + } + + @Test + public void test_bootstrap_bootstrap_native_registry_MetadataSources_example() { + + try { + //tag::bootstrap-bootstrap-native-registry-MetadataSources-example[] + ServiceRegistry standardRegistry = + new StandardServiceRegistryBuilder().build(); + + MetadataSources sources = new MetadataSources( standardRegistry ); + + // alternatively, we can build the MetadataSources without passing + // a service registry, in which case it will build a default + // BootstrapServiceRegistry to use. But the approach shown + // above is preferred + // MetadataSources sources = new MetadataSources(); + + // add a class using JPA/Hibernate annotations for mapping + sources.addAnnotatedClass( MyEntity.class ); + + // add the name of a class using JPA/Hibernate annotations for mapping. + // differs from above in that accessing the Class is deferred which is + // important if using runtime bytecode-enhancement + sources.addAnnotatedClassName( "org.hibernate.example.Customer" ); + + // Read package-level metadata. + sources.addPackage( "hibernate.example" ); + + // Read package-level metadata. + sources.addPackage( MyEntity.class.getPackage() ); + + // Adds the named hbm.xml resource as a source: which performs the + // classpath lookup and parses the XML + sources.addResource( "org/hibernate/example/Order.hbm.xml" ); + + // Adds the named JPA orm.xml resource as a source: which performs the + // classpath lookup and parses the XML + sources.addResource( "org/hibernate/example/Product.orm.xml" ); + + // Read all mapping documents from a directory tree. + // Assumes that any file named *.hbm.xml is a mapping document. + sources.addDirectory( new File( ".") ); + + // Read mappings from a particular XML file + sources.addFile( new File( "./mapping.xml") ); + + // Read all mappings from a jar file. + // Assumes that any file named *.hbm.xml is a mapping document. + sources.addJar( new File( "./entities.jar") ); + + // Read a mapping as an application resource using the convention that a class named foo.bar.MyEntity is + // mapped by a file named foo/bar/MyEntity.hbm.xml which can be resolved as a classpath resource. + sources.addClass( MyEntity.class ); + //end::bootstrap-bootstrap-native-registry-MetadataSources-example[] + } + catch (Exception ignore) { + + } + } + + + @Test + public void test_bootstrap_bootstrap_native_metadata_source_example() { + try { + { + //tag::bootstrap-native-metadata-source-example[] + ServiceRegistry standardRegistry = + new StandardServiceRegistryBuilder().build(); + + MetadataSources sources = new MetadataSources( standardRegistry ) + .addAnnotatedClass( MyEntity.class ) + .addAnnotatedClassName( "org.hibernate.example.Customer" ) + .addResource( "org/hibernate/example/Order.hbm.xml" ) + .addResource( "org/hibernate/example/Product.orm.xml" ); + //end::bootstrap-native-metadata-source-example[] + } + + { + AttributeConverter myAttributeConverter = new AttributeConverter() { + @Override + public Object convertToDatabaseColumn(Object attribute) { + return null; + } + + @Override + public Object convertToEntityAttribute(Object dbData) { + return null; + } + } ; + //tag::bootstrap-native-metadata-builder-example[] + ServiceRegistry standardRegistry = + new StandardServiceRegistryBuilder().build(); + + MetadataSources sources = new MetadataSources( standardRegistry ); + + MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); + + // Use the JPA-compliant implicit naming strategy + metadataBuilder.applyImplicitNamingStrategy( + ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ); + + // specify the schema name to use for tables, etc when none is explicitly specified + metadataBuilder.applyImplicitSchemaName( "my_default_schema" ); + + // specify a custom Attribute Converter + metadataBuilder.applyAttributeConverter( myAttributeConverter ); + + Metadata metadata = metadataBuilder.build(); + //end::bootstrap-native-metadata-builder-example[] + } + } + catch (Exception ignore) { + + } + } + + @Test + public void test_bootstrap_bootstrap_native_SessionFactory_example() { + try { + { + //tag::bootstrap-native-SessionFactory-example[] + StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder() + .configure( "org/hibernate/example/hibernate.cfg.xml" ) + .build(); + + Metadata metadata = new MetadataSources( standardRegistry ) + .addAnnotatedClass( MyEntity.class ) + .addAnnotatedClassName( "org.hibernate.example.Customer" ) + .addResource( "org/hibernate/example/Order.hbm.xml" ) + .addResource( "org/hibernate/example/Product.orm.xml" ) + .getMetadataBuilder() + .applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ) + .build(); + + SessionFactory sessionFactory = metadata.getSessionFactoryBuilder() + .applyBeanManager( getBeanManager() ) + .build(); + //end::bootstrap-native-SessionFactory-example[] + } + { + //tag::bootstrap-native-SessionFactoryBuilder-example[] + StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder() + .configure( "org/hibernate/example/hibernate.cfg.xml" ) + .build(); + + Metadata metadata = new MetadataSources( standardRegistry ) + .addAnnotatedClass( MyEntity.class ) + .addAnnotatedClassName( "org.hibernate.example.Customer" ) + .addResource( "org/hibernate/example/Order.hbm.xml" ) + .addResource( "org/hibernate/example/Product.orm.xml" ) + .getMetadataBuilder() + .applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ) + .build(); + + SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder(); + + // Supply a SessionFactory-level Interceptor + sessionFactoryBuilder.applyInterceptor( new CustomSessionFactoryInterceptor() ); + + // Add a custom observer + sessionFactoryBuilder.addSessionFactoryObservers( new CustomSessionFactoryObserver() ); + + // Apply a CDI BeanManager ( for JPA event listeners ) + sessionFactoryBuilder.applyBeanManager( getBeanManager() ); + + SessionFactory sessionFactory = sessionFactoryBuilder.build(); + //end::bootstrap-native-SessionFactoryBuilder-example[] + } + } + catch (Exception ignore) { + + } + } + + @Test + public void test_bootstrap_bootstrap_jpa_compliant_EntityManagerFactory_example() { + try { + //tag::bootstrap-jpa-compliant-EntityManagerFactory-example[] + // Create an EMF for our CRM persistence-unit. + EntityManagerFactory emf = Persistence.createEntityManagerFactory( "CRM" ); + //end::bootstrap-jpa-compliant-EntityManagerFactory-example[] + } catch (Exception ignore) {} + } + + @Test + public void test_bootstrap_bootstrap_native_EntityManagerFactory_example() { + + try { + //tag::bootstrap-native-EntityManagerFactory-example[] + String persistenceUnitName = "CRM"; + List entityClassNames = new ArrayList<>( ); + Properties properties = new Properties( ); + + PersistenceUnitInfoImpl persistenceUnitInfo = new PersistenceUnitInfoImpl( + persistenceUnitName, + entityClassNames, + properties + ); + + Map integrationSettings = new HashMap<>(); + integrationSettings.put( + AvailableSettings.INTERCEPTOR, + new CustomSessionFactoryInterceptor() + ); + + EntityManagerFactoryBuilderImpl entityManagerFactoryBuilder = + new EntityManagerFactoryBuilderImpl( + new PersistenceUnitInfoDescriptor( persistenceUnitInfo ), + integrationSettings + ); + + EntityManagerFactory emf = entityManagerFactoryBuilder.build(); + //end::bootstrap-native-EntityManagerFactory-example[] + } + catch (Exception ignore) { + } + } + + public Object getBeanManager() { + return null; + } + + @Entity + public static class MyEntity { + @Id + private Long id; + } + + //tag::bootstrap-event-listener-registration-example[] + public class MyIntegrator implements org.hibernate.integrator.spi.Integrator { + + @Override + public void integrate( + Metadata metadata, + SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + + // As you might expect, an EventListenerRegistry is the thing with which event + // listeners are registered + // It is a service so we look it up using the service registry + final EventListenerRegistry eventListenerRegistry = + serviceRegistry.getService( EventListenerRegistry.class ); + + // If you wish to have custom determination and handling of "duplicate" listeners, + // you would have to add an implementation of the + // org.hibernate.event.service.spi.DuplicationStrategy contract like this + eventListenerRegistry.addDuplicationStrategy( new CustomDuplicationStrategy() ); + + // EventListenerRegistry defines 3 ways to register listeners: + + // 1) This form overrides any existing registrations with + eventListenerRegistry.setListeners( EventType.AUTO_FLUSH, + DefaultAutoFlushEventListener.class ); + + // 2) This form adds the specified listener(s) to the beginning of the listener chain + eventListenerRegistry.prependListeners( EventType.PERSIST, + DefaultPersistEventListener.class ); + + // 3) This form adds the specified listener(s) to the end of the listener chain + eventListenerRegistry.appendListeners( EventType.MERGE, + DefaultMergeEventListener.class ); + } + + @Override + public void disintegrate( + SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + + } + } + //end::bootstrap-event-listener-registration-example[] + + public class CustomDuplicationStrategy implements DuplicationStrategy { + + @Override + public boolean areMatch(Object listener, Object original) { + return false; + } + + @Override + public Action getAction() { + return null; + } + } + + public class CustomSessionFactoryInterceptor extends EmptyInterceptor {} + + public class CustomSessionFactoryObserver implements SessionFactoryObserver { + + @Override + public void sessionFactoryCreated(SessionFactory factory) { + + } + + @Override + public void sessionFactoryClosed(SessionFactory factory) { + + } + } + + //tag::bootstrap-jpa-compliant-PersistenceUnit-example[] + @PersistenceUnit + private EntityManagerFactory emf; + //end::bootstrap-jpa-compliant-PersistenceUnit-example[] + + //tag::bootstrap-jpa-compliant-PersistenceUnit-configurable-example[] + @PersistenceUnit( + unitName = "CRM" + ) + private EntityManagerFactory entityManagerFactory; + //end::bootstrap-jpa-compliant-PersistenceUnit-configurable-example[] + + //tag::bootstrap-jpa-compliant-PersistenceContext-example[] + @PersistenceContext + private EntityManager em; + //end::bootstrap-jpa-compliant-PersistenceContext-example[] + + //tag::bootstrap-jpa-compliant-PersistenceContext-configurable-example[] + @PersistenceContext( + unitName = "CRM", + properties = { + @PersistenceProperty( + name="org.hibernate.flushMode", + value= "MANUAL" + ) + } + ) + private EntityManager entityManager; + //end::bootstrap-jpa-compliant-PersistenceContext-configurable-example[] + + //tag::bootstrap-native-PersistenceUnitInfoImpl-example[] + public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { + + private final String persistenceUnitName; + + private PersistenceUnitTransactionType transactionType = + PersistenceUnitTransactionType.RESOURCE_LOCAL; + + private final List managedClassNames; + + private final Properties properties; + + private DataSource jtaDataSource; + + private DataSource nonJtaDataSource; + + public PersistenceUnitInfoImpl( + String persistenceUnitName, + List managedClassNames, + Properties properties) { + this.persistenceUnitName = persistenceUnitName; + this.managedClassNames = managedClassNames; + this.properties = properties; + } + + @Override + public String getPersistenceUnitName() { + return persistenceUnitName; + } + + @Override + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return transactionType; + } + + @Override + public DataSource getJtaDataSource() { + return jtaDataSource; + } + + public PersistenceUnitInfoImpl setJtaDataSource(DataSource jtaDataSource) { + this.jtaDataSource = jtaDataSource; + this.nonJtaDataSource = null; + transactionType = PersistenceUnitTransactionType.JTA; + return this; + } + + @Override + public DataSource getNonJtaDataSource() { + return nonJtaDataSource; + } + + public PersistenceUnitInfoImpl setNonJtaDataSource(DataSource nonJtaDataSource) { + this.nonJtaDataSource = nonJtaDataSource; + this.jtaDataSource = null; + transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + return this; + } + + @Override + public List getMappingFileNames() { + return null; + } + + @Override + public List getJarFileUrls() { + return Collections.emptyList(); + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public List getManagedClassNames() { + return managedClassNames; + } + + @Override + public boolean excludeUnlistedClasses() { + return false; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return SharedCacheMode.UNSPECIFIED; + } + + @Override + public ValidationMode getValidationMode() { + return ValidationMode.AUTO; + } + + public Properties getProperties() { + return properties; + } + + @Override + public String getPersistenceXMLSchemaVersion() { + return "2.1"; + } + + @Override + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + @Override + public void addTransformer(ClassTransformer transformer) { + + } + + @Override + public ClassLoader getNewTempClassLoader() { + return null; + } + } + //end::bootstrap-native-PersistenceUnitInfoImpl-example[] + + @Test + public void test_basic_custom_type_register_BasicType_example() { + try { + //tag::basic-custom-type-register-BasicType-example[] + ServiceRegistry standardRegistry = + new StandardServiceRegistryBuilder().build(); + + MetadataSources sources = new MetadataSources( standardRegistry ); + + MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); + + metadataBuilder.applyBasicType( BitSetType.INSTANCE ); + //end::basic-custom-type-register-BasicType-example[] + } + catch (Exception ignore) { + + } + } + + @Test + public void test_basic_custom_type_register_UserType_example() { + try { + //tag::basic-custom-type-register-UserType-example[] + ServiceRegistry standardRegistry = + new StandardServiceRegistryBuilder().build(); + + MetadataSources sources = new MetadataSources( standardRegistry ); + + MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); + + metadataBuilder.applyBasicType( BitSetUserType.INSTANCE, "bitset" ); + //end::basic-custom-type-register-UserType-example[] + } + catch (Exception ignore) { + + } + } } diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/FirstLevelCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/FirstLevelCacheTest.java index b6730f6b2a2f..32d5182b2fcf 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/FirstLevelCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/FirstLevelCacheTest.java @@ -15,14 +15,11 @@ import javax.persistence.Id; import org.hibernate.Session; -import org.hibernate.cache.ehcache.EhCacheRegionFactory; import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.junit.Test; -import org.jboss.logging.Logger; - import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -31,8 +28,6 @@ */ public class FirstLevelCacheTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FirstLevelCacheTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -44,7 +39,7 @@ protected Class[] getAnnotatedClasses() { @SuppressWarnings( "unchecked" ) protected void addConfigOptions(Map options) { options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() ); - options.put( AvailableSettings.CACHE_REGION_FACTORY, EhCacheRegionFactory.class.getName() ); + options.put( AvailableSettings.CACHE_REGION_FACTORY, "jcache" ); } @Test diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java index d8be0d965286..91f339832676 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import javax.cache.configuration.MutableConfiguration; import javax.persistence.Cacheable; import javax.persistence.CascadeType; import javax.persistence.Entity; @@ -20,7 +21,7 @@ import javax.persistence.Version; import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.cache.ehcache.EhCacheRegionFactory; +import org.hibernate.cache.jcache.JCacheHelper; import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; @@ -36,9 +37,25 @@ */ public class NonStrictReadWriteCacheTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( NonStrictReadWriteCacheTest.class ); + @Override + public void buildEntityManagerFactory() { + JCacheHelper.locateStandardCacheManager().createCache( + "hibernate.test.org.hibernate.userguide.caching.NonStrictReadWriteCacheTest$Person", + new MutableConfiguration<>() + ); + JCacheHelper.locateStandardCacheManager().createCache( + "hibernate.test.org.hibernate.userguide.caching.NonStrictReadWriteCacheTest$Phone", + new MutableConfiguration<>() + ); + JCacheHelper.locateStandardCacheManager().createCache( + "hibernate.test.org.hibernate.userguide.caching.NonStrictReadWriteCacheTest$Person.phones", + new MutableConfiguration<>() + ); + + super.buildEntityManagerFactory(); + } - @Override + @Override protected Class[] getAnnotatedClasses() { return new Class[] { Person.class, @@ -50,7 +67,7 @@ protected Class[] getAnnotatedClasses() { @SuppressWarnings( "unchecked" ) protected void addConfigOptions(Map options) { options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() ); - options.put( AvailableSettings.CACHE_REGION_FACTORY, EhCacheRegionFactory.class.getName() ); + options.put( AvailableSettings.CACHE_REGION_FACTORY, "jcache" ); } @Test @@ -147,6 +164,10 @@ public static class Phone { @Version private int version; + //Getters and setters are omitted for brevity + + //end::caching-entity-mapping-example[] + public Phone() {} public Phone(String mobile) { @@ -168,6 +189,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::caching-entity-mapping-example[] } //end::caching-entity-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java index fdd769d37c17..4f12e691c2a6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java @@ -22,17 +22,15 @@ import org.hibernate.Session; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.NaturalId; -import org.hibernate.cache.ehcache.EhCacheRegionFactory; import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; -import org.hibernate.stat.SecondLevelCacheStatistics; +import org.hibernate.stat.CacheRegionStatistics; import org.hibernate.stat.Statistics; +import org.junit.Ignore; import org.junit.Test; -import org.jboss.logging.Logger; - import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertNotNull; @@ -40,10 +38,10 @@ /** * @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 { - private static final Logger log = Logger.getLogger( SecondLevelCacheTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -55,16 +53,15 @@ protected Class[] getAnnotatedClasses() { @SuppressWarnings( "unchecked" ) protected void addConfigOptions(Map options) { options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() ); - options.put( AvailableSettings.CACHE_REGION_FACTORY, EhCacheRegionFactory.class.getName() ); + options.put( AvailableSettings.CACHE_REGION_FACTORY, "jcache" ); options.put( AvailableSettings.USE_QUERY_CACHE, Boolean.TRUE.toString() ); options.put( AvailableSettings.GENERATE_STATISTICS, Boolean.TRUE.toString() ); - options.put( AvailableSettings.CACHE_REGION_PREFIX, "" ); + //options.put( AvailableSettings.CACHE_REGION_PREFIX, "" ); } @Test public void testCache() { doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person() ); entityManager.persist( new Person() ); Person aPerson= new Person(); aPerson.setName( "John Doe" ); @@ -189,8 +186,8 @@ public void testCache() { Session session = entityManager.unwrap( Session.class ); //tag::caching-statistics-example[] Statistics statistics = session.getSessionFactory().getStatistics(); - SecondLevelCacheStatistics secondLevelCacheStatistics = - statistics.getSecondLevelCacheStatistics( "query.cache.person" ); + CacheRegionStatistics secondLevelCacheStatistics = + statistics.getDomainDataRegionStatistics( "query.cache.person" ); long hitCount = secondLevelCacheStatistics.getHitCount(); long missCount = secondLevelCacheStatistics.getMissCount(); double hitRatio = (double) hitCount / ( hitCount + missCount ); @@ -270,7 +267,11 @@ public static class Person { @Column(name = "code", unique = true) private String code; - public Person() {} + //Getters and setters are omitted for brevity + + //end::caching-entity-natural-id-mapping-example[] + + public Person() {} public Person(String name) { this.name = name; @@ -295,6 +296,7 @@ public String getCode() { public void setCode(String code) { this.code = code; } + //tag::caching-entity-natural-id-mapping-example[] } //end::caching-entity-natural-id-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java index 3ef6e3601b86..fad6eb82ab06 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java @@ -51,8 +51,13 @@ public static class Person { @Id private Long id; + private String[] phones; + //Getters and setters are omitted for brevity + + //end::collections-array-binary-example[] + public Person() { } @@ -67,6 +72,7 @@ public String[] getPhones() { public void setPhones(String[] phones) { this.phones = phones; } + //tag::collections-array-binary-example[] } //end::collections-array-binary-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeCollectionTest.java index fc67e0a4a8b8..8def4aedf7c1 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeCollectionTest.java @@ -27,8 +27,6 @@ */ public class BasicTypeCollectionTest extends BaseCoreFunctionalTestCase { - private static final Logger log = Logger.getLogger( BasicTypeCollectionTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java index e45cd53108be..f432646cac10 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java @@ -25,8 +25,6 @@ */ public class BasicTypeElementCollectionTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( BasicTypeCollectionTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -35,7 +33,7 @@ protected Class[] getAnnotatedClasses() { } @Override - public void buildEntityManagerFactory() throws Exception { + public void buildEntityManagerFactory() { super.buildEntityManagerFactory(); doInJPA( this::entityManagerFactory, entityManager -> { Person person = new Person(); @@ -92,9 +90,14 @@ public static class Person { @ElementCollection private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-collection-proxy-entity-example[] + public List getPhones() { return phones; } + //tag::collections-collection-proxy-entity-example[] } //end::collections-collection-proxy-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeOrderColumnElementCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeOrderColumnElementCollectionTest.java index 569db9a708bb..3d3642d3fa6e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeOrderColumnElementCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeOrderColumnElementCollectionTest.java @@ -26,8 +26,6 @@ */ public class BasicTypeOrderColumnElementCollectionTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( BasicTypeOrderColumnElementCollectionTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java index fb537a348345..6b407f7c4a73 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java @@ -56,9 +56,14 @@ public static class Person { @Id private Long id; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-bag-example[] + public Person() { } @@ -70,6 +75,7 @@ public List getPhones() { return phones; } + //tag::collections-bidirectional-bag-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -96,6 +102,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-bag-example[] + public Phone() { } @@ -125,6 +135,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::collections-bidirectional-bag-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalComparatorSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalComparatorSortedSetTest.java index b61e75ef98c1..d79517a2654c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalComparatorSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalComparatorSortedSetTest.java @@ -34,8 +34,6 @@ */ public class BidirectionalComparatorSortedSetTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( BidirectionalComparatorSortedSetTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java index a04f7dd641d7..a9739f5007cf 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java @@ -68,11 +68,16 @@ public static class Person { @Id private Long id; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) @MapKey(name = "type") @MapKeyEnumerated private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-bidirectional-example[] + public Person() { } @@ -84,6 +89,7 @@ public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-bidirectional-example[] public void addPhone(Phone phone) { phone.setPerson( this ); phoneRegister.put( phone.getType(), phone ); @@ -107,6 +113,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-map-bidirectional-example[] + public Phone() { } @@ -135,6 +145,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::collections-map-bidirectional-example[] } //end::collections-map-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java index 16e3df90a7bf..171a3bd5f485 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java @@ -69,6 +69,10 @@ public static class Person { @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private Set phones = new HashSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-set-example[] + public Person() { } @@ -80,6 +84,7 @@ public Set getPhones() { return phones; } + //tag::collections-bidirectional-set-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -106,6 +111,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-set-example[] + public Phone() { } @@ -135,6 +144,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::collections-bidirectional-set-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSortedSetTest.java index ae1b62ea29ba..8d84af98ac34 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSortedSetTest.java @@ -33,8 +33,6 @@ */ public class BidirectionalSortedSetTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( BidirectionalSortedSetTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java index 1ec1ff06aa10..a1a4d272ca79 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java @@ -71,15 +71,18 @@ public static class Person { @Id private Long id; + @Temporal(TemporalType.TIMESTAMP) @ElementCollection @CollectionTable(name = "phone_register") @Column(name = "since") - @MapKeyJoinColumn(name = "phone_id", referencedColumnName = "id") private Map phoneRegister = new HashMap<>(); - public Person() { - } + //Getters and setters are omitted for brevity + + //end::collections-map-value-type-entity-key-example[] + + public Person() {} public Person(Long id) { this.id = id; @@ -88,6 +91,7 @@ public Person(Long id) { public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-value-type-entity-key-example[] } @Embeddable @@ -98,6 +102,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-map-value-type-entity-key-example[] + public Phone() { } @@ -113,6 +121,7 @@ public PhoneType getType() { public String getNumber() { return number; } + //tag::collections-map-value-type-entity-key-example[] } //end::collections-map-value-type-entity-key-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java index 8d38a3ccf6b7..128fd2f53735 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java @@ -55,9 +55,14 @@ public static class Person { @ElementCollection private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-embeddable-type-collection-lifecycle-entity-example[] + public List getPhones() { return phones; } + //tag::collections-embeddable-type-collection-lifecycle-entity-example[] } @Embeddable @@ -68,6 +73,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-embeddable-type-collection-lifecycle-entity-example[] + public Phone() { } @@ -83,6 +92,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-embeddable-type-collection-lifecycle-entity-example[] } //end::collections-embeddable-type-collection-lifecycle-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyClassTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyClassTest.java new file mode 100644 index 000000000000..ad718cb4e540 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyClassTest.java @@ -0,0 +1,180 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.collections; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +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.JoinColumn; +import javax.persistence.MapKeyClass; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class MapKeyClassTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::collections-map-key-class-persist-example[] + Person person = new Person(); + person.setId( 1L ); + person.getCallRegister().put( new MobilePhone( "01", "234", "567" ), 101 ); + person.getCallRegister().put( new MobilePhone( "01", "234", "789" ), 102 ); + + entityManager.persist( person ); + //end::collections-map-key-class-persist-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::collections-map-key-class-fetch-example[] + Person person = entityManager.find( Person.class, 1L ); + assertEquals( 2, person.getCallRegister().size() ); + + assertEquals( + Integer.valueOf( 101 ), + person.getCallRegister().get( MobilePhone.fromString( "01-234-567" ) ) + ); + + assertEquals( + Integer.valueOf( 102 ), + person.getCallRegister().get( MobilePhone.fromString( "01-234-789" ) ) + ); + //end::collections-map-key-class-fetch-example[] + } ); + } + + //tag::collections-map-key-class-mapping-example[] + @Entity + @Table(name = "person") + public static class Person { + + @Id + private Long id; + + @ElementCollection + @CollectionTable( + name = "call_register", + joinColumns = @JoinColumn(name = "person_id") + ) + @MapKeyColumn( name = "call_timestamp_epoch" ) + @MapKeyClass( MobilePhone.class ) + @Column(name = "call_register") + private Map callRegister = new HashMap<>(); + + //Getters and setters are omitted for brevity + //end::collections-map-key-class-mapping-example[] + + public void setId(Long id) { + this.id = id; + } + + public Map getCallRegister() { + return callRegister; + } + //tag::collections-map-key-class-mapping-example[] + } + //end::collections-map-key-class-mapping-example[] + + //tag::collections-map-key-class-type-mapping-example[] + public interface PhoneNumber { + + String get(); + } + + @Embeddable + public static class MobilePhone + implements PhoneNumber { + + static PhoneNumber fromString(String phoneNumber) { + String[] tokens = phoneNumber.split( "-" ); + if ( tokens.length != 3 ) { + throw new IllegalArgumentException( "invalid phone number: " + phoneNumber ); + } + int i = 0; + return new MobilePhone( + tokens[i++], + tokens[i++], + tokens[i] + ); + } + + private MobilePhone() { + } + + public MobilePhone( + String countryCode, + String operatorCode, + String subscriberCode) { + this.countryCode = countryCode; + this.operatorCode = operatorCode; + this.subscriberCode = subscriberCode; + } + + @Column(name = "country_code") + private String countryCode; + + @Column(name = "operator_code") + private String operatorCode; + + @Column(name = "subscriber_code") + private String subscriberCode; + + @Override + public String get() { + return String.format( + "%s-%s-%s", + countryCode, + operatorCode, + subscriberCode + ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + MobilePhone that = (MobilePhone) o; + return Objects.equals( countryCode, that.countryCode ) && + Objects.equals( operatorCode, that.operatorCode ) && + Objects.equals( subscriberCode, that.subscriberCode ); + } + + @Override + public int hashCode() { + return Objects.hash( countryCode, operatorCode, subscriberCode ); + } + } + //end::collections-map-key-class-type-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java new file mode 100644 index 000000000000..67c80a2e4b39 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java @@ -0,0 +1,164 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.collections; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; + +import org.hibernate.annotations.MapKeyType; +import org.hibernate.annotations.Type; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class MapKeyTypeTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + PersonDummy.class, + }; + } + + @Test + public void testLifecycle() { + + LocalDateTime firstCall = LocalDateTime.of( + 2017, 5, 23, 18, 21, 57 + ); + + LocalDateTime secondCall = LocalDateTime.of( + 2017, 5, 23, 18, 22, 19 + ); + + doInJPA( this::entityManagerFactory, entityManager -> { + PersonDummy person = new PersonDummy(); + person.setId( 1L ); + person.getPhoneRegister().put( Timestamp.valueOf( firstCall ).getTime(), 101 ); + person.getPhoneRegister().put( Timestamp.valueOf( secondCall ).getTime(), 102 ); + entityManager.persist( person ); + } ); + + EntityManagerFactory entityManagerFactory = null; + try { + Map settings = buildSettings(); + settings.put( + org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, + Collections.singletonList( + Person.class + ) + ); + settings.put( + AvailableSettings.HBM2DDL_AUTO, + "none" + ); + entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( + new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + settings + ).build().unwrap( SessionFactoryImplementor.class ); + + final EntityManagerFactory emf = entityManagerFactory; + + doInJPA( () -> emf, entityManager -> { + Person person = entityManager.find( Person.class, 1L ); + assertEquals( + Integer.valueOf( 101 ), + person.getCallRegister().get( Timestamp.valueOf( firstCall ) ) + ); + } ); + } + finally { + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + } + } + } + + @Entity + @Table(name = "person") + public static class PersonDummy { + + @Id + private Long id; + + @ElementCollection + @CollectionTable( + name = "call_register", + joinColumns = @JoinColumn(name = "person_id") + ) + @MapKeyColumn( name = "call_timestamp_epoch" ) + @Column(name = "phone_number") + private Map callRegister = new HashMap<>(); + + public void setId(Long id) { + this.id = id; + } + + public Map getPhoneRegister() { + return callRegister; + } + } + + //tag::collections-map-custom-key-type-mapping-example[] + @Entity + @Table(name = "person") + public static class Person { + + @Id + private Long id; + + @ElementCollection + @CollectionTable( + name = "call_register", + joinColumns = @JoinColumn(name = "person_id") + ) + @MapKeyType( + @Type( + type = "org.hibernate.userguide.collections.type.TimestampEpochType" + ) + ) + @MapKeyColumn( name = "call_timestamp_epoch" ) + @Column(name = "phone_number") + private Map callRegister = new HashMap<>(); + + //Getters and setters are omitted for brevity + + //end::collections-map-custom-key-type-mapping-example[] + + public void setId(Long id) { + this.id = id; + } + + public Map getCallRegister() { + return callRegister; + } + //tag::collections-map-custom-key-type-mapping-example[] + } + //end::collections-map-custom-key-type-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/OrderColumnListIndexBaseTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/OrderColumnListIndexBaseTest.java new file mode 100644 index 000000000000..71db2b625608 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/OrderColumnListIndexBaseTest.java @@ -0,0 +1,149 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.collections; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; + +import org.hibernate.annotations.ListIndexBase; +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class OrderColumnListIndexBaseTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class, + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::collections-customizing-ordered-list-ordinal-persist-example[] + Person person = new Person( 1L ); + entityManager.persist( person ); + person.addPhone( new Phone( 1L, "landline", "028-234-9876" ) ); + person.addPhone( new Phone( 2L, "mobile", "072-122-9876" ) ); + //end::collections-customizing-ordered-list-ordinal-persist-example[] + } ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + //tag::collections-customizing-ordered-list-ordinal-mapping-example[] + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) + @OrderColumn(name = "order_id") + @ListIndexBase(100) + private List phones = new ArrayList<>(); + //end::collections-customizing-ordered-list-ordinal-mapping-example[] + + public Person() { + } + + public Person(Long id) { + this.id = id; + } + + public List getPhones() { + return phones; + } + + public void addPhone(Phone phone) { + phones.add( phone ); + phone.setPerson( this ); + } + + public void removePhone(Phone phone) { + phones.remove( phone ); + phone.setPerson( null ); + } + } + + @Entity(name = "Phone") + public static class Phone { + + @Id + private Long id; + + private String type; + + @Column(name = "`number`", unique = true) + @NaturalId + private String number; + + @ManyToOne + private Person person; + + public Phone() { + } + + public Phone(Long id, String type, String number) { + this.id = id; + this.type = type; + this.number = number; + } + + public Long getId() { + return id; + } + + public String getType() { + return type; + } + + public String getNumber() { + return number; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.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( number, phone.number ); + } + + @Override + public int hashCode() { + return Objects.hash( number ); + } + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java new file mode 100644 index 000000000000..cd641a82492a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java @@ -0,0 +1,181 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.collections; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class OrderedBySQLTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Article.class, + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setId( 1L ); + person.setName( "Vlad Mihalcea" ); + + person.addArticle( + new Article( + "High-Performance JDBC", + "Connection Management, Statement Caching, Batch Updates" + ) + ); + person.addArticle( + new Article( + "High-Performance Hibernate", + "Associations, Lazy fetching, Concurrency Control, Second-level Caching" + ) + ); + entityManager.persist( person ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::collections-customizing-ordered-by-sql-clause-fetching-example[] + Person person = entityManager.find( Person.class, 1L ); + assertEquals( + "High-Performance Hibernate", + person.getArticles().get( 0 ).getName() + ); + //end::collections-customizing-ordered-by-sql-clause-fetching-example[] + } ); + } + + //tag::collections-customizing-ordered-by-sql-clause-mapping-example[] + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String name; + + @OneToMany( + mappedBy = "person", + cascade = CascadeType.ALL + ) + @org.hibernate.annotations.OrderBy( + clause = "CHAR_LENGTH(name) DESC" + ) + private List
articles = new ArrayList<>(); + + //Getters and setters are omitted for brevity + //end::collections-customizing-ordered-by-sql-clause-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List
getArticles() { + return articles; + } + + public void addArticle(Article article) { + article.setPerson( this ); + articles.add( article ); + } + //tag::collections-customizing-ordered-by-sql-clause-mapping-example[] + } + + @Entity(name = "Article") + public static class Article { + + @Id + @GeneratedValue + private Long id; + + private String name; + + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + private Person person; + + //Getters and setters are omitted for brevity + //end::collections-customizing-ordered-by-sql-clause-mapping-example[] + + private Article() { + } + + public Article(String name, String content) { + this.name = name; + this.content = content; + } + + 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 getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + //tag::collections-customizing-ordered-by-sql-clause-mapping-example[] + } + //end::collections-customizing-ordered-by-sql-clause-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/QueueTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/QueueTest.java index 72f909f402ca..6df6e46e2d4f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/QueueTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/QueueTest.java @@ -33,8 +33,6 @@ */ public class QueueTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( QueueTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -77,7 +75,7 @@ public static class Person { @CollectionType( type = "org.hibernate.userguide.collections.type.QueueType") private Collection phones = new LinkedList<>(); - //Getters and setters are omitted for brevity + //Constructors are omitted for brevity //end::collections-custom-collection-mapping-example[] @@ -88,10 +86,10 @@ public Person(Long id) { this.id = id; } + //tag::collections-custom-collection-mapping-example[] public Queue getPhones() { return (Queue) phones; } - //tag::collections-custom-collection-mapping-example[] } @Entity(name = "Phone") @@ -131,6 +129,7 @@ public String getNumber() { return number; } + //tag::collections-custom-collection-mapping-example[] @Override public int compareTo(Phone o) { return number.compareTo( o.getNumber() ); @@ -152,7 +151,6 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( number ); } - //tag::collections-custom-collection-mapping-example[] } //end::collections-custom-collection-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java index 35cb180a3f6e..4cea55699793 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java @@ -55,9 +55,14 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-bag-example[] + public Person() { } @@ -68,6 +73,7 @@ public Person(Long id) { public List getPhones() { return phones; } + //tag::collections-unidirectional-bag-example[] } @Entity(name = "Phone") @@ -81,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-bag-example[] + public Phone() { } @@ -101,6 +111,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-unidirectional-bag-example[] } //end::collections-unidirectional-bag-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java index 622e4829ac7f..c91d76b7b432 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java @@ -33,8 +33,6 @@ */ public class UnidirectionalComparatorSortedSetTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( UnidirectionalComparatorSortedSetTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -77,6 +75,10 @@ public static class Person { @SortComparator(ReverseComparator.class) private SortedSet phones = new TreeSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-custom-comparator-example[] + public Person() { } @@ -87,9 +89,11 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-sorted-set-custom-comparator-example[] } public static class ReverseComparator implements Comparator { + @Override public int compare(Phone o1, Phone o2) { return o2.compareTo( o1 ); @@ -108,6 +112,10 @@ public static class Phone implements Comparable { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-custom-comparator-example[] + public Phone() { } @@ -129,6 +137,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-sorted-set-custom-comparator-example[] @Override public int compareTo(Phone o) { return number.compareTo( o.getNumber() ); diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java index ecbd57d0629f..26bd94c90a7e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java @@ -74,15 +74,20 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinTable( - name = "phone_register", - joinColumns = @JoinColumn(name = "phone_id"), - inverseJoinColumns = @JoinColumn(name = "person_id")) + name = "phone_register", + joinColumns = @JoinColumn(name = "phone_id"), + inverseJoinColumns = @JoinColumn(name = "person_id")) @MapKey(name = "since") @MapKeyTemporal(TemporalType.TIMESTAMP) private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-unidirectional-example[] + public Person() { } @@ -94,6 +99,7 @@ public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-unidirectional-example[] public void addPhone(Phone phone) { phoneRegister.put( phone.getSince(), phone ); } @@ -113,6 +119,10 @@ public static class Phone { private Date since; + //Getters and setters are omitted for brevity + + //end::collections-map-unidirectional-example[] + public Phone() { } @@ -133,6 +143,7 @@ public String getNumber() { public Date getSince() { return since; } + //tag::collections-map-unidirectional-example[] } //end::collections-map-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java index f986287e190f..0aaf90a899f0 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java @@ -54,10 +54,15 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) @OrderBy("number") private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-ordered-list-order-by-example[] + public Person() { } @@ -68,6 +73,7 @@ public Person(Long id) { public List getPhones() { return phones; } + //tag::collections-unidirectional-ordered-list-order-by-example[] } @Entity(name = "Phone") @@ -81,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-ordered-list-order-by-example[] + public Phone() { } @@ -101,6 +111,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-unidirectional-ordered-list-order-by-example[] } //end::collections-unidirectional-ordered-list-order-by-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java index e531e713feaa..2f16438f95d7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java @@ -64,9 +64,13 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) private Set phones = new HashSet<>(); + //Getters and setters are omitted for brevity + //end::collections-unidirectional-set-example[] + public Person() { } @@ -77,6 +81,7 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-set-example[] } @Entity(name = "Phone") @@ -91,6 +96,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-set-example[] + public Phone() { } @@ -112,6 +121,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-set-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java index ef0ba0faba24..f330b31d0d2b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java @@ -32,8 +32,6 @@ */ public class UnidirectionalSortedSetTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( UnidirectionalSortedSetTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -71,10 +69,15 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) @SortNatural private SortedSet phones = new TreeSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-natural-comparator-example[] + public Person() { } @@ -85,6 +88,7 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-sorted-set-natural-comparator-example[] } @Entity(name = "Phone") @@ -99,6 +103,10 @@ public static class Phone implements Comparable { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-natural-comparator-example[] + public Phone() { } @@ -120,6 +128,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-sorted-set-natural-comparator-example[] @Override public int compareTo(Phone o) { return number.compareTo( o.getNumber() ); diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/type/TimestampEpochType.java b/documentation/src/test/java/org/hibernate/userguide/collections/type/TimestampEpochType.java new file mode 100644 index 000000000000..b2c81ced5006 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/type/TimestampEpochType.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.userguide.collections.type; + +import java.sql.Timestamp; +import java.util.Comparator; +import java.util.Date; + +import org.hibernate.HibernateException; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.LiteralType; +import org.hibernate.type.StringType; +import org.hibernate.type.VersionType; +import org.hibernate.type.descriptor.java.JdbcTimestampTypeDescriptor; +import org.hibernate.type.descriptor.sql.BigIntTypeDescriptor; + +/** + * @author Vlad Mihalcea + */ +//tag::collections-map-custom-key-type-mapping-example[] + +public class TimestampEpochType + extends AbstractSingleColumnStandardBasicType + implements VersionType, LiteralType { + + public static final TimestampEpochType INSTANCE = new TimestampEpochType(); + + public TimestampEpochType() { + super( + BigIntTypeDescriptor.INSTANCE, + JdbcTimestampTypeDescriptor.INSTANCE + ); + } + + @Override + public String getName() { + return "epoch"; + } + + @Override + public Date next( + Date current, + SharedSessionContractImplementor session) { + return seed( session ); + } + + @Override + public Date seed( + SharedSessionContractImplementor session) { + return new Timestamp( System.currentTimeMillis() ); + } + + @Override + public Comparator getComparator() { + return getJavaTypeDescriptor().getComparator(); + } + + @Override + public String objectToSQLString( + Date value, + Dialect dialect) throws Exception { + final Timestamp ts = Timestamp.class.isInstance( value ) + ? ( Timestamp ) value + : new Timestamp( value.getTime() ); + return StringType.INSTANCE.objectToSQLString( + ts.toString(), dialect + ); + } + + @Override + public Date fromStringValue( + String xml) throws HibernateException { + return fromString( xml ); + } +} +//end::collections-map-custom-key-type-mapping-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/CustomRevisionEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/CustomRevisionEntityTest.java new file mode 100644 index 000000000000..43ac06a8d599 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/CustomRevisionEntityTest.java @@ -0,0 +1,169 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NoResultException; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.DefaultRevisionEntity; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.envers.RevisionListener; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.envers.strategy.ValidityAuditStrategy; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +public class CustomRevisionEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + CustomRevisionEntity.class + }; + } + + @Test + public void test() { + //tag::envers-revisionlog-RevisionEntity-persist-example[] + CurrentUser.INSTANCE.logIn( "Vlad Mihalcea" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + } ); + + CurrentUser.INSTANCE.logOut(); + //end::envers-revisionlog-RevisionEntity-persist-example[] + } + + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } + + //tag::envers-revisionlog-CurrentUser-example[] + public static class CurrentUser { + + public static final CurrentUser INSTANCE = new CurrentUser(); + + private static final ThreadLocal storage = new ThreadLocal<>(); + + public void logIn(String user) { + storage.set( user ); + } + + public void logOut() { + storage.remove(); + } + + public String get() { + return storage.get(); + } + } + //end::envers-revisionlog-CurrentUser-example[] + + //tag::envers-revisionlog-RevisionEntity-example[] + @Entity(name = "CustomRevisionEntity") + @Table(name = "CUSTOM_REV_INFO") + @RevisionEntity( CustomRevisionEntityListener.class ) + public static class CustomRevisionEntity extends DefaultRevisionEntity { + + private String username; + + public String getUsername() { + return username; + } + + public void setUsername( String username ) { + this.username = username; + } + } + //end::envers-revisionlog-RevisionEntity-example[] + + //tag::envers-revisionlog-RevisionListener-example[] + public static class CustomRevisionEntityListener implements RevisionListener { + + public void newRevision( Object revisionEntity ) { + CustomRevisionEntity customRevisionEntity = + ( CustomRevisionEntity ) revisionEntity; + + customRevisionEntity.setUsername( + CurrentUser.INSTANCE.get() + ); + } + } + //end::envers-revisionlog-RevisionListener-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/DefaultAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/DefaultAuditTest.java new file mode 100644 index 000000000000..c9d16d1717bd --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/DefaultAuditTest.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 . + */ +package org.hibernate.userguide.envers; + +import java.util.Date; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NoResultException; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +public class DefaultAuditTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-audited-insert-example[] + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + //end::envers-audited-insert-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-audited-update-example[] + Customer customer = entityManager.find( Customer.class, 1L ); + customer.setLastName( "Doe Jr." ); + //end::envers-audited-update-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-audited-delete-example[] + Customer customer = entityManager.getReference( Customer.class, 1L ); + entityManager.remove( customer ); + //end::envers-audited-delete-example[] + } ); + + //tag::envers-audited-revisions-example[] + List revisions = doInJPA( this::entityManagerFactory, entityManager -> { + return AuditReaderFactory.get( entityManager ).getRevisions( + Customer.class, + 1L + ); + } ); + //end::envers-audited-revisions-example[] + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-audited-rev1-example[] + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( 0 ) ) + .getSingleResult(); + + assertEquals("Doe", customer.getLastName()); + //end::envers-audited-rev1-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-audited-rev2-example[] + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( 1 ) ) + .getSingleResult(); + + assertEquals("Doe Jr.", customer.getLastName()); + //end::envers-audited-rev2-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-audited-rev3-example[] + try { + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( 2 ) ) + .getSingleResult(); + + fail("The Customer was deleted at this revision: " + revisions.get( 2 )); + } + catch (NoResultException expected) { + } + //end::envers-audited-rev3-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-audited-rev4-example[] + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( + Customer.class, + Customer.class.getName(), + revisions.get( 2 ), + true ) + .getSingleResult(); + + assertEquals( Long.valueOf( 1L ), customer.getId() ); + assertNull( customer.getFirstName() ); + assertNull( customer.getLastName() ); + assertNull( customer.getCreatedOn() ); + //end::envers-audited-rev4-example[] + } ); + } + + //tag::envers-audited-mapping-example[] + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + //Getters and setters are omitted for brevity + + //end::envers-audited-mapping-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + //tag::envers-audited-mapping-example[] + } + //end::envers-audited-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java new file mode 100644 index 000000000000..2f94703a7de7 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java @@ -0,0 +1,204 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.envers.Audited; +import org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + CustomTrackingRevisionEntity.class + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + } ); + + EntityManagerFactory entityManagerFactory = null; + try { + Map settings = buildSettings(); + settings.put( + org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, + Arrays.asList( + ApplicationCustomer.class, + CustomTrackingRevisionEntity.class + ) + ); + settings.put( + AvailableSettings.HBM2DDL_AUTO, + "update" + ); + entityManagerFactory = Bootstrap + .getEntityManagerFactoryBuilder( + new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + settings ) + .build() + .unwrap( SessionFactoryImplementor.class ); + + final EntityManagerFactory emf = entityManagerFactory; + + doInJPA( () -> emf, entityManager -> { + ApplicationCustomer customer = new ApplicationCustomer(); + customer.setId( 2L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe Jr." ); + + entityManager.persist( customer ); + } ); + } + finally { + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + } + } + } + + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } + + @Audited + @Entity(name = "Customer") + public static class ApplicationCustomer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } + + //tag::envers-tracking-modified-entities-revchanges-example[] + @Entity(name = "CustomTrackingRevisionEntity") + @Table(name = "TRACKING_REV_INFO") + @RevisionEntity + public static class CustomTrackingRevisionEntity + extends DefaultTrackingModifiedEntitiesRevisionEntity { + + } + //end::envers-tracking-modified-entities-revchanges-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java new file mode 100644 index 000000000000..ed98720ab4cb --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java @@ -0,0 +1,256 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +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; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.DefaultRevisionEntity; +import org.hibernate.envers.ModifiedEntityNames; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.envers.tools.Pair; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + CustomTrackingRevisionEntity.class + }; + } + + @Test + public void test() { + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + } ); + + EntityManagerFactory entityManagerFactory = null; + try { + Map settings = buildSettings(); + settings.put( + org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, + Arrays.asList( + ApplicationCustomer.class, + Customer.class, + CustomTrackingRevisionEntity.class + ) + ); + settings.put( + AvailableSettings.HBM2DDL_AUTO, + "update" + ); + entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( + new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + settings + ).build().unwrap( SessionFactoryImplementor.class ); + + final EntityManagerFactory emf = entityManagerFactory; + + doInJPA( () -> emf, entityManager -> { + ApplicationCustomer customer = entityManager.find( ApplicationCustomer.class, 1L ); + customer.setLastName( "Doe Jr." ); + } ); + + doInJPA( () -> emf, entityManager -> { + //tag::envers-tracking-modified-entities-queries-example[] + assertEquals( + "org.hibernate.userguide.envers.EntityTypeChangeAuditTest$Customer", + AuditReaderFactory + .get( entityManager ) + .getCrossTypeRevisionChangesReader() + .findEntityTypes( 1 ) + .iterator().next() + .getFirst() + ); + + assertEquals( + "org.hibernate.userguide.envers.EntityTypeChangeAuditTest$ApplicationCustomer", + AuditReaderFactory + .get( entityManager ) + .getCrossTypeRevisionChangesReader() + .findEntityTypes( 2 ) + .iterator().next() + .getFirst() + ); + //end::envers-tracking-modified-entities-queries-example[] + } ); + } + finally { + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + } + } + } + + //tag::envers-tracking-modified-entities-revchanges-before-rename-example[] + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + //Getters and setters are omitted for brevity + //end::envers-tracking-modified-entities-revchanges-before-rename-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + //tag::envers-tracking-modified-entities-revchanges-before-rename-example[] + } + //end::envers-tracking-modified-entities-revchanges-before-rename-example[] + + //tag::envers-tracking-modified-entities-revchanges-after-rename-example[] + @Audited + @Entity(name = "Customer") + public static class ApplicationCustomer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + //Getters and setters are omitted for brevity + //end::envers-tracking-modified-entities-revchanges-after-rename-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + //tag::envers-tracking-modified-entities-revchanges-after-rename-example[] + } + //end::envers-tracking-modified-entities-revchanges-after-rename-example[] + + //tag::envers-tracking-modified-entities-revchanges-example[] + @Entity(name = "CustomTrackingRevisionEntity") + @Table(name = "TRACKING_REV_INFO") + @RevisionEntity + public static class CustomTrackingRevisionEntity extends DefaultRevisionEntity { + + @ElementCollection + @JoinTable( + name = "REVCHANGES", + joinColumns = @JoinColumn( name = "REV" ) + ) + @Column( name = "ENTITYNAME" ) + @ModifiedEntityNames + private Set modifiedEntityNames = new HashSet<>(); + + public Set getModifiedEntityNames() { + return modifiedEntityNames; + } + } + //end::envers-tracking-modified-entities-revchanges-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTrackingRevisionListenerTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTrackingRevisionListenerTest.java new file mode 100644 index 000000000000..2ed026030d02 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTrackingRevisionListenerTest.java @@ -0,0 +1,337 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.io.Serializable; +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.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.EntityTrackingRevisionListener; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.envers.RevisionNumber; +import org.hibernate.envers.RevisionTimestamp; +import org.hibernate.envers.RevisionType; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class EntityTypeChangeAuditTrackingRevisionListenerTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + CustomTrackingRevisionEntity.class, + EntityType.class + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + } ); + + EntityManagerFactory entityManagerFactory = null; + try { + Map settings = buildSettings(); + settings.put( + org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, + Arrays.asList( + ApplicationCustomer.class, + CustomTrackingRevisionEntity.class, + EntityType.class + ) + ); + settings.put( + AvailableSettings.HBM2DDL_AUTO, + "update" + ); + entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( + new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + settings + ).build().unwrap( SessionFactoryImplementor.class ); + + final EntityManagerFactory emf = entityManagerFactory; + + doInJPA( () -> emf, entityManager -> { + ApplicationCustomer customer = new ApplicationCustomer(); + customer.setId( 2L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe Jr." ); + + entityManager.persist( customer ); + } ); + + doInJPA( () -> emf, entityManager -> { + //tag::envers-tracking-modified-entities-revchanges-query-example[] + AuditReader auditReader = AuditReaderFactory.get( entityManager ); + + List revisions = auditReader.getRevisions( + ApplicationCustomer.class, + 1L + ); + + CustomTrackingRevisionEntity revEntity = auditReader.findRevision( + CustomTrackingRevisionEntity.class, + revisions.get( 0 ) + ); + + Set modifiedEntityTypes = revEntity.getModifiedEntityTypes(); + assertEquals( 1, modifiedEntityTypes.size() ); + + EntityType entityType = modifiedEntityTypes.iterator().next(); + assertEquals( + Customer.class.getName(), + entityType.getEntityClassName() + ); + //end::envers-tracking-modified-entities-revchanges-query-example[] + } ); + } + finally { + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + } + } + } + + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } + + @Audited + @Entity(name = "Customer") + public static class ApplicationCustomer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } + + //tag::envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example[] + public static class CustomTrackingRevisionListener implements EntityTrackingRevisionListener { + + @Override + public void entityChanged(Class entityClass, + String entityName, + Serializable entityId, + RevisionType revisionType, + Object revisionEntity ) { + String type = entityClass.getName(); + ( (CustomTrackingRevisionEntity) revisionEntity ).addModifiedEntityType( type ); + } + + @Override + public void newRevision( Object revisionEntity ) { + } + } + //end::envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example[] + + //tag::envers-tracking-modified-entities-revchanges-RevisionEntity-example[] + @Entity(name = "CustomTrackingRevisionEntity") + @Table(name = "TRACKING_REV_INFO") + @RevisionEntity( CustomTrackingRevisionListener.class ) + public static class CustomTrackingRevisionEntity { + + @Id + @GeneratedValue + @RevisionNumber + private int customId; + + @RevisionTimestamp + private long customTimestamp; + + @OneToMany( + mappedBy="revision", + cascade={ + CascadeType.PERSIST, + CascadeType.REMOVE + } + ) + private Set modifiedEntityTypes = new HashSet<>(); + + public Set getModifiedEntityTypes() { + return modifiedEntityTypes; + } + + public void addModifiedEntityType(String entityClassName ) { + modifiedEntityTypes.add( new EntityType( this, entityClassName ) ); + } + } + //end::envers-tracking-modified-entities-revchanges-RevisionEntity-example[] + + //tag::envers-tracking-modified-entities-revchanges-EntityType-example[] + @Entity(name = "EntityType") + public static class EntityType { + + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private CustomTrackingRevisionEntity revision; + + private String entityClassName; + + private EntityType() { + } + + public EntityType(CustomTrackingRevisionEntity revision, String entityClassName) { + this.revision = revision; + this.entityClassName = entityClassName; + } + + //Getters and setters are omitted for brevity + //end::envers-tracking-modified-entities-revchanges-EntityType-example[] + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public CustomTrackingRevisionEntity getRevision() { + return revision; + } + + public void setRevision(CustomTrackingRevisionEntity revision) { + this.revision = revision; + } + + public String getEntityClassName() { + return entityClassName; + } + + public void setEntityClassName(String entityClassName) { + this.entityClassName = entityClassName; + } + //tag::envers-tracking-modified-entities-revchanges-EntityType-example[] + } + //end::envers-tracking-modified-entities-revchanges-EntityType-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/ModifiedFlagsAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/ModifiedFlagsAuditTest.java new file mode 100644 index 000000000000..c690dd95ecf1 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/ModifiedFlagsAuditTest.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 . + */ +package org.hibernate.userguide.envers; + +import java.util.Date; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NoResultException; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.DefaultRevisionEntity; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +public class ModifiedFlagsAuditTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-tracking-properties-changes-example[] + Customer customer = entityManager.find( Customer.class, 1L ); + customer.setLastName( "Doe Jr." ); + //end::envers-tracking-properties-changes-example[] + } ); + } + + //tag::envers-tracking-properties-changes-mapping-example[] + @Audited(withModifiedFlag = true) + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + //Getters and setters are omitted for brevity + //end::envers-tracking-properties-changes-mapping-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + //tag::envers-tracking-properties-changes-mapping-example[] + } + //end::envers-tracking-properties-changes-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditAdressCountryTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditAdressCountryTest.java new file mode 100644 index 000000000000..c54808621212 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditAdressCountryTest.java @@ -0,0 +1,342 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.criteria.JoinType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.query.AuditQuery; +import org.hibernate.envers.strategy.ValidityAuditStrategy; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.test.legacy.Custom; +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 + */ +public class QueryAuditAdressCountryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + Address.class, + Country.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( + EnversSettings.AUDIT_STRATEGY, + ValidityAuditStrategy.class.getName() + ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Country country = new Country(); + country.setId( 1L ); + country.setName( "România" ); + entityManager.persist( country ); + + Address address = new Address(); + address.setId( 1L ); + address.setCountry( country ); + address.setCity( "Cluj-Napoca" ); + address.setStreet( "Bulevardul Eroilor" ); + address.setStreetNumber( "1 A" ); + entityManager.persist( address ); + + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + customer.setAddress( address ); + + entityManager.persist( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-querying-entity-relation-nested-join-restriction[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.INNER ) + .traverseRelation( "country", JoinType.INNER ) + .add( AuditEntity.property( "name" ).eq( "România" ) ) + .getResultList(); + + assertEquals( 1, customers.size() ); + //end::envers-querying-entity-relation-nested-join-restriction[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-querying-entity-relation-join-multiple-restrictions[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.LEFT, "a" ) + .add( + AuditEntity.or( + AuditEntity.property( "a", "city" ).eq( "Cluj-Napoca" ), + AuditEntity.relatedId( "country" ).eq( null ) + ) + ) + .getResultList(); + //end::envers-querying-entity-relation-join-multiple-restrictions[] + + assertEquals( 1, customers.size() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-querying-entity-relation-nested-join-multiple-restrictions[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.INNER, "a" ) + .traverseRelation( "country", JoinType.INNER, "cn" ) + .up() + .up() + .add( + AuditEntity.disjunction() + .add( AuditEntity.property( "a", "city" ).eq( "Cluj-Napoca" ) ) + .add( AuditEntity.property( "cn", "name" ).eq( "România" ) ) + ) + .addOrder( AuditEntity.property( "createdOn" ).asc() ) + .getResultList(); + //end::envers-querying-entity-relation-nested-join-multiple-restrictions[] + + assertEquals( 1, customers.size() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-querying-entity-relation-nested-join-multiple-restrictions-combined-entities[] + Customer customer = entityManager.createQuery( + "select c " + + "from Customer c " + + "join fetch c.address a " + + "join fetch a.country " + + "where c.id = :id", Customer.class ) + .setParameter( "id", 1L ) + .getSingleResult(); + + customer.setLastName( "Doe Sr." ); + + customer.getAddress().setCity( + customer.getAddress().getCountry().getName() + ); + //end::envers-querying-entity-relation-nested-join-multiple-restrictions-combined-entities[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::envers-querying-entity-relation-nested-join-multiple-restrictions-combined[] + List revisions = AuditReaderFactory.get( entityManager ).getRevisions( + Customer.class, + 1L + ); + + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( revisions.size() - 1 ) ) + .traverseRelation( "address", JoinType.INNER, "a" ) + .traverseRelation( "country", JoinType.INNER, "cn" ) + .up() + .up() + .add( AuditEntity.property( "a", "city" ).eqProperty( "cn", "name" ) ) + .getResultList(); + //end::envers-querying-entity-relation-nested-join-multiple-restrictions-combined[] + + assertEquals( 1, customers.size() ); + } ); + + } + + //tag::envers-generateschema-example[] + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + @ManyToOne(fetch = FetchType.LAZY) + private Address address; + + //Getters and setters omitted for brevity + //end::envers-generateschema-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + //tag::envers-generateschema-example[] + } + + @Audited + @Entity(name = "Address") + public static class Address { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Country country; + + private String city; + + private String street; + + private String streetNumber; + + //Getters and setters omitted for brevity + //end::envers-generateschema-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber(String streetNumber) { + this.streetNumber = streetNumber; + } + //tag::envers-generateschema-example[] + } + + @Audited + @Entity(name = "Country") + public static class Country { + + @Id + private Long id; + + private String name; + + //Getters and setters omitted for brevity + //end::envers-generateschema-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::envers-generateschema-example[] + } + //end::envers-generateschema-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditTest.java new file mode 100644 index 000000000000..ba6903ab1df8 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditTest.java @@ -0,0 +1,393 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.criteria.JoinType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.query.AuditQuery; +import org.hibernate.envers.strategy.ValidityAuditStrategy; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Chris Cranford + */ +public class QueryAuditTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + Address.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( + EnversSettings.AUDIT_STRATEGY, + ValidityAuditStrategy.class.getName() + ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Address address = new Address(); + address.setId( 1L ); + address.setCountry( "România" ); + address.setCity( "Cluj-Napoca" ); + address.setStreet( "Bulevardul Eroilor" ); + address.setStreetNumber( "1 A" ); + entityManager.persist( address ); + + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + customer.setAddress( address ); + + entityManager.persist( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, 1L ); + customer.setLastName( "Doe Jr." ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.getReference( Customer.class, 1L ); + entityManager.remove( customer ); + } ); + + List revisions = doInJPA( this::entityManagerFactory, entityManager -> { + return AuditReaderFactory.get( entityManager ).getRevisions( + Customer.class, + 1L + ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entities-at-revision-example[] + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( 0 ) ) + .getSingleResult(); + + assertEquals("Doe", customer.getLastName()); + //end::entities-at-revision-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entities-filtering-example[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, true, true ) + .add( AuditEntity.property( "firstName" ).eq( "John" ) ) + .getResultList(); + + assertEquals(2, customers.size()); + assertEquals( "Doe", customers.get( 0 ).getLastName() ); + assertEquals( "Doe Jr.", customers.get( 1 ).getLastName() ); + //end::entities-filtering-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entities-filtering-by-entity-example[] + Address address = entityManager.getReference( Address.class, 1L ); + + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, true, true ) + .add( AuditEntity.property( "address" ).eq( address ) ) + .getResultList(); + + assertEquals(2, customers.size()); + //end::entities-filtering-by-entity-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entities-filtering-by-entity-identifier-example[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, true, true ) + .add( AuditEntity.relatedId( "address" ).eq( 1L ) ) + .getResultList(); + + assertEquals(2, customers.size()); + //end::entities-filtering-by-entity-identifier-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entities-in-clause-filtering-by-entity-identifier-example[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, true, true ) + .add( AuditEntity.relatedId( "address" ).in( new Object[] { 1L, 2L } ) ) + .getResultList(); + + assertEquals(2, customers.size()); + //end::entities-in-clause-filtering-by-entity-identifier-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entities-filtering-and-pagination[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, true, true ) + .addOrder( AuditEntity.property( "lastName" ).desc() ) + .add( AuditEntity.relatedId( "address" ).eq( 1L ) ) + .setFirstResult( 1 ) + .setMaxResults( 2 ) + .getResultList(); + + assertEquals(1, customers.size()); + //end::entities-filtering-and-pagination[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::revisions-of-entity-query-example[] + AuditQuery query = AuditReaderFactory.get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, false, true ); + //end::revisions-of-entity-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::revisions-of-entity-query-by-revision-number-example[] + Number revision = (Number) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, false, true ) + .addProjection( AuditEntity.revisionNumber().min() ) + .add( AuditEntity.id().eq( 1L ) ) + .add( AuditEntity.revisionNumber().gt( 2 ) ) + .getSingleResult(); + //end::revisions-of-entity-query-by-revision-number-example[] + + assertEquals( 3, revision ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::revisions-of-entity-query-minimize-example[] + Number revision = (Number) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, false, true ) + .addProjection( AuditEntity.revisionNumber().min() ) + .add( AuditEntity.id().eq( 1L ) ) + .add( + AuditEntity.property( "createdOn" ) + .minimize() + .add( AuditEntity.property( "createdOn" ) + .ge( + Timestamp.from( + LocalDateTime.now() + .minusDays( 1 ) + .toInstant( ZoneOffset.UTC ) + ) + ) + ) + ) + .getSingleResult(); + //end::revisions-of-entity-query-minimize-example[] + + assertEquals( 1, revision ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + { + //tag::envers-querying-entity-relation-inner-join[] + AuditQuery innerJoinAuditQuery = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.INNER ); + //end::envers-querying-entity-relation-inner-join[] + } + { + //tag::envers-querying-entity-relation-left-join[] + AuditQuery innerJoinAuditQuery = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.LEFT ); + //end::envers-querying-entity-relation-left-join[] + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-querying-entity-relation-join-restriction[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.INNER ) + .add( AuditEntity.property( "country" ).eq( "România" ) ) + .getResultList(); + //end::envers-querying-entity-relation-join-restriction[] + + assertEquals( 1, customers.size() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::aggregate-max-revision-with-entity-example[] + List results = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, true, false ) + .add( AuditEntity.revisionNumber().maximize().computeAggregationInInstanceContext() ) + .getResultList(); + //end::aggregate-max-revision-with-entity-example[] + } ); + } + + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + @ManyToOne(fetch = FetchType.LAZY) + private Address address; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + @Audited + @Entity(name = "Address") + public static class Address { + + @Id + private Long id; + + private String country; + + private String city; + + private String street; + + private String streetNumber; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber(String streetNumber) { + this.streetNumber = streetNumber; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditWithModifiedFlagTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditWithModifiedFlagTest.java new file mode 100644 index 000000000000..886d092bc438 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditWithModifiedFlagTest.java @@ -0,0 +1,247 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.strategy.ValidityAuditStrategy; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +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 + */ +public class QueryAuditWithModifiedFlagTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + Address.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( + EnversSettings.AUDIT_STRATEGY, + ValidityAuditStrategy.class.getName() + ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Address address = new Address(); + address.setId( 1L ); + address.setCountry( "România" ); + address.setCity( "Cluj-Napoca" ); + address.setStreet( "Bulevardul Eroilor" ); + address.setStreetNumber( "1 A" ); + entityManager.persist( address ); + + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + customer.setAddress( address ); + + entityManager.persist( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, 1L ); + customer.setLastName( "Doe Jr." ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.getReference( Customer.class, 1L ); + entityManager.remove( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-tracking-properties-changes-queries-hasChanged-example[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, false, true ) + .add( AuditEntity.id().eq( 1L ) ) + .add( AuditEntity.property( "lastName" ).hasChanged() ) + .getResultList(); + //end::envers-tracking-properties-changes-queries-hasChanged-example[] + + assertEquals( 3, customers.size() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example[] + List customers = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forRevisionsOfEntity( Customer.class, false, true ) + .add( AuditEntity.id().eq( 1L ) ) + .add( AuditEntity.property( "lastName" ).hasChanged() ) + .add( AuditEntity.property( "firstName" ).hasNotChanged() ) + .getResultList(); + //end::envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example[] + + assertEquals( 1, customers.size() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-tracking-properties-changes-queries-at-revision-example[] + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesModifiedAtRevision( Customer.class, 2 ) + .add( AuditEntity.id().eq( 1L ) ) + .add( AuditEntity.property( "lastName" ).hasChanged() ) + .add( AuditEntity.property( "firstName" ).hasNotChanged() ) + .getSingleResult(); + //end::envers-tracking-properties-changes-queries-at-revision-example[] + + assertNotNull( customer ); + } ); + } + + //tag::envers-tracking-properties-changes-queries-entity-example[] + @Audited( withModifiedFlag = true ) + //end::envers-tracking-properties-changes-queries-entity-example[] + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + @ManyToOne(fetch = FetchType.LAZY) + private Address address; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + @Audited + @Entity(name = "Address") + public static class Address { + + @Id + private Long id; + + private String country; + + private String city; + + private String street; + + private String streetNumber; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber(String streetNumber) { + this.streetNumber = streetNumber; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/ValidityStrategyAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/ValidityStrategyAuditTest.java new file mode 100644 index 000000000000..468a66b79252 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/ValidityStrategyAuditTest.java @@ -0,0 +1,184 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NoResultException; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.envers.strategy.ValidityAuditStrategy; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +public class ValidityStrategyAuditTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + //tag::envers-audited-validity-configuration-example[] + options.put( + EnversSettings.AUDIT_STRATEGY, + ValidityAuditStrategy.class.getName() + ); + //end::envers-audited-validity-configuration-example[] + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, 1L ); + customer.setLastName( "Doe Jr." ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.getReference( Customer.class, 1L ); + entityManager.remove( customer ); + } ); + + List revisions = doInJPA( this::entityManagerFactory, entityManager -> { + return AuditReaderFactory.get( entityManager ).getRevisions( + Customer.class, + 1L + ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( 0 ) ) + .getSingleResult(); + + assertEquals("Doe", customer.getLastName()); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( 1 ) ) + .getSingleResult(); + + assertEquals("Doe Jr.", customer.getLastName()); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, revisions.get( 2 ) ) + .getSingleResult(); + + fail("The Customer was deleted at this revision: " + revisions.get( 2 )); + } + catch (NoResultException expected) { + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( + Customer.class, + Customer.class.getName(), + revisions.get( 2 ), + true ) + .getSingleResult(); + + assertEquals( Long.valueOf( 1L ), customer.getId() ); + assertNull( customer.getFirstName() ); + assertNull( customer.getLastName() ); + assertNull( customer.getCreatedOn() ); + } ); + } + + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java new file mode 100644 index 000000000000..213fb0268562 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java @@ -0,0 +1,39 @@ +package org.hibernate.userguide.events; + +import java.sql.Timestamp; +import javax.persistence.MappedSuperclass; + +/** + * @author Vlad Mihalcea + */ +//tag::events-default-listener-mapping-example[] +@MappedSuperclass +public abstract class BaseEntity { + + private Timestamp createdOn; + + private Timestamp updatedOn; + + //Getters and setters are omitted for brevity + +//end::events-default-listener-mapping-example[] + + public Timestamp getCreatedOn() { + return createdOn; + } + + void setCreatedOn(Timestamp createdOn) { + this.createdOn = createdOn; + } + + public Timestamp getUpdatedOn() { + return updatedOn; + } + + void setUpdatedOn(Timestamp updatedOn) { + this.updatedOn = updatedOn; + } +//tag::events-default-listener-mapping-example[] +} +//end::events-default-listener-mapping-example[] + diff --git a/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener-orm.xml b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener-orm.xml new file mode 100644 index 000000000000..8f010c8eeca7 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener-orm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener.java b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener.java new file mode 100644 index 000000000000..2a9642d2e42a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener.java @@ -0,0 +1,33 @@ +package org.hibernate.userguide.events; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +/** + * @author Vlad Mihalcea + */ +//tag::events-default-listener-mapping-example[] +public class DefaultEntityListener { + + public void onPersist(Object entity) { + if ( entity instanceof BaseEntity ) { + BaseEntity baseEntity = (BaseEntity) entity; + baseEntity.setCreatedOn( now() ); + } + } + + public void onUpdate(Object entity) { + if ( entity instanceof BaseEntity ) { + BaseEntity baseEntity = (BaseEntity) entity; + baseEntity.setUpdatedOn( now() ); + } + } + + private Timestamp now() { + return Timestamp.from( + LocalDateTime.now().toInstant( ZoneOffset.UTC ) + ); + } +} +//end::events-default-listener-mapping-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListenerTest.java b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListenerTest.java new file mode 100644 index 000000000000..f74570f01cd3 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListenerTest.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 . + */ +package org.hibernate.userguide.events; + +import javax.persistence.Entity; +import javax.persistence.ExcludeDefaultListeners; +import javax.persistence.ExcludeSuperclassListeners; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static junit.framework.TestCase.assertNull; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class DefaultEntityListenerTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Book.class, + Publisher.class + }; + } + + @Override + protected String[] getMappings() { + return new String[] { "org/hibernate/userguide/events/DefaultEntityListener-orm.xml" }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::events-default-listener-persist-example[] + Person author = new Person(); + author.setId( 1L ); + author.setName( "Vlad Mihalcea" ); + + entityManager.persist( author ); + + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( author ); + + entityManager.persist( book ); + //end::events-default-listener-persist-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::events-default-listener-update-example[] + Person author = entityManager.find( Person.class, 1L ); + author.setName( "Vlad-Alexandru Mihalcea" ); + + Book book = entityManager.find( Book.class, 1L ); + book.setTitle( "High-Performance Java Persistence 2nd Edition" ); + //end::events-default-listener-update-example[] + } ); + } + + @Test + public void testExclude() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::events-exclude-default-listener-persist-example[] + Publisher publisher = new Publisher(); + publisher.setId( 1L ); + publisher.setName( "Amazon" ); + + entityManager.persist( publisher ); + //end::events-exclude-default-listener-persist-example[] + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Publisher publisher = entityManager.find( Publisher.class, 1L ); + assertNull(publisher.getCreatedOn()); + } ); + } + + //tag::events-default-listener-mapping-example[] + @Entity(name = "Person") + public static class Person extends BaseEntity { + + @Id + private Long id; + + private String name; + + //Getters and setters omitted for brevity + //end::events-default-listener-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::events-default-listener-mapping-example[] + } + + @Entity(name = "Book") + public static class Book extends BaseEntity { + + @Id + private Long id; + + private String title; + + @ManyToOne + private Person author; + + //Getters and setters omitted for brevity + //end::events-default-listener-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Person getAuthor() { + return author; + } + + public void setAuthor(Person author) { + this.author = author; + } + //tag::events-default-listener-mapping-example[] + } + //end::events-default-listener-mapping-example[] + + //tag::events-exclude-default-listener-mapping-example[] + @Entity(name = "Publisher") + @ExcludeDefaultListeners + @ExcludeSuperclassListeners + public static class Publisher extends BaseEntity { + + @Id + private Long id; + + private String name; + + //Getters and setters omitted for brevity + //end::events-exclude-default-listener-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::events-exclude-default-listener-mapping-example[] + } + //end::events-exclude-default-listener-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/BatchFetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/BatchFetchingTest.java index f515ec2a3e46..1c28ac607f3d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/BatchFetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/BatchFetchingTest.java @@ -29,8 +29,6 @@ */ public class BatchFetchingTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( BatchFetchingTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java index 994745cf7a14..e464f8150ee3 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java @@ -28,8 +28,6 @@ @RequiresDialect(H2Dialect.class) public class DirectVsQueryFetchingTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( DirectVsQueryFetchingTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -81,7 +79,6 @@ public static class Department { //Getters and setters omitted for brevity } - //tag::fetching-direct-vs-query-domain-model-example[] @Entity(name = "Employee") public static class Employee { diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeJoinTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeJoinTest.java index 7420e1bc96bd..a0ebffc4243f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeJoinTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeJoinTest.java @@ -32,8 +32,6 @@ */ public class FetchModeJoinTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FetchModeJoinTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSelectTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSelectTest.java index 1c6e459f89b2..e2172177c3e7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSelectTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSelectTest.java @@ -32,8 +32,6 @@ */ public class FetchModeSelectTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FetchModeSelectTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSubselectTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSubselectTest.java index 23efcc88f581..ffa08e9764fc 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSubselectTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchModeSubselectTest.java @@ -32,8 +32,6 @@ */ public class FetchModeSubselectTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FetchModeSubselectTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { 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 a8d46cd62ce1..2dbec1f5b64d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java @@ -43,8 +43,6 @@ @RequiresDialect(H2Dialect.class) public class FetchingTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FetchingTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/GraphFetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/GraphFetchingTest.java index 1b8775a931c3..1fe3b15f88af 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/GraphFetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/GraphFetchingTest.java @@ -17,10 +17,13 @@ import javax.persistence.ManyToOne; import javax.persistence.NamedAttributeNode; import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedSubgraph; import javax.persistence.OneToMany; +import javax.persistence.QueryHint; import org.hibernate.annotations.ColumnTransformer; import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.QueryHints; import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; @@ -30,7 +33,9 @@ import org.jboss.logging.Logger; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; /** * @author Vlad Mihalcea @@ -38,8 +43,6 @@ @RequiresDialect(H2Dialect.class) public class GraphFetchingTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( GraphFetchingTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -72,6 +75,10 @@ public void test() { employee2.department = department; entityManager.persist( employee2 ); + Project project = new Project(); + project.id = 1L; + project.employees.add( employee1 ); + entityManager.persist( project ); } ); doInJPA( this::entityManagerFactory, entityManager -> { @@ -90,6 +97,20 @@ public void test() { assertNotNull(employee); } ); + //tag::fetching-strategies-dynamic-fetching-entity-subgraph-example[] + Project project = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( + Project.class, + 1L, + Collections.singletonMap( + "javax.persistence.fetchgraph", + entityManager.getEntityGraph( "project.employees" ) + ) + ); + } ); + //end::fetching-strategies-dynamic-fetching-entity-subgraph-example[] + assertEquals(1, project.getEmployees().size()); + assertEquals( Long.valueOf( 1L ), project.getEmployees().get( 0 ).getDepartment().getId() ); } @Entity(name = "Department") @@ -102,6 +123,22 @@ public static class Department { private List employees = new ArrayList<>(); //Getters and setters omitted for brevity + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getEmployees() { + return employees; + } + + public void setEmployees(List employees) { + this.employees = employees; + } } //tag::fetching-strategies-dynamic-fetching-entity-graph-mapping-example[] @@ -134,10 +171,69 @@ public static class Employee { private List projects = new ArrayList<>(); //Getters and setters omitted for brevity + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getAccessLevel() { + return accessLevel; + } + + public void setAccessLevel(int accessLevel) { + this.accessLevel = accessLevel; + } + + public Department getDepartment() { + return department; + } + + public void setDepartment(Department department) { + this.department = department; + } + + public List getProjects() { + return projects; + } + + public void setProjects(List projects) { + this.projects = projects; + } } + //tag::fetching-strategies-dynamic-fetching-entity-subgraph-mapping-example[] @Entity(name = "Project") - public class Project { + @NamedEntityGraph(name = "project.employees", + attributeNodes = @NamedAttributeNode( + value = "employees", + subgraph = "project.employees.department" + ), + subgraphs = @NamedSubgraph( + name = "project.employees.department", + attributeNodes = @NamedAttributeNode( "department" ) + ) + ) + public static class Project { @Id private Long id; @@ -146,5 +242,24 @@ public class Project { private List employees = new ArrayList<>(); //Getters and setters omitted for brevity + //end::fetching-strategies-dynamic-fetching-entity-subgraph-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getEmployees() { + return employees; + } + + public void setEmployees(List employees) { + this.employees = employees; + } + //tag::fetching-strategies-dynamic-fetching-entity-subgraph-mapping-example[] } + //end::fetching-strategies-dynamic-fetching-entity-subgraph-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/LazyCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/LazyCollectionTest.java index 091d41e745dc..e12942897965 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/LazyCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/LazyCollectionTest.java @@ -32,8 +32,6 @@ */ public class LazyCollectionTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( LazyCollectionTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/ProfileFetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/ProfileFetchingTest.java index 0ebe7d67a4b7..299927b7576c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/ProfileFetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/ProfileFetchingTest.java @@ -38,8 +38,6 @@ @RequiresDialect(H2Dialect.class) public class ProfileFetchingTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( ProfileFetchingTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/AlwaysFlushTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/AlwaysFlushTest.java index da130b50c437..f1d8b21826d1 100644 --- a/documentation/src/test/java/org/hibernate/userguide/flush/AlwaysFlushTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/flush/AlwaysFlushTest.java @@ -32,8 +32,6 @@ */ public class AlwaysFlushTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( AlwaysFlushTest.class); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -56,7 +54,7 @@ public void testFlushSQL() { Session session = entityManager.unwrap( Session.class); assertTrue(((Number) session - .createSQLQuery("select count(*) from Person") + .createNativeQuery("select count(*) from Person") .setFlushMode( FlushMode.ALWAYS) .uniqueResult()).intValue() == 1); //end::flushing-always-flush-sql-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java index c1872cba1c0c..1e7dd08a1970 100644 --- a/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java @@ -28,8 +28,6 @@ */ public class AutoFlushTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( AutoFlushTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -127,7 +125,7 @@ public void testFlushAutoSQLNativeSession() { entityManager.persist( person ); Session session = entityManager.unwrap(Session.class); - // for this to work, the Session/EntityManager must be put into COMMIT FlushMode + // For this to work, the Session/EntityManager must be put into COMMIT FlushMode // - this is a change since 5.2 to account for merging EntityManager functionality // directly into Session. Flushing would be the JPA-spec compliant behavior, // so we know do that by default. @@ -136,9 +134,9 @@ public void testFlushAutoSQLNativeSession() { //session.setHibernateFlushMode( FlushMode.COMMIT ); assertTrue(((Number) session - .createSQLQuery( "select count(*) from Person") + .createNativeQuery( "select count(*) from Person") .uniqueResult()).intValue() == 0 ); - //end::flushing-auto-flush-sql-native-example[\] + //end::flushing-auto-flush-sql-native-example[] } ); } @@ -159,7 +157,7 @@ public void testFlushAutoSQLSynchronization() { Session session = entityManager.unwrap( Session.class ); assertTrue(((Number) session - .createSQLQuery( "select count(*) from Person") + .createNativeQuery( "select count(*) from Person") .addSynchronizedEntityClass( Person.class ) .uniqueResult()).intValue() == 1 ); //end::flushing-auto-flush-sql-synchronization-example[] @@ -176,6 +174,10 @@ public static class Person { private String name; + //Getters and setters are omitted for brevity + + //end::flushing-auto-flush-jpql-entity-example[] + public Person() {} public Person(String name) { @@ -189,7 +191,7 @@ public Long getId() { public String getName() { return name; } - + //tag::flushing-auto-flush-jpql-entity-example[] } @Entity(name = "Advertisement") @@ -201,6 +203,10 @@ public static class Advertisement { private String title; + //Getters and setters are omitted for brevity + + //end::flushing-auto-flush-jpql-entity-example[] + public Long getId() { return id; } @@ -216,6 +222,7 @@ public String getTitle() { public void setTitle(String title) { this.title = title; } + //tag::flushing-auto-flush-jpql-entity-example[] } //end::flushing-auto-flush-jpql-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/CommitFlushTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/CommitFlushTest.java index db0376cd1286..a1ea96bc8a99 100644 --- a/documentation/src/test/java/org/hibernate/userguide/flush/CommitFlushTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/flush/CommitFlushTest.java @@ -32,8 +32,6 @@ */ public class CommitFlushTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( CommitFlushTest.class); - @Override protected Class[] getAnnotatedClasses() { return new Class[]{ diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java index 0d84a4c722e2..d124ece22590 100644 --- a/documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java @@ -22,8 +22,6 @@ */ public class FlushOrderTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FlushOrderTest.class); - @Override protected Class[] getAnnotatedClasses() { return new Class[]{ diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/HibernateAutoFlushTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/HibernateAutoFlushTest.java new file mode 100644 index 000000000000..6a0717e1a981 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/flush/HibernateAutoFlushTest.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.flush; + +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class HibernateAutoFlushTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Advertisement.class + }; + } + + @Test + public void testFlushAutoSQLNativeSession() { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "delete from Person" ).executeUpdate();; + } ); + doInHibernate( this::sessionFactory, session -> { + log.info( "testFlushAutoSQLNativeSession" ); + //tag::flushing-auto-flush-sql-native-example[] + assertTrue(((Number) session + .createNativeQuery( "select count(*) from Person") + .getSingleResult()).intValue() == 0 ); + + Person person = new Person( "John Doe" ); + session.persist( person ); + + assertTrue(((Number) session + .createNativeQuery( "select count(*) from Person") + .uniqueResult()).intValue() == 0 ); + //end::flushing-auto-flush-sql-native-example[] + } ); + } + + //tag::flushing-auto-flush-jpql-entity-example[] + @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 String getName() { + return name; + } + + } + + @Entity(name = "Advertisement") + public static class Advertisement { + + @Id + @GeneratedValue + private Long id; + + private String title; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + } + //end::flushing-auto-flush-jpql-entity-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/ManualFlushTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/ManualFlushTest.java index 516abb216fbe..0eba25231fb4 100644 --- a/documentation/src/test/java/org/hibernate/userguide/flush/ManualFlushTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/flush/ManualFlushTest.java @@ -32,8 +32,6 @@ */ public class ManualFlushTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( ManualFlushTest.class); - @Override protected Class[] getAnnotatedClasses() { return new Class[]{ @@ -62,7 +60,7 @@ public void testFlushSQL() { .getSingleResult()).intValue() == 0); assertTrue(((Number) session - .createSQLQuery("select count(*) from Person") + .createNativeQuery("select count(*) from Person") .uniqueResult()).intValue() == 0); //end::flushing-manual-flush-example[] }); diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java b/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java index 757693c023d3..34c1ff4a6680 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java @@ -16,14 +16,14 @@ public class CallStatistics { private final long total; private final int min; private final int max; - private final double abg; + private final double avg; - public CallStatistics(long count, long total, int min, int max, double abg) { + public CallStatistics(long count, long total, int min, int max, double avg) { this.count = count; this.total = total; this.min = min; this.max = max; - this.abg = abg; + this.avg = avg; } //Getters and setters omitted for brevity diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java index e7295231e7b6..6ddf6aef9a04 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java @@ -29,6 +29,7 @@ import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.type.StringType; import org.hibernate.userguide.model.AddressType; @@ -41,7 +42,10 @@ import org.hibernate.userguide.model.PhoneType; import org.hibernate.userguide.model.WireTransferPayment; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.junit.Before; import org.junit.Test; @@ -412,6 +416,7 @@ public void test_hql_collection_valued_associations_1() { .getResultList(); //end::hql-collection-valued-associations[] assertEquals(1, phones.size()); + assertEquals( "123-456-7890", phones.get( 0 ).getNumber() ); }); } @@ -426,7 +431,7 @@ public void test_hql_collection_valued_associations_2() { // alternate syntax List phones = session.createQuery( - "select pr " + + "select ph " + "from Person pr, " + "in (pr.phones) ph, " + "in (ph.calls) c " + @@ -437,6 +442,7 @@ public void test_hql_collection_valued_associations_2() { .list(); //end::hql-collection-valued-associations[] assertEquals(1, phones.size()); + assertEquals( "123-456-7890", phones.get( 0 ).getNumber() ); }); } @@ -580,6 +586,19 @@ public void test_jpql_api_named_query_example() { }); } + @Test + public void test_jpql_api_hibernate_named_query_example() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::jpql-api-hibernate-named-query-example[] + Phone phone = entityManager + .createNamedQuery( "get_phone_by_number", Phone.class ) + .setParameter( "number", "123-456-7890" ) + .getSingleResult(); + //end::jpql-api-hibernate-named-query-example[] + assertNotNull( phone ); + }); + } + @Test public void test_jpql_api_basic_usage_example() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -766,8 +785,8 @@ public void test_hql_api_positional_parameter_example() { org.hibernate.query.Query query = session.createQuery( "select p " + "from Person p " + - "where p.name like ? " ) - .setParameter( 0, "J%" ); + "where p.name like ?1" ) + .setParameter( 1, "J%" ); //end::hql-api-positional-parameter-example[] }); } @@ -1131,7 +1150,7 @@ public void test_hql_concat_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-concat-function-example[] List callHistory = entityManager.createQuery( - "select concat( p.number, ' : ' ,c.duration ) " + + "select concat( p.number, ' : ' , cast(c.duration as string) ) " + "from Call c " + "join c.phone p", String.class ) .getResultList(); @@ -1259,6 +1278,7 @@ public void test_hql_sqrt_function_example() { } @Test + @SkipForDialect(SQLServerDialect.class) public void test_hql_current_date_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-current-date-function-example[] @@ -1272,6 +1292,19 @@ public void test_hql_current_date_function_example() { }); } + @Test + @RequiresDialect(SQLServerDialect.class) + public void test_hql_current_date_function_example_sql_server() { + doInJPA( this::entityManagerFactory, entityManager -> { + List calls = entityManager.createQuery( + "select c " + + "from Call c " + + "where c.timestamp = current_date()", Call.class ) + .getResultList(); + assertEquals(0, calls.size()); + }); + } + @Test @RequiresDialect(H2Dialect.class) public void test_hql_current_time_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -1356,6 +1389,7 @@ public void test_hql_year_function_example() { } @Test + @SkipForDialect(SQLServerDialect.class) public void test_hql_str_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-str-function-example[] @@ -1368,6 +1402,20 @@ public void test_hql_str_function_example() { }); } + @Test + @RequiresDialect(SQLServerDialect.class) + public void test_hql_str_function_example_sql_server() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::hql-str-function-example[] + List timestamps = entityManager.createQuery( + "select str( cast(duration as float) / 60, 4, 2 ) " + + "from Call c ", String.class ) + .getResultList(); + //end::hql-str-function-example[] + assertEquals(2, timestamps.size()); + }); + } + @Test public void test_hql_collection_expressions_example_1() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -2159,6 +2207,7 @@ public void test_hql_in_predicate_example_5() { @Test + @RequiresDialectFeature(DialectChecks.SupportRowValueConstructorSyntaxInInList.class) public void test_hql_in_predicate_example_6() { doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/SelectDistinctTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/SelectDistinctTest.java index 856bd2142712..fdbd5f362a04 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/SelectDistinctTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/SelectDistinctTest.java @@ -37,8 +37,6 @@ */ public class SelectDistinctTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( SelectDistinctTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java b/documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java index f1755a6862c8..a4cb609d112c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java @@ -28,8 +28,6 @@ */ public class CollectionImmutabilityTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( CollectionImmutabilityTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java b/documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java index 015a3af6b7c6..81dea2f2053b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java @@ -25,8 +25,6 @@ */ public class EntityImmutabilityTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( EntityImmutabilityTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java index e3dea013e31e..2c3bbf9ef3c5 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java @@ -120,6 +120,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public Long getId() { return id; } @@ -151,6 +155,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "DebitAccount") @@ -159,6 +164,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -166,6 +175,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "CreditAccount") @@ -174,6 +184,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -181,6 +195,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "OtherAccount") @@ -189,6 +204,10 @@ public static class OtherAccount extends Account { private boolean active; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public boolean isActive() { return active; } @@ -196,6 +215,7 @@ public boolean isActive() { public void setActive(boolean active) { this.active = active; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } //end::entity-inheritance-single-table-discriminator-value-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java index d4fc162da793..bfe845bd7593 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java @@ -74,6 +74,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public Long getId() { return id; } @@ -105,6 +109,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } @Entity(name = "DebitAccount") @@ -113,6 +118,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -120,6 +129,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } //end::entity-inheritance-joined-table-primary-key-join-column-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java index 889ec4df5ba4..9442c377f72d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java @@ -76,6 +76,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public Long getId() { return id; } @@ -107,6 +111,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-joined-table-example[] } @Entity(name = "DebitAccount") @@ -114,6 +119,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -121,6 +130,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-joined-table-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-joined-table-example[] } //end::entity-inheritance-joined-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java index 4323f77913d6..1ddbf7f551c8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java @@ -65,6 +65,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public Long getId() { return id; } @@ -96,6 +100,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-mapped-superclass-example[] } @Entity(name = "DebitAccount") @@ -103,6 +108,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -110,6 +119,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-mapped-superclass-example[] } @Entity(name = "CreditAccount") @@ -117,6 +127,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -124,6 +138,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-mapped-superclass-example[] } //end::entity-inheritance-mapped-superclass-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java index 1cd3d890c869..ec59c3bc2da4 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java @@ -90,6 +90,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + public Long getId() { return id; } @@ -121,6 +125,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } @Entity(name = "DebitAccount") @@ -131,6 +136,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + private DebitAccount() { } @@ -149,6 +158,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } @Entity(name = "CreditAccount") @@ -159,6 +169,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + private CreditAccount() { } @@ -177,6 +191,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } //end::entity-inheritance-single-table-discriminator-formula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java index 1a225b6791eb..fc0316edfc36 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java @@ -78,6 +78,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public Long getId() { return id; } @@ -109,6 +113,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-example[] } @Entity(name = "DebitAccount") @@ -116,6 +121,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -123,6 +132,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-example[] } @Entity(name = "CreditAccount") @@ -130,6 +140,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -137,6 +151,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-example[] } //end::entity-inheritance-single-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java index d4994028d78d..2db426456cc8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java @@ -76,6 +76,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public Long getId() { return id; } @@ -107,6 +111,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-table-per-class-example[] } @Entity(name = "DebitAccount") @@ -114,6 +119,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -121,6 +130,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-table-per-class-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-table-per-class-example[] } //end::entity-inheritance-table-per-class-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/polymorphism/DomainModelEntity.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/polymorphism/DomainModelEntity.java new file mode 100644 index 000000000000..2e3a99005e0d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/polymorphism/DomainModelEntity.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 . + */ +package org.hibernate.userguide.inheritance.polymorphism; + +/** + * @author Vlad Mihalcea + */ +//tag::entity-inheritance-polymorphism-interface-example[] +public interface DomainModelEntity { + + ID getId(); + + Integer getVersion(); +} +//end::entity-inheritance-polymorphism-interface-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/polymorphism/ExplicitPolymorphismTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/polymorphism/ExplicitPolymorphismTest.java new file mode 100644 index 000000000000..6717a1baa705 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/polymorphism/ExplicitPolymorphismTest.java @@ -0,0 +1,157 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.inheritance.polymorphism; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.annotations.Polymorphism; +import org.hibernate.annotations.PolymorphismType; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class ExplicitPolymorphismTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + Blog.class, + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-inheritance-polymorphism-persist-example[] + Book book = new Book(); + book.setId( 1L ); + book.setAuthor( "Vlad Mihalcea" ); + book.setTitle( "High-Performance Java Persistence" ); + entityManager.persist( book ); + + Blog blog = new Blog(); + blog.setId( 1L ); + blog.setSite( "vladmihalcea.com" ); + entityManager.persist( blog ); + //end::entity-inheritance-polymorphism-persist-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-inheritance-polymorphism-fetch-example[] + List accounts = entityManager + .createQuery( + "select e " + + "from org.hibernate.userguide.inheritance.polymorphism.DomainModelEntity e" ) + .getResultList(); + + assertEquals(1, accounts.size()); + assertTrue( accounts.get( 0 ) instanceof Book ); + //end::entity-inheritance-polymorphism-fetch-example[] + } ); + } + + + //tag::entity-inheritance-polymorphism-mapping-example[] + @Entity(name = "Event") + public static class Book implements DomainModelEntity { + + @Id + private Long id; + + @Version + private Integer version; + + private String title; + + private String author; + + //Getter and setters omitted for brevity + //end::entity-inheritance-polymorphism-mapping-example[] + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public Integer getVersion() { + return version; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::entity-inheritance-polymorphism-mapping-example[] + } + + @Entity(name = "Blog") + @Polymorphism(type = PolymorphismType.EXPLICIT) + public static class Blog implements DomainModelEntity { + + @Id + private Long id; + + @Version + private Integer version; + + private String site; + + //Getter and setters omitted for brevity + //end::entity-inheritance-polymorphism-mapping-example[] + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public Integer getVersion() { + return version; + } + + public String getSite() { + return site; + } + + public void setSite(String site) { + this.site = site; + } + //tag::entity-inheritance-polymorphism-mapping-example[] + } + //end::entity-inheritance-polymorphism-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/jmx/JmxTest.java b/documentation/src/test/java/org/hibernate/userguide/jmx/JmxTest.java index 716840cca89c..867a4430bd0c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/jmx/JmxTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/jmx/JmxTest.java @@ -26,8 +26,6 @@ */ public class JmxTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( JmxTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/ExplicitLockingTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/ExplicitLockingTest.java index 61acb295a284..763b6252b360 100644 --- a/documentation/src/test/java/org/hibernate/userguide/locking/ExplicitLockingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/locking/ExplicitLockingTest.java @@ -40,8 +40,6 @@ */ public class ExplicitLockingTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( ExplicitLockingTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTest.java new file mode 100644 index 000000000000..4c45214cc5c6 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTest.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.locking; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Version; + +import org.hibernate.annotations.OptimisticLock; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class OptimisticLockTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Phone.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Phone phone = new Phone(); + phone.setId( 1L ); + phone.setNumber( "123-456-7890" ); + entityManager.persist( phone ); + + return phone; + } ); + + //tag::locking-optimistic-exclude-attribute-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + Phone phone = entityManager.find( Phone.class, 1L ); + phone.setNumber( "+123-456-7890" ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + Phone _phone = _entityManager.find( Phone.class, 1L ); + _phone.incrementCallCount(); + + log.info( "Bob changes the Phone call count" ); + } ); + + log.info( "Alice changes the Phone number" ); + } ); + //end::locking-optimistic-exclude-attribute-example[] + } + + //tag::locking-optimistic-exclude-attribute-mapping-example[] + @Entity(name = "Phone") + public static class Phone { + + @Id + private Long id; + + @Column(name = "`number`") + private String number; + + @OptimisticLock( excluded = true ) + private long callCount; + + @Version + private Long version; + + //Getters and setters are omitted for brevity + + //end::locking-optimistic-exclude-attribute-mapping-example[] + + 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 Long getVersion() { + return version; + } + + public long getCallCount() { + return callCount; + } + //tag::locking-optimistic-exclude-attribute-mapping-example[] + public void incrementCallCount() { + this.callCount++; + } + } + //end::locking-optimistic-exclude-attribute-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeAllTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeAllTest.java index 9d16fbfc47e7..3fdeb88e7c8f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeAllTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeAllTest.java @@ -27,8 +27,6 @@ */ public class OptimisticLockTypeAllTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( OptimisticLockTypeAllTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeDirtyTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeDirtyTest.java index 917f1e3cbadf..1d48c2cc72b8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeDirtyTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockTypeDirtyTest.java @@ -28,8 +28,6 @@ */ public class OptimisticLockTypeDirtyTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( OptimisticLockTypeDirtyTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingInstantTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingInstantTest.java new file mode 100644 index 000000000000..f28a1ac45de8 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingInstantTest.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.locking; + +import java.sql.Timestamp; +import java.time.Instant; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class OptimisticLockingInstantTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Test + public void test() { + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( ); + person.setName( "John Doe" ); + entityManager.persist( person ); + + return person; + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = entityManager.find( Person.class, _person.getId() ); + person.setName( person.getName().toUpperCase() ); + } ); + } + + //tag::locking-optimistic-entity-mapping-example[] + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "`name`") + private String name; + + //tag::locking-optimistic-version-number-example[] + @Version + private Instant version; + //end::locking-optimistic-version-number-example[] + + //Getters and setters are omitted for brevity + + //end::locking-optimistic-entity-mapping-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Instant getVersion() { + return version; + } + //tag::locking-optimistic-entity-mapping-example[] + } + //end::locking-optimistic-entity-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingTest.java index be748aa0c574..c42548460c91 100644 --- a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingTest.java @@ -27,8 +27,6 @@ */ public class OptimisticLockingTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( OptimisticLockingTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -60,6 +58,7 @@ public void test() { } ); } + //tag::locking-optimistic-entity-mapping-example[] @Entity(name = "Person") public static class Person { @@ -75,6 +74,9 @@ public static class Person { private long version; //end::locking-optimistic-version-number-example[] + //Getters and setters are omitted for brevity + + //end::locking-optimistic-entity-mapping-example[] public Long getId() { return id; } @@ -98,7 +100,9 @@ public long getVersion() { public void setVersion(long version) { this.version = version; } + //tag::locking-optimistic-entity-mapping-example[] } + //end::locking-optimistic-entity-mapping-example[] @Entity(name = "Phone") public static class Phone { @@ -145,9 +149,5 @@ public void setPerson(Person person) { public Date getVersion() { return version; } - - public void setVersion(Date version) { - this.version = version; - } } } diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingTimestampTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingTimestampTest.java new file mode 100644 index 000000000000..5a1e31d322b7 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/locking/OptimisticLockingTimestampTest.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.locking; + +import java.sql.Timestamp; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class OptimisticLockingTimestampTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Test + public void test() { + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( ); + person.setName( "John Doe" ); + entityManager.persist( person ); + + return person; + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = entityManager.find( Person.class, _person.getId() ); + person.setName( person.getName().toUpperCase() ); + } ); + } + + //tag::locking-optimistic-entity-mapping-example[] + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "`name`") + private String name; + + //tag::locking-optimistic-version-number-example[] + @Version + private Timestamp version; + //end::locking-optimistic-version-number-example[] + + //Getters and setters are omitted for brevity + + //end::locking-optimistic-entity-mapping-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Timestamp getVersion() { + return version; + } + //tag::locking-optimistic-entity-mapping-example[] + } + //end::locking-optimistic-entity-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/locking/VersionSourceTest.java b/documentation/src/test/java/org/hibernate/userguide/locking/VersionSourceTest.java new file mode 100644 index 000000000000..3b37e4601bd8 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/locking/VersionSourceTest.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 . + */ +package org.hibernate.userguide.locking; + +import java.util.Date; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.annotations.Source; +import org.hibernate.annotations.SourceType; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Vlad Mihalcea + */ +public class VersionSourceTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::locking-optimistic-version-timestamp-source-persist-example[] + Person person = new Person(); + person.setId( 1L ); + person.setFirstName( "John" ); + person.setLastName( "Doe" ); + assertNull( person.getVersion() ); + + entityManager.persist( person ); + assertNotNull( person.getVersion() ); + //end::locking-optimistic-version-timestamp-source-persist-example[] + } ); + } + + //tag::locking-optimistic-version-timestamp-source-mapping-example[] + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Version + @Source(value = SourceType.DB) + private Date version; + //end::locking-optimistic-version-timestamp-source-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getVersion() { + return version; + } + + //tag::locking-optimistic-version-timestamp-source-mapping-example[] + } + //end::locking-optimistic-version-timestamp-source-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/access/ElementCollectionAccessTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/access/ElementCollectionAccessTest.java new file mode 100644 index 000000000000..1368870be349 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/access/ElementCollectionAccessTest.java @@ -0,0 +1,131 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.access; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class ElementCollectionAccessTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.getAuthors().add( new Author( + "Vlad", + "Mihalcea" + ) ); + + entityManager.persist( book ); + } ); + } + + //tag::access-element-collection-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + @ElementCollection + @CollectionTable( + name = "book_author", + joinColumns = @JoinColumn(name = "book_id") + ) + private List authors = new ArrayList<>(); + + //Getters and setters are omitted for brevity + //end::access-element-collection-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List getAuthors() { + return authors; + } + //tag::access-element-collection-mapping-example[] + } + //end::access-element-collection-mapping-example[] + + //tag::access-embeddable-mapping-example[] + @Embeddable + @Access( AccessType.PROPERTY ) + public static class Author { + + private String firstName; + + private String lastName; + + public Author() { + } + + public Author(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + } + //end::access-embeddable-mapping-example[] + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/access/EmbeddableAccessTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/access/EmbeddableAccessTest.java new file mode 100644 index 000000000000..7fbc92996173 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/access/EmbeddableAccessTest.java @@ -0,0 +1,126 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.access; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class EmbeddableAccessTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( new Author( + "Vlad", + "Mihalcea" + ) ); + + entityManager.persist( book ); + } ); + } + + //tag::access-embedded-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + @Embedded + private Author author; + + //Getters and setters are omitted for brevity + //end::access-embedded-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + //tag::access-embedded-mapping-example[] + } + //end::access-embedded-mapping-example[] + + //tag::access-embeddable-mapping-example[] + @Embeddable + @Access( AccessType.PROPERTY ) + public static class Author { + + private String firstName; + + private String lastName; + + public Author() { + } + + public Author(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + } + //end::access-embeddable-mapping-example[] + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/access/FieldAccessTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/access/FieldAccessTest.java new file mode 100644 index 000000000000..b85c952489a3 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/access/FieldAccessTest.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.userguide.mapping.access; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class FieldAccessTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + } + + //tag::access-field-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::access-field-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::access-field-mapping-example[] + } + //end::access-field-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/access/PropertyAccessOverrideTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/access/PropertyAccessOverrideTest.java new file mode 100644 index 000000000000..844bddf9652c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/access/PropertyAccessOverrideTest.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.userguide.mapping.access; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class PropertyAccessOverrideTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + } + + //tag::access-property-override-mapping-example[] + @Entity(name = "Book") + public static class Book { + + private Long id; + + private String title; + + private String author; + + @Access( AccessType.FIELD ) + @Version + private int version; + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + } + //end::access-property-override-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/access/PropertyAccessTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/access/PropertyAccessTest.java new file mode 100644 index 000000000000..0a5a752127c5 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/access/PropertyAccessTest.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.access; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class PropertyAccessTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + } + + //tag::access-property-mapping-example[] + @Entity(name = "Book") + public static class Book { + + private Long id; + + private String title; + + private String author; + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + } + //end::access-property-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java new file mode 100644 index 000000000000..43eeb43f084c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.basic; + +import java.util.BitSet; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class BitSetTypeDefTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + + //tag::basic-custom-type-BitSetTypeDef-persistence-example[] + BitSet bitSet = BitSet.valueOf( new long[] {1, 2, 3} ); + + doInHibernate( this::sessionFactory, session -> { + Product product = new Product( ); + product.setId( 1 ); + product.setBitSet( bitSet ); + session.persist( product ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Product product = session.get( Product.class, 1 ); + assertEquals(bitSet, product.getBitSet()); + } ); + //end::basic-custom-type-BitSetTypeDef-persistence-example[] + } + + //tag::basic-custom-type-BitSetTypeDef-mapping-example[] + @Entity(name = "Product") + @TypeDef( + name = "bitset", + defaultForType = BitSet.class, + typeClass = BitSetType.class + ) + public static class Product { + + @Id + private Integer id; + + private BitSet bitSet; + + //Getters and setters are omitted for brevity + //end::basic-custom-type-BitSetTypeDef-mapping-example[] + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public BitSet getBitSet() { + return bitSet; + } + + public void setBitSet(BitSet bitSet) { + this.bitSet = bitSet; + } + //tag::basic-custom-type-BitSetTypeDef-mapping-example[] + } + //end::basic-custom-type-BitSetTypeDef-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java index 9d76689f7c7a..d7e426463351 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java @@ -76,6 +76,9 @@ public Integer getId() { return id; } + //Getters and setters are omitted for brevity + //end::basic-custom-type-BitSetType-mapping-example[] + public void setId(Integer id) { this.id = id; } @@ -87,6 +90,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetType-mapping-example[] } //end::basic-custom-type-BitSetType-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserType.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserType.java index 00bb29433484..f70cdaf47efa 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserType.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserType.java @@ -32,7 +32,7 @@ public int[] sqlTypes() { @Override public Class returnedClass() { - return String.class; + return BitSet.class; } @Override 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 d512686cf59a..a0c7fd7293fe 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,8 +7,12 @@ package org.hibernate.userguide.mapping.basic; import java.util.BitSet; +import javax.persistence.ColumnResult; +import javax.persistence.ConstructorResult; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.NamedNativeQuery; +import javax.persistence.SqlResultSetMapping; import org.hibernate.annotations.Type; import org.hibernate.cfg.Configuration; @@ -60,6 +64,52 @@ public void test() { } ); } + @Test + public void testNativeQuery() { + BitSet bitSet = BitSet.valueOf( new long[] {1, 2, 3} ); + + doInHibernate( this::sessionFactory, session -> { + Product product = new Product( ); + product.setId( 1 ); + product.setBitSet( bitSet ); + session.persist( product ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Product product = (Product) session.getNamedNativeQuery( + "find_person_by_bitset") + .setParameter( "id", 1L) + .getSingleResult(); + + assertEquals(bitSet, product.getBitSet()); + } ); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @NamedNativeQuery( + name = "find_person_by_bitset", + query = + "SELECT " + + " pr.id AS \"pr.id\", " + + " pr.bitset AS \"pr.bitset\" " + + "FROM Product pr " + + "WHERE pr.id = :id", + resultSetMapping = "Person" + ) + @SqlResultSetMapping( + name = "Person", + classes = @ConstructorResult( + targetClass = Product.class, + columns = { + @ColumnResult(name = "pr.id"), + @ColumnResult(name = "pr.bitset", type = BitSetUserType.class) + } + ) + ) //tag::basic-custom-type-BitSetUserType-mapping-example[] @Entity(name = "Product") public static class Product { @@ -70,6 +120,16 @@ public static class Product { @Type( type = "bitset" ) private BitSet bitSet; + //Constructors, getters, and setters are omitted for brevity + //end::basic-custom-type-BitSetUserType-mapping-example[] + public Product() { + } + + public Product(Number id, BitSet bitSet) { + this.id = id.intValue(); + this.bitSet = bitSet; + } + public Integer getId() { return id; } @@ -85,6 +145,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetUserType-mapping-example[] } //end::basic-custom-type-BitSetUserType-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobByteArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobByteArrayTest.java index 2eea0faa708f..d8d390838ed0 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobByteArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobByteArrayTest.java @@ -22,70 +22,71 @@ */ public class BlobByteArrayTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Product.class - }; - } - - @Test - public void test() { - Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { - final Product product = new Product( ); - product.setId( 1 ); - product.setName( "Mobile phone" ); - product.setImage( new byte[] {1, 2, 3} ); - - entityManager.persist( product ); - return product.getId(); - } ); - doInJPA( this::entityManagerFactory, entityManager -> { - Product product = entityManager.find( Product.class, productId ); - assertArrayEquals( new byte[] {1, 2, 3}, product.getImage() ); - } ); - } - - //tag::basic-blob-byte-array-example[] - @Entity(name = "Product") - public static class Product { - - @Id - private Integer id; - - private String name; - - @Lob - private byte[] image; - - //Getters and setters are omitted for brevity - - //end::basic-blob-byte-array-example[] - 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 byte[] getImage() { - return image; - } - - public void setImage(byte[] image) { - this.image = image; - } - - //tag::basic-blob-byte-array-example[] - } - //end::basic-blob-byte-array-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { + final Product product = new Product( ); + product.setId( 1 ); + product.setName( "Mobile phone" ); + product.setImage( new byte[] {1, 2, 3} ); + + entityManager.persist( product ); + return product.getId(); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = entityManager.find( Product.class, productId ); + + assertArrayEquals( new byte[] {1, 2, 3}, product.getImage() ); + } ); + } + + //tag::basic-blob-byte-array-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Integer id; + + private String name; + + @Lob + private byte[] image; + + //Getters and setters are omitted for brevity + + //end::basic-blob-byte-array-example[] + 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 byte[] getImage() { + return image; + } + + public void setImage(byte[] image) { + this.image = image; + } + + //tag::basic-blob-byte-array-example[] + } + //end::basic-blob-byte-array-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobTest.java index 1ec91355682a..834ebb512803 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BlobTest.java @@ -30,103 +30,104 @@ */ public class BlobTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Product.class - }; - } - - @Test - public void test() { - Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { - Session session = entityManager.unwrap( Session.class ); - - //tag::basic-blob-persist-example[] - byte[] image = new byte[] {1, 2, 3}; - - final Product product = new Product(); - product.setId( 1 ); - product.setName( "Mobile phone" ); - - session.doWork( connection -> { - product.setImage( BlobProxy.generateProxy( image ) ); - } ); - - entityManager.persist( product ); - //end::basic-blob-persist-example[] - - return product.getId(); - } ); - doInJPA( this::entityManagerFactory, entityManager -> { - try { - //tag::basic-blob-find-example[] - - Product product = entityManager.find( Product.class, productId ); - - try (InputStream inputStream = product.getImage().getBinaryStream()) { - assertArrayEquals(new byte[] {1, 2, 3}, toBytes( inputStream ) ); - } - //end::basic-blob-find-example[] - } - catch (Exception e) { - fail( e.getMessage() ); - } - } ); - } - - private byte[] toBytes(InputStream inputStream) throws IOException { - BufferedInputStream bufferedInputStream = new BufferedInputStream( inputStream); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - int result = bufferedInputStream.read(); - while(result != -1) { - byteArrayOutputStream.write((byte) result); - result = bufferedInputStream.read(); - } - return byteArrayOutputStream.toByteArray(); - } - - - //tag::basic-blob-example[] - @Entity(name = "Product") - public static class Product { - - @Id - private Integer id; - - private String name; - - @Lob - private Blob image; - - //Getters and setters are omitted for brevity - - //end::basic-blob-example[] - 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 Blob getImage() { - return image; - } - - public void setImage(Blob image) { - this.image = image; - } - - //tag::basic-blob-example[] - } - //end::basic-blob-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + + //tag::basic-blob-persist-example[] + byte[] image = new byte[] {1, 2, 3}; + + final Product product = new Product(); + product.setId( 1 ); + product.setName( "Mobile phone" ); + + product.setImage( BlobProxy.generateProxy( image ) ); + + entityManager.persist( product ); + //end::basic-blob-persist-example[] + + return product.getId(); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + try { + //tag::basic-blob-find-example[] + + Product product = entityManager.find( Product.class, productId ); + + try (InputStream inputStream = product.getImage().getBinaryStream()) { + assertArrayEquals(new byte[] {1, 2, 3}, toBytes( inputStream ) ); + } + //end::basic-blob-find-example[] + } + catch (Exception e) { + fail( e.getMessage() ); + } + } ); + } + + private byte[] toBytes(InputStream inputStream) throws IOException { + BufferedInputStream bufferedInputStream = new BufferedInputStream( inputStream); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + int result = bufferedInputStream.read(); + + while(result != -1) { + byteArrayOutputStream.write((byte) result); + result = bufferedInputStream.read(); + } + + return byteArrayOutputStream.toByteArray(); + } + + + //tag::basic-blob-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Integer id; + + private String name; + + @Lob + private Blob image; + + //Getters and setters are omitted for brevity + + //end::basic-blob-example[] + 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 Blob getImage() { + return image; + } + + public void setImage(Blob image) { + this.image = image; + } + + //tag::basic-blob-example[] + } + //end::basic-blob-example[] } 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 bf51ce215115..503413c63659 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 @@ -30,102 +30,104 @@ */ public class ClobTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Product.class - }; - } - - @Test - public void test() { - Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { - Session session = entityManager.unwrap( Session.class ); - - //tag::basic-clob-persist-example[] - String warranty = "My product warranty"; - - final Product product = new Product(); - product.setId( 1 ); - product.setName( "Mobile phone" ); - - session.doWork( connection -> { - product.setWarranty( ClobProxy.generateProxy( warranty ) ); - } ); - - entityManager.persist( product ); - //end::basic-clob-persist-example[] - - return product.getId(); - } ); - doInJPA( this::entityManagerFactory, entityManager -> { - try { - //tag::basic-clob-find-example[] - - Product product = entityManager.find( Product.class, productId ); - try (Reader reader = product.getWarranty().getCharacterStream()) { - assertEquals( "My product warranty", toString( reader ) ); - } - //end::basic-clob-find-example[] - } - catch (Exception e) { - fail( e.getMessage() ); - } - } ); - } - - private String toString(Reader reader) throws IOException { - BufferedReader bufferedReader = new BufferedReader( reader); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - int result = bufferedReader.read(); - while(result != -1) { - byteArrayOutputStream.write((byte) result); - result = bufferedReader.read(); - } - return byteArrayOutputStream.toString(); - } - - - //tag::basic-clob-example[] - @Entity(name = "Product") - public static class Product { - - @Id - private Integer id; - - private String name; - - @Lob - private Clob warranty; - - //Getters and setters are omitted for brevity - - //end::basic-clob-example[] - 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 Clob getWarranty() { - return warranty; - } - - public void setWarranty(Clob warranty) { - this.warranty = warranty; - } - - //tag::basic-clob-example[] - } - //end::basic-clob-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + + //tag::basic-clob-persist-example[] + String warranty = "My product warranty"; + + final Product product = new Product(); + product.setId( 1 ); + product.setName( "Mobile phone" ); + + product.setWarranty( ClobProxy.generateProxy( warranty ) ); + + entityManager.persist( product ); + //end::basic-clob-persist-example[] + + return product.getId(); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + try { + //tag::basic-clob-find-example[] + + Product product = entityManager.find( Product.class, productId ); + + try (Reader reader = product.getWarranty().getCharacterStream()) { + assertEquals( "My product warranty", toString( reader ) ); + } + //end::basic-clob-find-example[] + } + catch (Exception e) { + fail( e.getMessage() ); + } + } ); + } + + private String toString(Reader reader) throws IOException { + BufferedReader bufferedReader = new BufferedReader( reader); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + int result = bufferedReader.read(); + + while(result != -1) { + byteArrayOutputStream.write((byte) result); + result = bufferedReader.read(); + } + + return byteArrayOutputStream.toString(); + } + + + //tag::basic-clob-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Integer id; + + private String name; + + @Lob + private Clob warranty; + + //Getters and setters are omitted for brevity + + //end::basic-clob-example[] + 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 Clob getWarranty() { + return warranty; + } + + public void setWarranty(Clob warranty) { + this.warranty = warranty; + } + + //tag::basic-clob-example[] + } + //end::basic-clob-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java index c80bc2fde0b5..83c3466bcfe7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -35,182 +36,190 @@ */ public class FilterJoinTableTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FilterJoinTableTest.class ); - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Client.class, - Account.class - }; - } - - @Test - public void testLifecycle() { - //tag::mapping-filter-join-table-persistence-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - - Client client = new Client(); - client.setId( 1L ); - client.setName( "John Doe" ); - entityManager.persist( client ); - - Account account1 = new Account( ); - account1.setId( 1L ); - account1.setType( AccountType.CREDIT ); - account1.setAmount( 5000d ); - account1.setRate( 1.25 / 100 ); - account1.setActive( true ); - client.getAccounts().add( account1 ); - entityManager.persist( account1 ); - - Account account2 = new Account( ); - account2.setId( 2L ); - account2.setType( AccountType.DEBIT ); - account2.setAmount( 0d ); - account2.setRate( 1.05 / 100 ); - account2.setActive( false ); - client.getAccounts().add( account2 ); - entityManager.persist( account2 ); - - Account account3 = new Account( ); - account3.setType( AccountType.DEBIT ); - account3.setId( 3L ); - account3.setAmount( 250d ); - account3.setRate( 1.05 / 100 ); - account3.setActive( true ); - client.getAccounts().add( account3 ); - entityManager.persist( account3 ); - } ); - //end::mapping-filter-join-table-persistence-example[] - - //tag::mapping-filter-join-table-collection-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 3, client.getAccounts().size()); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "firstAccounts"); - - Client client = entityManager.find( Client.class, 1L ); - - entityManager - .unwrap( Session.class ) - .enableFilter( "firstAccounts" ) - .setParameter( "maxOrderId", 1); - - assertEquals( 2, client.getAccounts().size()); - } ); - //end::mapping-filter-join-table-collection-query-example[] - } - - //tag::mapping-filter-join-table-example[] - public enum AccountType { - DEBIT, - CREDIT - } - - @Entity(name = "Client") - @FilterDef(name="firstAccounts", parameters=@ParamDef( name="maxOrderId", type="int" ) ) - @Filter(name="firstAccounts", condition="order_id <= :maxOrderId") - public static class Client { - - @Id - private Long id; - - private String name; - - @OneToMany - @OrderColumn(name = "order_id") - @FilterJoinTable(name="firstAccounts", condition="order_id <= :maxOrderId") - private List accounts = new ArrayList<>( ); - - //Getters and setters omitted for brevity - - //end::mapping-filter-join-table-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getAccounts() { - return accounts; - } - //tag::mapping-filter-join-table-example[] - } - - @Entity(name = "Account") - public static class Account { - - @Id - private Long id; - - @Column(name = "account_type") - @Enumerated(EnumType.STRING) - private AccountType type; - - private Double amount; - - private Double rate; - - private boolean active; - - //Getters and setters omitted for brevity - - //end::mapping-filter-join-table-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public AccountType getType() { - return type; - } - - public void setType(AccountType type) { - this.type = type; - } - - public Double getAmount() { - return amount; - } - - public void setAmount(Double amount) { - this.amount = amount; - } - - public Double getRate() { - return rate; - } - - public void setRate(Double rate) { - this.rate = rate; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - //tag::mapping-filter-join-table-example[] - } - //end::mapping-filter-join-table-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-filter-join-table-persistence-example[] + Client client = new Client() + .setId( 1L ) + .setName( "John Doe" ); + + client.addAccount( + new Account() + .setId( 1L ) + .setType( AccountType.CREDIT ) + .setAmount( 5000d ) + .setRate( 1.25 / 100 ) + ); + + client.addAccount( + new Account() + .setId( 2L ) + .setType( AccountType.DEBIT ) + .setAmount( 0d ) + .setRate( 1.05 / 100 ) + ); + + client.addAccount( + new Account() + .setType( AccountType.DEBIT ) + .setId( 3L ) + .setAmount( 250d ) + .setRate( 1.05 / 100 ) + ); + + entityManager.persist( client ); + //end::mapping-filter-join-table-persistence-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-join-table-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 3, client.getAccounts().size()); + //end::mapping-no-filter-join-table-collection-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "firstAccounts"); + + //tag::mapping-filter-join-table-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + entityManager + .unwrap( Session.class ) + .enableFilter( "firstAccounts" ) + .setParameter( "maxOrderId", 1); + + assertEquals( 2, client.getAccounts().size()); + //end::mapping-filter-join-table-collection-query-example[] + } ); + } + + public enum AccountType { + DEBIT, + CREDIT + } + + //tag::mapping-filter-join-table-example[] + @Entity(name = "Client") + @FilterDef( + name="firstAccounts", + parameters=@ParamDef( + name="maxOrderId", + type="int" + ) + ) + @Filter( + name="firstAccounts", + condition="order_id <= :maxOrderId" + ) + public static class Client { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @OrderColumn(name = "order_id") + @FilterJoinTable( + name="firstAccounts", + condition="order_id <= :maxOrderId" + ) + private List accounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + //end::mapping-filter-join-table-example[] + public Long getId() { + return id; + } + + public Client setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Client setName(String name) { + this.name = name; + return this; + } + + public List getAccounts() { + return accounts; + } + //tag::mapping-filter-join-table-example[] + + public void addAccount(Account account) { + this.accounts.add( account ); + } + } + + @Entity(name = "Account") + public static class Account { + + @Id + private Long id; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + //Getters and setters omitted for brevity + //end::mapping-filter-join-table-example[] + public Long getId() { + return id; + } + + public Account setId(Long id) { + this.id = id; + return this; + } + + public AccountType getType() { + return type; + } + + public Account setType(AccountType type) { + this.type = type; + return this; + } + + public Double getAmount() { + return amount; + } + + public Account setAmount(Double amount) { + this.amount = amount; + return this; + } + + public Double getRate() { + return rate; + } + + public Account setRate(Double rate) { + this.rate = rate; + return this; + } + + //tag::mapping-filter-join-table-example[] + } + //end::mapping-filter-join-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterSqlFragementAliasTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterSqlFragementAliasTest.java new file mode 100644 index 000000000000..0487bf88c53d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterSqlFragementAliasTest.java @@ -0,0 +1,174 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.basic; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.NoResultException; +import javax.persistence.OneToMany; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; + +import org.hibernate.Session; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.ParamDef; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SqlFragmentAlias; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class FilterSqlFragementAliasTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Account.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + + Account account1 = new Account( ); + account1.setId( 1L ); + account1.setAmount( 5000d ); + account1.setRate( 1.25 / 100 ); + account1.setActive( true ); + entityManager.persist( account1 ); + + Account account2 = new Account( ); + account2.setId( 2L ); + account2.setAmount( 0d ); + account2.setRate( 1.05 / 100 ); + account2.setActive( false ); + entityManager.persist( account2 ); + + Account account3 = new Account( ); + account3.setId( 3L ); + account3.setAmount( 250d ); + account3.setRate( 1.05 / 100 ); + account3.setActive( true ); + entityManager.persist( account3 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + //tag::mapping-filter-sql-fragment-alias-query-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + //end::mapping-filter-sql-fragment-alias-query-example[] + assertEquals( 2, accounts.size()); + } ); + } + + //tag::mapping-filter-sql-fragment-alias-example[] + @Entity(name = "Account") + @Table(name = "account") + @SecondaryTable( + name = "account_details" + ) + @SQLDelete( + sql = "UPDATE account_details SET deleted = true WHERE id = ? " + ) + @FilterDef( + name="activeAccount", + parameters = @ParamDef( + name="active", + type="boolean" + ) + ) + @Filter( + name="activeAccount", + condition="{a}.active = :active and {ad}.deleted = false", + aliases = { + @SqlFragmentAlias( alias = "a", table= "account"), + @SqlFragmentAlias( alias = "ad", table= "account_details"), + } + ) + public static class Account { + + @Id + private Long id; + + private Double amount; + + private Double rate; + + private boolean active; + + @Column(table = "account_details") + private boolean deleted; + + //Getters and setters omitted for brevity + + //end::mapping-filter-sql-fragment-alias-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Double getAmount() { + return amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public Double getRate() { + return rate; + } + + public void setRate(Double rate) { + this.rate = rate; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + //tag::mapping-filter-sql-fragment-alias-example[] + } + //end::mapping-filter-sql-fragment-alias-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java index c050a51f43e4..f678989df3e9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java @@ -8,10 +8,12 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.NoResultException; @@ -39,270 +41,301 @@ */ public class FilterTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( FilterTest.class ); - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Client.class, - Account.class - }; - } - - @Test - public void testLifecycle() { - //tag::mapping-filter-persistence-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - - Client client = new Client(); - client.setId( 1L ); - client.setName( "John Doe" ); - entityManager.persist( client ); - - Account account1 = new Account( ); - account1.setId( 1L ); - account1.setType( AccountType.CREDIT ); - account1.setAmount( 5000d ); - account1.setRate( 1.25 / 100 ); - account1.setActive( true ); - account1.setClient( client ); - client.getAccounts().add( account1 ); - entityManager.persist( account1 ); - - Account account2 = new Account( ); - account2.setId( 2L ); - account2.setType( AccountType.DEBIT ); - account2.setAmount( 0d ); - account2.setRate( 1.05 / 100 ); - account2.setActive( false ); - account2.setClient( client ); - client.getAccounts().add( account2 ); - entityManager.persist( account2 ); - - Account account3 = new Account( ); - account3.setType( AccountType.DEBIT ); - account3.setId( 3L ); - account3.setAmount( 250d ); - account3.setRate( 1.05 / 100 ); - account3.setActive( true ); - account3.setClient( client ); - client.getAccounts().add( account3 ); - entityManager.persist( account3 ); - } ); - //end::mapping-filter-persistence-example[] - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account1 = entityManager.find( Account.class, 1L ); - Account account2 = entityManager.find( Account.class, 2L ); - - assertNotNull( account1 ); - assertNotNull( account2 ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account1 = entityManager.createQuery( - "select a from Account a where a.id = :id", Account.class) - .setParameter( "id", 1L ) - .getSingleResult(); - assertNotNull( account1 ); - try { - Account account2 = entityManager.createQuery( - "select a from Account a where a.id = :id", Account.class) - .setParameter( "id", 2L ) - .getSingleResult(); - } - catch (NoResultException expected) { - expected.fillInStackTrace(); - } - } ); - - //tag::mapping-filter-entity-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account = entityManager.find( Account.class, 2L ); - assertFalse( account.isActive() ); - } ); - //end::mapping-filter-entity-example[] - - // tag::mapping-filter-entity-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - List accounts = entityManager.createQuery( - "select a from Account a", Account.class) - .getResultList(); - assertEquals( 3, accounts.size()); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - List accounts = entityManager.createQuery( - "select a from Account a", Account.class) - .getResultList(); - assertEquals( 2, accounts.size()); - } ); - //end::mapping-filter-entity-query-example[] - - //tag::mapping-filter-collection-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 3, client.getAccounts().size() ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 2, client.getAccounts().size() ); - } ); - //end::mapping-filter-collection-query-example[] - } - - //tag::mapping-filter-example[] - public enum AccountType { - DEBIT, - CREDIT - } - - @Entity(name = "Client") - public static class Client { - - @Id - private Long id; - - private String name; - - @OneToMany(mappedBy = "client") - @Filter(name="activeAccount", condition="active = :active") - private List accounts = new ArrayList<>( ); - - //Getters and setters omitted for brevity - - //end::mapping-filter-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getAccounts() { - return accounts; - } - //tag::mapping-filter-example[] - } - - @Entity(name = "Account") - @FilterDef(name="activeAccount", parameters=@ParamDef( name="active", type="boolean" ) ) - @Filter(name="activeAccount", condition="active = :active") - public static class Account { - - @Id - private Long id; - - @ManyToOne - private Client client; - - @Column(name = "account_type") - @Enumerated(EnumType.STRING) - private AccountType type; - - private Double amount; - - private Double rate; - - private boolean active; - - //Getters and setters omitted for brevity - - //end::mapping-filter-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Client getClient() { - return client; - } - - public void setClient(Client client) { - this.client = client; - } - - public AccountType getType() { - return type; - } - - public void setType(AccountType type) { - this.type = type; - } - - public Double getAmount() { - return amount; - } - - public void setAmount(Double amount) { - this.amount = amount; - } - - public Double getRate() { - return rate; - } - - public void setRate(Double rate) { - this.rate = rate; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - //tag::mapping-filter-example[] - } - //end::mapping-filter-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::mapping-filter-persistence-example[] + Client client = new Client() + .setId( 1L ) + .setName( "John Doe" ); + + client.addAccount( + new Account() + .setId( 1L ) + .setType( AccountType.CREDIT ) + .setAmount( 5000d ) + .setRate( 1.25 / 100 ) + .setActive( true ) + ); + + client.addAccount( + new Account() + .setId( 2L ) + .setType( AccountType.DEBIT ) + .setAmount( 0d ) + .setRate( 1.05 / 100 ) + .setActive( false ) + ); + + client.addAccount( + new Account() + .setType( AccountType.DEBIT ) + .setId( 3L ) + .setAmount( 250d ) + .setRate( 1.05 / 100 ) + .setActive( true ) + ); + + entityManager.persist( client ); + //end::mapping-filter-persistence-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account1 = entityManager.find( Account.class, 1L ); + Account account2 = entityManager.find( Account.class, 2L ); + + assertNotNull( account1 ); + assertNotNull( account2 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account1 = entityManager.createQuery( + "select a from Account a where a.id = :id", Account.class) + .setParameter( "id", 1L ) + .getSingleResult(); + assertNotNull( account1 ); + try { + Account account2 = entityManager.createQuery( + "select a from Account a where a.id = :id", Account.class) + .setParameter( "id", 2L ) + .getSingleResult(); + } + catch (NoResultException expected) { + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + //tag::mapping-filter-entity-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account = entityManager.find( Account.class, 2L ); + + assertFalse( account.isActive() ); + //end::mapping-filter-entity-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-entity-query-example[] + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + + assertEquals( 3, accounts.size()); + //end::mapping-no-filter-entity-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + //tag::mapping-filter-entity-query-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + + assertEquals( 2, accounts.size()); + //end::mapping-filter-entity-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 3, client.getAccounts().size() ); + //end::mapping-no-filter-collection-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + //tag::mapping-filter-collection-query-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 2, client.getAccounts().size() ); + //end::mapping-filter-collection-query-example[] + } ); + } + + public enum AccountType { + DEBIT, + CREDIT + } + + //tag::mapping-filter-Client-example[] + @Entity(name = "Client") + public static class Client { + + @Id + private Long id; + + private String name; + + @OneToMany( + mappedBy = "client", + cascade = CascadeType.ALL + ) + @Filter( + name="activeAccount", + condition="active_status = :active" + ) + private List accounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + //end::mapping-filter-Client-example[] + public Long getId() { + return id; + } + + public Client setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Client setName(String name) { + this.name = name; + return this; + } + + public List getAccounts() { + return accounts; + } + //tag::mapping-filter-Client-example[] + + public void addAccount(Account account) { + account.setClient( this ); + this.accounts.add( account ); + } + } + //end::mapping-filter-Client-example[] + + //tag::mapping-filter-Account-example[] + @Entity(name = "Account") + @FilterDef( + name="activeAccount", + parameters = @ParamDef( + name="active", + type="boolean" + ) + ) + @Filter( + name="activeAccount", + condition="active_status = :active" + ) + public static class Account { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Client client; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + @Column(name = "active_status") + private boolean active; + + //Getters and setters omitted for brevity + //end::mapping-filter-Account-example[] + public Long getId() { + return id; + } + + public Account setId(Long id) { + this.id = id; + return this; + } + + public Client getClient() { + return client; + } + + public Account setClient(Client client) { + this.client = client; + return this; + } + + public AccountType getType() { + return type; + } + + public Account setType(AccountType type) { + this.type = type; + return this; + } + + public Double getAmount() { + return amount; + } + + public Account setAmount(Double amount) { + this.amount = amount; + return this; + } + + public Double getRate() { + return rate; + } + + public Account setRate(Double rate) { + this.rate = rate; + return this; + } + + public boolean isActive() { + return active; + } + + public Account setActive(boolean active) { + this.active = active; + return this; + } + + //tag::mapping-filter-Account-example[] + } + //end::mapping-filter-Account-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java index 81aa68ae5b35..0ed53059ab95 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java @@ -31,8 +31,6 @@ */ public class JoinColumnOrFormulaTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( JoinColumnOrFormulaTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -166,8 +164,11 @@ public void setCountry(Country country) { this.country = country; } - //tag::mapping-JoinColumnOrFormula-example[] + //tag::mapping-JoinColumnOrFormula-example[] } + //end::mapping-JoinColumnOrFormula-example[] + + //tag::mapping-JoinColumnOrFormula-example[] @Entity(name = "Country") @Table(name = "countries") @@ -183,6 +184,10 @@ public static class Country implements Serializable { @Column(name = "is_default") private boolean _default; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::mapping-JoinColumnOrFormula-example[] + public int getId() { return id; } @@ -231,6 +236,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId() ); } + //tag::mapping-JoinColumnOrFormula-example[] } //end::mapping-JoinColumnOrFormula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java index 796da0bab669..53c129cf9eab 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java @@ -30,8 +30,6 @@ @RequiresDialect(PostgreSQL82Dialect.class) public class JoinFormulaTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( JoinFormulaTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -145,6 +143,9 @@ public Country getCountry() { //tag::mapping-JoinFormula-example[] } + //end::mapping-JoinFormula-example[] + + //tag::mapping-JoinFormula-example[] @Entity(name = "Country") @Table(name = "countries") @@ -155,6 +156,10 @@ public static class Country { private String name; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::mapping-JoinFormula-example[] + public int getId() { return id; } @@ -187,6 +192,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId() ); } + //tag::mapping-JoinFormula-example[] } //end::mapping-JoinFormula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/LocalDateTimeWithTemporalTimeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/LocalDateTimeWithTemporalTimeTest.java index 68bb84bb36b5..e4ad710ffc4b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/LocalDateTimeWithTemporalTimeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/LocalDateTimeWithTemporalTimeTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; /** * @author Vlad Mihalcea @@ -34,15 +35,19 @@ protected Class[] getAnnotatedClasses() { public void testLifecycle() { doInJPA( this::entityManagerFactory, entityManager -> { DateEvent dateEvent = new DateEvent( LocalDateTime.now() ); + dateEvent.id = 1L; entityManager.persist( dateEvent ); } ); + doInJPA( this::entityManagerFactory, entityManager -> { + DateEvent dateEvent = entityManager.find( DateEvent.class, 1L ); + assertNotNull(dateEvent.getTimestamp()); + } ); } @Entity(name = "DateEvent") public static class DateEvent { @Id - @GeneratedValue private Long id; //throws org.hibernate.AnnotationException: @Temporal should only be set on a java.util.Date or java.util.Calendar property 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 144c1eaa25dd..a5110d5f8bad 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 @@ -25,79 +25,80 @@ * @author Vlad Mihalcea */ @SkipForDialect( - value = { - PostgreSQL81Dialect.class, - MySQL5Dialect.class - }, - comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695" + value = { + PostgreSQL81Dialect.class, + MySQL5Dialect.class + }, + comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695" ) public class NClobCharArrayTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Product.class - }; - } - - @Test - public void test() { - Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { - final Product product = new Product( ); - product.setId( 1 ); - product.setName( "Mobile phone" ); - product.setWarranty( "My product warranty".toCharArray() ); - - entityManager.persist( product ); - return product.getId(); - } ); - doInJPA( this::entityManagerFactory, entityManager -> { - Product product = entityManager.find( Product.class, productId ); - assertArrayEquals( "My product warranty".toCharArray(), product.getWarranty() ); - } ); - } - - //tag::basic-nclob-char-array-example[] - @Entity(name = "Product") - public static class Product { - - @Id - private Integer id; - - private String name; - - @Lob - @Nationalized - private char[] warranty; - - //Getters and setters are omitted for brevity - - //end::basic-nclob-char-array-example[] - 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 char[] getWarranty() { - return warranty; - } - - public void setWarranty(char[] warranty) { - this.warranty = warranty; - } - - //tag::basic-nclob-char-array-example[] - } - //end::basic-nclob-char-array-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { + final Product product = new Product( ); + product.setId( 1 ); + product.setName( "Mobile phone" ); + product.setWarranty( "My product warranty".toCharArray() ); + + entityManager.persist( product ); + return product.getId(); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = entityManager.find( Product.class, productId ); + + assertArrayEquals( "My product warranty".toCharArray(), product.getWarranty() ); + } ); + } + + //tag::basic-nclob-char-array-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Integer id; + + private String name; + + @Lob + @Nationalized + private char[] warranty; + + //Getters and setters are omitted for brevity + + //end::basic-nclob-char-array-example[] + 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 char[] getWarranty() { + return warranty; + } + + public void setWarranty(char[] warranty) { + this.warranty = warranty; + } + + //tag::basic-nclob-char-array-example[] + } + //end::basic-nclob-char-array-example[] } 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 c03cd703bf1d..561d3922847b 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 @@ -25,79 +25,80 @@ * @author Vlad Mihalcea */ @SkipForDialect( - value = { - PostgreSQL81Dialect.class, - MySQL5Dialect.class - }, - comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695" + value = { + PostgreSQL81Dialect.class, + MySQL5Dialect.class + }, + comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695" ) public class NClobStringTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Product.class - }; - } - - @Test - public void test() { - Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { - final Product product = new Product( ); - product.setId( 1 ); - product.setName( "Mobile phone" ); - product.setWarranty( "My product warranty" ); - - entityManager.persist( product ); - return product.getId(); - } ); - doInJPA( this::entityManagerFactory, entityManager -> { - Product product = entityManager.find( Product.class, productId ); - assertEquals( "My product warranty", product.getWarranty() ); - } ); - } - - //tag::basic-nclob-string-example[] - @Entity(name = "Product") - public static class Product { - - @Id - private Integer id; - - private String name; - - @Lob - @Nationalized - private String warranty; - - //Getters and setters are omitted for brevity - - //end::basic-nclob-string-example[] - 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 getWarranty() { - return warranty; - } - - public void setWarranty(String warranty) { - this.warranty = warranty; - } - - //tag::basic-nclob-string-example[] - } - //end::basic-nclob-string-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { + final Product product = new Product( ); + product.setId( 1 ); + product.setName( "Mobile phone" ); + product.setWarranty( "My product warranty" ); + + entityManager.persist( product ); + return product.getId(); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = entityManager.find( Product.class, productId ); + + assertEquals( "My product warranty", product.getWarranty() ); + } ); + } + + //tag::basic-nclob-string-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Integer id; + + private String name; + + @Lob + @Nationalized + private String warranty; + + //Getters and setters are omitted for brevity + + //end::basic-nclob-string-example[] + 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 getWarranty() { + return warranty; + } + + public void setWarranty(String warranty) { + this.warranty = warranty; + } + + //tag::basic-nclob-string-example[] + } + //end::basic-nclob-string-example[] } 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 97339aeb46c5..d04ddc4eb293 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 @@ -17,8 +17,10 @@ import org.hibernate.Session; import org.hibernate.annotations.Nationalized; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.engine.jdbc.NClobProxy; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -32,114 +34,116 @@ * @author Vlad Mihalcea */ @SkipForDialect( - value = { - PostgreSQL81Dialect.class, - MySQL5Dialect.class - }, - comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695" + value = { + PostgreSQL81Dialect.class, + MySQL5Dialect.class, + AbstractHANADialect.class + }, + comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695" ) public class NClobTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Product.class - }; - } - - @Test - public void test() { - Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { - Session session = entityManager.unwrap( Session.class ); - - //tag::basic-nclob-persist-example[] - String warranty = "My product warranty"; - - final Product product = new Product(); - product.setId( 1 ); - product.setName( "Mobile phone" ); - - session.doWork( connection -> { - product.setWarranty( connection.createNClob() ); - product.getWarranty().setString( 1, warranty ); - } ); - - entityManager.persist( product ); - //end::basic-nclob-persist-example[] - - return product.getId(); - } ); - doInJPA( this::entityManagerFactory, entityManager -> { - try { - //tag::basic-nclob-find-example[] - - Product product = entityManager.find( Product.class, productId ); - try (Reader reader = product.getWarranty().getCharacterStream()) { - assertEquals( "My product warranty", toString( reader ) ); - } - //end::basic-nclob-find-example[] - } - catch (Exception e) { - fail( e.getMessage() ); - } - } ); - } - - private String toString(Reader reader) throws IOException { - BufferedReader bufferedReader = new BufferedReader( reader); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - int result = bufferedReader.read(); - while(result != -1) { - byteArrayOutputStream.write((byte) result); - result = bufferedReader.read(); - } - return byteArrayOutputStream.toString(); - } - - - //tag::basic-nclob-example[] - @Entity(name = "Product") - public static class Product { - - @Id - private Integer id; - - private String name; - - @Lob - @Nationalized - // Clob also works, because NClob extends Clob. - // The database type is still NCLOB either way and handled as such. - private NClob warranty; - - //Getters and setters are omitted for brevity - - //end::basic-nclob-example[] - 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 NClob getWarranty() { - return warranty; - } - - public void setWarranty(NClob warranty) { - this.warranty = warranty; - } - - //tag::basic-nclob-example[] - } - //end::basic-nclob-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + + //tag::basic-nclob-persist-example[] + String warranty = "My product warranty"; + + final Product product = new Product(); + product.setId( 1 ); + product.setName( "Mobile phone" ); + + product.setWarranty( NClobProxy.generateProxy( warranty ) ); + + entityManager.persist( product ); + //end::basic-nclob-persist-example[] + + return product.getId(); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + try { + //tag::basic-nclob-find-example[] + + Product product = entityManager.find( Product.class, productId ); + + try (Reader reader = product.getWarranty().getCharacterStream()) { + assertEquals( "My product warranty", toString( reader ) ); + } + //end::basic-nclob-find-example[] + } + catch (Exception e) { + fail( e.getMessage() ); + } + } ); + } + + private String toString(Reader reader) throws IOException { + BufferedReader bufferedReader = new BufferedReader( reader); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + int result = bufferedReader.read(); + + while(result != -1) { + byteArrayOutputStream.write((byte) result); + result = bufferedReader.read(); + } + + return byteArrayOutputStream.toString(); + } + + + //tag::basic-nclob-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Integer id; + + private String name; + + @Lob + @Nationalized + // Clob also works, because NClob extends Clob. + // The database type is still NCLOB either way and handled as such. + private NClob warranty; + + //Getters and setters are omitted for brevity + + //end::basic-nclob-example[] + 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 NClob getWarranty() { + return warranty; + } + + public void setWarranty(NClob warranty) { + this.warranty = warranty; + } + + //tag::basic-nclob-example[] + } + //end::basic-nclob-example[] } 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 71e2d0a10520..5a7cb3f6dd9c 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 @@ -23,80 +23,81 @@ * @author Vlad Mihalcea */ @SkipForDialect( - value = { - PostgreSQL81Dialect.class - }, - comment = "@see https://hibernate.atlassian.net/browse/HHH-10693" + value = { + PostgreSQL81Dialect.class + }, + comment = "@see https://hibernate.atlassian.net/browse/HHH-10693" ) public class NationalizedTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Product.class - }; - } - - @Test - public void test() { - Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { - //tag::basic-nationalized-persist-example[] - final Product product = new Product(); - product.setId( 1 ); - product.setName( "Mobile phone" ); - product.setWarranty( "My product warranty" ); - - entityManager.persist( product ); - //end::basic-nationalized-persist-example[] - - return product.getId(); - } ); - doInJPA( this::entityManagerFactory, entityManager -> { - Product product = entityManager.find( Product.class, productId ); - assertEquals( "My product warranty", product.getWarranty() ); - } ); - } - - //tag::basic-nationalized-example[] - @Entity(name = "Product") - public static class Product { - - @Id - private Integer id; - - private String name; - - @Nationalized - private String warranty; - - //Getters and setters are omitted for brevity - - //end::basic-nationalized-example[] - 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 getWarranty() { - return warranty; - } - - public void setWarranty(String warranty) { - this.warranty = warranty; - } - - //tag::basic-nationalized-example[] - } - //end::basic-nationalized-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + Integer productId = doInJPA( this::entityManagerFactory, entityManager -> { + //tag::basic-nationalized-persist-example[] + final Product product = new Product(); + product.setId( 1 ); + product.setName( "Mobile phone" ); + product.setWarranty( "My product warranty" ); + + entityManager.persist( product ); + //end::basic-nationalized-persist-example[] + + return product.getId(); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = entityManager.find( Product.class, productId ); + + assertEquals( "My product warranty", product.getWarranty() ); + } ); + } + + //tag::basic-nationalized-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Integer id; + + private String name; + + @Nationalized + private String warranty; + + //Getters and setters are omitted for brevity + + //end::basic-nationalized-example[] + 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 getWarranty() { + return warranty; + } + + public void setWarranty(String warranty) { + this.warranty = warranty; + } + + //tag::basic-nationalized-example[] + } + //end::basic-nationalized-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java new file mode 100644 index 000000000000..6a9b25b800bc --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.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.userguide.mapping.basic; + +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.Parent; +import org.hibernate.annotations.Target; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * @author Vlad Mihalcea + */ +public class ParentTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + City.class, + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-Parent-persist-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + + City cluj = new City(); + cluj.setName( "Cluj" ); + cluj.setCoordinates( new GPS( 46.77120, 23.62360 ) ); + + entityManager.persist( cluj ); + } ); + //end::mapping-Parent-persist-example[] + + + //tag::mapping-Parent-fetching-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + + City cluj = entityManager.find( City.class, 1L ); + + assertSame( cluj, cluj.getCoordinates().getCity() ); + } ); + //end::mapping-Parent-fetching-example[] + } + + //tag::mapping-Parent-example[] + + @Embeddable + public static class GPS { + + private double latitude; + + private double longitude; + + @Parent + private City city; + + //Getters and setters omitted for brevity + + //end::mapping-Parent-example[] + + private GPS() { + } + + public GPS(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + public City getCity() { + return city; + } + + public void setCity(City city) { + this.city = city; + } + //tag::mapping-Parent-example[] + } + //end::mapping-Parent-example[] + + //tag::mapping-Parent-example[] + + @Entity(name = "City") + public static class City { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Embedded + @Target( GPS.class ) + private GPS coordinates; + + //Getters and setters omitted for brevity + + //end::mapping-Parent-example[] + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public GPS getCoordinates() { + return coordinates; + } + + public void setCoordinates(GPS coordinates) { + this.coordinates = coordinates; + } + //tag::mapping-Parent-example[] + } + //end::mapping-Parent-example[] +} 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 new file mode 100644 index 000000000000..a437155c7b03 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java @@ -0,0 +1,285 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.basic; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.Subselect; +import org.hibernate.annotations.Synchronize; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class SubselectTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class, + AccountTransaction.class, + AccountSummary.class + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-Subselect-entity-find-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + Client client = new Client(); + client.setId( 1L ); + client.setFirstName( "John" ); + client.setLastName( "Doe" ); + entityManager.persist( client ); + + Account account = new Account(); + account.setId( 1L ); + account.setClient( client ); + account.setDescription( "Checking account" ); + entityManager.persist( account ); + + AccountTransaction transaction = new AccountTransaction(); + transaction.setAccount( account ); + transaction.setDescription( "Salary" ); + transaction.setCents( 100 * 7000 ); + entityManager.persist( transaction ); + + AccountSummary summary = entityManager.createQuery( + "select s " + + "from AccountSummary s " + + "where s.id = :id", AccountSummary.class) + .setParameter( "id", account.getId() ) + .getSingleResult(); + + assertEquals( "John Doe", summary.getClientName() ); + assertEquals( 100 * 7000, summary.getBalance() ); + } ); + //end::mapping-Subselect-entity-find-example[] + + //tag::mapping-Subselect-entity-refresh-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + AccountSummary summary = entityManager.find( AccountSummary.class, 1L ); + assertEquals( "John Doe", summary.getClientName() ); + assertEquals( 100 * 7000, summary.getBalance() ); + + AccountTransaction transaction = new AccountTransaction(); + transaction.setAccount( entityManager.getReference( Account.class, 1L ) ); + transaction.setDescription( "Shopping" ); + transaction.setCents( -100 * 2200 ); + entityManager.persist( transaction ); + entityManager.flush(); + + entityManager.refresh( summary ); + assertEquals( 100 * 4800, summary.getBalance() ); + } ); + + //end::mapping-Subselect-entity-refresh-example[] + } + + //tag::mapping-Subselect-example[] + @Entity(name = "Client") + @Table(name = "client") + public static class Client { + + @Id + private Long id; + + @Column(name = "first_name") + private String firstName; + + @Column(name = "last_name") + private String lastName; + + //Getters and setters omitted for brevity + + //end::mapping-Subselect-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + //tag::mapping-Subselect-example[] + } + + @Entity(name = "Account") + @Table(name = "account") + public static class Account { + + @Id + private Long id; + + @ManyToOne + private Client client; + + private String description; + + //Getters and setters omitted for brevity + + //end::mapping-Subselect-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + //tag::mapping-Subselect-example[] + } + + @Entity(name = "AccountTransaction") + @Table(name = "account_transaction") + public static class AccountTransaction { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne + private Account account; + + private Integer cents; + + private String description; + + //Getters and setters omitted for brevity + + //end::mapping-Subselect-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + public Integer getCents() { + return cents; + } + + public void setCents(Integer cents) { + this.cents = cents; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + //tag::mapping-Subselect-example[] + } + + @Entity(name = "AccountSummary") + @Subselect( + "select " + + " a.id as id, " + + " concat(concat(c.first_name, ' '), c.last_name) as clientName, " + + " sum(at.cents) as balance " + + "from account a " + + "join client c on c.id = a.client_id " + + "join account_transaction at on a.id = at.account_id " + + "group by a.id, concat(concat(c.first_name, ' '), c.last_name)" + ) + @Synchronize( {"client", "account", "account_transaction"} ) + public static class AccountSummary { + + @Id + private Long id; + + private String clientName; + + private int balance; + + //Getters and setters omitted for brevity + + //end::mapping-Subselect-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + public int getBalance() { + return balance; + } + + public void setBalance(int balance) { + this.balance = balance; + } + //tag::mapping-Subselect-example[] + } + //end::mapping-Subselect-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/TargetTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/TargetTest.java new file mode 100644 index 000000000000..1d7ac4538bcf --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/TargetTest.java @@ -0,0 +1,131 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.basic; + +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.Target; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class TargetTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + City.class, + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-Target-persist-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + + City cluj = new City(); + cluj.setName( "Cluj" ); + cluj.setCoordinates( new GPS( 46.77120, 23.62360 ) ); + + entityManager.persist( cluj ); + } ); + //end::mapping-Target-persist-example[] + + + //tag::mapping-Target-fetching-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + + City cluj = entityManager.find( City.class, 1L ); + + assertEquals( 46.77120, cluj.getCoordinates().x(), 0.00001 ); + assertEquals( 23.62360, cluj.getCoordinates().y(), 0.00001 ); + } ); + //end::mapping-Target-fetching-example[] + } + + //tag::mapping-Target-example[] + public interface Coordinates { + double x(); + double y(); + } + + @Embeddable + public static class GPS implements Coordinates { + + private double latitude; + + private double longitude; + + private GPS() { + } + + public GPS(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + @Override + public double x() { + return latitude; + } + + @Override + public double y() { + return longitude; + } + } + + @Entity(name = "City") + public static class City { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Embedded + @Target( GPS.class ) + private Coordinates coordinates; + + //Getters and setters omitted for brevity + + //end::mapping-Target-example[] + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Coordinates getCoordinates() { + return coordinates; + } + + public void setCoordinates(Coordinates coordinates) { + this.coordinates = coordinates; + } + //tag::mapping-Target-example[] + } + //end::mapping-Target-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/WhereJoinTableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/WhereJoinTableTest.java new file mode 100644 index 000000000000..cba33f0b394e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/WhereJoinTableTest.java @@ -0,0 +1,187 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.basic; + +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; + +import org.hibernate.Session; +import org.hibernate.annotations.WhereJoinTable; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class WhereJoinTableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + Reader.class + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-where-persistence-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + + entityManager.unwrap( Session.class ).doWork( connection -> { + try(Statement statement = connection.createStatement()) { + statement.executeUpdate( + "ALTER TABLE Book_Reader ADD created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP" + ); + } + } ); + + //tag::mapping-where-join-table-persist-example[] + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vad Mihalcea" ); + entityManager.persist( book ); + + Reader reader1 = new Reader(); + reader1.setId( 1L ); + reader1.setName( "John Doe" ); + entityManager.persist( reader1 ); + + Reader reader2 = new Reader(); + reader2.setId( 2L ); + reader2.setName( "John Doe Jr." ); + entityManager.persist( reader2 ); + //end::mapping-where-join-table-persist-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.unwrap( Session.class ).doWork( connection -> { + try(Statement statement = connection.createStatement()) { + //tag::mapping-where-join-table-persist-example[] + + statement.executeUpdate( + "INSERT INTO Book_Reader " + + " (book_id, reader_id) " + + "VALUES " + + " (1, 1) " + ); + statement.executeUpdate( + "INSERT INTO Book_Reader " + + " (book_id, reader_id, created_on) " + + "VALUES " + + " (1, 2, DATEADD( 'DAY', -10, CURRENT_TIMESTAMP() )) " + ); + //end::mapping-where-join-table-persist-example[] + }} + ); + + //tag::mapping-where-join-table-fetch-example[] + Book book = entityManager.find( Book.class, 1L ); + assertEquals( 1, book.getCurrentWeekReaders().size() ); + //end::mapping-where-join-table-fetch-example[] + } ); + } + + //tag::mapping-where-join-table-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + @ManyToMany + @JoinTable( + name = "Book_Reader", + joinColumns = @JoinColumn(name = "book_id"), + inverseJoinColumns = @JoinColumn(name = "reader_id") + ) + @WhereJoinTable( clause = "created_on > DATEADD( 'DAY', -7, CURRENT_TIMESTAMP() )") + private List currentWeekReaders = new ArrayList<>( ); + + //Getters and setters omitted for brevity + + //end::mapping-where-join-table-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public List getCurrentWeekReaders() { + return currentWeekReaders; + } + + //tag::mapping-where-join-table-example[] + } + + @Entity(name = "Reader") + public static class Reader { + + @Id + private Long id; + + private String name; + + //Getters and setters omitted for brevity + + //end::mapping-where-join-table-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::mapping-where-join-table-example[] + } + //end::mapping-where-join-table-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/AnyTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/AnyTest.java index cacef48568ee..a912aa732ff7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/AnyTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/AnyTest.java @@ -36,33 +36,38 @@ protected String[] getAnnotatedPackages() { @Test public void test() { - //tag::mapping-column-any-persistence-example[] doInHibernate( this::sessionFactory, session -> { + //tag::mapping-column-any-persist-example[] IntegerProperty ageProperty = new IntegerProperty(); ageProperty.setId( 1L ); ageProperty.setName( "age" ); ageProperty.setValue( 23 ); + session.persist( ageProperty ); + StringProperty nameProperty = new StringProperty(); nameProperty.setId( 1L ); nameProperty.setName( "name" ); nameProperty.setValue( "John Doe" ); - session.persist( ageProperty ); session.persist( nameProperty ); PropertyHolder namePropertyHolder = new PropertyHolder(); namePropertyHolder.setId( 1L ); namePropertyHolder.setProperty( nameProperty ); + session.persist( namePropertyHolder ); + //end::mapping-column-any-persist-example[] } ); doInHibernate( this::sessionFactory, session -> { + //tag::mapping-column-any-query-example[] PropertyHolder propertyHolder = session.get( PropertyHolder.class, 1L ); + assertEquals("name", propertyHolder.getProperty().getName()); assertEquals("John Doe", propertyHolder.getProperty().getValue()); + //end::mapping-column-any-query-example[] } ); - //end::mapping-column-any-persistence-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/IntegerProperty.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/IntegerProperty.java index a0b588aad159..cf340e5c24e6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/IntegerProperty.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/IntegerProperty.java @@ -26,6 +26,19 @@ public class IntegerProperty implements Property { @Column(name = "`value`") private Integer value; + @Override + public String getName() { + return name; + } + + @Override + public Integer getValue() { + return value; + } + + //Getters and setters omitted for brevity +//end::mapping-column-any-property-example[] + public Long getId() { return id; } @@ -34,22 +47,14 @@ public void setId(Long id) { this.id = id; } - @Override - public String getName() { - return name; - } - public void setName(String name) { this.name = name; } - public Integer getValue() { - return value; - } - public void setValue(Integer value) { this.value = value; } +//tag::mapping-column-any-property-example[] } //end::mapping-column-any-property-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/ManyToAnyTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/ManyToAnyTest.java index d6554229b294..13a5d5c91d33 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/ManyToAnyTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/ManyToAnyTest.java @@ -37,36 +37,43 @@ protected String[] getAnnotatedPackages() { @Test public void test() { - //tag::mapping-column-many-to-any-persistence-example[] doInHibernate( this::sessionFactory, session -> { + //tag::mapping-column-many-to-any-persist-example[] IntegerProperty ageProperty = new IntegerProperty(); ageProperty.setId( 1L ); ageProperty.setName( "age" ); ageProperty.setValue( 23 ); + session.persist( ageProperty ); + StringProperty nameProperty = new StringProperty(); nameProperty.setId( 1L ); nameProperty.setName( "name" ); nameProperty.setValue( "John Doe" ); - session.persist( ageProperty ); session.persist( nameProperty ); PropertyRepository propertyRepository = new PropertyRepository(); propertyRepository.setId( 1L ); + propertyRepository.getProperties().add( ageProperty ); propertyRepository.getProperties().add( nameProperty ); + session.persist( propertyRepository ); + //end::mapping-column-many-to-any-persist-example[] } ); doInHibernate( this::sessionFactory, session -> { + //tag::mapping-column-many-to-any-query-example[] PropertyRepository propertyRepository = session.get( PropertyRepository.class, 1L ); + assertEquals(2, propertyRepository.getProperties().size()); + for(Property property : propertyRepository.getProperties()) { assertNotNull( property.getValue() ); } + //end::mapping-column-many-to-any-query-example[] } ); - //end::mapping-column-many-to-any-persistence-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyHolder.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyHolder.java index fc8060248058..a6ba93d59367 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyHolder.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyHolder.java @@ -29,7 +29,7 @@ public class PropertyHolder { @JoinColumn( name = "property_id" ) private Property property; - //Getters and setters are omitted for brevity + //Getters and setters are omitted for brevity //end::mapping-column-any-example[] public Long getId() { diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyRepository.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyRepository.java index 19119c6058dd..68156a1156df 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyRepository.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyRepository.java @@ -37,7 +37,7 @@ public class PropertyRepository { ) private List> properties = new ArrayList<>( ); - //Getters and setters are omitted for brevity + //Getters and setters are omitted for brevity //end::mapping-column-many-to-any-example[] public Long getId() { diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/StringProperty.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/StringProperty.java index 8449ac807e36..828a5f843dca 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/StringProperty.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/StringProperty.java @@ -26,6 +26,19 @@ public class StringProperty implements Property { @Column(name = "`value`") private String value; + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + //Getters and setters omitted for brevity +//end::mapping-column-any-property-example[] + public Long getId() { return id; } @@ -34,21 +47,13 @@ public void setId(Long id) { this.id = id; } - @Override - public String getName() { - return name; - } - public void setName(String name) { this.name = name; } - public String getValue() { - return value; - } - public void setValue(String value) { this.value = value; } +//tag::mapping-column-any-property-example[] } //end::mapping-column-any-property-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java index 708966facc22..6fc5389b293f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java @@ -1,25 +1,8 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2013, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . */ package org.hibernate.userguide.mapping.converter; @@ -71,6 +54,9 @@ public static class Money { private long cents; + //Getters and setters are omitted for brevity + //end::basic-jpa-convert-money-converter-mapping-example[] + public Money(long cents) { this.cents = cents; } @@ -82,23 +68,12 @@ public long getCents() { public void setCents(long cents) { this.cents = cents; } + //tag::basic-jpa-convert-money-converter-mapping-example[] } - - public static class MoneyConverter - implements AttributeConverter { - - @Override - public Long convertToDatabaseColumn(Money attribute) { - return attribute == null ? null : attribute.getCents(); - } - - @Override - public Money convertToEntityAttribute(Long dbData) { - return dbData == null ? null : new Money( dbData ); - } - } + //end::basic-jpa-convert-money-converter-mapping-example[] //tag::basic-jpa-convert-money-converter-mapping-example[] + @Entity(name = "Account") public static class Account { @@ -111,8 +86,7 @@ public static class Account { private Money balance; //Getters and setters are omitted for brevity - - //end::basic-jpa-convert-money-converter-mapping-example[] + //end::basic-jpa-convert-money-converter-mapping-example[] public Long getId() { return id; } @@ -138,5 +112,19 @@ public void setBalance(Money balance) { } //tag::basic-jpa-convert-money-converter-mapping-example[] } + + public static class MoneyConverter + implements AttributeConverter { + + @Override + public Long convertToDatabaseColumn(Money attribute) { + return attribute == null ? null : attribute.getCents(); + } + + @Override + public Money convertToEntityAttribute(Long dbData) { + return dbData == null ? null : new Money( dbData ); + } + } //end::basic-jpa-convert-money-converter-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/Account.java b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/Account.java new file mode 100644 index 000000000000..7cb546985913 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/Account.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.converter.hbm; + +//tag::basic-hbm-attribute-converter-mapping-account-example[] +public class Account { + + private Long id; + + private String owner; + + private Money balance; + + //Getters and setters are omitted for brevity + //end::basic-hbm-attribute-converter-mapping-account-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public Money getBalance() { + return balance; + } + + public void setBalance(Money balance) { + this.balance = balance; + } +//tag::basic-hbm-attribute-converter-mapping-account-example[] +} +//end::basic-hbm-attribute-converter-mapping-account-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/Money.java b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/Money.java new file mode 100644 index 000000000000..6a09348264c8 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/Money.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.converter.hbm; + +//tag::basic-hbm-attribute-converter-mapping-money-example[] +public class Money { + + private long cents; + + public Money(long cents) { + this.cents = cents; + } + + public long getCents() { + return cents; + } + + public void setCents(long cents) { + this.cents = cents; + } +} +//end::basic-hbm-attribute-converter-mapping-money-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/MoneyConverter.java b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/MoneyConverter.java new file mode 100644 index 000000000000..1f1d8b0b6181 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/MoneyConverter.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 . + */ +package org.hibernate.userguide.mapping.converter.hbm; + +import javax.persistence.AttributeConverter; + +//tag::basic-hbm-attribute-converter-mapping-moneyconverter-example[] +public class MoneyConverter + implements AttributeConverter { + + @Override + public Long convertToDatabaseColumn(Money attribute) { + return attribute == null ? null : attribute.getCents(); + } + + @Override + public Money convertToEntityAttribute(Long dbData) { + return dbData == null ? null : new Money( dbData ); + } +} +//end::basic-hbm-attribute-converter-mapping-moneyconverter-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/MoneyConverterHbmTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/MoneyConverterHbmTest.java new file mode 100644 index 000000000000..8779ecf228ab --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/hbm/MoneyConverterHbmTest.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 . + */ +package org.hibernate.userguide.mapping.converter.hbm; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class MoneyConverterHbmTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void testConverterMutability() { + + doInJPA( this::entityManagerFactory, entityManager -> { + Account account = new Account(); + account.setId( 1L ); + account.setOwner( "John Doe" ); + account.setBalance( new Money( 250 * 100L ) ); + + entityManager.persist( account ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::basic-hbm-convert-money-converter-mutability-plan-example[] + Account account = entityManager.find( Account.class, 1L ); + account.getBalance().setCents( 150 * 100L ); + entityManager.persist( account ); + //end::basic-hbm-convert-money-converter-mutability-plan-example[] + } ); + } + + @Override + protected String[] getMappings() { + return new String[] { + "org/hibernate/userguide/mapping/converter/hbm/MoneyConverterHbmTest.hbm.xml" + }; + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/dynamic/Book.java b/documentation/src/test/java/org/hibernate/userguide/mapping/dynamic/Book.java new file mode 100644 index 000000000000..54d1fc1312aa --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/dynamic/Book.java @@ -0,0 +1,46 @@ +package org.hibernate.userguide.mapping.dynamic; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//tag::access-field-mapping-example[] +@Entity(name = "Book") +public class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::access-field-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::access-field-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/dynamic/DynamicEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/dynamic/DynamicEntityTest.java new file mode 100644 index 000000000000..be465e6ee43a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/dynamic/DynamicEntityTest.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.dynamic; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.EntityMode; +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class DynamicEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { + "org/hibernate/userguide/mapping/dynamic/Book.hbm.xml" + }; + } + + @Override + protected Map buildSettings() { + Map settings = super.buildSettings(); + //tag::mapping-model-dynamic-setting-example[] + settings.put( "hibernate.default_entity_mode", "dynamic-map" ); + //end::mapping-model-dynamic-setting-example[] + return settings; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-model-dynamic-example[] + + Map book = new HashMap<>(); + book.put( "isbn", "978-9730228236" ); + book.put( "title", "High-Performance Java Persistence" ); + book.put( "author", "Vlad Mihalcea" ); + + entityManager + .unwrap(Session.class) + .save( "Book", book ); + //end::mapping-model-dynamic-example[] + } ); + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java new file mode 100644 index 000000000000..c6dc6f87f0a1 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java @@ -0,0 +1,203 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.embeddable; + +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * @author Vlad Mihalcea + */ +public class EmbeddableImplicitOverrideTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + Country.class + }; + } + + @Override + protected void initialize(MetadataBuilder metadataBuilder) { + super.initialize( metadataBuilder ); + //tag::embeddable-multiple-ImplicitNamingStrategyComponentPathImpl[] + metadataBuilder.applyImplicitNamingStrategy( + ImplicitNamingStrategyComponentPathImpl.INSTANCE + ); + //end::embeddable-multiple-ImplicitNamingStrategyComponentPathImpl[] + } + + @Test + public void testLifecycle() { + doInHibernate( this::sessionFactory, session -> { + Country canada = new Country(); + canada.setName( "Canada" ); + session.persist( canada ); + + Country usa = new Country(); + usa.setName( "USA" ); + session.persist( usa ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Country canada = session.byNaturalId( Country.class ).using( "name", "Canada" ).load(); + Country usa = session.byNaturalId( Country.class ).using( "name", "USA" ).load(); + + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setEbookPublisher( new Publisher( "Leanpub", canada ) ); + book.setPaperBackPublisher( new Publisher( "Amazon", usa ) ); + + session.persist( book ); + } ); + } + + //tag::embeddable-multiple-namingstrategy-entity-mapping[] + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + private Publisher ebookPublisher; + + private Publisher paperBackPublisher; + + //Getters and setters are omitted for brevity + //end::embeddable-multiple-namingstrategy-entity-mapping[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Publisher getEbookPublisher() { + return ebookPublisher; + } + + public void setEbookPublisher(Publisher ebookPublisher) { + this.ebookPublisher = ebookPublisher; + } + + public Publisher getPaperBackPublisher() { + return paperBackPublisher; + } + + public void setPaperBackPublisher(Publisher paperBackPublisher) { + this.paperBackPublisher = paperBackPublisher; + } + //tag::embeddable-multiple-namingstrategy-entity-mapping[] + } + + @Embeddable + public static class Publisher { + + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + private Country country; + + //Getters and setters, equals and hashCode methods omitted for brevity + //end::embeddable-multiple-namingstrategy-entity-mapping[] + + public Publisher(String name, Country country) { + this.name = name; + this.country = country; + } + + private Publisher() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + //tag::embeddable-multiple-namingstrategy-entity-mapping[] + } + + @Entity(name = "Country") + public static class Country { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String name; + + //Getters and setters are omitted for brevity + //end::embeddable-multiple-namingstrategy-entity-mapping[] + + 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; + } + //tag::embeddable-multiple-namingstrategy-entity-mapping[] + } + //end::embeddable-multiple-namingstrategy-entity-mapping[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java new file mode 100644 index 000000000000..31d070b62c5a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java @@ -0,0 +1,224 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.embeddable; + +import javax.persistence.AssociationOverride; +import javax.persistence.AssociationOverrides; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.SkipForDialect; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +@SkipForDialect(Oracle8iDialect.class) +public class EmbeddableOverrideTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + Country.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + Country canada = new Country(); + canada.setName( "Canada" ); + entityManager.persist( canada ); + + Country usa = new Country(); + usa.setName( "USA" ); + entityManager.persist( usa ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + Country canada = session.byNaturalId( Country.class ).using( "name", "Canada" ).load(); + Country usa = session.byNaturalId( Country.class ).using( "name", "USA" ).load(); + + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setEbookPublisher( new Publisher( "Leanpub", canada ) ); + book.setPaperBackPublisher( new Publisher( "Amazon", usa ) ); + + entityManager.persist( book ); + } ); + } + + //tag::embeddable-type-override-mapping-example[] + @Entity(name = "Book") + @AttributeOverrides({ + @AttributeOverride( + name = "ebookPublisher.name", + column = @Column(name = "ebook_publisher_name") + ), + @AttributeOverride( + name = "paperBackPublisher.name", + column = @Column(name = "paper_back_publisher_name") + ) + }) + @AssociationOverrides({ + @AssociationOverride( + name = "ebookPublisher.country", + joinColumns = @JoinColumn(name = "ebook_publisher_country_id") + ), + @AssociationOverride( + name = "paperBackPublisher.country", + joinColumns = @JoinColumn(name = "paper_back_publisher_country_id") + ) + }) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + private Publisher ebookPublisher; + + private Publisher paperBackPublisher; + + //Getters and setters are omitted for brevity + //end::embeddable-type-override-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Publisher getEbookPublisher() { + return ebookPublisher; + } + + public void setEbookPublisher(Publisher ebookPublisher) { + this.ebookPublisher = ebookPublisher; + } + + public Publisher getPaperBackPublisher() { + return paperBackPublisher; + } + + public void setPaperBackPublisher(Publisher paperBackPublisher) { + this.paperBackPublisher = paperBackPublisher; + } + //tag::embeddable-type-override-mapping-example[] + } + //end::embeddable-type-override-mapping-example[] + + //tag::embeddable-type-association-mapping-example[] + @Embeddable + public static class Publisher { + + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + private Country country; + + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::embeddable-type-association-mapping-example[] + + public Publisher(String name, Country country) { + this.name = name; + this.country = country; + } + + private Publisher() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + //tag::embeddable-type-association-mapping-example[] + } + + @Entity(name = "Country") + public static class Country { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String name; + + //Getters and setters are omitted for brevity + //end::embeddable-type-association-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::embeddable-type-association-mapping-example[] + } + //end::embeddable-type-association-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/NestedEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/NestedEmbeddableTest.java new file mode 100644 index 000000000000..53ac2616d0e3 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/NestedEmbeddableTest.java @@ -0,0 +1,175 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.embeddable; + +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class NestedEmbeddableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setPublisher( + new Publisher( + "Amazon", + new Location( + "USA", + "Seattle" + ) + ) + ); + + entityManager.persist( book ); + } ); + } + + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + private Publisher publisher; + + //Getters and setters are omitted for brevity + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Publisher getPublisher() { + return publisher; + } + + public void setPublisher(Publisher publisher) { + this.publisher = publisher; + } + + } + + //tag::embeddable-type-mapping-example[] + @Embeddable + public static class Publisher { + + private String name; + + private Location location; + + public Publisher(String name, Location location) { + this.name = name; + this.location = location; + } + + private Publisher() {} + + //Getters and setters are omitted for brevity + //end::embeddable-type-mapping-example[] + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + //tag::embeddable-type-mapping-example[] + } + + @Embeddable + public static class Location { + + private String country; + + private String city; + + public Location(String country, String city) { + this.country = country; + this.city = city; + } + + private Location() {} + + //Getters and setters are omitted for brevity + //end::embeddable-type-mapping-example[] + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + //tag::embeddable-type-mapping-example[] + } + //end::embeddable-type-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableEquivalentTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableEquivalentTest.java new file mode 100644 index 000000000000..39890360e99d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableEquivalentTest.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.embeddable; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class SimpleEmbeddableEquivalentTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setPublisherName("Amazon"); + book.setPublisherCountry("USA"); + + entityManager.persist( book ); + } ); + } + + //tag::embeddable-type-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + @Column(name = "publisher_name") + private String publisherName; + + @Column(name = "publisher_country") + private String publisherCountry; + + //Getters and setters are omitted for brevity + //end::embeddable-type-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getPublisherName() { + return publisherName; + } + + public void setPublisherName(String publisherName) { + this.publisherName = publisherName; + } + + public String getPublisherCountry() { + return publisherCountry; + } + + public void setPublisherCountry(String publisherCountry) { + this.publisherCountry = publisherCountry; + } + + + //tag::embeddable-type-mapping-example[] + } + //end::embeddable-type-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java new file mode 100644 index 000000000000..6ae683fde56d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java @@ -0,0 +1,144 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.embeddable; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class SimpleEmbeddableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setPublisher( + new Publisher( + "Amazon", + "USA" + ) + ); + + entityManager.persist( book ); + } ); + } + + //tag::embeddable-type-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + private Publisher publisher; + + //Getters and setters are omitted for brevity + //end::embeddable-type-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Publisher getPublisher() { + return publisher; + } + + public void setPublisher(Publisher publisher) { + this.publisher = publisher; + } + + //tag::embeddable-type-mapping-example[] + } + + @Embeddable + public static class Publisher { + + @Column(name = "publisher_name") + private String name; + + @Column(name = "publisher_country") + private String country; + + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::embeddable-type-mapping-example[] + + + public Publisher(String name, String country) { + this.name = name; + this.country = country; + } + + private Publisher() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + //tag::embeddable-type-mapping-example[] + } + //end::embeddable-type-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java index 0d89292922c3..4da5ae8b2008 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java @@ -53,6 +53,9 @@ public static class Event { @CreationTimestamp private Date timestamp; + //Constructors, getters, and setters are omitted for brevity + //end::mapping-generated-CreationTimestamp-example[] + public Event() {} public Long getId() { @@ -62,6 +65,7 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-generated-CreationTimestamp-example[] } //end::mapping-generated-CreationTimestamp-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java index f758bf49f217..870997a9b4d3 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java @@ -56,6 +56,9 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; + //Constructors, getters, and setters are omitted for brevity + //end::mapping-database-generated-value-example[] + public Event() {} public Long getId() { @@ -65,7 +68,11 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-database-generated-value-example[] } + //end::mapping-database-generated-value-example[] + + //tag::mapping-database-generated-value-example[] @ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java index d3bd2e47ed95..7457a5137343 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java @@ -56,6 +56,8 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; + //Constructors, getters, and setters are omitted for brevity + //end::mapping-in-memory-generated-value-example[] public Event() {} public Long getId() { @@ -65,7 +67,11 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-in-memory-generated-value-example[] } + //end::mapping-in-memory-generated-value-example[] + + //tag::mapping-in-memory-generated-value-example[] @ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/UpdateTimestampTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/UpdateTimestampTest.java new file mode 100644 index 000000000000..6f9fb504cca4 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/UpdateTimestampTest.java @@ -0,0 +1,107 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.generated; + +import java.util.Date; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.UpdateTimestamp; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class UpdateTimestampTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Bid.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-generated-UpdateTimestamp-persist-example[] + Bid bid = new Bid(); + bid.setUpdatedBy( "John Doe" ); + bid.setCents( 150 * 100L ); + entityManager.persist( bid ); + //end::mapping-generated-UpdateTimestamp-persist-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-generated-UpdateTimestamp-update-example[] + Bid bid = entityManager.find( Bid.class, 1L ); + + bid.setUpdatedBy( "John Doe Jr." ); + bid.setCents( 160 * 100L ); + entityManager.persist( bid ); + //end::mapping-generated-UpdateTimestamp-update-example[] + } ); + } + + //tag::mapping-generated-UpdateTimestamp-example[] + @Entity(name = "Bid") + public static class Bid { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "updated_on") + @UpdateTimestamp + private Date updatedOn; + + @Column(name = "updated_by") + private String updatedBy; + + private Long cents; + + //Getters and setters are omitted for brevity + + //end::mapping-generated-UpdateTimestamp-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Date getUpdatedOn() { + return updatedOn; + } + + public String getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(String updatedBy) { + this.updatedBy = updatedBy; + } + + public Long getCents() { + return cents; + } + + public void setCents(Long cents) { + this.cents = cents; + } + //tag::mapping-generated-UpdateTimestamp-example[] + } + //end::mapping-generated-UpdateTimestamp-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/AssignedIdentifierTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/AssignedIdentifierTest.java new file mode 100644 index 000000000000..a15fb3e2a49c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/AssignedIdentifierTest.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 . + */ +package org.hibernate.userguide.mapping.identifier; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class AssignedIdentifierTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + } + + + @Test + public void testIdentityScope() { + + } + + //tag::identifiers-simple-assigned-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::identifiers-simple-assigned-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::identifiers-simple-assigned-mapping-example[] + } + //end::identifiers-simple-assigned-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/CacheableNaturalIdTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/CacheableNaturalIdTest.java new file mode 100644 index 000000000000..d5dc09344c56 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/CacheableNaturalIdTest.java @@ -0,0 +1,134 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.Map; +import javax.cache.configuration.MutableConfiguration; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; +import org.hibernate.cache.jcache.JCacheHelper; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class CacheableNaturalIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public void buildEntityManagerFactory() { + JCacheHelper.locateStandardCacheManager().createCache( "default-update-timestamps-region", new MutableConfiguration<>() ); + JCacheHelper.locateStandardCacheManager().createCache( "default-query-results-region", new MutableConfiguration<>() ); + JCacheHelper.locateStandardCacheManager().createCache( "org.hibernate.userguide.mapping.identifier.CacheableNaturalIdTest$Book##NaturalId", new MutableConfiguration<>() ); +// JCacheHelper.locateStandardCacheManager().createCache( "", new MutableConfiguration<>() ); + + super.buildEntityManagerFactory(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Override + @SuppressWarnings( "unchecked" ) + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() ); + options.put( AvailableSettings.CACHE_REGION_FACTORY, "jcache" ); + options.put( AvailableSettings.USE_QUERY_CACHE, Boolean.TRUE.toString() ); + options.put( AvailableSettings.GENERATE_STATISTICS, Boolean.TRUE.toString() ); + options.put( AvailableSettings.CACHE_REGION_PREFIX, "" ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setIsbn( "978-9730228236" ); + + entityManager.persist( book ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::naturalid-cacheable-load-access-example[] + Book book = entityManager + .unwrap(Session.class) + .bySimpleNaturalId( Book.class ) + .load( "978-9730228236" ); + //end::naturalid-cacheable-load-access-example[] + + assertEquals("High-Performance Java Persistence", book.getTitle()); + } ); + } + + //tag::naturalid-cacheable-mapping-example[] + @Entity(name = "Book") + @NaturalIdCache + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + @NaturalId + private String isbn; + + //Getters and setters are omitted for brevity + //end::naturalid-cacheable-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + //tag::naturalid-cacheable-mapping-example[] + } + //end::naturalid-cacheable-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/CompositeNaturalIdTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/CompositeNaturalIdTest.java new file mode 100644 index 000000000000..d1a48f4603ea --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/CompositeNaturalIdTest.java @@ -0,0 +1,193 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class CompositeNaturalIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setIsbn( new Isbn( + "973022823X", + "978-9730228236" + ) ); + + entityManager.persist( book ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::naturalid-simple-load-access-example[] + + Book book = entityManager + .unwrap(Session.class) + .bySimpleNaturalId( Book.class ) + .load( + new Isbn( + "973022823X", + "978-9730228236" + ) + ); + //end::naturalid-simple-load-access-example[] + + assertEquals("High-Performance Java Persistence", book.getTitle()); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::naturalid-load-access-example[] + + Book book = entityManager + .unwrap(Session.class) + .byNaturalId( Book.class ) + .using( + "isbn", + new Isbn( + "973022823X", + "978-9730228236" + ) ) + .load(); + //end::naturalid-load-access-example[] + + assertEquals("High-Performance Java Persistence", book.getTitle()); + } ); + } + + //tag::naturalid-single-embedded-attribute-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + @NaturalId + @Embedded + private Isbn isbn; + + //Getters and setters are omitted for brevity + //end::naturalid-single-embedded-attribute-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Isbn getIsbn() { + return isbn; + } + + public void setIsbn(Isbn isbn) { + this.isbn = isbn; + } + //tag::naturalid-single-embedded-attribute-mapping-example[] + } + + @Embeddable + public static class Isbn implements Serializable { + + private String isbn10; + + private String isbn13; + + //Getters and setters are omitted for brevity + //end::naturalid-single-embedded-attribute-mapping-example[] + + public Isbn() { + } + + public Isbn(String isbn10, String isbn13) { + this.isbn10 = isbn10; + this.isbn13 = isbn13; + } + + public String getIsbn10() { + return isbn10; + } + + public void setIsbn10(String isbn10) { + this.isbn10 = isbn10; + } + + public String getIsbn13() { + return isbn13; + } + + public void setIsbn13(String isbn13) { + this.isbn13 = isbn13; + } + + //tag::naturalid-single-embedded-attribute-mapping-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Isbn isbn = (Isbn) o; + return Objects.equals( isbn10, isbn.isbn10 ) && + Objects.equals( isbn13, isbn.isbn13 ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn10, isbn13 ); + } + } + //end::naturalid-single-embedded-attribute-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/EmbeddedIdManyToOneTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/EmbeddedIdManyToOneTest.java new file mode 100644 index 000000000000..3d22e6493b3f --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/EmbeddedIdManyToOneTest.java @@ -0,0 +1,174 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +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.ManyToOne; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class EmbeddedIdManyToOneTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SystemUser.class, + Subsystem.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Subsystem subsystem = new Subsystem(); + subsystem.setId( "Hibernate Forum" ); + subsystem.setDescription( "Hibernate projects forum" ); + entityManager.persist( subsystem ); + + SystemUser systemUser = new SystemUser(); + systemUser.setPk( new PK( + subsystem, + "vlad" + ) ); + systemUser.setName( "Vlad Mihalcea" ); + + entityManager.persist( systemUser ); + } ); + } + + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Subsystem subsystem = entityManager.find( + Subsystem.class, + "Hibernate Forum" + ); + SystemUser systemUser = entityManager.find( + SystemUser.class, + new PK( + subsystem, + "vlad" + ) + ); + + assertEquals( "Vlad Mihalcea", systemUser.getName() ); + } ); + + } + + //tag::identifiers-basic-embeddedid-manytoone-mapping-example[] + @Entity(name = "SystemUser") + public static class SystemUser { + + @EmbeddedId + private PK pk; + + private String name; + + //Getters and setters are omitted for brevity + //end::identifiers-basic-embeddedid-manytoone-mapping-example[] + + public PK getPk() { + return pk; + } + + public void setPk(PK pk) { + this.pk = pk; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::identifiers-basic-embeddedid-manytoone-mapping-example[] + } + + @Entity(name = "Subsystem") + public static class Subsystem { + + @Id + private String id; + + private String description; + + //Getters and setters are omitted for brevity + //end::identifiers-basic-embeddedid-manytoone-mapping-example[] + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + //tag::identifiers-basic-embeddedid-manytoone-mapping-example[] + } + + @Embeddable + public static class PK implements Serializable { + + @ManyToOne(fetch = FetchType.LAZY) + private Subsystem subsystem; + + private String username; + + public PK(Subsystem subsystem, String username) { + this.subsystem = subsystem; + this.username = username; + } + + private PK() { + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + PK pk = (PK) o; + return Objects.equals( subsystem, pk.subsystem ) && + Objects.equals( username, pk.username ); + } + + @Override + public int hashCode() { + return Objects.hash( subsystem, username ); + } + } + //end::identifiers-basic-embeddedid-manytoone-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/EmbeddedIdTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/EmbeddedIdTest.java new file mode 100644 index 000000000000..718d0a13483c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/EmbeddedIdTest.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.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class EmbeddedIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SystemUser.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + SystemUser systemUser = new SystemUser(); + systemUser.setPk( new PK( + "Hibernate Forum", + "vlad" + ) ); + systemUser.setName( "Vlad Mihalcea" ); + + entityManager.persist( systemUser ); + } ); + } + + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + SystemUser systemUser = entityManager.find( + SystemUser.class, + new PK( + "Hibernate Forum", + "vlad" + ) + ); + + assertEquals( "Vlad Mihalcea", systemUser.getName() ); + } ); + + } + + //tag::identifiers-basic-embeddedid-mapping-example[] + @Entity(name = "SystemUser") + public static class SystemUser { + + @EmbeddedId + private PK pk; + + private String name; + + //Getters and setters are omitted for brevity + //end::identifiers-basic-embeddedid-mapping-example[] + + public PK getPk() { + return pk; + } + + public void setPk(PK pk) { + this.pk = pk; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::identifiers-basic-embeddedid-mapping-example[] + } + + @Embeddable + public static class PK implements Serializable { + + private String subsystem; + + private String username; + + public PK(String subsystem, String username) { + this.subsystem = subsystem; + this.username = username; + } + + private PK() { + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + PK pk = (PK) o; + return Objects.equals( subsystem, pk.subsystem ) && + Objects.equals( username, pk.username ); + } + + @Override + public int hashCode() { + return Objects.hash( subsystem, username ); + } + } + //end::identifiers-basic-embeddedid-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/GeneratedIdentifierTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/GeneratedIdentifierTest.java new file mode 100644 index 000000000000..1b07bebcc1fb --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/GeneratedIdentifierTest.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class GeneratedIdentifierTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + } + + + @Test + public void test() { + + } + + //tag::identifiers-simple-generated-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::identifiers-simple-generated-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::identifiers-simple-generated-mapping-example[] + } + //end::identifiers-simple-generated-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassGeneratedValueTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassGeneratedValueTest.java new file mode 100644 index 000000000000..c839865d07f9 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassGeneratedValueTest.java @@ -0,0 +1,175 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.IdClass; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class IdClassGeneratedValueTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SystemUser.class + }; + } + + @Test + public void test() { + SystemUser _systemUser = doInJPA( this::entityManagerFactory, entityManager -> { + SystemUser systemUser = new SystemUser(); + systemUser.setId( + new PK( + "Hibernate Forum", + "vlad" + ) + ); + systemUser.setName( "Vlad Mihalcea" ); + + entityManager.persist( systemUser ); + + return systemUser; + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + SystemUser systemUser = entityManager.find( + SystemUser.class, + _systemUser.getId() + ); + + assertEquals( "Vlad Mihalcea", systemUser.getName() ); + } ); + + } + + //tag::identifiers-basic-idclass-generatedvalue-mapping-example[] + @Entity(name = "SystemUser") + @IdClass( PK.class ) + public static class SystemUser { + + @Id + private String subsystem; + + @Id + private String username; + + @Id + @GeneratedValue + private Integer registrationId; + + private String name; + + public PK getId() { + return new PK( + subsystem, + username, + registrationId + ); + } + + public void setId(PK id) { + this.subsystem = id.getSubsystem(); + this.username = id.getUsername(); + this.registrationId = id.getRegistrationId(); + } + + //Getters and setters are omitted for brevity + //end::identifiers-basic-idclass-generatedvalue-mapping-example[] + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::identifiers-basic-idclass-generatedvalue-mapping-example[] + } + + public static class PK implements Serializable { + + private String subsystem; + + private String username; + + private Integer registrationId; + + public PK(String subsystem, String username) { + this.subsystem = subsystem; + this.username = username; + } + + public PK(String subsystem, String username, Integer registrationId) { + this.subsystem = subsystem; + this.username = username; + this.registrationId = registrationId; + } + + private PK() { + } + + //Getters and setters are omitted for brevity + //end::identifiers-basic-idclass-generatedvalue-mapping-example[] + + public String getSubsystem() { + return subsystem; + } + + public String getUsername() { + return username; + } + + public Integer getRegistrationId() { + return registrationId; + } + + //tag::identifiers-basic-idclass-generatedvalue-mapping-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + PK pk = (PK) o; + return Objects.equals( subsystem, pk.subsystem ) && + Objects.equals( username, pk.username ) && + Objects.equals( registrationId, pk.registrationId ); + } + + @Override + public int hashCode() { + return Objects.hash( subsystem, username, registrationId ); + } + } + //end::identifiers-basic-idclass-generatedvalue-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassManyToOneTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassManyToOneTest.java new file mode 100644 index 000000000000..ba38df119b5b --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassManyToOneTest.java @@ -0,0 +1,201 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.ManyToOne; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class IdClassManyToOneTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SystemUser.class, + Subsystem.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Subsystem subsystem = new Subsystem(); + subsystem.setId( "Hibernate Forum" ); + subsystem.setDescription( "Hibernate projects forum" ); + entityManager.persist( subsystem ); + + SystemUser systemUser = new SystemUser(); + systemUser.setId( new PK( + subsystem, + "vlad" + ) ); + systemUser.setName( "Vlad Mihalcea" ); + + entityManager.persist( systemUser ); + } ); + } + + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Subsystem subsystem = entityManager.find( + Subsystem.class, + "Hibernate Forum" + ); + SystemUser systemUser = entityManager.find( + SystemUser.class, + new PK( + subsystem, + "vlad" + ) + ); + + assertEquals( "Vlad Mihalcea", systemUser.getName() ); + } ); + + } + + //tag::identifiers-basic-idclass-manytoone-mapping-example[] + @Entity(name = "SystemUser") + @IdClass( PK.class ) + public static class SystemUser { + + @Id + @ManyToOne(fetch = FetchType.LAZY) + private Subsystem subsystem; + + @Id + private String username; + + private String name; + + //Getters and setters are omitted for brevity + //end::identifiers-basic-idclass-manytoone-mapping-example[] + + public PK getId() { + return new PK( + subsystem, + username + ); + } + + public void setId(PK id) { + this.subsystem = id.getSubsystem(); + this.username = id.getUsername(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::identifiers-basic-idclass-manytoone-mapping-example[] + } + + @Entity(name = "Subsystem") + public static class Subsystem { + + @Id + private String id; + + private String description; + + //Getters and setters are omitted for brevity + //end::identifiers-basic-idclass-manytoone-mapping-example[] + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + //tag::identifiers-basic-idclass-manytoone-mapping-example[] + } + + public static class PK implements Serializable { + + private Subsystem subsystem; + + private String username; + + public PK(Subsystem subsystem, String username) { + this.subsystem = subsystem; + this.username = username; + } + + private PK() { + } + + //Getters and setters are omitted for brevity + //end::identifiers-basic-idclass-manytoone-mapping-example[] + + public Subsystem getSubsystem() { + return subsystem; + } + + public void setSubsystem(Subsystem subsystem) { + this.subsystem = subsystem; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + PK pk = (PK) o; + return Objects.equals( subsystem, pk.subsystem ) && + Objects.equals( username, pk.username ); + } + + @Override + public int hashCode() { + return Objects.hash( subsystem, username ); + } + //tag::identifiers-basic-idclass-manytoone-mapping-example[] + } + //end::identifiers-basic-idclass-manytoone-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassTest.java new file mode 100644 index 000000000000..584e9ae5b261 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdClassTest.java @@ -0,0 +1,168 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class IdClassTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SystemUser.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + SystemUser systemUser = new SystemUser(); + systemUser.setId( + new PK( + "Hibernate Forum", + "vlad" + ) + ); + systemUser.setName( "Vlad Mihalcea" ); + + entityManager.persist( systemUser ); + } ); + } + + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + SystemUser systemUser = entityManager.find( + SystemUser.class, + new PK( + "Hibernate Forum", + "vlad" + ) + ); + + assertEquals( "Vlad Mihalcea", systemUser.getName() ); + } ); + + } + + //tag::identifiers-basic-idclass-mapping-example[] + @Entity(name = "SystemUser") + @IdClass( PK.class ) + public static class SystemUser { + + @Id + private String subsystem; + + @Id + private String username; + + private String name; + + public PK getId() { + return new PK( + subsystem, + username + ); + } + + public void setId(PK id) { + this.subsystem = id.getSubsystem(); + this.username = id.getUsername(); + } + + //Getters and setters are omitted for brevity + //end::identifiers-basic-idclass-mapping-example[] + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::identifiers-basic-idclass-mapping-example[] + } + + public static class PK implements Serializable { + + private String subsystem; + + private String username; + + public PK(String subsystem, String username) { + this.subsystem = subsystem; + this.username = username; + } + + private PK() { + } + + //Getters and setters are omitted for brevity + //end::identifiers-basic-idclass-mapping-example[] + + public String getSubsystem() { + return subsystem; + } + + public void setSubsystem(String subsystem) { + this.subsystem = subsystem; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + //tag::identifiers-basic-idclass-mapping-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + PK pk = (PK) o; + return Objects.equals( subsystem, pk.subsystem ) && + Objects.equals( username, pk.username ); + } + + @Override + public int hashCode() { + return Objects.hash( subsystem, username ); + } + } + //end::identifiers-basic-idclass-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdManyToOneTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdManyToOneTest.java new file mode 100644 index 000000000000..fc1f53c6e945 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/IdManyToOneTest.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 . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.ManyToOne; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class IdManyToOneTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + Author.class, + Publisher.class + }; + } + + @Test + public void test() { + Author author = new Author(); + Publisher publisher = new Publisher(); + + doInJPA( this::entityManagerFactory, entityManager -> { + author.setName( "Vlad Mihalcea" ); + entityManager.persist( author ); + + publisher.setName( "Amazon" ); + entityManager.persist( publisher ); + + Book book = new Book(); + book.setAuthor( author ); + book.setPublisher( publisher ); + book.setTitle( "High-Performance Java Persistence" ); + entityManager.persist( book ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::identifiers-composite-id-fetching-example[] + Book book = entityManager.find( Book.class, new Book( + author, + publisher, + "High-Performance Java Persistence" + ) ); + + assertEquals( "Vlad Mihalcea", book.getAuthor().getName() ); + //end::identifiers-composite-id-fetching-example[] + } ); + + } + + //tag::identifiers-composite-id-mapping-example[] + @Entity(name = "Book") + public static class Book implements Serializable { + + @Id + @ManyToOne(fetch = FetchType.LAZY) + private Author author; + + @Id + @ManyToOne(fetch = FetchType.LAZY) + private Publisher publisher; + + @Id + private String title; + + public Book(Author author, Publisher publisher, String title) { + this.author = author; + this.publisher = publisher; + this.title = title; + } + + private Book() { + } + + //Getters and setters are omitted for brevity + //end::identifiers-composite-id-mapping-example[] + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public Publisher getPublisher() { + return publisher; + } + + public void setPublisher(Publisher publisher) { + this.publisher = publisher; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + //tag::identifiers-composite-id-mapping-example[] + + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( author, book.author ) && + Objects.equals( publisher, book.publisher ) && + Objects.equals( title, book.title ); + } + + @Override + public int hashCode() { + return Objects.hash( author, publisher, title ); + } + } + + @Entity(name = "Author") + public static class Author implements Serializable { + + @Id + private String name; + + //Getters and setters are omitted for brevity + //end::identifiers-composite-id-mapping-example[] + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::identifiers-composite-id-mapping-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Author author = (Author) o; + return Objects.equals( name, author.name ); + } + + @Override + public int hashCode() { + return Objects.hash( name ); + } + } + + @Entity(name = "Publisher") + public static class Publisher implements Serializable { + + @Id + private String name; + + //Getters and setters are omitted for brevity + //end::identifiers-composite-id-mapping-example[] + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::identifiers-composite-id-mapping-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Publisher publisher = (Publisher) o; + return Objects.equals( name, publisher.name ); + } + + @Override + public int hashCode() { + return Objects.hash( name ); + } + } + //end::identifiers-composite-id-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/MultipleNaturalIdTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/MultipleNaturalIdTest.java new file mode 100644 index 000000000000..c68bd261978f --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/MultipleNaturalIdTest.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.userguide.mapping.identifier; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class MultipleNaturalIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + Publisher.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Publisher publisher = new Publisher(); + publisher.setId( 1L ); + publisher.setName( "Amazon" ); + entityManager.persist( publisher ); + + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setProductNumber( "973022823X" ); + book.setPublisher( publisher ); + + entityManager.persist( book ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Publisher publisher = entityManager.getReference( Publisher.class, 1L ); + //tag::naturalid-load-access-example[] + + Book book = entityManager + .unwrap(Session.class) + .byNaturalId( Book.class ) + .using("productNumber", "973022823X") + .using("publisher", publisher) + .load(); + //end::naturalid-load-access-example[] + + assertEquals("High-Performance Java Persistence", book.getTitle()); + } ); + } + + //tag::naturalid-multiple-attribute-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + @NaturalId + private String productNumber; + + @NaturalId + @ManyToOne(fetch = FetchType.LAZY) + private Publisher publisher; + + //Getters and setters are omitted for brevity + //end::naturalid-multiple-attribute-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getProductNumber() { + return productNumber; + } + + public void setProductNumber(String productNumber) { + this.productNumber = productNumber; + } + + public Publisher getPublisher() { + return publisher; + } + + public void setPublisher(Publisher publisher) { + this.publisher = publisher; + } + //tag::naturalid-multiple-attribute-mapping-example[] + } + + @Entity(name = "Publisher") + public static class Publisher implements Serializable { + + @Id + private Long id; + + private String name; + + //Getters and setters are omitted for brevity + //end::naturalid-multiple-attribute-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::naturalid-multiple-attribute-mapping-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Publisher publisher = (Publisher) o; + return Objects.equals( id, publisher.id ) && + Objects.equals( name, publisher.name ); + } + + @Override + public int hashCode() { + return Objects.hash( id, name ); + } + } + //end::naturalid-multiple-attribute-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/MutableNaturalIdTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/MutableNaturalIdTest.java new file mode 100644 index 000000000000..174e1be49d77 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/MutableNaturalIdTest.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.userguide.mapping.identifier; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Vlad Mihalcea + */ +public class MutableNaturalIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Author.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Author author = new Author(); + author.setId( 1L ); + author.setName( "John Doe" ); + author.setEmail( "john@acme.com" ); + + entityManager.persist( author ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::naturalid-mutable-synchronized-example[] + //tag::naturalid-mutable-example[] + Author author = entityManager + .unwrap(Session.class) + .bySimpleNaturalId( Author.class ) + .load( "john@acme.com" ); + //end::naturalid-mutable-example[] + + author.setEmail( "john.doe@acme.com" ); + + assertNull( + entityManager + .unwrap(Session.class) + .bySimpleNaturalId( Author.class ) + .setSynchronizationEnabled( false ) + .load( "john.doe@acme.com" ) + ); + + assertSame( author, + entityManager + .unwrap(Session.class) + .bySimpleNaturalId( Author.class ) + .setSynchronizationEnabled( true ) + .load( "john.doe@acme.com" ) + ); + //end::naturalid-mutable-example[] + + //end::naturalid-mutable-synchronized-example[] + } ); + } + + //tag::naturalid-mutable-mapping-example[] + @Entity(name = "Author") + public static class Author { + + @Id + private Long id; + + private String name; + + @NaturalId(mutable = true) + private String email; + + //Getters and setters are omitted for brevity + //end::naturalid-mutable-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + //tag::naturalid-mutable-mapping-example[] + } + //end::naturalid-mutable-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaiveEqualsHashCodeEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaiveEqualsHashCodeEntityTest.java new file mode 100644 index 000000000000..9b201eb31a9e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaiveEqualsHashCodeEntityTest.java @@ -0,0 +1,201 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class NaiveEqualsHashCodeEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Library.class, + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Library library = new Library(); + library.setId( 1L ); + library.setName( "Amazon" ); + + entityManager.persist( library ); + } ); + } + + @Test + public void testPersist() { + + //tag::entity-pojo-naive-equals-hashcode-persist-example[] + Book book1 = new Book(); + book1.setTitle( "High-Performance Java Persistence" ); + + Book book2 = new Book(); + book2.setTitle( "Java Persistence with Hibernate" ); + + Library library = doInJPA( this::entityManagerFactory, entityManager -> { + Library _library = entityManager.find( Library.class, 1L ); + + _library.getBooks().add( book1 ); + _library.getBooks().add( book2 ); + + return _library; + } ); + + assertFalse( library.getBooks().contains( book1 ) ); + assertFalse( library.getBooks().contains( book2 ) ); + //end::entity-pojo-naive-equals-hashcode-persist-example[] + } + + @Test + public void testPersistForceFlush() { + + //tag::entity-pojo-naive-equals-hashcode-persist-force-flush-example[] + Book book1 = new Book(); + book1.setTitle( "High-Performance Java Persistence" ); + + Book book2 = new Book(); + book2.setTitle( "Java Persistence with Hibernate" ); + + Library library = doInJPA( this::entityManagerFactory, entityManager -> { + Library _library = entityManager.find( Library.class, 1L ); + + entityManager.persist( book1 ); + entityManager.persist( book2 ); + entityManager.flush(); + + _library.getBooks().add( book1 ); + _library.getBooks().add( book2 ); + + return _library; + } ); + + assertTrue( library.getBooks().contains( book1 ) ); + assertTrue( library.getBooks().contains( book2 ) ); + //end::entity-pojo-naive-equals-hashcode-persist-force-flush-example[] + } + + //tag::entity-pojo-naive-equals-hashcode-example[] + @Entity(name = "Library") + public static class Library { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn(name = "book_id") + private Set books = new HashSet<>(); + + //Getters and setters are omitted for brevity + //end::entity-pojo-naive-equals-hashcode-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + //tag::entity-pojo-naive-equals-hashcode-example[] + } + + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::entity-pojo-naive-equals-hashcode-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + //tag::entity-pojo-naive-equals-hashcode-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( id, book.id ); + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } + } + //end::entity-pojo-naive-equals-hashcode-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaturalIdEqualsHashCodeEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaturalIdEqualsHashCodeEntityTest.java new file mode 100644 index 000000000000..a5644b6f585d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaturalIdEqualsHashCodeEntityTest.java @@ -0,0 +1,180 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class NaturalIdEqualsHashCodeEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Library.class, + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Library library = new Library(); + library.setId( 1L ); + library.setName( "Amazon" ); + + entityManager.persist( library ); + } ); + } + + @Test + public void testPersist() { + + //tag::entity-pojo-natural-id-equals-hashcode-persist-example[] + Book book1 = new Book(); + book1.setTitle( "High-Performance Java Persistence" ); + book1.setIsbn( "978-9730228236" ); + + Library library = doInJPA( this::entityManagerFactory, entityManager -> { + Library _library = entityManager.find( Library.class, 1L ); + + _library.getBooks().add( book1 ); + + return _library; + } ); + + assertTrue( library.getBooks().contains( book1 ) ); + //end::entity-pojo-natural-id-equals-hashcode-persist-example[] + } + + //tag::entity-pojo-natural-id-equals-hashcode-example[] + @Entity(name = "Library") + public static class Library { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn(name = "book_id") + private Set books = new HashSet<>(); + + //Getters and setters are omitted for brevity + //end::entity-pojo-natural-id-equals-hashcode-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + //tag::entity-pojo-natural-id-equals-hashcode-example[] + } + + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + @NaturalId + private String isbn; + + //Getters and setters are omitted for brevity + //end::entity-pojo-natural-id-equals-hashcode-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + //tag::entity-pojo-natural-id-equals-hashcode-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( isbn, book.isbn ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn ); + } + } + //end::entity-pojo-natural-id-equals-hashcode-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/RowIdTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/RowIdTest.java new file mode 100644 index 000000000000..350b0d7207da --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/RowIdTest.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Formula; +import org.hibernate.annotations.RowId; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(Oracle8iDialect.class) +public class RowIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = new Product(); + product.setId( 1L ); + product.setName( "Mobile phone" ); + product.setNumber( "123-456-7890" ); + entityManager.persist( product ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::identifiers-rowid-example[] + Product product = entityManager.find( Product.class, 1L ); + + product.setName( "Smart phone" ); + //end::identifiers-rowid-example[] + } ); + } + + //tag::identifiers-rowid-mapping[] + @Entity(name = "Product") + @RowId("ROWID") + public static class Product { + + @Id + private Long id; + + @Column(name = "`name`") + private String name; + + @Column(name = "`number`") + private String number; + + //Getters and setters are omitted for brevity + + //end::identifiers-rowid-mapping[] + + 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 getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + + //tag::identifiers-rowid-mapping[] + } + //end::identifiers-rowid-mapping[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorConfiguredTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorConfiguredTest.java new file mode 100644 index 000000000000..09a3debcfabd --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorConfiguredTest.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.userguide.mapping.identifier; + +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.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; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialectFeature(DialectChecks.SupportsSequences.class) +public class SequenceGeneratorConfiguredTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( long i = 1; i <= 5; i++ ) { + if(i % 3 == 0) { + entityManager.flush(); + } + Product product = new Product(); + product.setName( String.format( "Product %d", i ) ); + entityManager.persist( product ); + } + } ); + } + + //tag::identifiers-generators-sequence-mapping-example[] + @Entity(name = "Product") + public static class Product { + + @Id + @GeneratedValue( + strategy = GenerationType.SEQUENCE, + generator = "sequence-generator" + ) + @SequenceGenerator( + name = "sequence-generator", + sequenceName = "product_sequence", + allocationSize = 5 + ) + private Long id; + + @Column(name = "product_name") + private String name; + + //Getters and setters are omitted for brevity + + //end::identifiers-generators-sequence-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::identifiers-generators-sequence-mapping-example[] + } + //end::identifiers-generators-sequence-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorNamedTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorNamedTest.java new file mode 100644 index 000000000000..e62ba1a29f95 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorNamedTest.java @@ -0,0 +1,94 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +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.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +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; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialectFeature(DialectChecks.SupportsSequences.class) +public class SequenceGeneratorNamedTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( long i = 1; i <= 5; i++ ) { + if(i % 3 == 0) { + entityManager.flush(); + } + Product product = new Product(); + product.setName( String.format( "Product %d", i ) ); + entityManager.persist( product ); + } + } ); + } + + //tag::identifiers-generators-sequence-mapping-example[] + @Entity(name = "Product") + public static class Product { + + @Id + @GeneratedValue( + strategy = GenerationType.SEQUENCE, + generator = "sequence-generator" + ) + @SequenceGenerator( + name = "sequence-generator", + sequenceName = "product_sequence" + ) + private Long id; + + @Column(name = "product_name") + private String name; + + //Getters and setters are omitted for brevity + + //end::identifiers-generators-sequence-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::identifiers-generators-sequence-mapping-example[] + } + //end::identifiers-generators-sequence-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorUnnamedTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorUnnamedTest.java new file mode 100644 index 000000000000..3c4bc2dc3288 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SequenceGeneratorUnnamedTest.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.userguide.mapping.identifier; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +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; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialectFeature(DialectChecks.SupportsSequences.class) +public class SequenceGeneratorUnnamedTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( long i = 1; i <= 5; i++ ) { + if(i % 3 == 0) { + entityManager.flush(); + } + Product product = new Product(); + product.setName( String.format( "Product %d", i ) ); + entityManager.persist( product ); + } + } ); + } + + //tag::identifiers-generators-sequence-mapping-example[] + @Entity(name = "Product") + public static class Product { + + @Id + @GeneratedValue( + strategy = GenerationType.SEQUENCE + ) + private Long id; + + @Column(name = "product_name") + private String name; + + //Getters and setters are omitted for brevity + + //end::identifiers-generators-sequence-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::identifiers-generators-sequence-mapping-example[] + } + //end::identifiers-generators-sequence-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTableTest.java new file mode 100644 index 000000000000..b9b9156d9f22 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTableTest.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.userguide.mapping.identifier; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class SimpleEntityTableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + + } + + //tag::entity-pojo-table-mapping-example[] + @Entity(name = "Book") + @Table( + catalog = "public", + schema = "store", + name = "book" + ) + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::entity-pojo-table-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::entity-pojo-table-mapping-example[] + } + //end::entity-pojo-table-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTest.java new file mode 100644 index 000000000000..2d6b5c874aee --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTest.java @@ -0,0 +1,236 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class SimpleEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Library.class, + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Library library = new Library(); + library.setId( 1L ); + library.setName( "Amazon" ); + + entityManager.persist( library ); + + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + } + + + @Test + public void testIdentityScope() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-pojo-identity-scope-example[] + Book book1 = entityManager.find( Book.class, 1L ); + Book book2 = entityManager.find( Book.class, 1L ); + + assertTrue( book1 == book2 ); + //end::entity-pojo-identity-scope-example[] + } ); + + } + + @Test + public void testSetIdentityScope() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-pojo-set-identity-scope-example[] + Library library = entityManager.find( Library.class, 1L ); + + Book book1 = entityManager.find( Book.class, 1L ); + Book book2 = entityManager.find( Book.class, 1L ); + + library.getBooks().add( book1 ); + library.getBooks().add( book2 ); + + assertEquals( 1, library.getBooks().size() ); + //end::entity-pojo-set-identity-scope-example[] + } ); + } + + @Test + public void testMultiSessionIdentityScope() { + + //tag::entity-pojo-multi-session-identity-scope-example[] + Book book1 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + + Book book2 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + + assertFalse( book1 == book2 ); + //end::entity-pojo-multi-session-identity-scope-example[] + } + + @Test + public void testMultiSessionSetIdentityScope() { + + Book book1 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + + Book book2 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + //tag::entity-pojo-multi-session-set-identity-scope-example[] + + doInJPA( this::entityManagerFactory, entityManager -> { + Set books = new HashSet<>(); + + books.add( book1 ); + books.add( book2 ); + + assertEquals( 2, books.size() ); + } ); + //end::entity-pojo-multi-session-set-identity-scope-example[] + } + + @Test + public void testTransientSetIdentityScope() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-pojo-transient-set-identity-scope-example[] + Library library = entityManager.find( Library.class, 1L ); + + Book book1 = new Book(); + book1.setId( 100L ); + book1.setTitle( "High-Performance Java Persistence" ); + + Book book2 = new Book(); + book2.setId( 101L ); + book2.setTitle( "Java Persistence with Hibernate" ); + + library.getBooks().add( book1 ); + library.getBooks().add( book2 ); + + assertEquals( 2, library.getBooks().size() ); + //end::entity-pojo-transient-set-identity-scope-example[] + } ); + } + + //tag::entity-pojo-set-mapping-example[] + @Entity(name = "Library") + public static class Library { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn(name = "book_id") + private Set books = new HashSet<>(); + + //Getters and setters are omitted for brevity + //end::entity-pojo-set-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + //tag::entity-pojo-set-mapping-example[] + } + //end::entity-pojo-set-mapping-example[] + + //tag::entity-pojo-mapping-example[] + @Entity(name = "Book") + public static class Book { + + //tag::entity-pojo-identifier-mapping-example[] + @Id + private Long id; + //end::entity-pojo-identifier-mapping-example[] + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::entity-pojo-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::entity-pojo-mapping-example[] + } + //end::entity-pojo-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleNaturalIdTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleNaturalIdTest.java new file mode 100644 index 000000000000..cd49dc675209 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleNaturalIdTest.java @@ -0,0 +1,118 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class SimpleNaturalIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + book.setIsbn( "978-9730228236" ); + + entityManager.persist( book ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::naturalid-simple-load-access-example[] + Book book = entityManager + .unwrap(Session.class) + .bySimpleNaturalId( Book.class ) + .load( "978-9730228236" ); + //end::naturalid-simple-load-access-example[] + + assertEquals("High-Performance Java Persistence", book.getTitle()); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::naturalid-load-access-example[] + Book book = entityManager + .unwrap(Session.class) + .byNaturalId( Book.class ) + .using( "isbn", "978-9730228236" ) + .load(); + //end::naturalid-load-access-example[] + + assertEquals("High-Performance Java Persistence", book.getTitle()); + } ); + } + + //tag::naturalid-simple-basic-attribute-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + @NaturalId + private String isbn; + + //Getters and setters are omitted for brevity + //end::naturalid-simple-basic-attribute-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + //tag::naturalid-simple-basic-attribute-mapping-example[] + } + //end::naturalid-simple-basic-attribute-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/TableGeneratorConfiguredTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/TableGeneratorConfiguredTest.java new file mode 100644 index 000000000000..e05b548f717f --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/TableGeneratorConfiguredTest.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.TableGenerator; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class TableGeneratorConfiguredTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::identifiers-generators-table-persist-example[] + for ( long i = 1; i <= 3; i++ ) { + Product product = new Product(); + product.setName( String.format( "Product %d", i ) ); + entityManager.persist( product ); + } + //end::identifiers-generators-table-persist-example[] + } ); + } + + //tag::identifiers-generators-table-mapping-example[] + @Entity(name = "Product") + public static class Product { + + @Id + @GeneratedValue( + strategy = GenerationType.TABLE, + generator = "table-generator" + ) + @TableGenerator( + name = "table-generator", + table = "table_identifier", + pkColumnName = "table_name", + valueColumnName = "product_id", + allocationSize = 5 + ) + private Long id; + + @Column(name = "product_name") + private String name; + + //Getters and setters are omitted for brevity + + //end::identifiers-generators-table-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::identifiers-generators-table-mapping-example[] + } + //end::identifiers-generators-table-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/TableGeneratorUnnamedTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/TableGeneratorUnnamedTest.java new file mode 100644 index 000000000000..d446fcf77dc0 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/TableGeneratorUnnamedTest.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.userguide.mapping.identifier; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +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; + +/** + * @author Vlad Mihalcea + */ +public class TableGeneratorUnnamedTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( long i = 1; i <= 5; i++ ) { + if(i % 3 == 0) { + entityManager.flush(); + } + Product product = new Product(); + product.setName( String.format( "Product %d", i ) ); + entityManager.persist( product ); + } + } ); + } + + //tag::identifiers-generators-table-mapping-example[] + @Entity(name = "Product") + public static class Product { + + @Id + @GeneratedValue( + strategy = GenerationType.TABLE + ) + private Long id; + + @Column(name = "product_name") + private String name; + + //Getters and setters are omitted for brevity + + //end::identifiers-generators-table-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::identifiers-generators-table-mapping-example[] + } + //end::identifiers-generators-table-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/UuidCustomGeneratedValueTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/UuidCustomGeneratedValueTest.java new file mode 100644 index 000000000000..a5ad2b030ed0 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/UuidCustomGeneratedValueTest.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.UUID; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ +public class UuidCustomGeneratedValueTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + Book book = new Book(); + + doInJPA( this::entityManagerFactory, entityManager -> { + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + + assertNotNull( book.getId() ); + } + + //tag::identifiers-generators-custom-uuid-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue( generator = "custom-uuid" ) + @GenericGenerator( + name = "custom-uuid", + strategy = "org.hibernate.id.UUIDGenerator", + parameters = { + @Parameter( + name = "uuid_gen_strategy_class", + value = "org.hibernate.id.uuid.CustomVersionOneStrategy" + ) + } + ) + private UUID id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::identifiers-generators-custom-uuid-mapping-example[] + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::identifiers-generators-custom-uuid-mapping-example[] + } + //end::identifiers-generators-custom-uuid-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/UuidGeneratedValueTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/UuidGeneratedValueTest.java new file mode 100644 index 000000000000..8463454259ba --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/UuidGeneratedValueTest.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 . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.UUID; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ +public class UuidGeneratedValueTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + Book book = new Book(); + + doInJPA( this::entityManagerFactory, entityManager -> { + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + + assertNotNull( book.getId() ); + } + + //tag::identifiers-generators-uuid-mapping-example[] + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private UUID id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::identifiers-generators-uuid-mapping-example[] + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::identifiers-generators-uuid-mapping-example[] + } + //end::identifiers-generators-uuid-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/naming/AcmeCorpPhysicalNamingStrategy.java b/documentation/src/test/java/org/hibernate/userguide/naming/AcmeCorpPhysicalNamingStrategy.java index 80c67bdd0816..404f62801eca 100644 --- a/documentation/src/test/java/org/hibernate/userguide/naming/AcmeCorpPhysicalNamingStrategy.java +++ b/documentation/src/test/java/org/hibernate/userguide/naming/AcmeCorpPhysicalNamingStrategy.java @@ -40,7 +40,7 @@ public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnv @Override public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) { // Acme naming standards do not apply to schema names - return null; + return name; } @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/osgi/_native/HibernateUtil.java b/documentation/src/test/java/org/hibernate/userguide/osgi/_native/HibernateUtil.java new file mode 100644 index 000000000000..044be8747a29 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/osgi/_native/HibernateUtil.java @@ -0,0 +1,35 @@ +package org.hibernate.userguide.osgi._native; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +//tag::osgi-discover-SessionFactory[] +public class HibernateUtil { + + private SessionFactory sf; + + public Session getSession() { + return getSessionFactory().openSession(); + } + + private SessionFactory getSessionFactory() { + if ( sf == null ) { + Bundle thisBundle = FrameworkUtil.getBundle( + HibernateUtil.class + ); + BundleContext context = thisBundle.getBundleContext(); + + ServiceReference sr = context.getServiceReference( + SessionFactory.class.getName() + ); + sf = ( SessionFactory ) context.getService( sr ); + } + return sf; + } +} +//end::osgi-discover-SessionFactory[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/osgi/jpa/HibernateUtil.java b/documentation/src/test/java/org/hibernate/userguide/osgi/jpa/HibernateUtil.java new file mode 100644 index 000000000000..3a9402df7129 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/osgi/jpa/HibernateUtil.java @@ -0,0 +1,44 @@ +package org.hibernate.userguide.osgi.jpa; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.spi.PersistenceProvider; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +//tag::osgi-discover-EntityManagerFactory[] +public class HibernateUtil { + + private EntityManagerFactory emf; + + public EntityManager getEntityManager() { + return getEntityManagerFactory().createEntityManager(); + } + + private EntityManagerFactory getEntityManagerFactory() { + if ( emf == null ) { + Bundle thisBundle = FrameworkUtil.getBundle( + HibernateUtil.class + ); + BundleContext context = thisBundle.getBundleContext(); + + ServiceReference serviceReference = context.getServiceReference( + PersistenceProvider.class.getName() + ); + PersistenceProvider persistenceProvider = ( PersistenceProvider ) context + .getService( + serviceReference + ); + + emf = persistenceProvider.createEntityManagerFactory( + "YourPersistenceUnitName", + null + ); + } + return emf; + } +} +//end::osgi-discover-EntityManagerFactory[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java index e082d7bcd668..e8f447acb745 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java @@ -88,6 +88,10 @@ public class Customer { @LazyGroup( "lobs" ) private Blob image; + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-lazy-loading-example[] + public Integer getId() { return id; } @@ -119,6 +123,7 @@ public Blob getImage() { public void setImage(Blob image) { this.image = image; } + //tag::BytecodeEnhancement-lazy-loading-example[] } //end::BytecodeEnhancement-lazy-loading-example[] @@ -132,7 +137,11 @@ public static class Person { private String name; @OneToMany(mappedBy = "author") - private List books = new ArrayList<>( ); + private List books = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] public Long getId() { return id; @@ -153,6 +162,7 @@ public void setName(String name) { public List getBooks() { return books; } + //tag::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } @Entity(name = "Book") @@ -169,6 +179,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] + public Long getId() { return id; } @@ -200,6 +214,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java new file mode 100644 index 000000000000..be78241ad8ed --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java @@ -0,0 +1,129 @@ +package org.hibernate.userguide.pc; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class CascadeOnDeleteTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setId( 1L ); + person.setName( "John Doe" ); + entityManager.persist( person ); + + Phone phone = new Phone(); + phone.setId( 1L ); + phone.setNumber( "123-456-7890" ); + phone.setOwner( person ); + entityManager.persist( phone ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::pc-cascade-on-delete-example[] + Person person = entityManager.find( Person.class, 1L ); + entityManager.remove( person ); + //end::pc-cascade-on-delete-example[] + } ); + } + + //tag::pc-cascade-on-delete-mapping-Person-example[] + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String name; + + //Getters and setters are omitted for brevity + + //end::pc-cascade-on-delete-mapping-Person-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::pc-cascade-on-delete-mapping-Person-example[] + } + //end::pc-cascade-on-delete-mapping-Person-example[] + + //tag::pc-cascade-on-delete-mapping-Phone-example[] + @Entity(name = "Phone") + public static class Phone { + + @Id + private Long id; + + @Column(name = "`number`") + private String number; + + @ManyToOne(fetch = FetchType.LAZY) + @OnDelete( action = OnDeleteAction.CASCADE ) + private Person owner; + + //Getters and setters are omitted for brevity + + //end::pc-cascade-on-delete-mapping-Phone-example[] + + 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 getOwner() { + return owner; + } + + public void setOwner(Person owner) { + this.owner = owner; + } + //tag::pc-cascade-on-delete-mapping-Phone-example[] + } + //end::pc-cascade-on-delete-mapping-Phone-example[] +} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/DynamicUpdateTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/DynamicUpdateTest.java new file mode 100644 index 000000000000..d17521b62b5b --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/DynamicUpdateTest.java @@ -0,0 +1,125 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.pc; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.Target; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.test.annotations.id.entities.Shoe; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class DynamicUpdateTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class, + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + + Product book = new Product(); + book.setId( 1L ); + book.setName( "High-Performance Java Persistence" ); + book.setDescription( "get the most out of your persistence layer" ); + book.setPriceCents( 29_99 ); + book.setQuantity( 10_000 ); + + entityManager.persist( book ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + Product book = entityManager.find( Product.class, 1L ); + book.setPriceCents( 24_99 ); + } ); + } + + //tag::pc-managed-state-dynamic-update-mapping-example[] + @Entity(name = "Product") + @DynamicUpdate + public static class Product { + + @Id + private Long id; + + @Column + private String name; + + @Column + private String description; + + @Column(name = "price_cents") + private Integer priceCents; + + @Column + private Integer quantity; + + //Getters and setters are omitted for brevity + + //end::pc-managed-state-dynamic-update-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getPriceCents() { + return priceCents; + } + + public void setPriceCents(Integer priceCents) { + this.priceCents = priceCents; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + //tag::pc-managed-state-dynamic-update-mapping-example[] + } + //end::pc-managed-state-dynamic-update-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/NoDynamicUpdateTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/NoDynamicUpdateTest.java new file mode 100644 index 000000000000..0802b8d62983 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/NoDynamicUpdateTest.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 . + */ +package org.hibernate.userguide.pc; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class NoDynamicUpdateTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class, + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::pc-managed-state-update-persist-example[] + Product book = new Product(); + book.setId( 1L ); + book.setName( "High-Performance Java Persistence" ); + book.setDescription( "Get the most out of your persistence layer" ); + book.setPriceCents( 29_99 ); + book.setQuantity( 10_000 ); + + entityManager.persist( book ); + //end::pc-managed-state-update-persist-example[] + } ); + + + //tag::pc-managed-state-update-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + Product book = entityManager.find( Product.class, 1L ); + book.setPriceCents( 24_99 ); + } ); + //end::pc-managed-state-update-example[] + } + + //tag::pc-managed-state-update-mapping-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Long id; + + @Column + private String name; + + @Column + private String description; + + @Column(name = "price_cents") + private Integer priceCents; + + @Column + private Integer quantity; + + //Getters and setters are omitted for brevity + + //end::pc-managed-state-update-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getPriceCents() { + return priceCents; + } + + public void setPriceCents(Integer priceCents) { + this.priceCents = priceCents; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + //tag::pc-managed-state-update-mapping-example[] + } + //end::pc-managed-state-update-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java index 8173dcd3eabb..48908b4f63ca 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java @@ -42,8 +42,6 @@ */ public class PersistenceContextTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( PersistenceContextTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -225,7 +223,7 @@ public void test() { //tag::pc-managed-state-native-example[] Person person = session.byId( Person.class ).load( personId ); person.setName("John Doe"); - entityManager.flush(); + session.flush(); //end::pc-managed-state-native-example[] } ); @@ -420,6 +418,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::pc-find-by-natural-id-entity-example[] + public Long getId() { return id; } @@ -451,6 +453,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::pc-find-by-natural-id-entity-example[] } //end::pc-find-by-natural-id-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Person.java b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java index 452e8a918602..620af209a6c8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/Person.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java @@ -22,6 +22,9 @@ public class Person { @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity +//end::pc-cascade-domain-model-example[] + public Long getId() { return id; } @@ -42,6 +45,8 @@ public List getPhones() { return phones; } +//tag::pc-cascade-domain-model-example[] + public void addPhone(Phone phone) { this.phones.add( phone ); phone.setOwner( this ); diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java index d9e58df21254..9208313a0cbe 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java @@ -22,6 +22,9 @@ public class Phone { @ManyToOne(fetch = FetchType.LAZY) private Person owner; + //Getters and setters are omitted for brevity +//end::pc-cascade-domain-model-example[] + public Long getId() { return id; } @@ -45,5 +48,6 @@ public Person getOwner() { public void setOwner(Person owner) { this.owner = owner; } +//tag::pc-cascade-domain-model-example[] } //end::pc-cascade-domain-model-example[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/persister/Author.java b/documentation/src/test/java/org/hibernate/userguide/persister/Author.java new file mode 100644 index 000000000000..4dd34a788188 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/persister/Author.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.userguide.persister; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.Persister; + + +/** + * @author Shawn Clowater + */ +//tag::entity-persister-mapping[] +@Entity +@Persister( impl = EntityPersister.class ) +public class Author { + + @Id + public Integer id; + + @OneToMany( mappedBy = "author" ) + @Persister( impl = CollectionPersister.class ) + public Set books = new HashSet<>(); + + //Getters and setters omitted for brevity + + //end::entity-persister-mapping[] + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Set getBooks() { + return books; + } + + //tag::entity-persister-mapping[] + public void addBook(Book book) { + this.books.add( book ); + book.setAuthor( this ); + } +} +//end::entity-persister-mapping[] diff --git a/documentation/src/test/java/org/hibernate/userguide/persister/Book.java b/documentation/src/test/java/org/hibernate/userguide/persister/Book.java new file mode 100644 index 000000000000..a15925d927d5 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/persister/Book.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.userguide.persister; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.annotations.Persister; + +/** + * @author Shawn Clowater + */ +//tag::entity-persister-mapping[] + +@Entity +@Persister( impl = EntityPersister.class ) +public class Book { + + @Id + public Integer id; + + private String title; + + @ManyToOne(fetch = FetchType.LAZY) + public Author author; + + //Getters and setters omitted for brevity + //end::entity-persister-mapping[] + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } +//tag::entity-persister-mapping[] +} +//end::entity-persister-mapping[] diff --git a/documentation/src/test/java/org/hibernate/userguide/persister/CollectionPersister.java b/documentation/src/test/java/org/hibernate/userguide/persister/CollectionPersister.java new file mode 100644 index 000000000000..2fdc057ecd3b --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/persister/CollectionPersister.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.userguide.persister; + +import org.hibernate.MappingException; +import org.hibernate.cache.CacheException; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.mapping.Collection; +import org.hibernate.persister.collection.OneToManyPersister; +import org.hibernate.persister.spi.PersisterCreationContext; + +/** + * @author Shawn Clowater + */ +//tag::entity-persister-mapping[] + +public class CollectionPersister + extends OneToManyPersister { + + public CollectionPersister( + Collection collectionBinding, + CollectionDataAccess cacheAccessStrategy, + PersisterCreationContext creationContext) + throws MappingException, CacheException { + super( collectionBinding, cacheAccessStrategy, creationContext ); + } +} +//end::entity-persister-mapping[] diff --git a/documentation/src/test/java/org/hibernate/userguide/persister/EntityPersister.java b/documentation/src/test/java/org/hibernate/userguide/persister/EntityPersister.java new file mode 100644 index 000000000000..057167447c46 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/persister/EntityPersister.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.persister; + +import org.hibernate.HibernateException; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.persister.entity.SingleTableEntityPersister; +import org.hibernate.persister.spi.PersisterCreationContext; + +/** + * @author Shawn Clowater + */ +//tag::entity-persister-mapping[] + +public class EntityPersister + extends SingleTableEntityPersister { + + public EntityPersister( + PersistentClass persistentClass, + EntityDataAccess cache, + NaturalIdDataAccess naturalIdRegionAccessStrategy, + PersisterCreationContext creationContext) + throws HibernateException { + super( persistentClass, cache, naturalIdRegionAccessStrategy, creationContext ); + } +} +//end::entity-persister-mapping[] + diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/ProxyInterfaceTest.java b/documentation/src/test/java/org/hibernate/userguide/proxy/ProxyInterfaceTest.java new file mode 100644 index 000000000000..cdab9f7b265d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/ProxyInterfaceTest.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.userguide.proxy; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Proxy; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * + * @author lgathy + */ +public class ProxyInterfaceTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Book.class }; + } + + @Test + public void testProxyClassLoader() { + + //tag::entity-proxy-persist-mapping[] + doInHibernate( this::sessionFactory, session -> { + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + session.persist( book ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Identifiable book = session.getReference( Book.class, 1L ); + + assertTrue( + "Loaded entity is not an instance of the proxy interface", + book instanceof Identifiable + ); + assertFalse( + "Proxy class was not created", + book instanceof Book + ); + } ); + //end::entity-proxy-persist-mapping[] + } + + //tag::entity-proxy-interface-mapping[] + public interface Identifiable { + + Long getId(); + + void setId(Long id); + } + + @Entity( name = "Book" ) + @Proxy(proxyClass = Identifiable.class) + public static final class Book implements Identifiable { + + @Id + private Long id; + + private String title; + + private String author; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + //Other getters and setters omitted for brevity + //end::entity-proxy-interface-mapping[] + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::entity-proxy-interface-mapping[] + } + //end::entity-proxy-interface-mapping[] + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/Country.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/Country.java new file mode 100644 index 000000000000..a0f86418bf14 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/Country.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +//$Id$ +package org.hibernate.userguide.proxy.tuplizer; +import javax.persistence.Column; +import javax.persistence.Embeddable; + +/** + * @author Emmanuel Bernard + */ +//tag::entity-tuplizer-entity-mapping[] + +@Embeddable +public interface Country { + + @Column(name = "CountryName") + String getName(); + + void setName(String name); +} +//end::entity-tuplizer-entity-mapping[] diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/Cuisine.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/Cuisine.java new file mode 100644 index 000000000000..80ce4b353407 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/Cuisine.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 . + */ + +//$Id$ +package org.hibernate.userguide.proxy.tuplizer; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.Tuplizer; + +/** + * @author Emmanuel Bernard + */ +//tag::entity-tuplizer-entity-mapping[] +@Entity +@Tuplizer(impl = DynamicEntityTuplizer.class) +public interface Cuisine { + + @Id + @GeneratedValue + Long getId(); + void setId(Long id); + + String getName(); + void setName(String name); + + @Tuplizer(impl = DynamicEmbeddableTuplizer.class) + Country getCountry(); + void setCountry(Country country); +} +//end::entity-tuplizer-entity-mapping[] diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DataProxyHandler.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DataProxyHandler.java new file mode 100644 index 000000000000..1d8852ff7c91 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DataProxyHandler.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 . + */ + +//$Id$ +package org.hibernate.userguide.proxy.tuplizer; +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * A simple {@link InvocationHandler} to act as the handler for our generated + * {@link java.lang.reflect.Proxy}-based entity instances. + *

+ * This is a trivial impl which simply keeps the property values into + * a Map. + * + * @author Steve Ebersole + */ +//tag::entity-tuplizer-instantiator[] + +public final class DataProxyHandler implements InvocationHandler { + + private String entityName; + + private Map data = new HashMap<>(); + + public DataProxyHandler(String entityName, Serializable id) { + this.entityName = entityName; + data.put( "Id", id ); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + if ( methodName.startsWith( "set" ) ) { + String propertyName = methodName.substring( 3 ); + data.put( propertyName, args[0] ); + } + else if ( methodName.startsWith( "get" ) ) { + String propertyName = methodName.substring( 3 ); + return data.get( propertyName ); + } + else if ( "toString".equals( methodName ) ) { + return entityName + "#" + data.get( "Id" ); + } + else if ( "hashCode".equals( methodName ) ) { + return this.hashCode(); + } + return null; + } + + public String getEntityName() { + return entityName; + } +} +//end::entity-tuplizer-instantiator[] diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicEmbeddableTuplizer.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicEmbeddableTuplizer.java new file mode 100644 index 000000000000..932021beb636 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicEmbeddableTuplizer.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 . + */ + +//$Id$ +package org.hibernate.userguide.proxy.tuplizer; +import org.hibernate.mapping.Component; +import org.hibernate.tuple.Instantiator; +import org.hibernate.tuple.component.PojoComponentTuplizer; + +/** + * @author Emmanuel Bernard + */ +//tag::entity-tuplizer-instantiator[] + +public class DynamicEmbeddableTuplizer + extends PojoComponentTuplizer { + + public DynamicEmbeddableTuplizer(Component embeddable) { + super( embeddable ); + } + + protected Instantiator buildInstantiator(Component embeddable) { + return new DynamicInstantiator( + embeddable.getComponentClassName() + ); + } +} +//end::entity-tuplizer-instantiator[] diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicEntityTuplizer.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicEntityTuplizer.java new file mode 100644 index 000000000000..cbb364986d18 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicEntityTuplizer.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 . + */ +package org.hibernate.userguide.proxy.tuplizer; + +import org.hibernate.mapping.PersistentClass; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.property.access.spi.Setter; +import org.hibernate.proxy.ProxyFactory; +import org.hibernate.tuple.Instantiator; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.tuple.entity.PojoEntityTuplizer; + +/** + * @author Emmanuel Bernard + */ +//tag::entity-tuplizer-instantiator[] +public class DynamicEntityTuplizer extends PojoEntityTuplizer { + + public DynamicEntityTuplizer( + EntityMetamodel entityMetamodel, + PersistentClass mappedEntity) { + super( entityMetamodel, mappedEntity ); + } + + @Override + protected Instantiator buildInstantiator( + EntityMetamodel entityMetamodel, + PersistentClass persistentClass) { + return new DynamicInstantiator( + persistentClass.getClassName() + ); + } + + @Override + protected ProxyFactory buildProxyFactory( + PersistentClass persistentClass, + Getter idGetter, + Setter idSetter) { + return super.buildProxyFactory( + persistentClass, idGetter, + idSetter + ); + } +} +//end::entity-tuplizer-instantiator[] diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicInstantiator.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicInstantiator.java new file mode 100644 index 000000000000..820c6305b8d6 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/DynamicInstantiator.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 . + */ + +//$Id$ +package org.hibernate.userguide.proxy.tuplizer; + +import java.io.Serializable; + +import org.hibernate.HibernateException; +import org.hibernate.tuple.Instantiator; + +/** + * @author Emmanuel Bernard + */ +//tag::entity-tuplizer-instantiator[] + +public class DynamicInstantiator + implements Instantiator { + + private final Class targetClass; + + public DynamicInstantiator(String targetClassName) { + try { + this.targetClass = Class.forName( targetClassName ); + } + catch (ClassNotFoundException e) { + throw new HibernateException( e ); + } + } + + public Object instantiate(Serializable id) { + return ProxyHelper.newProxy( targetClass, id ); + } + + public Object instantiate() { + return instantiate( null ); + } + + public boolean isInstance(Object object) { + try { + return targetClass.isInstance( object ); + } + catch( Throwable t ) { + throw new HibernateException( + "could not get handle to entity as interface : " + t + ); + } + } +} +//end::entity-tuplizer-instantiator[] diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/EntityNameInterceptor.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/EntityNameInterceptor.java new file mode 100644 index 000000000000..38d34c81dfe7 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/EntityNameInterceptor.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 . + */ + +//$Id$ +package org.hibernate.userguide.proxy.tuplizer; +import org.hibernate.EmptyInterceptor; + +/** + * @author Emmanuel Bernard + */ +public class EntityNameInterceptor extends EmptyInterceptor { + /** + * The callback from Hibernate to determine the entity name given + * a presumed entity instance. + * + * @param object The presumed entity instance. + * @return The entity name (pointing to the proper entity mapping). + */ + public String getEntityName(Object object) { + String entityName = ProxyHelper.extractEntityName( object ); + if ( entityName == null ) { + entityName = super.getEntityName( object ); + } + return entityName; + } +} + diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/ProxyHelper.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/ProxyHelper.java new file mode 100644 index 000000000000..df87668c859b --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/ProxyHelper.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 . + */ + +//$Id$ +package org.hibernate.userguide.proxy.tuplizer; +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +/** + * @author Emmanuel Bernard + */ +//tag::entity-tuplizer-instantiator[] + +public class ProxyHelper { + + public static T newProxy(Class targetClass, Serializable id) { + return ( T ) Proxy.newProxyInstance( + targetClass.getClassLoader(), + new Class[] { + targetClass + }, + new DataProxyHandler( + targetClass.getName(), + id + ) + ); + } + + public static String extractEntityName(Object object) { + if ( Proxy.isProxyClass( object.getClass() ) ) { + InvocationHandler handler = Proxy.getInvocationHandler( + object + ); + if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) { + DataProxyHandler myHandler = (DataProxyHandler) handler; + return myHandler.getEntityName(); + } + } + return null; + } +} +//end::entity-tuplizer-instantiator[] + diff --git a/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/TuplizerTest.java b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/TuplizerTest.java new file mode 100644 index 000000000000..168e1731ad48 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/proxy/tuplizer/TuplizerTest.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.proxy.tuplizer; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Emmanuel Bernard + */ +public class TuplizerTest extends BaseCoreFunctionalTestCase { + @Test + public void testEntityTuplizer() throws Exception { + //tag::entity-tuplizer-dynamic-proxy-example[] + Cuisine _cuisine = doInHibernateSessionBuilder( + () -> sessionFactory() + .withOptions() + .interceptor( new EntityNameInterceptor() ), + session -> { + Cuisine cuisine = ProxyHelper.newProxy( Cuisine.class, null ); + cuisine.setName( "Française" ); + + Country country = ProxyHelper.newProxy( Country.class, null ); + country.setName( "France" ); + + cuisine.setCountry( country ); + session.persist( cuisine ); + + return cuisine; + } ); + + doInHibernateSessionBuilder( + () -> sessionFactory() + .withOptions() + .interceptor( new EntityNameInterceptor() ), + session -> { + Cuisine cuisine = session.get( Cuisine.class, _cuisine.getId() ); + + assertEquals( "Française", cuisine.getName() ); + assertEquals( "France", cuisine.getCountry().getName() ); + } ); + //end::entity-tuplizer-dynamic-proxy-example[] + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Cuisine.class }; + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/IndexTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/IndexTest.java new file mode 100644 index 000000000000..2d21f5bafc6e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/schema/IndexTest.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 . + */ +package org.hibernate.userguide.schema; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.util.ExceptionUtil; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ + +public class IndexTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Author.class, + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Author author = new Author(); + author.setFirstName( "Vlad" ); + author.setLastName( "Mihalcea" ); + entityManager.persist( author ); + } ); + } + + //tag::schema-generation-columns-index-mapping-example[] + @Entity + @Table( + name = "author", + indexes = @Index( + name = "idx_author_first_last_name", + columnList = "first_name, last_name", + unique = false + ) + ) + public static class Author { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "first_name") + private String firstName; + + @Column(name = "last_name") + private String lastName; + + //Getter and setters omitted for brevity + //end::schema-generation-columns-index-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + //tag::schema-generation-columns-index-mapping-example[] + } + //end::schema-generation-columns-index-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java index 0f75862e0292..dc0ecf863a5e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java @@ -91,6 +91,10 @@ public class Customer { @LazyGroup( "lobs" ) private Blob image; + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] + public Integer getId() { return id; } @@ -122,6 +126,7 @@ public Blob getImage() { public void setImage(Blob image) { this.image = image; } + //tag::schema-generation-domain-model-example[] } @Entity(name = "Person") @@ -133,7 +138,11 @@ public static class Person { private String name; @OneToMany(mappedBy = "author") - private List books = new ArrayList<>( ); + private List books = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] public Long getId() { return id; @@ -154,6 +163,7 @@ public void setName(String name) { public List getBooks() { return books; } + //tag::schema-generation-domain-model-example[] } @Entity(name = "Book") @@ -170,6 +180,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] + public Long getId() { return id; } @@ -201,6 +215,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::schema-generation-domain-model-example[] } //end::schema-generation-domain-model-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java new file mode 100644 index 000000000000..ba6f042ff885 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java @@ -0,0 +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.userguide.schema; + +import javax.persistence.Column; +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.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.util.ExceptionUtil; +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 + */ + +public class UniqueConstraintTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + Author.class, + }; + } + + @Test + public void test() { + //tag::schema-generation-columns-unique-constraint-persist-example[] + Author _author = doInJPA( this::entityManagerFactory, entityManager -> { + Author author = new Author(); + author.setFirstName( "Vlad" ); + author.setLastName( "Mihalcea" ); + entityManager.persist( author ); + + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( author ); + entityManager.persist( book ); + + return author; + } ); + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( _author ); + entityManager.persist( book ); + } ); + } + catch (Exception expected) { + assertNotNull( ExceptionUtil.findCause( expected, ConstraintViolationException.class ) ); + } + //end::schema-generation-columns-unique-constraint-persist-example[] + } + + //tag::schema-generation-columns-unique-constraint-mapping-example[] + @Entity + @Table( + name = "book", + uniqueConstraints = @UniqueConstraint( + name = "uk_book_title_author", + columnNames = { + "title", + "author_id" + } + ) + ) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn( + name = "author_id", + foreignKey = @ForeignKey(name = "fk_book_author_id") + ) + private Author author; + + //Getter and setters omitted for brevity + //end::schema-generation-columns-unique-constraint-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + //tag::schema-generation-columns-unique-constraint-mapping-example[] + } + + @Entity + @Table(name = "author") + public static class Author { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "first_name") + private String firstName; + + @Column(name = "last_name") + private String lastName; + + //Getter and setters omitted for brevity + //end::schema-generation-columns-unique-constraint-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + //tag::schema-generation-columns-unique-constraint-mapping-example[] + } + //end::schema-generation-columns-unique-constraint-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/spatial/SpatialTest.java b/documentation/src/test/java/org/hibernate/userguide/spatial/SpatialTest.java index c6618e069565..70b55cfe1368 100644 --- a/documentation/src/test/java/org/hibernate/userguide/spatial/SpatialTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/spatial/SpatialTest.java @@ -11,6 +11,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.spatial.dialect.postgis.PostgisDialect; +import org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect; import org.hibernate.testing.RequiresDialect; import org.junit.Test; @@ -30,10 +31,10 @@ /** * @author Vlad Mihalcea */ -@RequiresDialect(PostgisDialect.class) +@RequiresDialect(PostgisPG95Dialect.class) public class SpatialTest extends BaseEntityManagerFunctionalTestCase { - GeometryFactory geometryFactory = new GeometryFactory(); + private GeometryFactory geometryFactory = new GeometryFactory(); @Override protected Class[] getAnnotatedClasses() { diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java b/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java index aaa2e2a76c57..a48168b5a56a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java @@ -21,6 +21,10 @@ public class Captain { @EmbeddedId private Identity id; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public Identity getId() { return id; } @@ -28,5 +32,6 @@ public Identity getId() { public void setId(Identity id) { this.id = id; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/CollectionLoaderTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/CollectionLoaderTest.java index fd5501438013..885de38aaa1c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/CollectionLoaderTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/CollectionLoaderTest.java @@ -47,8 +47,6 @@ @RequiresDialect(PostgreSQL82Dialect.class) public class CollectionLoaderTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( CollectionLoaderTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java index 70d243dc1a05..6f7067f747f7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java @@ -43,124 +43,127 @@ @RequiresDialect(PostgreSQL82Dialect.class) public class CustomSQLSecondaryTableTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( CustomSQLSecondaryTableTest.class ); - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Person.class - }; - } - - @Before - public void init() { - doInJPA( this::entityManagerFactory, entityManager -> { - Session session = entityManager.unwrap( Session.class ); - session.doWork( connection -> { - try(Statement statement = connection.createStatement(); ) { - statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" ); - statement.executeUpdate( "ALTER TABLE person_details ADD COLUMN valid boolean" ); - } - } ); - }); - } - - @Test - public void test_sql_custom_crud() { - - Person _person = doInJPA( this::entityManagerFactory, entityManager -> { - Person person = new Person(); - person.setName( "John Doe" ); - entityManager.persist( person ); - person.setImage( new byte[] {1, 2, 3} ); - return person; - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - Long postId = _person.getId(); - Person person = entityManager.find( Person.class, postId ); - assertArrayEquals(new byte[] {1, 2, 3}, person.getImage()); - entityManager.remove( person ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - Long postId = _person.getId(); - Person person = entityManager.find( Person.class, postId ); - assertNull(person); - } ); - } - - - //tag::sql-custom-crud-secondary-table-example[] - @Entity(name = "Person") - @Table(name = "person") - @SQLInsert( - sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) " - ) - @SQLDelete( - sql = "UPDATE person SET valid = false WHERE id = ? " - ) - @SecondaryTable(name = "person_details", - pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id")) - @org.hibernate.annotations.Table( - appliesTo = "person_details", - sqlInsert = @SQLInsert( - sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ", - check = ResultCheckStyle.COUNT - ), - sqlDelete = @SQLDelete( - sql = "UPDATE person_details SET valid = false WHERE person_id = ? " - ) - ) - @Loader(namedQuery = "find_valid_person") - @NamedNativeQueries({ - @NamedNativeQuery( - name = "find_valid_person", - query = "select " + - " p.id, " + - " p.name, " + - " pd.image " + - "from person p " + - "left outer join person_details pd on p.id = pd.person_id " + - "where p.id = ? and p.valid = true and pd.valid = true", - resultClass = Person.class - ) - }) - public static class Person { - - @Id - @GeneratedValue - private Long id; - - private String name; - - @Column(name = "image", table = "person_details") - private byte[] image; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public byte[] getImage() { - return image; - } - - public void setImage(byte[] image) { - this.image = image; - } - } - //end::sql-custom-crud-secondary-table-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + session.doWork( connection -> { + try(Statement statement = connection.createStatement(); ) { + statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" ); + statement.executeUpdate( "ALTER TABLE person_details ADD COLUMN valid boolean" ); + } + } ); + }); + } + + @Test + public void test_sql_custom_crud() { + + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setName( "John Doe" ); + entityManager.persist( person ); + person.setImage( new byte[] {1, 2, 3} ); + return person; + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Long postId = _person.getId(); + Person person = entityManager.find( Person.class, postId ); + assertArrayEquals(new byte[] {1, 2, 3}, person.getImage()); + entityManager.remove( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Long postId = _person.getId(); + Person person = entityManager.find( Person.class, postId ); + assertNull(person); + } ); + } + + + //tag::sql-custom-crud-secondary-table-example[] + @Entity(name = "Person") + @Table(name = "person") + @SQLInsert( + sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) " + ) + @SQLDelete( + sql = "UPDATE person SET valid = false WHERE id = ? " + ) + @SecondaryTable(name = "person_details", + pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id")) + @org.hibernate.annotations.Table( + appliesTo = "person_details", + sqlInsert = @SQLInsert( + sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ", + check = ResultCheckStyle.COUNT + ), + sqlDelete = @SQLDelete( + sql = "UPDATE person_details SET valid = false WHERE person_id = ? " + ) + ) + @Loader(namedQuery = "find_valid_person") + @NamedNativeQueries({ + @NamedNativeQuery( + name = "find_valid_person", + query = "SELECT " + + " p.id, " + + " p.name, " + + " pd.image " + + "FROM person p " + + "LEFT OUTER JOIN person_details pd ON p.id = pd.person_id " + + "WHERE p.id = ? AND p.valid = true AND pd.valid = true", + resultClass = Person.class + ) + }) + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Column(name = "image", table = "person_details") + private byte[] image; + + //Getters and setters are omitted for brevity + + //end::sql-custom-crud-secondary-table-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte[] getImage() { + return image; + } + + public void setImage(byte[] image) { + this.image = image; + } + //tag::sql-custom-crud-secondary-table-example[] + } + //end::sql-custom-crud-secondary-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java index 1fd50bf41a35..0489bcb251c6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java @@ -45,8 +45,6 @@ @RequiresDialect(PostgreSQL82Dialect.class) public class CustomSQLTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( CustomSQLTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -101,7 +99,6 @@ public void test_sql_custom_crud() { } ); } - //tag::sql-custom-crud-example[] @Entity(name = "Person") @SQLInsert( @@ -109,9 +106,11 @@ public void test_sql_custom_crud() { check = ResultCheckStyle.COUNT ) @SQLUpdate( - sql = "UPDATE person SET name = ? where id = ? ") + sql = "UPDATE person SET name = ? where id = ? " + ) @SQLDelete( - sql = "UPDATE person SET valid = false WHERE id = ? ") + sql = "UPDATE person SET valid = false WHERE id = ? " + ) @Loader(namedQuery = "find_valid_person") @NamedNativeQueries({ @NamedNativeQuery( @@ -138,6 +137,10 @@ public static class Person { @Where( clause = "valid = true" ) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::sql-custom-crud-example[] + public Long getId() { return id; } @@ -157,7 +160,7 @@ public void setName(String name) { public List getPhones() { return phones; } + //tag::sql-custom-crud-example[] } //end::sql-custom-crud-example[] - } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java b/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java index 30c8f9ff6ca9..e34918adf321 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java @@ -21,6 +21,10 @@ public class Dimensions { private int width; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public int getLength() { return length; } @@ -36,5 +40,6 @@ public int getWidth() { public void setWidth(int width) { this.width = width; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java b/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java index af6162aba20c..ea195067bc99 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java @@ -22,6 +22,10 @@ public class Identity implements Serializable { private String lastname; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public String getFirstname() { return firstname; } @@ -38,6 +42,7 @@ public void setLastname(String lastname) { this.lastname = lastname; } +//tag::sql-composite-key-entity-associations_named-query-example[] public boolean equals(Object o) { if ( this == o ) return true; if ( o == null || getClass() != o.getClass() ) return false; diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/OracleCustomSQLWithStoredProcedureTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/OracleCustomSQLWithStoredProcedureTest.java index 7e3a1d282ef8..fcc7213d7ceb 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/OracleCustomSQLWithStoredProcedureTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/OracleCustomSQLWithStoredProcedureTest.java @@ -37,8 +37,6 @@ @RequiresDialect(Oracle8iDialect.class) public class OracleCustomSQLWithStoredProcedureTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( OracleCustomSQLWithStoredProcedureTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/OracleStoredProcedureTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/OracleStoredProcedureTest.java index 171da04d0caf..fdffcfb90c5e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/OracleStoredProcedureTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/OracleStoredProcedureTest.java @@ -184,6 +184,20 @@ public void testStoredProcedureRefCursor() { }); } + @Test + public void testStoredProcedureRefCursorUsingNamedQuery() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::sql-jpa-call-sp-ref-cursor-oracle-named-query-example[] + List postComments = entityManager + .createNamedStoredProcedureQuery( "sp_person_phones" ) + .setParameter( "personId", 1L ) + .getResultList(); + //end::sql-jpa-call-sp-ref-cursor-oracle-named-query-example[] + + assertNotNull( postComments ); + }); + } + @Test public void testHibernateProcedureCallRefCursor() { doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java b/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java index 0a1d2f223573..92d6d1e459ce 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java @@ -16,6 +16,8 @@ public class PersonSummaryDTO { private String name; + //Getters and setters are omitted for brevity + public Number getId() { return id; } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java index fd061f7b6996..dec186e2a262 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java @@ -29,6 +29,7 @@ import org.hibernate.userguide.model.Partner; import org.hibernate.userguide.model.Person; import org.hibernate.userguide.model.PersonNames; +import org.hibernate.userguide.model.PersonPhoneCount; import org.hibernate.userguide.model.Phone; import org.hibernate.userguide.model.PhoneType; import org.hibernate.userguide.model.WireTransferPayment; @@ -42,6 +43,7 @@ import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; /** @@ -49,8 +51,6 @@ */ public class SQLTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( SQLTest.class ); - @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -184,7 +184,7 @@ public void test_sql_hibernate_query_scalar_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-all-columns-scalar-query-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT * FROM Person" ) .list(); //end::sql-hibernate-all-columns-scalar-query-example[] @@ -197,7 +197,7 @@ public void test_sql_hibernate_custom_column_selection_scalar_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-custom-column-selection-scalar-query-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT id, name FROM Person" ) .list(); @@ -215,7 +215,7 @@ public void test_sql_hibernate_query_scalar_explicit_result_set_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-scalar-query-explicit-result-set-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT * FROM Person" ) .addScalar( "id", LongType.INSTANCE ) .addScalar( "name", StringType.INSTANCE ) @@ -236,7 +236,7 @@ public void test_sql_hibernate_query_scalar_partial_explicit_result_set_example( doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-scalar-query-partial-explicit-result-set-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT * FROM Person" ) .addScalar( "id", LongType.INSTANCE ) .addScalar( "name" ) @@ -268,7 +268,7 @@ public void test_sql_hibernate_entity_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-query-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT * FROM Person" ) .addEntity( Person.class ) .list(); @@ -295,7 +295,7 @@ public void test_sql_hibernate_entity_query_explicit_result_set_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-query-explicit-result-set-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT id, name, nickName, address, createdOn, version " + "FROM Person" ) .addEntity( Person.class ) @@ -323,7 +323,7 @@ public void test_sql_hibernate_entity_associations_query_many_to_one_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-associations-query-many-to-one-example[] - List phones = session.createSQLQuery( + List phones = session.createNativeQuery( "SELECT id, phone_number, phone_type, person_id " + "FROM Phone" ) .addEntity( Phone.class ) @@ -344,7 +344,7 @@ public void test_sql_jpa_entity_associations_query_many_to_one_join_example() { .getResultList(); for(Phone phone : phones) { - Person person = phone.getPerson(); + assertNotNull( phone.getPerson().getName() ); } //end::sql-jpa-entity-associations-query-many-to-one-join-example[] assertEquals(3, phones.size()); @@ -356,7 +356,7 @@ public void test_sql_hibernate_entity_associations_query_many_to_one_join_exampl doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-associations-query-many-to-one-join-example[] - List tuples = session.createSQLQuery( + List tuples = session.createNativeQuery( "SELECT * " + "FROM Phone ph " + "JOIN Person pr ON ph.person_id = pr.id" ) @@ -367,6 +367,7 @@ public void test_sql_hibernate_entity_associations_query_many_to_one_join_exampl for(Object[] tuple : tuples) { Phone phone = (Phone) tuple[0]; Person person = (Person) tuple[1]; + assertNotNull( person.getName() ); } //end::sql-hibernate-entity-associations-query-many-to-one-join-example[] assertEquals(3, tuples.size()); @@ -378,7 +379,7 @@ public void test_sql_hibernate_entity_associations_query_many_to_one_join_result doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-associations-query-many-to-one-join-result-transformer-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT * " + "FROM Phone ph " + "JOIN Person pr ON ph.person_id = pr.id" ) @@ -421,7 +422,7 @@ public void test_sql_hibernate_entity_associations_query_one_to_many_join_exampl try { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); - List phones = session.createSQLQuery( + List phones = session.createNativeQuery( "SELECT * " + "FROM Phone ph " + "JOIN phone_call c ON c.phone_id = ph.id" ) @@ -450,7 +451,7 @@ public void test_sql_hibernate_entity_associations_query_one_to_many_join_exampl doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-associations-query-one-to-many-join-example[] - List tuples = session.createSQLQuery( + List tuples = session.createNativeQuery( "SELECT * " + "FROM Phone ph " + "JOIN phone_call c ON c.phone_id = ph.id" ) @@ -493,11 +494,11 @@ public void test_sql_hibernate_multi_entity_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-multi-entity-query-example[] - List entities = session.createSQLQuery( - "SELECT * " + - "FROM Person pr, Partner pt " + - "WHERE pr.name = pt.name" ) - .list(); + List entities = session.createNativeQuery( + "SELECT * " + + "FROM Person pr, Partner pt " + + "WHERE pr.name = pt.name" ) + .list(); //end::sql-hibernate-multi-entity-query-example[] assertEquals( 2, entities.size() ); } ); @@ -516,7 +517,7 @@ public void test_sql_hibernate_multi_entity_query_alias_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-multi-entity-query-alias-example[] - List entities = session.createSQLQuery( + List entities = session.createNativeQuery( "SELECT {pr.*}, {pt.*} " + "FROM Person pr, Partner pt " + "WHERE pr.name = pt.name" ) @@ -533,7 +534,7 @@ public void test_sql_hibernate_dto_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-dto-query-example[] - List dtos = session.createSQLQuery( + List dtos = session.createNativeQuery( "SELECT p.id as \"id\", p.name as \"name\" " + "FROM Person p") .setResultTransformer( Transformers.aliasToBean( PersonSummaryDTO.class ) ) @@ -548,7 +549,7 @@ public void test_sql_hibernate_inheritance_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-inheritance-query-example[] - List payments = session.createSQLQuery( + List payments = session.createNativeQuery( "SELECT * " + "FROM Payment p " + "JOIN CreditCardPayment cp on cp.id = p.id" ) @@ -579,7 +580,7 @@ public void test_sql_hibernate_query_parameters_example() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-query-parameters-example[] - List persons = session.createSQLQuery( + List persons = session.createNativeQuery( "SELECT * " + "FROM Person " + "WHERE name like :name" ) @@ -676,6 +677,21 @@ public void test_sql_hibernate_multiple_scalar_values_dto_named_query_example() }); } + @Test + public void test_sql_hibernate_multiple_scalar_values_dto_hibernate_named_query_example() { + doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + //tag::sql-hibernate-multiple-scalar-values-dto-hibernate-named-query-example[] + List personNames = session.getNamedNativeQuery( + "get_person_phone_count") + .getResultList(); + //end::sql-hibernate-multiple-scalar-values-dto-hibernate-named-query-example[] + assertEquals(2, personNames.size()); + assertEquals(1, personNames.stream().filter( person -> person.getName().equals( "John Doe" ) ).map( PersonPhoneCount::getPhoneCount ).findAny().get().intValue()); + assertEquals(2, personNames.stream().filter( person -> person.getName().equals( "Mrs. John Doe" ) ).map( PersonPhoneCount::getPhoneCount ).findAny().get().intValue()); + }); + } + @Test public void test_sql_jpa_entity_named_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java b/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java index 9fca478e1cbb..c2b56e7f6509 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java @@ -81,6 +81,10 @@ public class SpaceShip { private Dimensions dimensions; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public String getName() { return name; } @@ -120,5 +124,6 @@ public Dimensions getDimensions() { public void setDimensions(Dimensions dimensions) { this.dimensions = dimensions; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] diff --git a/documentation/src/test/resources/hibernate.properties b/documentation/src/test/resources/hibernate.properties index 5b40489d55c3..8b93710f2628 100644 --- a/documentation/src/test/resources/hibernate.properties +++ b/documentation/src/test/resources/hibernate.properties @@ -13,7 +13,6 @@ hibernate.connection.password @jdbc.pass@ hibernate.connection.pool_size 5 -hibernate.show_sql true hibernate.format_sql true hibernate.max_fetch_depth 5 diff --git a/documentation/src/test/resources/log4j.properties b/documentation/src/test/resources/log4j.properties index e96e1ddd33a0..49358a1a2d75 100644 --- a/documentation/src/test/resources/log4j.properties +++ b/documentation/src/test/resources/log4j.properties @@ -27,6 +27,10 @@ log4j.logger.org.hibernate.SQL=debug ### log JDBC bind parameters ### log4j.logger.org.hibernate.type=trace log4j.logger.org.hibernate.type.descriptor.sql=trace +log4j.logger.org.hibernate.id.enhanced.TableGenerator=trace +log4j.logger.org.hibernate.id.IdentifierGeneratorHelper=trace +log4j.logger.org.hibernate.persister.entity.AbstractEntityPersister=trace +log4j.logger.org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl=trace ### log schema export/update ### log4j.logger.org.hibernate.tool.hbm2ddl=info diff --git a/documentation/src/test/resources/org/hibernate/userguide/mapping/converter/hbm/MoneyConverterHbmTest.hbm.xml b/documentation/src/test/resources/org/hibernate/userguide/mapping/converter/hbm/MoneyConverterHbmTest.hbm.xml new file mode 100644 index 000000000000..e25aad3c88dc --- /dev/null +++ b/documentation/src/test/resources/org/hibernate/userguide/mapping/converter/hbm/MoneyConverterHbmTest.hbm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/src/test/resources/org/hibernate/userguide/mapping/dynamic/Book.hbm.xml b/documentation/src/test/resources/org/hibernate/userguide/mapping/dynamic/Book.hbm.xml new file mode 100644 index 000000000000..f617e3592b87 --- /dev/null +++ b/documentation/src/test/resources/org/hibernate/userguide/mapping/dynamic/Book.hbm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/etc/hibernate.properties.template b/etc/hibernate.properties.template index 2df7e5eee8d7..e147bc9986a0 100644 --- a/etc/hibernate.properties.template +++ b/etc/hibernate.properties.template @@ -43,8 +43,6 @@ hibernate.connection.url @DB_URL@ #hibernate.dialect org.hibernate.dialect.MySQLDialect -#hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect -#hibernate.dialect org.hibernate.dialect.MySQLMyISAMDialect #hibernate.connection.driver_class org.gjt.mm.mysql.Driver #hibernate.connection.driver_class com.mysql.jdbc.Driver #hibernate.connection.url jdbc:mysql:///test @@ -213,6 +211,15 @@ hibernate.connection.url @DB_URL@ #hibernate.connection.password hibernate +## HANA + +#hibernate.dialect org.hibernate.dialect.HANAColumnStoreDialect +#hibernate.connection.driver_class com.sap.db.jdbc.Driver +#hibernate.connection.url jdbc:sap://localhost:30015 +#hibernate.connection.username HIBERNATE_TEST +#hibernate.connection.password H1bernate_test + + ################################# ### Hibernate Connection Pool ### diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle new file mode 100644 index 000000000000..0e635a11f381 --- /dev/null +++ b/gradle/base-information.gradle @@ -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 + */ + +apply plugin: 'base' + +ext { + ormVersion = new HibernateVersion( '5.3.20.Final', project ) + baselineJavaVersion = '1.8' + jpaVersion = new JpaVersion('2.2') +} + +group = 'org.hibernate' +version = project.ormVersion.fullName + +class JpaVersion { + /** The *normal* name (1.0, 2.0, ..) */ + final String name; + + final String osgiName + + JpaVersion(String version){ + this.name = version + this.osgiName = version + ".0" + } + + @Override + String toString() { + return name + } +} + +class HibernateVersion { + final String fullName + final String majorVersion + final String family + + final String osgiVersion + + final boolean isSnapshot + + HibernateVersion(String fullName, Project project) { + this.fullName = fullName + + final String[] hibernateVersionComponents = fullName.split( '\\.' ) + this.majorVersion = hibernateVersionComponents[0] + this.family = hibernateVersionComponents[0] + '.' + hibernateVersionComponents[1] + + this.isSnapshot = fullName.endsWith( '-SNAPSHOT' ) + + this.osgiVersion = isSnapshot ? family + '.' + hibernateVersionComponents[2] + '.SNAPSHOT' : fullName + } + + @Override + String toString() { + return this.fullName + } +} diff --git a/gradle/databases.gradle b/gradle/databases.gradle new file mode 100644 index 000000000000..8e741bab1706 --- /dev/null +++ b/gradle/databases.gradle @@ -0,0 +1,118 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +// build a map of the database settings to use. +ext { + db = project.hasProperty('db') ? project.getProperty('db') : 'h2' + dbBundle = [ + h2 : [ + 'db.dialect' : 'org.hibernate.dialect.H2Dialect', + 'jdbc.driver': 'org.h2.Driver', + 'jdbc.user' : 'sa', + 'jdbc.pass' : '', + 'jdbc.url' : 'jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=10000', + ], + hsqldb : [ + 'db.dialect' : 'org.hibernate.dialect.HSQLDialect', + 'jdbc.driver': 'org.hsqldb.jdbc.JDBCDriver', + 'jdbc.user' : 'sa', + 'jdbc.pass' : '', + 'jdbc.url' : 'jdbc:hsqldb:mem:test' + ], + 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' + ], + pgsql : [ + 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', + 'jdbc.driver': 'org.postgresql.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:postgresql:hibernate_orm_test' + ], + pgsql_docker : [ + 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', + 'jdbc.driver': 'org.postgresql.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:postgresql://127.0.0.1/hibernate_orm_test' + ], + mysql : [ + 'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect', + 'jdbc.driver': 'com.mysql.jdbc.Driver', + 'jdbc.user' : 'hibernateormtest', + 'jdbc.pass' : 'hibernateormtest', + 'jdbc.url' : 'jdbc:mysql://localhost/hibernate_orm_test' + ], + 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://127.0.0.1/hibernate_orm_test?useSSL=false' + ], + mariadb : [ + 'db.dialect' : 'org.hibernate.dialect.MariaDB102Dialect', + 'jdbc.driver': 'org.mariadb.jdbc.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:mariadb://127.0.0.1/hibernate_orm_test' + ], + postgis : [ + 'db.dialect' : 'org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect', + 'jdbc.driver': 'org.postgresql.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:postgresql:hibernate_orm_test' + ], + oracle : [ + 'db.dialect' : 'org.hibernate.dialect.Oracle10gDialect', + 'jdbc.driver': 'oracle.jdbc.driver.OracleDriver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:oracle:thin:@localhost:1521/xe' + ], + 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://localhost;instance=SQLEXPRESS;databaseName=hibernate_orm_test' + ], + informix : [ + 'db.dialect' : 'org.hibernate.dialect.InformixDialect', + 'jdbc.driver': 'com.informix.jdbc.IfxDriver', + 'jdbc.user' : 'informix', + 'jdbc.pass' : 'in4mix', + 'jdbc.url' : 'jdbc:informix-sqli://127.0.0.1:9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix' + ], + 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://127.0.0.1:50000/hibern8' + ], + hana : [ + 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', + 'jdbc.driver': 'com.sap.db.jdbc.Driver', + 'jdbc.user' : 'HIBERNATE_TEST', + 'jdbc.pass' : 'H1bernate_test', + 'jdbc.url' : 'jdbc:sap://localhost:30015/' + ], + hana_vlad : [ + 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', + 'jdbc.driver': 'com.sap.db.jdbc.Driver', + 'jdbc.user' : 'VLAD', + 'jdbc.pass' : 'V1ad_test', + 'jdbc.url' : 'jdbc:sap://localhost:39015/' + ] + ] +} diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle new file mode 100644 index 000000000000..3ad72b135a7b --- /dev/null +++ b/gradle/java-module.gradle @@ -0,0 +1,362 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'de.thetaphi:forbiddenapis:2.6' + } +} + +import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis +import org.apache.tools.ant.filters.ReplaceTokens + +/** + * Support for modules that contain Java code + */ + +apply from: rootProject.file( 'gradle/base-information.gradle' ) +apply from: rootProject.file( 'gradle/libraries.gradle' ) +apply from: rootProject.file( 'gradle/databases.gradle' ) + +apply plugin: 'java' +apply plugin: 'osgi' + +apply plugin: 'checkstyle' +apply plugin: 'build-dashboard' +apply plugin: 'project-report' + +ext { + java9ModuleNameBase = project.name.startsWith( 'hibernate-' ) ? name.drop( 'hibernate-'.length() ): name + java9ModuleName = "org.hibernate.orm.$project.java9ModuleNameBase" + forbiddenAPITargetJDKCompatibility = '11' +} + + +sourceCompatibility = project.baselineJavaVersion +targetCompatibility = project.baselineJavaVersion + +afterEvaluate { + if ( !project.description ) { + project.description = "The Hibernate ORM $project.name module" + } +} + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Configurations and Dependencies + +configurations { + provided { + description = 'Non-exported compile-time dependencies.' + } + jbossLoggingTool { + description = 'Dependencies for running the jboss-logging tooling.' + } + asciidoclet { + description = "Dependencies for Asciidoctor Javadoc taglet" + } +} + +configurations.all*.exclude group: 'xml-apis', module: 'xml-apis' + + +dependencies { + compile libraries.logging + + provided libraries.logging_annotations + + jbossLoggingTool( libraries.logging_processor ) + + testCompile( libraries.junit ) + testCompile( libraries.byteman ) + testCompile( libraries.byteman_install ) + testCompile( libraries.byteman_bmunit ) + + testRuntime( libraries.log4j ) + testRuntime( libraries.javassist ) + testRuntime( libraries.byteBuddy ) + + //Databases + testRuntime( libraries.h2 ) + testRuntime( libraries.hsqldb ) + testRuntime( libraries.postgresql ) + testRuntime( libraries.mysql ) + testRuntime( libraries.mariadb ) + testRuntime( libraries.mssql ) + testRuntime( libraries.informix ) + testRuntime( libraries.hana ) + + asciidoclet 'org.asciidoctor:asciidoclet:1.+' + + if ( db.equalsIgnoreCase( 'oracle' ) ) { + testRuntime( libraries.oracle ) { + exclude group: 'com.oracle.jdbc', module: 'xmlparserv2' + } + } + else if ( db.equalsIgnoreCase( 'db2' ) ) { + testRuntime( libraries.db2 ) + } + else if ( db.equalsIgnoreCase( 'hana' ) ) { + testRuntime( libraries.hana ) + } + + // Mac-specific + project.ext.toolsJar = file("${System.getProperty('java.home')}/../lib/tools.jar") + if ( project.toolsJar.exists() ) { + testCompile files( project.toolsJar ) + } +} + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Compilation + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +task compile(dependsOn: [compileJava, processResources, compileTestJava, processTestResources] ) + +sourceSets.main { + compileClasspath += configurations.provided + compileClasspath += configurations.jbossLoggingTool +} + +convention.findPlugin( JavaPluginConvention.class ).sourceSets.each { sourceSet -> + JavaCompile javaCompileTask = project.tasks.findByName( sourceSet.compileJavaTaskName ) as JavaCompile + + // NOTE : this aptDir stuff is needed until we can have IntelliJ run annotation processors for us + // which cannot happen until we can fold hibernate-testing back into hibernate-core/src/test + // which cannot happen until... ugh + File aptDir = file( "${buildDir}/generated-src/apt/${sourceSet.name}" ) + sourceSet.allJava.srcDir( aptDir ) + + javaCompileTask.options.compilerArgs += [ + "-nowarn", + "-encoding", "UTF-8", + "-s", "${aptDir.absolutePath}" + ] + + javaCompileTask.doFirst { + aptDir.mkdirs() + } +} + + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// mac-specific stuff +final File toolsJar = file("${System.getProperty('java.home')}/../lib/tools.jar") +if ( ext.toolsJar.exists() ) { + dependencies{ + testCompile files( toolsJar ) + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Testing + +tasks.withType( Test.class ).all { task -> + if ( JavaVersion.current().isJava9Compatible() ) { + // Byteman needs this property to be set, https://developer.jboss.org/thread/274997 + task.jvmArgs += ["-Djdk.attach.allowAttachSelf=true"] + } + task.jvmArgs += [ + '-XX:+HeapDumpOnOutOfMemoryError', + "-XX:HeapDumpPath=${file( "${buildDir}/OOM-dump.hprof" ).absolutePath}", + '-XX:MetaspaceSize=512M' + ] + + task.maxHeapSize = '4G' + + task.systemProperties['hibernate.test.validatefailureexpected'] = true + task.systemProperties += System.properties.findAll { it.key.startsWith( "hibernate." ) } +} + +processTestResources { + inputs.property( "db", db ) + doLast { + copy { + from( sourceSets.test.java.srcDirs ) { + include '**/*.properties' + include '**/*.xml' + } + into sourceSets.test.java.outputDir + } + copy { + from file( 'src/test/resources' ) + into file( "${buildDir}/resources/test" ) + exclude 'src/test/resources/arquillian.xml' + exclude 'src/test/resources/hibernate.properties' + } + copy { + from file( 'src/test/resources/hibernate.properties' ) + into file( "${buildDir}/resources/test" ) + filter( ReplaceTokens, tokens: dbBundle[db] ) + } + } +} + +// Enable the experimental features of ByteBuddy with JDK 12+ +test { + //Only safe to attempt to parse the version as an integer since JDK11 + if ( JavaVersion.current().isJava11Compatible() ) { + int majorJVMVersionInt = Integer.valueOf(JavaVersion.current().toString()); + //Set the -Dnet.bytebuddy.experimental=true property only when we need it: + if (majorJVMVersionInt >= 12) { + systemProperty 'net.bytebuddy.experimental', true + } + } +} + +test { + if ( project.findProperty( 'log-test-progress' )?.toString()?.toBoolean() ) { + // Log a statement for each test. + // Used in the Travis build so that Travis doesn't end up panicking because there's no output for a long time. + testLogging { + events "passed", "skipped", "failed" + } + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// IDE + + +//idea { +// module { +// jdkName = project.sourceCompatibility +// +// excludeDirs = [file( ".gradle" )] +// excludeDirs += file( "$buildDir/classes" ) +// excludeDirs += file( "$buildDir/bundles" ) +// excludeDirs += file( "$buildDir/packages" ) +// excludeDirs += file( "$buildDir/dependency-cache" ) +// excludeDirs += file( "$buildDir/libs" ) +// excludeDirs += file( "$buildDir/reports" ) +// excludeDirs += file( "$buildDir/test-results" ) +// excludeDirs += file( "$buildDir/tmp" ) +// excludeDirs += file( "$buildDir/matrix" ) +// excludeDirs += file( "$buildDir/resources" ) +// +// downloadSources = true +// scopes.PROVIDED.plus += [configurations.provided] +// } +//} +// +/* + The latest versions of IntelliJ copy and use the test resources into out/test/resources + this occurs before the placeholder in the test config file are substituted + with the testing values. + + This behaviour prevents the execution of the hibernate tests from inside the IDE. + + A solution is to enable the 'After Build' Execution of the copyResourcesToIntelliJOutFolder task + from the 'Gradle project' IntelliJ tool window ( The task can be found under hibernate-orm > Task > other) + */ +task copyResourcesToIntelliJOutFolder { + doLast { + copy { + from "$buildDir/resources/test" + into 'out/test/resources' + } + } +} +// +// +// +//eclipse { +// jdt { +// sourceCompatibility = project.sourceCompatibility +// targetCompatibility = project.targetCompatibility +// } +// classpath { +// plusConfigurations.add( configurations.provided ) +// } +//} +// +//// eclipseClasspath will not add sources to classpath unless the dirs actually exist. +//// TODO: Eclipse's annotation processor handling is also fairly stupid (and completely lacks in the +//// Gradle plugin). For now, just compile first in order to get the logging classes. +//eclipseClasspath.dependsOn compile + +/* + Use this task to set the current DB in a given module. + + > gradlew sDB -Pdb=mysql + + Afterward, you can run any test from the IDE against that particular DB. + */ +task setDataBase { + inputs.property( "db", db ) + doLast { + processTestResources.execute() + copyResourcesToIntelliJOutFolder.execute() + + println( 'Setting current database to ' + db ) + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Report configs + +checkstyle { + sourceSets = [ project.sourceSets.main ] + configFile = rootProject.file( 'shared/config/checkstyle/checkstyle.xml' ) + showViolations = false +} +// exclude generated java sources - by explicitly setting the base source dir +checkstyleMain.source = 'src/main/java' + +// define a second checkstyle task for checking non-fatal violations +task nonFatalCheckstyle(type:Checkstyle) { + source = project.sourceSets.main.java + classpath = project.configurations.checkstyle + showViolations = false + configFile = rootProject.file( 'shared/config/checkstyle/checkstyle-non-fatal.xml' ) +} + +// because cfg package is a mess mainly from annotation stuff +checkstyleMain.exclude '**/org/hibernate/cfg/**' +checkstyleMain.exclude '**/org/hibernate/cfg/*' + + +task forbiddenApisSystemOut(type: CheckForbiddenApis, dependsOn: compileJava) { + classesDirs = project.sourceSets.main.output.classesDirs + classpath = project.sourceSets.main.compileClasspath + project.sourceSets.main.runtimeClasspath + targetCompatibility = project.forbiddenAPITargetJDKCompatibility + bundledSignatures += 'jdk-system-out' + suppressAnnotations += ['org.hibernate.internal.build.AllowSysOut', 'org.hibernate.internal.build.AllowPrintStacktrace'] +} + +task forbiddenApisUnsafe(type: CheckForbiddenApis, dependsOn: compileJava) { + classesDirs = project.sourceSets.main.output.classesDirs + classpath = project.sourceSets.main.compileClasspath + project.sourceSets.main.runtimeClasspath + targetCompatibility = project.forbiddenAPITargetJDKCompatibility + bundledSignatures += "jdk-unsafe-${baselineJavaVersion}".toString() + + // unfortunately we currently have many uses of default Locale implicitly (~370) which need to be fixed + // before we can fully enabled this check + // + // No idea how findbugs was missing these b4 + ignoreFailures = true +} + +task forbiddenApisNonPortable(type: CheckForbiddenApis, dependsOn: compileJava) { + classesDirs = project.sourceSets.main.output.classesDirs + classpath = project.sourceSets.main.compileClasspath + project.sourceSets.main.runtimeClasspath + targetCompatibility = project.forbiddenAPITargetJDKCompatibility + bundledSignatures += 'jdk-non-portable' +} + +task forbiddenApis +project.tasks.withType( CheckForbiddenApis ).each { task -> forbiddenApis.finalizedBy task } + +project.tasks.check.finalizedBy forbiddenApis diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle new file mode 100644 index 000000000000..bd5d0b770171 --- /dev/null +++ b/gradle/libraries.gradle @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +// 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 { + + junitVersion = '4.12' + h2Version = '1.4.196' + bytemanVersion = '4.0.3' //Compatible with JDK10 + jnpVersion = '5.0.6.CR1' + + hibernateValidatorVersion = '6.0.20.Final' + hibernateCommonsVersion = '5.0.4.Final' + validationApiVersion = '2.0.1.Final' + elVersion = '3.0.1-b08' + + cdiVersion = '2.0' + weldVersion = '3.0.0.Final' + + javassistVersion = '3.23.2-GA' + byteBuddyVersion = '1.9.11' + + geolatteVersion = '1.3.0' + + // Wildfly version targeted by module ZIP; Arquillian/Shrinkwrap versions used for CDI testing and testing the module ZIP + wildflyVersion = '17.0.1.Final' + arquillianVersion = '1.4.1.Final' + shrinkwrapVersion = '1.2.6' + shrinkwrapDescriptorsVersion = '2.0.0' + wildflyArquillianContainerVersion = '2.2.0.Final' + + jodaTimeVersion = '2.3' + + jaxbApiVersion = '2.3.1' + // We can't upgrade JAXB in Karaf (yet), but fortunately everything works fine with the version built in Karaf + jaxbApiVersionOsgiRange = "[2.2,3)" + jaxbRuntimeVersion = '2.3.1' + + libraries = [ + // Ant + ant: 'org.apache.ant:ant:1.8.2', + + // Antlr + antlr: 'antlr:antlr:2.7.7', + + // Annotations + commons_annotations: "org.hibernate.common:hibernate-commons-annotations:${hibernateCommonsVersion}", + jandex: 'org.jboss:jandex:2.0.5.Final', + classmate: 'com.fasterxml:classmate:1.3.4', + + // Dom4J + dom4j: 'org.dom4j:dom4j:2.1.3@jar', + + // Javassist + javassist: "org.javassist:javassist:${javassistVersion}", + + // Byte Buddy + byteBuddy: "net.bytebuddy:byte-buddy:${byteBuddyVersion}", + + // javax + jpa: "javax.persistence:javax.persistence-api:${project.jpaVersion}", + jta: 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final', + validation: "javax.validation:validation-api:${validationApiVersion}", + jacc: 'org.jboss.spec.javax.security.jacc:jboss-jacc-api_1.4_spec:1.0.2.Final', + interceptor: 'javax.interceptor:javax.interceptor-api:1.2', + // required by JAXB from JDK 9 as it is not available anymore in JDK 9 + activation: 'javax.activation:javax.activation-api:1.2.0', + + // logging + logging: 'org.jboss.logging:jboss-logging:3.3.2.Final', + logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.1.0.Final', + logging_processor: 'org.jboss.logging:jboss-logging-processor:2.1.0.Final', + + // jaxb task + jaxb_api: "javax.xml.bind:jaxb-api:${jaxbApiVersion}", + jaxb_runtime: "org.glassfish.jaxb:jaxb-runtime:${jaxbRuntimeVersion}", + jaxb_xjc: "org.glassfish.jaxb:jaxb-xjc:${jaxbRuntimeVersion}", + // Note that jaxb2_basics is a set of tools on *top* of JAXB. + // See https://github.com/highsource/jaxb2-basics + jaxb2_basics: 'org.jvnet.jaxb2_commons:jaxb2-basics:0.12.0', + jaxb2_basics_ant: 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.12.0', + + geolatte: "org.geolatte:geolatte-geom:${geolatteVersion}", + + // Animal Sniffer Ant Task and Java 1.6 API signature file + // not using 1.9 for the time being due to MANIMALSNIFFER-34 + animal_sniffer: 'org.codehaus.mojo:animal-sniffer-ant-tasks:1.13', + as_asm: 'org.ow2.asm:asm-all:5.0.3', + java16_signature: 'org.codehaus.mojo.signature:java16:1.0@signature', + + //Maven plugin framework + maven_core: 'org.apache.maven:maven-core:3.0.5', + maven_artifact: 'org.apache.maven:maven-artifact:3.0.5', + maven_plugin: 'org.apache.maven:maven-plugin-api:3.0.5', + maven_plugin_tools: 'org.apache.maven.plugin-tools:maven-plugin-annotations:3.2', + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~ testing + + log4j: "log4j:log4j:1.2.17", + junit: "junit:junit:${junitVersion}", + byteman: "org.jboss.byteman:byteman:${bytemanVersion}", + byteman_install: "org.jboss.byteman:byteman-install:${bytemanVersion}", + byteman_bmunit: "org.jboss.byteman:byteman-bmunit:${bytemanVersion}", + h2: "com.h2database:h2:${h2Version}", + hsqldb: "org.hsqldb:hsqldb:2.3.2", + derby: "org.apache.derby:derby:10.11.1.1", + postgresql: 'org.postgresql:postgresql:42.2.2', + mysql: 'mysql:mysql-connector-java:8.0.12', + mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', + + oracle: 'com.oracle.jdbc:ojdbc8:12.2.0.1', + mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8', + db2: 'com.ibm.db2:db2jcc:10.5', + hana: 'com.sap.cloud.db.jdbc:ngdbc:2.2.16', // for HANA 1 the minimum required client version is 1.120.20 + + jodaTime: "joda-time:joda-time:${jodaTimeVersion}", + + informix: 'com.ibm.informix:jdbc:4.10.7.20160517', + jboss_jta: "org.jboss.jbossts:jbossjta:4.16.4.Final", + xapool: "com.experlog:xapool:1.5.0", + mockito: 'org.mockito:mockito-core:2.19.1', + mockito_inline: 'org.mockito:mockito-inline:2.19.1', + + validator: "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}", + // EL required by Hibernate Validator at test runtime + expression_language: "org.glassfish:javax.el:${elVersion}", + + c3p0: "com.mchange:c3p0:0.9.5.2", + ehcache: "net.sf.ehcache:ehcache:2.10.3", + ehcache3: "org.ehcache:ehcache:3.0.0", + jcache: "javax.cache:cache-api:1.0.0", + proxool: "proxool:proxool:0.8.3", + hikaricp: "com.zaxxer:HikariCP:2.5.1", + vibur: "org.vibur:vibur-dbcp:22.1", + agroal_api: "io.agroal:agroal-api:0.4", + agroal_pool: "io.agroal:agroal-pool:0.4", + + cdi: "javax.enterprise:cdi-api:${cdiVersion}", + weld: "org.jboss.weld.se:weld-se-shaded:${weldVersion}", + + // Arquillian/Shrinkwrap + arquillian_junit_container: "org.jboss.arquillian.junit:arquillian-junit-container:${arquillianVersion}", + arquillian_protocol_servlet: "org.jboss.arquillian.protocol:arquillian-protocol-servlet:${arquillianVersion}", + + shrinkwrap_api: "org.jboss.shrinkwrap:shrinkwrap-api:${shrinkwrapVersion}", + shrinkwrap: "org.jboss.shrinkwrap:shrinkwrap-impl-base:${shrinkwrapVersion}", + + shrinkwrap_descriptors_api_javaee: "org.jboss.shrinkwrap.descriptors:shrinkwrap-descriptors-api-javaee:${shrinkwrapDescriptorsVersion}", + shrinkwrap_descriptors_impl_javaee: "org.jboss.shrinkwrap.descriptors:shrinkwrap-descriptors-impl-javaee:${shrinkwrapDescriptorsVersion}", + + wildfly_arquillian_container_managed: "org.wildfly.arquillian:wildfly-arquillian-container-managed:${wildflyArquillianContainerVersion}", + jboss_vfs: "org.jboss:jboss-vfs:3.2.11.Final", + jipijapa_spi: "org.wildfly:jipijapa-spi:${wildflyVersion}", + wildfly_transaction_client : 'org.wildfly.transaction:wildfly-transaction-client:1.0.3.Final', + + jboss_ejb_spec_jar : 'org.jboss.spec.javax.ejb:jboss-ejb-api_3.2_spec:1.0.0.Final', + jboss_annotation_spec_jar : 'org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:1.0.0.Final' + ] +} + +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + //Force the "byte buddy agent" version to match the Byte Buddy version we use, as Mockito might pull in a mismatched version transitively: + if (details.requested.group + ":" + details.requested.name == 'net.bytebuddy:byte-buddy-agent') { + details.useVersion byteBuddyVersion + } + } +} diff --git a/gradle/published-java-module.gradle b/gradle/published-java-module.gradle new file mode 100644 index 000000000000..ac9043be271b --- /dev/null +++ b/gradle/published-java-module.gradle @@ -0,0 +1,149 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +apply from: rootProject.file( 'gradle/java-module.gradle' ) + +apply from: rootProject.file( 'gradle/publishing-repos.gradle' ) +apply from: rootProject.file( 'gradle/publishing-pom.gradle' ) + + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Jar + +jar { + manifest = osgiManifest { + // GRADLE-1411: Even if we override Imports and Exports + // auto-generation with instructions, classesDir and classpath + // need to be here (temporarily). + + if ( project.pluginManager.hasPlugin( 'groovy' ) ) { + classesDir = sourceSets.main.groovy.outputDir + } + else { + classesDir = sourceSets.main.output.classesDir + } + classpath = configurations.runtime + + + // Java 9 module name + instruction 'Automatic-Module-Name', project.java9ModuleName + + // the OSGi metadata + symbolicName project.java9ModuleName + vendor 'Hibernate.org' + description project.description + docURL "http://www.hibernate.org/orm/${project.ormVersion.family}" + + instruction 'Import-Package', + // Temporarily support JTA 1.1 -- Karaf and other frameworks still + // use it. Without this, the plugin generates [1.2,2). + 'javax.transaction;version="[1.1,2)"', + // Tell Gradle OSGi to still dynamically import the other packages. + // IMPORTANT: Do not include the * in the modules' .gradle files. + // If it exists more than once, the manifest will physically contain a *. + '*' + + // Basic JAR manifest metadata + instruction 'Specification-Title', project.name + instruction 'Specification-Version', project.version + instruction 'Specification-Vendor', 'Hibernate.org' + instruction 'Implementation-Title', project.name + instruction 'Implementation-Version', project.version + instruction 'Implementation-Vendor', 'Hibernate.org' + instruction 'Implementation-Vendor-Id', 'org.hibernate' + instruction 'Implementation-Url', 'http://hibernate.org/orm' + + instruction 'Hibernate-VersionFamily', project.ormVersion.family + instruction 'Hibernate-JpaVersion', project.jpaVersion.name + } +} + + +task sourcesJar(type: Jar) { + from project.sourceSets.main.allSource + manifest = project.tasks.jar.manifest + classifier = 'sources' +} + +task javadocJar(type: Jar) { + from project.tasks.javadoc.outputs + manifest = project.tasks.jar.manifest + classifier = 'javadoc' +} + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Javadoc + +javadoc { + exclude( "**/internal/*" ) + exclude( "**/generated-src/**" ) + + final int currentYear = new GregorianCalendar().get( Calendar.YEAR ) + + configure( options ) { + windowTitle = "$project.name JavaDocs" + docTitle = "$project.name JavaDocs ($project.version)" + bottom = "Copyright © 2001-$currentYear Red Hat, Inc. All Rights Reserved." + use = true + encoding = 'UTF-8' + links += [ + 'https://docs.oracle.com/javase/8/docs/api/', + 'http://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', + 'http://docs.jboss.org/cdi/api/2.0/', + 'https://javaee.github.io/javaee-spec/javadocs/' + ] + + if ( JavaVersion.current().isJava11Compatible() ) { + //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 + // but after excluding the first two builds; see also specific comments on + // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 + // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people + // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. + System.out.println("Forcing Javadoc in Java 8 compatible mode"); + options.source = project.baselineJavaVersion + } + + if ( JavaVersion.current().isJava8Compatible() ) { + options.addStringOption( 'Xdoclint:none', '-quiet' ) + } + + doFirst { + // ordering problems if we try to do this during config phase :( + classpath += project.sourceSets.main.output + project.sourceSets.main.compileClasspath + project.configurations.provided + } + } +} + + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Publishing + +publishing { + publications { + publishedArtifacts { + from components.java + + artifact( sourcesJar ) { + // todo : do these really need to be specified twice? + classifier 'sources' + } + + artifact( javadocJar ) { + // todo : do these really need to be specified twice? + classifier "javadoc" + } + } + } +} + +task ciBuild( dependsOn: [test, publish] ) + +task release( dependsOn: [test, bintrayUpload] ) + diff --git a/gradle/publishing-pom.gradle b/gradle/publishing-pom.gradle new file mode 100644 index 000000000000..99cb3131376d --- /dev/null +++ b/gradle/publishing-pom.gradle @@ -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 + */ + +apply plugin: 'maven-publish' + + +publishing { + + publications { + publishedArtifacts { + pom { + name = 'Hibernate ORM - ' + project.name + description = project.description + url = 'http://hibernate.org/orm' + + organization { + name = 'Hibernate.org' + url = 'http://hibernate.org' + } + + licenses { + license { + name = 'GNU Library General Public License v2.1 or later' + url = 'http://www.opensource.org/licenses/LGPL-2.1' + comments = 'See discussion at http://hibernate.org/community/license/ for more details.' + distribution = 'repo' + } + } + + scm { + url = 'http://github.com/hibernate/hibernate-orm' + connection = 'scm:git:http://github.com/hibernate/hibernate-orm.git' + developerConnection = 'scm:git:git@github.com:hibernate/hibernate-orm.git' + } + + issueManagement { + system = 'jira' + url = 'https://hibernate.atlassian.net/browse/HHH' + } + + developers { + developer { + id = 'hibernate-team' + name = 'The Hibernate Development Team' + organization = 'Hibernate.org' + organizationUrl = 'http://hibernate.org' + } + } + + } + + } + } + +} diff --git a/gradle/publishing-repos.gradle b/gradle/publishing-repos.gradle new file mode 100644 index 000000000000..7a5031b47b9f --- /dev/null +++ b/gradle/publishing-repos.gradle @@ -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 + */ + +apply from: rootProject.file( 'gradle/base-information.gradle' ) + + +apply plugin: 'maven-publish' +apply plugin: 'maven-publish-auth' +apply plugin: 'com.jfrog.bintray' + + +ext { + bintrayUser = project.hasProperty( 'PERSONAL_BINTRAY_USER' ) ? project.property( 'PERSONAL_BINTRAY_USER' ) : null + bintrayKey = project.hasProperty( 'PERSONAL_BINTRAY_API_KEY' ) ? project.property( 'PERSONAL_BINTRAY_API_KEY' ) : null +} + + +publishing { + publications { + publishedArtifacts( MavenPublication ) + } + + repositories { + maven { + name 'jboss-snapshots-repository' + url 'https://repository.jboss.org/nexus/content/repositories/snapshots' + } + } +} + +bintray { + user = project.bintrayUser + key = project.bintrayKey + + publications = ['publishedArtifacts'] + + pkg { + userOrg = 'hibernate' + repo = 'artifacts' + name = 'hibernate-orm' + + publish = true + + version { + name = project.version + released = new Date() + vcsTag = project.version + gpg { + sign = true + } + attributes = [ + 'jpa': project.jpaVersion, + 'family': project.ormVersion.family + ] + mavenCentralSync { + sync = true + } + } + } +} + +model { + tasks.generatePomFileForPublishedArtifactsPublication { + destination = file( "${buildDir}/generated-pom.xml" ) + } +} + +task generatePomFile( dependsOn: 'generatePomFileForPublishedArtifactsPublication' ) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 51288f9c2f05..94336fcae912 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 97650c1eef3c..ae45383b6d25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Dec 16 15:26:37 GMT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip diff --git a/gradlew b/gradlew index 4453ccea33d9..cccdd3d517fc 100755 --- a/gradlew +++ b/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/hibernate-agroal/hibernate-agroal.gradle b/hibernate-agroal/hibernate-agroal.gradle new file mode 100644 index 000000000000..fb6825d4432a --- /dev/null +++ b/hibernate-agroal/hibernate-agroal.gradle @@ -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 . + */ + +apply from: rootProject.file( 'gradle/published-java-module.gradle' ) + +description = 'Integration for Agroal as a ConnectionProvider for Hibernate ORM' + +dependencies { + compile project( ':hibernate-core' ) + compile( libraries.agroal_api ) + + runtime( libraries.agroal_pool ) + + testCompile project( ':hibernate-testing' ) + testCompile( libraries.mockito ) + testCompile( libraries.mockito_inline ) +} diff --git a/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/AgroalConnectionProvider.java b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/AgroalConnectionProvider.java new file mode 100644 index 000000000000..460a8bbf1936 --- /dev/null +++ b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/AgroalConnectionProvider.java @@ -0,0 +1,144 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +package org.hibernate.agroal.internal; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration; +import io.agroal.api.configuration.supplier.AgroalConnectionFactoryConfigurationSupplier; +import io.agroal.api.configuration.supplier.AgroalPropertiesReader; +import io.agroal.api.security.NamePrincipal; +import io.agroal.api.security.SimplePassword; +import org.hibernate.HibernateException; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.UnknownUnwrapTypeException; +import org.hibernate.service.spi.Configurable; +import org.hibernate.service.spi.Stoppable; +import org.jboss.logging.Logger; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * ConnectionProvider based on Agroal connection pool + * To use this ConnectionProvider set:
 hibernate.connection.provider_class AgroalConnectionProvider 
+ * ( @see AvailableSettings#CONNECTION_PROVIDER ) + * + * Usual hibernate properties are supported: + *
+ *     hibernate.connection.driver_class
+ *     hibernate.connection.url
+ *     hibernate.connection.username
+ *     hibernate.connection.password
+ *     hibernate.connection.autocommit
+ *     hibernate.connection.isolation
+ * 
+ * + * Other configuration options are available, using the
hibernate.agroal
prefix ( @see AgroalPropertiesReader ) + * + * @author Luis Barreiro + */ +public class AgroalConnectionProvider implements ConnectionProvider, Configurable, Stoppable { + + public static final String CONFIG_PREFIX = "hibernate.agroal."; + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger( AgroalConnectionProvider.class ); + private AgroalDataSource agroalDataSource = null; + + // --- Configurable + + private static void resolveIsolationSetting(Map properties, AgroalConnectionFactoryConfigurationSupplier cf) { + Integer isolation = ConnectionProviderInitiator.extractIsolation( properties ); + if ( isolation != null ) { + // Agroal resolves transaction isolation from the 'nice' name + String isolationString = ConnectionProviderInitiator.toIsolationNiceName( isolation ); + cf.jdbcTransactionIsolation( AgroalConnectionFactoryConfiguration.TransactionIsolation.valueOf( isolationString ) ); + } + } + + private static void copyProperty(Map properties, String key, Consumer consumer, Function converter) { + String value = properties.get( key ); + if ( value != null ) { + consumer.accept( converter.apply( value ) ); + } + } + + @Override + @SuppressWarnings( "unchecked" ) + public void configure(Map props) throws HibernateException { + LOGGER.debug( "Configuring Agroal" ); + try { + AgroalPropertiesReader agroalProperties = new AgroalPropertiesReader( CONFIG_PREFIX ).readProperties( props ); + agroalProperties.modify().connectionPoolConfiguration( cp -> cp.connectionFactoryConfiguration( cf -> { + copyProperty( props, AvailableSettings.DRIVER, cf::driverClassName, Function.identity() ); + copyProperty( props, AvailableSettings.URL, cf::jdbcUrl, Function.identity() ); + copyProperty( props, AvailableSettings.USER, cf::principal, NamePrincipal::new ); + copyProperty( props, AvailableSettings.PASS, cf::credential, SimplePassword::new ); + copyProperty( props, AvailableSettings.AUTOCOMMIT, cf::autoCommit, Boolean::valueOf ); + resolveIsolationSetting( props, cf ); + return cf; + } ) ); + + agroalDataSource = AgroalDataSource.from( agroalProperties ); + } + catch ( Exception e ) { + throw new HibernateException( e ); + } + LOGGER.debug( "Agroal Configured" ); + } + + // --- ConnectionProvider + + @Override + public Connection getConnection() throws SQLException { + return agroalDataSource == null ? null : agroalDataSource.getConnection(); + } + + @Override + public void closeConnection(Connection conn) throws SQLException { + conn.close(); + } + + @Override + public boolean supportsAggressiveRelease() { + // Agroal supports integration with Narayana as the JTA provider, that would enable aggressive release + // That logic is similar with what Hibernate does (however with better performance since it's integrated in the pool) + // and therefore that integration is not leveraged right now. + return false; + } + + @Override + @SuppressWarnings( "rawtypes" ) + public boolean isUnwrappableAs(Class unwrapType) { + return ConnectionProvider.class.equals( unwrapType ) || AgroalConnectionProvider.class.isAssignableFrom( unwrapType ) || DataSource.class.isAssignableFrom( unwrapType ); + } + + @Override + @SuppressWarnings( "unchecked" ) + public T unwrap(Class unwrapType) { + if ( ConnectionProvider.class.equals( unwrapType ) || AgroalConnectionProvider.class.isAssignableFrom( unwrapType ) ) { + return (T) this; + } + if ( DataSource.class.isAssignableFrom( unwrapType ) ) { + return (T) agroalDataSource; + } + throw new UnknownUnwrapTypeException( unwrapType ); + } + + // --- Stoppable + + @Override + public void stop() { + agroalDataSource.close(); + } +} diff --git a/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/StrategyRegistrationProviderImpl.java b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/StrategyRegistrationProviderImpl.java new file mode 100644 index 000000000000..bfb531141310 --- /dev/null +++ b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/StrategyRegistrationProviderImpl.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 . + */ +package org.hibernate.agroal.internal; + +import org.hibernate.boot.registry.selector.SimpleStrategyRegistrationImpl; +import org.hibernate.boot.registry.selector.StrategyRegistration; +import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; + +import java.util.Collections; +import java.util.List; + +/** + * Provides the {@link AgroalConnectionProvider} to the + * {@link org.hibernate.boot.registry.selector.spi.StrategySelector} service. + * + * @author Luis Barreiro + */ +public class StrategyRegistrationProviderImpl implements StrategyRegistrationProvider { + + private static final List REGISTRATIONS = Collections.singletonList( + new SimpleStrategyRegistrationImpl<>( + ConnectionProvider.class, + AgroalConnectionProvider.class, + AgroalConnectionProvider.class.getSimpleName(), + "agroal", + "Agroal", + // for consistency's sake + "org.hibernate.connection.AgroalConnectionProvider" + ) + ); + + @Override + public Iterable getStrategyRegistrations() { + return REGISTRATIONS; + } +} diff --git a/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/package-info.java b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/package-info.java new file mode 100644 index 000000000000..8ac3c9e6943a --- /dev/null +++ b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/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 . + */ + +/** + * Implementation of ConnectionProvider using Agroal. + */ +package org.hibernate.agroal.internal; diff --git a/hibernate-agroal/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider b/hibernate-agroal/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider new file mode 100644 index 000000000000..cd58cd1a2002 --- /dev/null +++ b/hibernate-agroal/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider @@ -0,0 +1,13 @@ +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# +org.hibernate.agroal.internal.StrategyRegistrationProviderImpl diff --git a/hibernate-agroal/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/hibernate-agroal/src/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000000..71b48a990a68 --- /dev/null +++ b/hibernate-agroal/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalConnectionProviderTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalConnectionProviderTest.java new file mode 100644 index 000000000000..f7f4acd0bef9 --- /dev/null +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalConnectionProviderTest.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.agroal; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.ConnectionProviderJdbcConnectionAccess; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.agroal.internal.AgroalConnectionProvider; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Brett Meyer + */ +public class AgroalConnectionProviderTest extends BaseCoreFunctionalTestCase { + + @Test + public void testAgroalConnectionProvider() throws Exception { + JdbcServices jdbcServices = serviceRegistry().getService( JdbcServices.class ); + ConnectionProviderJdbcConnectionAccess connectionAccess = assertTyping( + ConnectionProviderJdbcConnectionAccess.class, + jdbcServices.getBootstrapJdbcConnectionAccess() + ); + assertTyping( AgroalConnectionProvider.class, connectionAccess.getConnectionProvider() ); + + AgroalConnectionProvider agroalConnectionProvider = (AgroalConnectionProvider) connectionAccess.getConnectionProvider(); + // For simplicity's sake, using the following in hibernate.properties: + // hibernate.agroal.maxSize 2 + // hibernate.agroal.minSize 2 + List conns = new ArrayList<>(); + for ( int i = 0; i < 2; i++ ) { + Connection conn = agroalConnectionProvider.getConnection(); + assertNotNull( conn ); + assertFalse( conn.isClosed() ); + conns.add( conn ); + } + + try { + agroalConnectionProvider.getConnection(); + fail( "SQLException expected -- no more connections should have been available in the pool." ); + } + catch (SQLException e) { + // expected + assertTrue( e.getMessage().contains( "timeout" ) ); + } + + for ( Connection conn : conns ) { + agroalConnectionProvider.closeConnection( conn ); + assertTrue( conn.isClosed() ); + } + + releaseSessionFactory(); + + try { + agroalConnectionProvider.getConnection(); + fail( "Exception expected -- the pool should have been shutdown." ); + } + catch (Exception e) { + // expected + assertTrue( e.getMessage().contains( "closed" ) ); + } + } +} diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java new file mode 100644 index 000000000000..d32cc2be75f1 --- /dev/null +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.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 . + */ +package org.hibernate.test.agroal; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.test.agroal.util.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import javax.persistence.Entity; +import javax.persistence.Id; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +public class AgroalSkipAutoCommitTest extends BaseCoreFunctionalTestCase { + + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); + + @Override + protected void configure(Configuration configuration) { + configuration.getProperties().put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); + configuration.getProperties().put( AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, Boolean.TRUE ); + configuration.getProperties().put( AvailableSettings.AUTOCOMMIT, Boolean.FALSE.toString() ); + } + + @Override + public void releaseSessionFactory() { + super.releaseSessionFactory(); + connectionProvider.stop(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ City.class }; + } + + @Test + public void test() { + connectionProvider.clear(); + doInHibernate( this::sessionFactory, session -> { + City city = new City(); + city.setId( 1L ); + city.setName( "Cluj-Napoca" ); + session.persist( city ); + + assertTrue( connectionProvider.getAcquiredConnections().isEmpty() ); + assertTrue( connectionProvider.getReleasedConnections().isEmpty() ); + } ); + verifyConnections(); + + connectionProvider.clear(); + doInHibernate( this::sessionFactory, session -> { + City city = session.find( City.class, 1L ); + assertEquals( "Cluj-Napoca", city.getName() ); + } ); + verifyConnections(); + } + + private void verifyConnections() { + assertTrue( connectionProvider.getAcquiredConnections().isEmpty() ); + + List connections = connectionProvider.getReleasedConnections(); + assertEquals( 1, connections.size() ); + Connection connection = connections.get( 0 ); + try { + verify(connection, never()).setAutoCommit( false ); + } + catch (SQLException e) { + fail(e.getMessage()); + } + } + + @Entity(name = "City" ) + public static class City { + + @Id + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalTransactionIsolationConfigTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalTransactionIsolationConfigTest.java new file mode 100644 index 000000000000..db424e5cda6f --- /dev/null +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalTransactionIsolationConfigTest.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.test.agroal; + +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.agroal.internal.AgroalConnectionProvider; + +import org.hibernate.testing.common.connections.BaseTransactionIsolationConfigTest; + +/** + * @author Steve Ebersole + */ +public class AgroalTransactionIsolationConfigTest extends BaseTransactionIsolationConfigTest { + @Override + protected ConnectionProvider getConnectionProviderUnderTest() { + return new AgroalConnectionProvider(); + } +} diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java new file mode 100644 index 000000000000..43b8ba31e892 --- /dev/null +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.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 . + */ +package org.hibernate.test.agroal.util; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.hibernate.agroal.internal.AgroalConnectionProvider; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; + +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.mockito.internal.util.MockUtil; + +/** + * This {@link ConnectionProvider} extends any other ConnectionProvider that would be used by default taken the current configuration properties, and it + * intercept the underlying {@link PreparedStatement} method calls. + * + * @author Vlad Mihalcea + */ +public class PreparedStatementSpyConnectionProvider extends AgroalConnectionProvider { + + private final Map preparedStatementMap = new LinkedHashMap<>(); + + private final List acquiredConnections = new ArrayList<>( ); + private final List releasedConnections = new ArrayList<>( ); + + public PreparedStatementSpyConnectionProvider() { + } + + protected Connection actualConnection() throws SQLException { + return super.getConnection(); + } + + @Override + public Connection getConnection() throws SQLException { + Connection connection = spy( actualConnection() ); + acquiredConnections.add( connection ); + return connection; + } + + @Override + public void closeConnection(Connection conn) throws SQLException { + acquiredConnections.remove( conn ); + releasedConnections.add( conn ); + super.closeConnection( conn ); + } + + @Override + public void stop() { + clear(); + super.stop(); + } + + private Connection spy(Connection connection) { + if ( MockUtil.isMock( connection ) ) { + return connection; + } + Connection connectionSpy = Mockito.spy( connection ); + try { + Mockito.doAnswer( invocation -> { + PreparedStatement statement = (PreparedStatement) invocation.callRealMethod(); + PreparedStatement statementSpy = Mockito.spy( statement ); + String sql = (String) invocation.getArguments()[0]; + preparedStatementMap.put( statementSpy, sql ); + return statementSpy; + } ).when( connectionSpy ).prepareStatement( ArgumentMatchers.anyString() ); + + Mockito.doAnswer( invocation -> { + Statement statement = (Statement) invocation.callRealMethod(); + Statement statementSpy = Mockito.spy( statement ); + return statementSpy; + } ).when( connectionSpy ).createStatement(); + } + catch ( SQLException e ) { + throw new IllegalArgumentException( e ); + } + return connectionSpy; + } + + /** + * Clears the recorded PreparedStatements and reset the associated Mocks. + */ + public void clear() { + acquiredConnections.clear(); + releasedConnections.clear(); + preparedStatementMap.keySet().forEach( Mockito::reset ); + preparedStatementMap.clear(); + } + + /** + * Get a list of current acquired Connections. + * @return list of current acquired Connections + */ + public List getAcquiredConnections() { + return acquiredConnections; + } + + /** + * Get a list of current released Connections. + * @return list of current released Connections + */ + public List getReleasedConnections() { + return releasedConnections; + } +} diff --git a/hibernate-agroal/src/test/resources/hibernate.properties b/hibernate-agroal/src/test/resources/hibernate.properties new file mode 100644 index 000000000000..6b80862911be --- /dev/null +++ b/hibernate-agroal/src/test/resources/hibernate.properties @@ -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 . +# +hibernate.dialect @db.dialect@ +hibernate.connection.driver_class @jdbc.driver@ +hibernate.connection.url @jdbc.url@ +hibernate.connection.username @jdbc.user@ +hibernate.connection.password @jdbc.pass@ + +hibernate.jdbc.batch_size 10 +hibernate.connection.provider_class AgroalConnectionProvider + +hibernate.agroal.maxSize 2 +hibernate.agroal.acquisitionTimeout PT1s +hibernate.agroal.reapTimeout PT10s \ No newline at end of file diff --git a/hibernate-agroal/src/test/resources/log4j.properties b/hibernate-agroal/src/test/resources/log4j.properties new file mode 100644 index 000000000000..eb96581a28b5 --- /dev/null +++ b/hibernate-agroal/src/test/resources/log4j.properties @@ -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 . +# +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n +#log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (hibernateLoadPlanWalkPath->%X{hibernateLoadPlanWalkPath}) - %m%n + +#log4j.appender.stdout-mdc=org.apache.log4j.ConsoleAppender +#log4j.appender.stdout-mdc.Target=System.out +#log4j.appender.stdout-mdc.layout=org.apache.log4j.PatternLayout +#log4j.appender.stdout-mdc.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (walk path -> %X{hibernateLoadPlanWalkPath}) - %m%n + +log4j.appender.unclosedSessionFactoryFile=org.apache.log4j.FileAppender +log4j.appender.unclosedSessionFactoryFile.append=true +log4j.appender.unclosedSessionFactoryFile.file=target/tmp/log/UnclosedSessionFactoryWarnings.log +log4j.appender.unclosedSessionFactoryFile.layout=org.apache.log4j.PatternLayout +log4j.appender.unclosedSessionFactoryFile.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n + +log4j.rootLogger=info, stdout + +#log4j.logger.org.hibernate.loader.plan=trace, stdout-mdc +#log4j.additivity.org.hibernate.loader.plan=false +#log4j.logger.org.hibernate.persister.walking=trace, stdout-mdc +#log4j.additivity.org.hibernate.persister.walking=false + +log4j.logger.org.hibernate.tool.hbm2ddl=trace +log4j.logger.org.hibernate.testing.cache=debug + +# SQL Logging - HHH-6833 +log4j.logger.org.hibernate.SQL=debug + +log4j.logger.org.hibernate.type.descriptor.sql.BasicBinder=trace +log4j.logger.org.hibernate.type.descriptor.sql.BasicExtractor=trace + +log4j.logger.org.hibernate.hql.internal.ast=debug + +log4j.logger.org.hibernate.sql.ordering.antlr=debug + +log4j.logger.org.hibernate.loader.plan2.build.internal.LoadPlanImpl=debug +log4j.logger.org.hibernate.loader.plan2.build.spi.LoadPlanTreePrinter=debug +log4j.logger.org.hibernate.loader.plan2.exec.spi.EntityLoadQueryDetails=debug + +log4j.logger.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=info + +log4j.logger.org.hibernate.boot.model.source.internal.hbm.ModelBinder=debug +log4j.logger.org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry=debug + + +### When entity copy merge functionality is enabled using: +### hibernate.event.merge.entity_copy_observer=log, the following will +### provide information about merged entity copies. +### log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=debug + +log4j.logger.org.hibernate.testing.junit4.TestClassMetadata=info, unclosedSessionFactoryFile +log4j.logger.org.hibernate.boot.model.process.internal.ScanningCoordinator=debug diff --git a/hibernate-agroal/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/hibernate-agroal/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000000..1f0955d450f0 --- /dev/null +++ b/hibernate-agroal/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/hibernate-c3p0/hibernate-c3p0.gradle b/hibernate-c3p0/hibernate-c3p0.gradle index 2f2a7a9ba7c6..d76872a461ed 100644 --- a/hibernate-c3p0/hibernate-c3p0.gradle +++ b/hibernate-c3p0/hibernate-c3p0.gradle @@ -4,57 +4,30 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ + +apply from: rootProject.file( 'gradle/published-java-module.gradle' ) + +description = 'Integration for c3p0 Connection pooling into Hibernate ORM' + dependencies { compile project( ':hibernate-core' ) compile( libraries.c3p0 ) testCompile project( ':hibernate-testing' ) + testCompile( libraries.mockito ) + testCompile( libraries.mockito_inline ) testCompile( libraries.validator ) { // for test runtime transitive = true } // EL libraries are provided scope in Validator - testRuntime( libraries.expression_language_api ) - testRuntime( libraries.expression_language_impl ) + testRuntime( libraries.expression_language ) - if (db.equalsIgnoreCase("oracle")) { - dependencies { - testRuntime( libraries.oracle ) + if ( db.equalsIgnoreCase( 'oracle' ) ) { + testRuntime( libraries.oracle ) { + exclude group: 'com.oracle.jdbc', module: 'xmlparserv2' } } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Java 9 ftw! - if ( JavaVersion.current().isJava9Compatible() ) { - // The JDK used to run Gradle is Java 9+, and we assume that that is the same - // JDK for executing tasks - compile( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - compile( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - compile( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - compile( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - compile( 'javax:javaee-api:7.0' ) - - testCompile( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testCompile( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - testCompile( 'javax:javaee-api:7.0' ) - - testRuntime( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testRuntime( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - testRuntime( 'javax:javaee-api:7.0' ) - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -} - -mavenPom { - name = 'Hibernate/c3p0 Integration' - description = 'Integration for c3p0 Connection pooling into Hibernate O/RM' } -def osgiDescription() { - return mavenPom.description -} \ No newline at end of file diff --git a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java index 043026021bf4..d93b987227dd 100644 --- a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java +++ b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java @@ -21,7 +21,6 @@ import org.hibernate.cfg.Environment; import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; -import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.spi.Configurable; @@ -71,7 +70,7 @@ public class C3P0ConnectionProvider @SuppressWarnings("UnnecessaryUnboxing") public Connection getConnection() throws SQLException { final Connection c = ds.getConnection(); - if ( isolation != null ) { + if ( isolation != null && !isolation.equals( c.getTransactionIsolation() ) ) { c.setTransactionIsolation( isolation.intValue() ); } if ( c.getAutoCommit() != autocommit ) { diff --git a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0MessageLogger.java b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0MessageLogger.java index 76ff4a9c8536..448ba0007f72 100644 --- a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0MessageLogger.java +++ b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0MessageLogger.java @@ -22,7 +22,7 @@ * The jboss-logging {@link MessageLogger} for the hibernate-c3p0 module. It reserves message ids ranging from * 10001 to 15000 inclusively. *

- * New messages must be added afterQuery the last message defined to ensure message codes are unique. + * New messages must be added after the last message defined to ensure message codes are unique. */ @MessageLogger(projectCode = "HHH") public interface C3P0MessageLogger extends ConnectionPoolingLogger { diff --git a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DefaultIsolationLevelTest.java b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DefaultIsolationLevelTest.java new file mode 100644 index 000000000000..37ca6529eda2 --- /dev/null +++ b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DefaultIsolationLevelTest.java @@ -0,0 +1,100 @@ +package org.hibernate.test.c3p0; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +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.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-12749") +@RequiresDialect(H2Dialect.class) +public class C3P0DefaultIsolationLevelTest extends + BaseNonConfigCoreFunctionalTestCase { + + private C3P0ProxyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + }; + } + + @Override + protected void addSettings(Map settings) { + connectionProvider = new C3P0ProxyConnectionProvider(); + settings.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); + settings.put( AvailableSettings.ISOLATION, "READ_COMMITTED" ); + } + + @Test + public void testStoredProcedureOutParameter() throws SQLException { + clearSpies(); + + doInHibernate( this::sessionFactory, session -> { + Person person = new Person(); + person.id = 1L; + person.name = "Vlad Mihalcea"; + + session.persist( person ); + } ); + + assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().startsWith( "insert into" ) ); + Connection connectionSpy = connectionProvider.getConnectionSpyMap().keySet().iterator().next(); + verify( connectionSpy, never() ).setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED ); + + clearSpies(); + + doInHibernate( this::sessionFactory, session -> { + Person person = session.find( Person.class, 1L ); + + assertEquals( "Vlad Mihalcea", person.name ); + } ); + + assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().startsWith( "select" ) ); + connectionSpy = connectionProvider.getConnectionSpyMap().keySet().iterator().next(); + verify( connectionSpy, never() ).setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED ); + } + + private void clearSpies() { + sqlStatementInterceptor.getSqlQueries().clear(); + connectionProvider.clear(); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String name; + } + +} diff --git a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DifferentIsolationLevelTest.java b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DifferentIsolationLevelTest.java new file mode 100644 index 000000000000..e8560d3e740d --- /dev/null +++ b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DifferentIsolationLevelTest.java @@ -0,0 +1,101 @@ +package org.hibernate.test.c3p0; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +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.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-12749") +@RequiresDialect(H2Dialect.class) +public class C3P0DifferentIsolationLevelTest extends + BaseNonConfigCoreFunctionalTestCase { + + private C3P0ProxyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + }; + } + + @Override + protected void addSettings(Map settings) { + connectionProvider = new C3P0ProxyConnectionProvider(); + settings.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); + settings.put( AvailableSettings.ISOLATION, "REPEATABLE_READ" ); + } + + @Test + public void testStoredProcedureOutParameter() throws SQLException { + clearSpies(); + + doInHibernate( this::sessionFactory, session -> { + Person person = new Person(); + person.id = 1L; + person.name = "Vlad Mihalcea"; + + session.persist( person ); + } ); + + assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().startsWith( "insert into" ) ); + Connection connectionSpy = connectionProvider.getConnectionSpyMap().keySet().iterator().next(); + verify( connectionSpy, times(1) ).setTransactionIsolation( Connection.TRANSACTION_REPEATABLE_READ ); + + clearSpies(); + + doInHibernate( this::sessionFactory, session -> { + Person person = session.find( Person.class, 1L ); + + assertEquals( "Vlad Mihalcea", person.name ); + } ); + + assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().startsWith( "select" ) ); + connectionSpy = connectionProvider.getConnectionSpyMap().keySet().iterator().next(); + verify( connectionSpy, times(1) ).setTransactionIsolation( Connection.TRANSACTION_REPEATABLE_READ ); + } + + private void clearSpies() { + sqlStatementInterceptor.getSqlQueries().clear(); + connectionProvider.clear(); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String name; + } + +} diff --git a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0ProxyConnectionProvider.java b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0ProxyConnectionProvider.java new file mode 100644 index 000000000000..7e19668a899f --- /dev/null +++ b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0ProxyConnectionProvider.java @@ -0,0 +1,65 @@ +package org.hibernate.test.c3p0; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import javax.sql.DataSource; + +import org.hibernate.c3p0.internal.C3P0ConnectionProvider; + +import org.hibernate.testing.util.ReflectionUtil; + +import org.mockito.MockSettings; +import org.mockito.Mockito; + +/** + * @author Vlad Mihalcea + */ +public class C3P0ProxyConnectionProvider extends C3P0ConnectionProvider { + + private static final MockSettings VERIFIEABLE_MOCK_SETTINGS = Mockito.withSettings() + .defaultAnswer( org.mockito.Answers.CALLS_REAL_METHODS ); + + private final Map connectionSpyMap = new HashMap<>(); + + private static T spy(T subject) { + return Mockito.mock( (Class) subject.getClass(), VERIFIEABLE_MOCK_SETTINGS.spiedInstance( subject ) ); + } + + @Override + public void configure(Map props) { + super.configure( props ); + DataSource ds = unwrap( DataSource.class ); + DataSource dataSource = spy( ds ); + + try { + Mockito.doAnswer( invocation -> { + Connection connection = (Connection) invocation.callRealMethod(); + Connection connectionSpy = spy( connection ); + connectionSpyMap.put( connectionSpy, connection ); + return connectionSpy; + } ).when( dataSource ).getConnection(); + } + catch (SQLException e) { + throw new IllegalStateException( e ); + } + + ReflectionUtil.setField( C3P0ConnectionProvider.class.cast( this ), "ds", dataSource ); + } + + @Override + public void closeConnection(Connection conn) throws SQLException { + Connection originalConnection = connectionSpyMap.get( conn ); + + super.closeConnection( originalConnection != null ? originalConnection : conn ); + } + + public Map getConnectionSpyMap() { + return connectionSpyMap; + } + + public void clear() { + connectionSpyMap.clear(); + } +} diff --git a/hibernate-c3p0/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/hibernate-c3p0/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000000..ca6ee9cea8ec --- /dev/null +++ b/hibernate-c3p0/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/hibernate-core/hibernate-core.gradle b/hibernate-core/hibernate-core.gradle index 48b35f8a2ace..576ff4b19529 100644 --- a/hibernate-core/hibernate-core.gradle +++ b/hibernate-core/hibernate-core.gradle @@ -1,16 +1,18 @@ -import org.apache.tools.ant.filters.ReplaceTokens - /* * Hibernate, Relational Persistence for 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 org.apache.tools.ant.filters.ReplaceTokens + +apply from: rootProject.file( 'gradle/published-java-module.gradle' ) apply plugin: 'antlr' apply plugin: 'hibernate-matrix-testing' - apply plugin: 'org.hibernate.build.gradle.xjc' +description = 'Hibernate\'s core ORM functionality' + configurations { hibernateJpaModelGenTool { description = "Dependencies for running the Hibernate JPA Metamodel Generator AnnotationProcessor tool" @@ -21,14 +23,17 @@ configurations { } dependencies { + compile( libraries.jpa ) + // Javassist is no longer the default enhancer but still required for other purposes, e.g. Scanning compile( libraries.javassist ) - // provided until 6.0 when we make it the default and drop Javassist support - provided( libraries.byteBuddy ) + // Could be made optional? + compile( libraries.byteBuddy ) compile( libraries.antlr ) compile( libraries.jta ) compile( libraries.jandex ) compile( libraries.classmate ) + compile( libraries.activation ) // We need dom4j for a number of things temporarily: // 1) (unsupported) EntityMode.DOM4J support @@ -36,51 +41,34 @@ dependencies { // 3) hibernate-commons-annotations compile( libraries.dom4j ) compile( libraries.commons_annotations ) - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Java 9 ftw! - if ( JavaVersion.current().isJava9Compatible() ) { - // The JDK used to run Gradle is Java 9+, and we assume that that is the same - // JDK for executing tasks - xjc( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - xjc( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - xjc( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - xjc( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - xjc( 'javax:javaee-api:7.0' ) - - testCompile( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testCompile( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - testCompile( 'javax:javaee-api:7.0' ) - - testRuntime( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testRuntime( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - testRuntime( 'javax:javaee-api:7.0' ) - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - antlr( libraries.antlr ) + // JAXB + compile( libraries.jaxb_api ) + compile( libraries.jaxb_runtime ) + xjc( libraries.jaxb_runtime ) + xjc( libraries.jaxb_xjc ) + xjc( libraries.jaxb2_basics ) + xjc( libraries.jaxb2_basics_ant ) + xjc( libraries.activation ) + provided( libraries.jacc ) provided( libraries.validation ) provided( libraries.ant ) - provided( "javax.enterprise:cdi-api:${cdiVersion}" ) + provided( libraries.cdi ) testCompile( project(':hibernate-testing') ) testCompile( libraries.shrinkwrap_api ) testCompile( libraries.shrinkwrap ) + testCompile( libraries.jacc ) testCompile( libraries.validation ) testCompile( libraries.jandex ) testCompile( libraries.classmate ) testCompile( libraries.mockito ) testCompile( libraries.mockito_inline ) - testCompile( 'joda-time:joda-time:2.3' ) -// testCompile( "org.jboss.weld:weld-core:2.3.4.Final" ) -// testCompile( "org.jboss.arquillian.container:arquillian-weld-ee-embedded-1.1:1.0.0.CR9" ) - testCompile( "javax.enterprise:cdi-api:${cdiVersion}" ) { + testCompile( libraries.jodaTime ) + + testCompile( libraries.cdi ) { // we need to force it to make sure we influence the one coming from arquillian force=true } @@ -94,13 +82,14 @@ dependencies { testCompile( libraries.derby ) testRuntime( "org.jboss.spec.javax.ejb:jboss-ejb-api_3.2_spec:1.0.0.Final" ) - testRuntime( libraries.expression_language_api ) - testRuntime( libraries.expression_language_impl ) + testRuntime( libraries.expression_language ) testRuntime( 'jaxen:jaxen:1.1' ) testRuntime( libraries.javassist ) testRuntime( libraries.byteBuddy ) + testRuntime( libraries.weld ) + testRuntime(libraries.wildfly_transaction_client) - testCompile( project( ':hibernate-jpamodelgen' ) ) + testCompile( project( ':hibernate-jpamodelgen' ) ) testCompile libraries.arquillian_junit_container testCompile libraries.arquillian_protocol_servlet @@ -112,15 +101,6 @@ dependencies { testCompile libraries.jboss_annotation_spec_jar } -mavenPom { - name = 'Core Hibernate O/RM functionality' - description = 'The core O/RM functionality as provided by Hibernate' -} - -def osgiDescription() { - return mavenPom.description -} - jar { manifest { mainAttributes( 'Main-Class': 'org.hibernate.Version' ) @@ -134,11 +114,7 @@ jar { 'javax.validation.metadata;resolution:=optional', // TODO: Shouldn't have to explicitly list this, but the plugin // generates it with a [1.0,2) version. - 'javax.persistence;version="2.1.0"', - // Temporarily support JTA 1.1 -- Karaf and other frameworks still - // use it. Without this, the plugin generates [1.2,2). - // build.gradle adds javax.transaction for all modules - 'javax.transaction.xa;version="[1.1,2)"', + "javax.persistence;version=\"${project.jpaVersion.osgiName}\"", // optionals 'javax.management;resolution:=optional', 'javax.naming.event;resolution:=optional', @@ -149,7 +125,11 @@ jar { '!javax.enterprise*', 'javax.enterprise.context.spi;resolution:=optional', 'javax.enterprise.inject.spi;resolution:=optional', - 'net.bytebuddy.*;resolution:=optional' + 'javax.inject;resolution:=optional', + 'net.bytebuddy.*;resolution:=optional', + // We must specify the version explicitly to allow Karaf + // to use an older version of JAXB (the only one we can use in Karaf) + "javax.xml.bind.*;version=\"${project.jaxbApiVersionOsgiRange}\"" // // TODO: Uncomment once EntityManagerFactoryBuilderImpl no longer // // uses ClassLoaderServiceImpl. @@ -164,7 +144,7 @@ ext { } sourceSets.main { - java.srcDir jaxbTargetDir + java.srcDir project.jaxbTargetDir } // resources inherently exclude sources @@ -172,14 +152,14 @@ sourceSets.test.resources { setSrcDirs( ['src/test/java','src/test/resources'] ) } -idea { - module { - sourceDirs += file( "${buildDir}/generated-src/antlr/main" ) - } -} +//idea { +// module { +// sourceDirs += file( "${buildDir}/generated-src/antlr/main" ) +// } +//} xjc { - outputDir = jaxbTargetDir + outputDir = project.jaxbTargetDir schemas { cfg { @@ -200,14 +180,18 @@ xjc { tasks.compile.dependsOn generateGrammarSource task copyBundleResources (type: Copy) { - ext.bundlesTargetDir = file( "${buildDir}/bundles" ) + ext { + bundlesTargetDir = file( "${buildDir}/bundles" ) + bundleTokens = dbBundle[db] + ext.bundleTokens['buildDirName'] = buildDir.absolutePath + } + from file('src/test/bundles') - into bundlesTargetDir - ext.bundleTokens = dbBundle[db] - ext.bundleTokens["buildDirName"] = buildDir.absolutePath - filter( ReplaceTokens, tokens: bundleTokens); + into ext.bundlesTargetDir + filter( ReplaceTokens, tokens: ext.bundleTokens) + doFirst { - bundlesTargetDir.mkdirs() + ext.bundlesTargetDir.mkdirs() } } processTestResources.dependsOn copyBundleResources @@ -221,25 +205,6 @@ artifacts { tests testJar } -if ( JavaVersion.current().isJava9Compatible() ) { - logger.warn( '[WARN] Skipping Javassist-related tests for hibernate-core due to Javassist JDK 9 incompatibility' ) - - // we need to exclude tests using Javassist enhancement, which does not properly support - // Java 9 yet - https://issues.jboss.org/browse/JASSIST-261 - test { - // rather than wild-cards, keep an explicit list - exclude 'org/hibernate/jpa/test/enhancement/InterceptFieldClassFileTransformerTest.class' - exclude 'org/hibernate/jpa/test/enhancement/runtime/JpaRuntimeEnhancementTest.class' - exclude 'org/hibernate/test/bytecode/enhancement/EnhancerTest.class' - exclude 'org/hibernate/test/bytecode/enhancement/basic/BasicInSessionTest.class' - - // also, any tests using Arquillian for in-container testing with WildFly currently - // need to be excluded because WildFly does not yet work with Java 9 - exclude 'org/hibernate/test/wf/ddl/**' - exclude 'org/hibernate/jpa/test/cdi/**' - } -} - processTestResources { doLast { copy { @@ -247,15 +212,20 @@ processTestResources { into file( "${buildDir}/resources/test" ) include 'arquillian.xml' include 'org/hibernate/test/wf/ddl/manifest.mf' - expand wildFlyInstallDir: "${rootProject.buildDir.absolutePath}/wildfly-${wildflyVersion}", - hibernateMajorMinorVersion: "${rootProject.hibernateMajorMinorVersion}", + expand wildFlyInstallDir: project( ':hibernate-orm-modules' ).wildFlyInstallDir, + hibernateMajorMinorVersion: "${project.ormVersion.family}", arquillianDeploymentExportDir: "${rootProject.buildDir.absolutePath}/arquillian-deployments" } } } -test.dependsOn ":hibernate-orm-modules:prepareWildFlyForTests" +test.dependsOn ':hibernate-orm-modules:prepareWildFlyForTests' test { - systemProperty "file.encoding", "utf-8" -} \ No newline at end of file + systemProperty 'file.encoding', 'utf-8' + //Set the port-offset to match the offset in arquillian.xml + systemProperty 'jboss.socket.binding.port-offset', '137' + beforeTest { descriptor -> + //println "Starting test: " + descriptor + } +} diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index 3e6d22687c44..5265979d5eed 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -209,6 +209,8 @@ tokens protected void resolve(AST node) throws SemanticException { } + protected void resolve(AST node, AST predicateNode) throws SemanticException { } + protected void resolveSelectExpression(AST dotNode) throws SemanticException { } protected void processFunction(AST functionCall,boolean inSelect) throws SemanticException { } @@ -223,8 +225,8 @@ tokens return #( [NAMED_PARAM, nameNode.getText()] ); } - protected AST generatePositionalParameter(AST inputNode) throws SemanticException { - return #( [PARAM, "?"] ); + protected AST generatePositionalParameter(AST delimiterNode, AST numberNode) throws SemanticException { + return #( [PARAM, numberNode.getText()] ); } protected void lookupAlias(AST ident) throws SemanticException { } @@ -335,7 +337,7 @@ assignment // For now, just use expr. Revisit after ejb3 solidifies this. newValue - : expr | query + : expr [ null ] | query ; // The query / subquery rule. Pops the current 'from node' context @@ -380,7 +382,7 @@ nullPrecedence orderExpr : { isOrderExpressionResultVariableRef( _t ) }? resultVariableRef - | expr + | expr [ null ] ; resultVariableRef! @@ -392,7 +394,7 @@ resultVariableRef! ; groupClause - : #(GROUP { handleClauseStart( GROUP ); } (expr)+ ( #(HAVING logicalExpr) )? ) { + : #(GROUP { handleClauseStart( GROUP ); } (expr [ null ])+ ( #(HAVING logicalExpr) )? ) { handleClauseEnd(); } ; @@ -429,7 +431,7 @@ selectExpr | count | collectionFunction // elements() or indices() | constant - | arithmeticExpr + | arithmeticExpr [ null ] | logicalExpr | parameter | query @@ -448,8 +450,9 @@ constructor ; aggregateExpr - : expr //p:propertyRef { resolve(#p); } + : expr [ null ] //p:propertyRef { resolve(#p); } | collectionFunction + | selectStatement ; // Establishes the list of aliases being used by this query. @@ -502,7 +505,7 @@ joinElement! { } ; -// Returns an node type integer that represents the join type +// Returns a node type integer that represents the join type // tokens. joinType returns [int j] { j = INNER; @@ -572,36 +575,37 @@ logicalExpr ; // TODO: Add any other comparison operators here. +// We pass through the comparisonExpr AST to the expressions so that joins can be avoided for EQ/IN/NULLNESS comparisonExpr : - ( #(EQ exprOrSubquery exprOrSubquery) - | #(NE exprOrSubquery exprOrSubquery) - | #(LT exprOrSubquery exprOrSubquery) - | #(GT exprOrSubquery exprOrSubquery) - | #(LE exprOrSubquery exprOrSubquery) - | #(GE exprOrSubquery exprOrSubquery) - | #(LIKE exprOrSubquery expr ( #(ESCAPE expr) )? ) - | #(NOT_LIKE exprOrSubquery expr ( #(ESCAPE expr) )? ) - | #(BETWEEN exprOrSubquery exprOrSubquery exprOrSubquery) - | #(NOT_BETWEEN exprOrSubquery exprOrSubquery exprOrSubquery) - | #(IN exprOrSubquery inRhs ) - | #(NOT_IN exprOrSubquery inRhs ) - | #(IS_NULL exprOrSubquery) - | #(IS_NOT_NULL exprOrSubquery) -// | #(IS_TRUE expr) -// | #(IS_FALSE expr) - | #(EXISTS ( expr | collectionFunctionOrSubselect ) ) + ( #(EQ exprOrSubquery [ currentAST.root ] exprOrSubquery [ currentAST.root ]) + | #(NE exprOrSubquery [ currentAST.root ] exprOrSubquery [ currentAST.root ]) + | #(LT exprOrSubquery [ null ] exprOrSubquery [ null ]) + | #(GT exprOrSubquery [ null ] exprOrSubquery [ null ]) + | #(LE exprOrSubquery [ null ] exprOrSubquery [ null ]) + | #(GE exprOrSubquery [ null ] exprOrSubquery [ null ]) + | #(LIKE exprOrSubquery [ null ] expr [ null ] ( #(ESCAPE expr [ null ]) )? ) + | #(NOT_LIKE exprOrSubquery [ null ] expr [ null ] ( #(ESCAPE expr [ null ]) )? ) + | #(BETWEEN exprOrSubquery [ null ] exprOrSubquery [ null ] exprOrSubquery [ null ]) + | #(NOT_BETWEEN exprOrSubquery [ null ] exprOrSubquery [ null ] exprOrSubquery [ null ]) + | #(IN exprOrSubquery [ currentAST.root ] inRhs [ currentAST.root ] ) + | #(NOT_IN exprOrSubquery [ currentAST.root ] inRhs [ currentAST.root ] ) + | #(IS_NULL exprOrSubquery [ currentAST.root ]) + | #(IS_NOT_NULL exprOrSubquery [ currentAST.root ]) +// | #(IS_TRUE expr [ null ]) +// | #(IS_FALSE expr [ null ]) + | #(EXISTS ( expr [ null ] | collectionFunctionOrSubselect ) ) ) { prepareLogicOperator( #comparisonExpr ); } ; -inRhs - : #(IN_LIST ( collectionFunctionOrSubselect | ( (expr)* ) ) ) +inRhs [ AST predicateNode ] + : #(IN_LIST ( collectionFunctionOrSubselect | ( (expr [ predicateNode ])* ) ) ) ; -exprOrSubquery - : expr +exprOrSubquery [ AST predicateNode ] + : expr [ predicateNode ] | query | #(ANY collectionFunctionOrSubselect) | #(ALL collectionFunctionOrSubselect) @@ -613,55 +617,55 @@ collectionFunctionOrSubselect | query ; -expr - : ae:addrExpr [ true ] { resolve(#ae); } // Resolve the top level 'address expression' - | #( VECTOR_EXPR (expr)* ) +expr [ AST predicateNode ] + : ae:addrExpr [ true ] { resolve(#ae, predicateNode); } // Resolve the top level 'address expression' + | #( VECTOR_EXPR (expr [ predicateNode ])* ) | constant - | arithmeticExpr + | arithmeticExpr [ predicateNode ] | functionCall // Function call, not in the SELECT clause. | parameter | count // Count, not in the SELECT clause. ; -arithmeticExpr - : #(PLUS exprOrSubquery exprOrSubquery) { prepareArithmeticOperator( #arithmeticExpr ); } - | #(MINUS exprOrSubquery exprOrSubquery) { prepareArithmeticOperator( #arithmeticExpr ); } - | #(DIV exprOrSubquery exprOrSubquery) { prepareArithmeticOperator( #arithmeticExpr ); } - | #(MOD exprOrSubquery exprOrSubquery) { prepareArithmeticOperator( #arithmeticExpr ); } - | #(STAR exprOrSubquery exprOrSubquery) { prepareArithmeticOperator( #arithmeticExpr ); } -// | #(CONCAT expr (expr)+ ) { prepareArithmeticOperator( #arithmeticExpr ); } - | #(UNARY_MINUS expr) { prepareArithmeticOperator( #arithmeticExpr ); } - | caseExpr +arithmeticExpr [ AST predicateNode ] + : #(PLUS exprOrSubquery [ null ] exprOrSubquery [ null ]) { prepareArithmeticOperator( #arithmeticExpr ); } + | #(MINUS exprOrSubquery [ null ] exprOrSubquery [ null ]) { prepareArithmeticOperator( #arithmeticExpr ); } + | #(DIV exprOrSubquery [ null ] exprOrSubquery [ null ]) { prepareArithmeticOperator( #arithmeticExpr ); } + | #(MOD exprOrSubquery [ null ] exprOrSubquery [ null ]) { prepareArithmeticOperator( #arithmeticExpr ); } + | #(STAR exprOrSubquery [ null ] exprOrSubquery [ null ]) { prepareArithmeticOperator( #arithmeticExpr ); } +// | #(CONCAT expr [ null ] (expr [ null ])+ ) { prepareArithmeticOperator( #arithmeticExpr ); } + | #(UNARY_MINUS expr [ null ]) { prepareArithmeticOperator( #arithmeticExpr ); } + | caseExpr [ predicateNode ] ; -caseExpr - : simpleCaseExpression - | searchedCaseExpression +caseExpr [ AST predicateNode ] + : simpleCaseExpression [ predicateNode ] + | searchedCaseExpression [ predicateNode ] ; -expressionOrSubQuery - : expr +expressionOrSubQuery [ AST predicateNode ] + : expr [ predicateNode ] | query ; -simpleCaseExpression - : #(CASE2 {inCase=true;} expressionOrSubQuery (simpleCaseWhenClause)+ (elseClause)?) {inCase=false;} +simpleCaseExpression [ AST predicateNode ] + : #(CASE2 {inCase=true;} expressionOrSubQuery [ currentAST.root ] (simpleCaseWhenClause [ currentAST.root, predicateNode ])+ (elseClause [ predicateNode ])?) {inCase=false;} ; -simpleCaseWhenClause - : #(WHEN expressionOrSubQuery expressionOrSubQuery) +simpleCaseWhenClause [ AST predicateNode, AST superPredicateNode ] + : #(WHEN expressionOrSubQuery [ predicateNode ] expressionOrSubQuery [ superPredicateNode ]) ; -elseClause - : #(ELSE expressionOrSubQuery) +elseClause [ AST predicateNode ] + : #(ELSE expressionOrSubQuery [ predicateNode ]) ; -searchedCaseExpression - : #(CASE {inCase = true;} (searchedCaseWhenClause)+ (elseClause)?) {inCase = false;} +searchedCaseExpression [ AST predicateNode ] + : #(CASE {inCase = true;} (searchedCaseWhenClause [ predicateNode ])+ (elseClause [ predicateNode ])?) {inCase = false;} ; -searchedCaseWhenClause - : #(WHEN logicalExpr expressionOrSubQuery) +searchedCaseWhenClause [ AST predicateNode ] + : #(WHEN logicalExpr expressionOrSubQuery [ predicateNode ]) ; @@ -675,11 +679,11 @@ collectionFunction ; functionCall - : #(METHOD_CALL {inFunctionCall=true;} pathAsIdent ( #(EXPR_LIST (exprOrSubquery)* ) )? ) { + : #(METHOD_CALL {inFunctionCall=true;} pathAsIdent ( #(EXPR_LIST (exprOrSubquery [ null ])* ) )? ) { processFunction( #functionCall, inSelect ); inFunctionCall=false; } - | #(CAST {inFunctionCall=true;} exprOrSubquery pathAsIdent) { + | #(CAST {inFunctionCall=true;} exprOrSubquery [ null ] pathAsIdent) { processCastFunction( #functionCall, inSelect ); inFunctionCall=false; } @@ -715,7 +719,7 @@ addrExpr! [ boolean root ] #addrExpr = #(#d, #lhs, #rhs); #addrExpr = lookupProperty(#addrExpr,root,false); } - | #(i:INDEX_OP lhs2:addrExprLhs rhs2:expr) { + | #(i:INDEX_OP lhs2:addrExprLhs rhs2:expr [ null ]) { #addrExpr = #(#i, #lhs2, #rhs2); processIndex(#addrExpr); } @@ -794,31 +798,20 @@ mapComponentReference ; mapPropertyExpression - : e:expr { + : e:expr [ null ] { validateMapPropertyExpression( #e ); } ; parameter! : #(c:COLON a:identifier) { - // Create a NAMED_PARAM node instead of (COLON IDENT). - #parameter = generateNamedParameter( c, a ); -// #parameter = #([NAMED_PARAM,a.getText()]); -// namedParameter(#parameter); - } - | #(p:PARAM (n:NUM_INT)?) { - if ( n != null ) { - // An ejb3-style "positional parameter", which we handle internally as a named-param - #parameter = generateNamedParameter( p, n ); -// #parameter = #([NAMED_PARAM,n.getText()]); -// namedParameter(#parameter); - } - else { - #parameter = generatePositionalParameter( p ); -// #parameter = #([PARAM,"?"]); -// positionalParameter(#parameter); - } - } + // Create a NAMED_PARAM node instead of (COLON IDENT) - semantics ftw! + #parameter = generateNamedParameter( c, a ); + } + | #(p:PARAM (n:NUM_INT)? ) { + // Create a (POSITIONAL_)PARAM node instead of (PARAM NUM_INT) - semantics ftw! + #parameter = generatePositionalParameter( p, n ); + } ; numericInteger diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index 82cd8cb51815..ac9e2a4ac346 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -200,6 +200,9 @@ tokens public void firstPathTokenWeakKeywords() throws TokenStreamException { } + public void handlePrimaryExpressionDotIdent() throws TokenStreamException { + } + /** * Manages the case of an optional FROM allowing the path to start with the "from" keyword */ @@ -239,7 +242,7 @@ tokens } statement - : ( updateStatement | deleteStatement | selectStatement | insertStatement ) + : ( updateStatement | deleteStatement | selectStatement | insertStatement ) (EOF!) ; // Without the optionalVersioned if the path starts with a keyword the parser fails @@ -456,7 +459,7 @@ propertyFetch //## GROUP_BY path ( COMMA path )*; groupByClause - : GROUP^ + : GROUP^ "by"! expression ( COMMA! expression )* (havingClause)? ; @@ -706,7 +709,7 @@ quantifiedExpression // * method call ( '.' ident '(' exprList ') ) // * function : differentiated from method call via explicit keyword atom - : primaryExpression + : {handlePrimaryExpressionDotIdent();} primaryExpression ( DOT^ identifier ( options { greedy=true; } : @@ -785,7 +788,7 @@ vectorExpr // the method looks a head to find keywords after DOT and turns them into identifiers. identPrimary : i:identPrimaryBase { handleDotIdent(); } - ( options { greedy=true; } : DOT^ ( identifier | ELEMENTS | o:OBJECT { #o.setType(IDENT); } ) )* + ( options { greedy=true; } : DOT^ ( identifier { handleDotIdent(); } | ELEMENTS | o:OBJECT { #o.setType(IDENT); } ) )* ( options { greedy=true; } : ( op:OPEN^ { #op.setType(METHOD_CALL);} e:exprList CLOSE! ) { AST path = #e.getFirstChild(); @@ -810,13 +813,13 @@ identPrimaryBase ; castedIdentPrimaryBase - : i:IDENT! OPEN! p:path AS! a:path! CLOSE! { i.getText().equals("treat") }? { + : i:IDENT! OPEN! p:path AS! a:path! CLOSE! { i.getText().equalsIgnoreCase("treat") }? { registerTreat( #p, #a ); } ; aggregate - : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! additiveExpression CLOSE! { #aggregate.setType(AGGREGATE); } + : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! ( additiveExpression | selectStatement ) CLOSE! { #aggregate.setType(AGGREGATE); } // Special case for count - It's 'parameters' can be keywords. | COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr | NUM_INT | caseExpression ) ) ) CLOSE! | collectionExpr diff --git a/hibernate-core/src/main/antlr/order-by.g b/hibernate-core/src/main/antlr/order-by.g index 33ecbcccf140..aababad48139 100644 --- a/hibernate-core/src/main/antlr/order-by.g +++ b/hibernate-core/src/main/antlr/order-by.g @@ -68,6 +68,7 @@ tokens * * @param msg The trace message. */ + @org.hibernate.internal.build.AllowSysOut protected void trace(String msg) { System.out.println( msg ); } diff --git a/hibernate-core/src/main/java/org/hibernate/BasicQueryContract.java b/hibernate-core/src/main/java/org/hibernate/BasicQueryContract.java index 63685e544d73..9042ec5af185 100644 --- a/hibernate-core/src/main/java/org/hibernate/BasicQueryContract.java +++ b/hibernate-core/src/main/java/org/hibernate/BasicQueryContract.java @@ -17,7 +17,7 @@ * @deprecated (since 5.2) use {@link CommonQueryContract} instead. */ @Deprecated -public interface BasicQueryContract { +public interface BasicQueryContract { /** * (Re)set the current FlushMode in effect for this query. * @@ -30,9 +30,9 @@ public interface BasicQueryContract { * @deprecated (since 5.2) use {@link #setHibernateFlushMode} instead */ @Deprecated - default CommonQueryContract setFlushMode(FlushMode flushMode) { + default BasicQueryContract setFlushMode(FlushMode flushMode) { setHibernateFlushMode( flushMode ); - return (CommonQueryContract) this; + return this; } /** @@ -55,7 +55,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * * @see #getHibernateFlushMode() */ - CommonQueryContract setHibernateFlushMode(FlushMode flushMode); + T setHibernateFlushMode(FlushMode flushMode); /** * Obtain the CacheMode in effect for this query. By default, the query inherits the CacheMode of the Session @@ -80,7 +80,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * * @see #getCacheMode() */ - CommonQueryContract setCacheMode(CacheMode cacheMode); + T setCacheMode(CacheMode cacheMode); /** * Are the results of this query eligible for second level query caching? This is different that second level @@ -105,7 +105,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * * @see #isCacheable */ - CommonQueryContract setCacheable(boolean cacheable); + T setCacheable(boolean cacheable); /** * Obtain the name of the second level query cache region in which query results will be stored (if they are @@ -127,7 +127,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * * @see #getCacheRegion() */ - CommonQueryContract setCacheRegion(String cacheRegion); + T setCacheRegion(String cacheRegion); /** * Obtain the query timeout in seconds. This value is eventually passed along to the JDBC query via @@ -152,7 +152,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * * @see #getTimeout() */ - CommonQueryContract setTimeout(int timeout); + T setTimeout(int timeout); /** * Obtain the JDBC fetch size hint in effect for this query. This value is eventually passed along to the JDBC @@ -178,7 +178,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * * @see #getFetchSize() */ - CommonQueryContract setFetchSize(int fetchSize); + T setFetchSize(int fetchSize); /** * Should entities and proxies loaded by this Query be put in read-only mode? If the @@ -189,7 +189,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() * * The read-only/modifiable setting has no impact on entities/proxies returned by the - * query that existed in the session beforeQuery the query was executed. + * query that existed in the session before the query was executed. * * @return {@code true} if the entities and proxies loaded by the query will be put * in read-only mode; {@code false} otherwise (they will be modifiable) @@ -216,7 +216,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * proxy has, regardless of the session's current setting. * * The read-only/modifiable setting has no impact on entities/proxies - * returned by the query that existed in the session beforeQuery the query was executed. + * returned by the query that existed in the session before the query was executed. * * @return {@code this}, for method chaining * @@ -224,7 +224,7 @@ default CommonQueryContract setFlushMode(FlushMode flushMode) { * are to be put in read-only mode; {@code false} indicates that entities and proxies * loaded by the query will be put in modifiable mode */ - CommonQueryContract setReadOnly(boolean readOnly); + T setReadOnly(boolean readOnly); /** * Return the Hibernate types of the query results. diff --git a/hibernate-core/src/main/java/org/hibernate/Cache.java b/hibernate-core/src/main/java/org/hibernate/Cache.java index b4b33cf9de29..f6346e11cde6 100644 --- a/hibernate-core/src/main/java/org/hibernate/Cache.java +++ b/hibernate-core/src/main/java/org/hibernate/Cache.java @@ -18,6 +18,7 @@ * * @author Steve Ebersole */ +@SuppressWarnings( {"UnusedDeclaration"}) public interface Cache extends javax.persistence.Cache { /** * Access to the SessionFactory this Cache is bound to. @@ -26,6 +27,11 @@ public interface Cache extends javax.persistence.Cache { */ SessionFactory getSessionFactory(); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity data + /** * Determine whether the cache contains data for the given entity "instance". *

@@ -58,59 +64,83 @@ public interface Cache extends javax.persistence.Cache { * * @param entityClass The entity class. * @param identifier The entity identifier + * + * @since 5.3 */ - void evictEntity(Class entityClass, Serializable identifier); + void evictEntityData(Class entityClass, Serializable identifier); /** * Evicts the entity data for a particular entity "instance". * * @param entityName The entity name. * @param identifier The entity identifier + * + * @since 5.3 */ - void evictEntity(String entityName, Serializable identifier); + void evictEntityData(String entityName, Serializable identifier); /** * Evicts all entity data from the given region (i.e. for all entities of * type). * * @param entityClass The entity class. + * + * @since 5.3 */ - void evictEntityRegion(Class entityClass); + void evictEntityData(Class entityClass); /** * Evicts all entity data from the given region (i.e. for all entities of * type). * * @param entityName The entity name. + * + * @since 5.3 */ - void evictEntityRegion(String entityName); + void evictEntityData(String entityName); /** * Evict data from all entity regions. + * + * @since 5.3 */ - void evictEntityRegions(); + void evictEntityData(); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Natural-id data + /** - * Evicts all naturalId data from the given region (i.e. for all entities of - * type). + * Evict cached data for the given entity's natural-id * - * @param naturalIdClass The naturalId class. + * @param entityClass The entity class. + * + * @since 5.3 */ - @SuppressWarnings( {"UnusedDeclaration"}) - void evictNaturalIdRegion(Class naturalIdClass); + void evictNaturalIdData(Class entityClass); /** - * Evicts all naturalId data from the given region (i.e. for all entities of - * type). + * Evict cached data for the given entity's natural-id + * + * @param entityName The entity name. * - * @param naturalIdName The naturalId name. + * @since 5.3 */ - void evictNaturalIdRegion(String naturalIdName); + void evictNaturalIdData(String entityName); /** - * Evict data from all naturalId regions. + * Evict cached data for all natural-ids (for all entities) + * + * @since 5.3 */ - void evictNaturalIdRegions(); + void evictNaturalIdData(); + + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection data /** * Determine whether the cache contains data for the given collection. @@ -128,26 +158,38 @@ public interface Cache extends javax.persistence.Cache { @SuppressWarnings( {"UnusedDeclaration"}) boolean containsCollection(String role, Serializable ownerIdentifier); + /** - * Evicts the cache data for the given identified collection instance. + * Evicts the cache data for the given identified collection "instance" * * @param role The "collection role" (in form [owner-entity-name].[collection-property-name]). * @param ownerIdentifier The identifier of the owning entity + * + * @since 5.3 */ - void evictCollection(String role, Serializable ownerIdentifier); + void evictCollectionData(String role, Serializable ownerIdentifier); /** - * Evicts all entity data from the given region (i.e. evicts cached data - * for all of the specified collection role). + * Evicts cached data for the given collection role * * @param role The "collection role" (in form [owner-entity-name].[collection-property-name]). + * + * @since 5.3 */ - void evictCollectionRegion(String role); + void evictCollectionData(String role); /** - * Evict data from all collection regions. + * Evict cache data for all collections + * + * @since 5.3 */ - void evictCollectionRegions(); + void evictCollectionData(); + + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Query result data /** * Determine whether the cache contains data for the given query. @@ -159,7 +201,6 @@ public interface Cache extends javax.persistence.Cache { * * @return True if the underlying cache contains corresponding data; false otherwise. */ - @SuppressWarnings( {"UnusedDeclaration"}) boolean containsQuery(String regionName); /** @@ -178,9 +219,181 @@ public interface Cache extends javax.persistence.Cache { * Evict data from all query regions. */ void evictQueryRegions(); - + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Misc + + /** + * Evict all data from the named cache region + * + * @since 5.3 + */ + void evictRegion(String regionName); + + /** + * {@inheritDoc} + * + * @apiNote Hibernate impl - we only evict entity data here in keeping + * with the JPA intent (JPA only defines caching for entity data). For + * evicting all cache regions (collections, natural-ids and query results), + * use {@link #evictAllRegions} instead. + */ + @Override + default void evictAll() { + // Evict only the "JPA cache", which is purely defined as the entity regions. + evictEntityData(); + } + + /** + * Evict data from all cache regions. + */ + default void evictAllRegions() { + evictEntityData(); + evictNaturalIdData(); + evictCollectionData(); + evictDefaultQueryRegion(); + evictQueryRegions(); + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Deprecations (5.3) + + /** + * Evicts the entity data for a particular entity "instance". + * + * @param entityClass The entity class. + * @param identifier The entity identifier + * + * @deprecated Use {@link Cache#evictEntityData(Class, Serializable)} instead + */ + @Deprecated + default void evictEntity(Class entityClass, Serializable identifier) { + evictEntityData( entityClass, identifier ); + } + + /** + * Evicts the entity data for a particular entity "instance". + * + * @param entityName The entity name. + * @param identifier The entity identifier + * + * @deprecated Use {@link Cache#evictEntityData(String, Serializable)} instead + */ + @Deprecated + default void evictEntity(String entityName, Serializable identifier) { + evictEntityData( entityName, identifier ); + } + + /** + * Evicts all entity data from the given region (i.e. for all entities of + * type). + * + * @param entityClass The entity class. + * + * @deprecated Use {@link Cache#evictEntityData(Class)} instead + */ + @Deprecated + default void evictEntityRegion(Class entityClass) { + evictEntityData( entityClass ); + } + + /** + * Evicts all entity data from the given region (i.e. for all entities of + * type). + * + * @param entityName The entity name. + * + * @deprecated Use {@link Cache#evictEntityData(String)} instead + */ + @Deprecated + default void evictEntityRegion(String entityName) { + evictEntityData( entityName ); + } + /** - * Evict all data from the cache. + * Evict data from all entity regions. + * + * @deprecated Use {@link Cache#evictEntityData()} instead */ - void evictAllRegions(); + @Deprecated + default void evictEntityRegions() { + evictEntityData(); + } + + /** + * Evicts all naturalId data from the given region (i.e. for all entities of + * type). + * + * @param entityClass The entity class. + * + * @deprecated Use {@link Cache#evictNaturalIdData(Class)} instead + */ + @Deprecated + default void evictNaturalIdRegion(Class entityClass) { + evictNaturalIdData( entityClass ); + } + + /** + * Evicts all naturalId data from the given region (i.e. for all entities of + * type). + * + * @param entityName The entity name. + * + * @deprecated Use {@link Cache#evictNaturalIdData(String)} instead + */ + @Deprecated + default void evictNaturalIdRegion(String entityName) { + evictNaturalIdData( entityName ); + } + + /** + * Evict data from all naturalId regions. + * + * @deprecated Use {@link Cache#evictNaturalIdData()} instead + */ + @Deprecated + default void evictNaturalIdRegions() { + evictNaturalIdData(); + } + + /** + * Evicts the cache data for the given identified collection instance. + * + * @param role The "collection role" (in form [owner-entity-name].[collection-property-name]). + * @param ownerIdentifier The identifier of the owning entity + * + * @deprecated Use {@link Cache#evictCollectionData(String, Serializable)} instead + */ + @Deprecated + default void evictCollection(String role, Serializable ownerIdentifier) { + evictCollectionData( role, ownerIdentifier ); + } + + /** + * Evicts all entity data from the given region (i.e. evicts cached data + * for all of the specified collection role). + * + * @param role The "collection role" (in form [owner-entity-name].[collection-property-name]). + * + * @deprecated Use {@link Cache#evictCollectionData(String)} instead + */ + @Deprecated + default void evictCollectionRegion(String role) { + evictCollectionData( role ); + } + + /** + * Evict data from all collection regions. + * + * @deprecated Use {@link Cache#evictCollectionData()} instead + */ + @Deprecated + default void evictCollectionRegions() { + evictCollectionData(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/CallbackException.java b/hibernate-core/src/main/java/org/hibernate/CallbackException.java index 1b69ffb82a9f..d47cd643bc19 100644 --- a/hibernate-core/src/main/java/org/hibernate/CallbackException.java +++ b/hibernate-core/src/main/java/org/hibernate/CallbackException.java @@ -9,7 +9,7 @@ /** * Intended to be thrown from {@link org.hibernate.classic.Lifecycle} and {@link Interceptor} callbacks. *

- * IMPL NOTE : This is a legacy exception type from back in the day beforeQuery Hibernate moved to a untyped (runtime) + * IMPL NOTE : This is a legacy exception type from back in the day before Hibernate moved to a untyped (runtime) * exception strategy. * * @author Gavin King diff --git a/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java b/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java index 4653f82ead81..80f96245d8f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java +++ b/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java @@ -18,7 +18,7 @@ */ public enum ConnectionReleaseMode{ /** - * Indicates that JDBC connection should be aggressively released afterQuery each + * Indicates that JDBC connection should be aggressively released after each * SQL statement is executed. In this mode, the application must * explicitly close all iterators and scrollable results. This mode may * only be used with a JTA datasource. @@ -26,7 +26,7 @@ public enum ConnectionReleaseMode{ AFTER_STATEMENT, /** - * Indicates that JDBC connections should be released afterQuery each transaction + * Indicates that JDBC connections should be released after each transaction * ends (works with both JTA-registered synch and HibernateTransaction API). * This mode may not be used with an application server JTA datasource. *

diff --git a/hibernate-core/src/main/java/org/hibernate/Criteria.java b/hibernate-core/src/main/java/org/hibernate/Criteria.java index cf44df013597..3487995ccc93 100644 --- a/hibernate-core/src/main/java/org/hibernate/Criteria.java +++ b/hibernate-core/src/main/java/org/hibernate/Criteria.java @@ -403,7 +403,7 @@ public interface Criteria extends CriteriaSpecification { * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() * * The read-only/modifiable setting has no impact on entities/proxies returned by the - * Criteria that existed in the session beforeQuery the Criteria was executed. + * Criteria that existed in the session before the Criteria was executed. * * @return true, entities and proxies loaded by the criteria will be put in read-only mode * false, entities and proxies loaded by the criteria will be put in modifiable mode @@ -433,7 +433,7 @@ public interface Criteria extends CriteriaSpecification { * proxy has, regardless of the session's current setting. * * The read-only/modifiable setting has no impact on entities/proxies - * returned by the criteria that existed in the session beforeQuery the criteria was executed. + * returned by the criteria that existed in the session before the criteria was executed. * * @param readOnly true, entities and proxies loaded by the criteria will be put in read-only mode * false, entities and proxies loaded by the criteria will be put in modifiable mode diff --git a/hibernate-core/src/main/java/org/hibernate/CustomEntityDirtinessStrategy.java b/hibernate-core/src/main/java/org/hibernate/CustomEntityDirtinessStrategy.java index 0b59d71ed4db..a9ddcf9b79ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/CustomEntityDirtinessStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/CustomEntityDirtinessStrategy.java @@ -47,7 +47,7 @@ public interface CustomEntityDirtinessStrategy { /** * Callback used by Hibernate to signal that the entity dirty flag should be cleared. Generally this - * happens afterQuery previous dirty changes were written to the database. + * happens after previous dirty changes were written to the database. * * @param entity The entity to reset * @param persister The persister corresponding to the given entity diff --git a/hibernate-core/src/main/java/org/hibernate/Filter.java b/hibernate-core/src/main/java/org/hibernate/Filter.java index 463e500bf7c2..6ca4d087fe22 100644 --- a/hibernate-core/src/main/java/org/hibernate/Filter.java +++ b/hibernate-core/src/main/java/org/hibernate/Filter.java @@ -64,7 +64,7 @@ public interface Filter { /** * Perform validation of the filter state. This is used to verify the - * state of the filter afterQuery its enablement and beforeQuery its use. + * state of the filter after its enablement and before its use. * * @throws HibernateException If the state is not currently valid. */ diff --git a/hibernate-core/src/main/java/org/hibernate/FlushMode.java b/hibernate-core/src/main/java/org/hibernate/FlushMode.java index 7408b597bdf8..509249d699eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/FlushMode.java +++ b/hibernate-core/src/main/java/org/hibernate/FlushMode.java @@ -34,14 +34,14 @@ public enum FlushMode { COMMIT(5 ), /** - * The {@link Session} is sometimes flushed beforeQuery query execution + * The {@link Session} is sometimes flushed before query execution * in order to ensure that queries never return stale state. This * is the default flush mode. */ AUTO(10 ), /** - * The {@link Session} is flushed beforeQuery every query. This is + * The {@link Session} is flushed before every query. This is * almost always unnecessary and inefficient. */ ALWAYS(20 ); @@ -70,7 +70,7 @@ public boolean lessThan(FlushMode other) { * * @return true/false * - * @deprecated Just use equality check against {@link #MANUAL}. Legacy from beforeQuery this was an enum + * @deprecated Just use equality check against {@link #MANUAL}. Legacy from before this was an enum */ @Deprecated public static boolean isManualFlushMode(FlushMode mode) { diff --git a/hibernate-core/src/main/java/org/hibernate/Hibernate.java b/hibernate-core/src/main/java/org/hibernate/Hibernate.java index efb700535b93..c46f9d219f9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/Hibernate.java +++ b/hibernate-core/src/main/java/org/hibernate/Hibernate.java @@ -8,6 +8,7 @@ import java.util.Iterator; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.HibernateIterator; @@ -63,6 +64,13 @@ public static void initialize(Object proxy) throws HibernateException { else if ( proxy instanceof PersistentCollection ) { ( (PersistentCollection) proxy ).forceInitialization(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) proxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( proxy, null ); + } + } } /** @@ -76,6 +84,13 @@ public static boolean isInitialized(Object proxy) { if ( proxy instanceof HibernateProxy ) { return !( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUninitialized(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) proxy ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + return true; + } else if ( proxy instanceof PersistentCollection ) { return ( (PersistentCollection) proxy ).wasInitialized(); } @@ -187,7 +202,10 @@ public static boolean isPropertyInitialized(Object proxy, String propertyName) { if ( entity instanceof PersistentAttributeInterceptable ) { PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor != null && interceptor instanceof LazyAttributeLoadingInterceptor ) { + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( propertyName ); } } @@ -195,4 +213,21 @@ public static boolean isPropertyInitialized(Object proxy, String propertyName) { return true; } + /** + * Unproxies a {@link HibernateProxy}. If the proxy is uninitialized, it automatically triggers an initialization. + * In case the supplied object is null or not a proxy, the object will be returned as-is. + * + * @param proxy the {@link HibernateProxy} to be unproxied + * @return the proxy's underlying implementation object, or the supplied object otherwise + */ + public static Object unproxy(Object proxy) { + if ( proxy instanceof HibernateProxy ) { + HibernateProxy hibernateProxy = (HibernateProxy) proxy; + LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); + return initializer.getImplementation(); + } + else { + return proxy; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/Interceptor.java b/hibernate-core/src/main/java/org/hibernate/Interceptor.java index f209fdc9586e..f71df01f6494 100644 --- a/hibernate-core/src/main/java/org/hibernate/Interceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/Interceptor.java @@ -14,7 +14,7 @@ /** * Allows user code to inspect and/or change property values. * - * Inspection occurs beforeQuery property values are written and afterQuery they are read + * Inspection occurs before property values are written and after they are read * from the database. * * There might be a single instance of Interceptor for a SessionFactory, or a new instance @@ -37,7 +37,7 @@ */ public interface Interceptor { /** - * Called just beforeQuery an object is initialized. The interceptor may change the state, which will + * Called just before an object is initialized. The interceptor may change the state, which will * be propagated to the persistent object. Note that when this method is called, entity will be * an empty uninitialized instance of the class. *

@@ -85,7 +85,7 @@ boolean onFlushDirty( Type[] types) throws CallbackException; /** - * Called beforeQuery an object is saved. The interceptor may modify the state, which will be used for + * Called before an object is saved. The interceptor may modify the state, which will be used for * the SQL INSERT and propagated to the persistent object. * * @param entity The entity instance whose state is being inserted @@ -101,7 +101,7 @@ boolean onFlushDirty( boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException; /** - * Called beforeQuery an object is deleted. It is not recommended that the interceptor modify the state. + * Called before an object is deleted. It is not recommended that the interceptor modify the state. * * @param entity The entity instance being deleted * @param id The identifier of the entity @@ -114,7 +114,7 @@ boolean onFlushDirty( void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException; /** - * Called beforeQuery a collection is (re)created. + * Called before a collection is (re)created. * * @param collection The collection instance. * @param key The collection key value. @@ -124,7 +124,7 @@ boolean onFlushDirty( void onCollectionRecreate(Object collection, Serializable key) throws CallbackException; /** - * Called beforeQuery a collection is deleted. + * Called before a collection is deleted. * * @param collection The collection instance. * @param key The collection key value. @@ -134,7 +134,7 @@ boolean onFlushDirty( void onCollectionRemove(Object collection, Serializable key) throws CallbackException; /** - * Called beforeQuery a collection is updated. + * Called before a collection is updated. * * @param collection The collection instance. * @param key The collection key value. @@ -144,7 +144,7 @@ boolean onFlushDirty( void onCollectionUpdate(Object collection, Serializable key) throws CallbackException; /** - * Called beforeQuery a flush. + * Called before a flush. * * @param entities The entities to be flushed. * @@ -153,7 +153,7 @@ boolean onFlushDirty( void preFlush(Iterator entities) throws CallbackException; /** - * Called afterQuery a flush that actually ends in execution of the SQL statements required to synchronize + * Called after a flush that actually ends in execution of the SQL statements required to synchronize * in-memory state with the database. * * @param entities The entities that were flushed. @@ -250,14 +250,14 @@ int[] findDirty( void afterTransactionBegin(Transaction tx); /** - * Called beforeQuery a transaction is committed (but not beforeQuery rollback). + * Called before a transaction is committed (but not before rollback). * * @param tx The Hibernate transaction facade object */ void beforeTransactionCompletion(Transaction tx); /** - * Called afterQuery a transaction is committed or rolled back. + * Called after a transaction is committed or rolled back. * * @param tx The Hibernate transaction facade object */ diff --git a/hibernate-core/src/main/java/org/hibernate/LazyInitializationException.java b/hibernate-core/src/main/java/org/hibernate/LazyInitializationException.java index abc225ffd430..df10981d61dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/LazyInitializationException.java +++ b/hibernate-core/src/main/java/org/hibernate/LazyInitializationException.java @@ -13,7 +13,7 @@ /** * Indicates an attempt to access not-yet-fetched data outside of a session context. * - * For example, when an uninitialized proxy or collection is accessed afterQuery the session was closed. + * For example, when an uninitialized proxy or collection is accessed after the session was closed. * * @see Hibernate#initialize(java.lang.Object) * @see Hibernate#isInitialized(java.lang.Object) diff --git a/hibernate-core/src/main/java/org/hibernate/Metamodel.java b/hibernate-core/src/main/java/org/hibernate/Metamodel.java index 137ec8b1ec1e..beb4e15f14a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/Metamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/Metamodel.java @@ -39,7 +39,15 @@ default EntityType getEntityTypeByName(String entityName) { String getImportedClassName(String className); /** - * Get the names of all persistent classes that implement/extend the given interface/class + * Given the name of an entity class, determine all the class and interface names by which it can be + * referenced in an HQL query. + * + * @param entityName The name of the entity class + * + * @return the names of all persistent (mapped) classes that extend or implement the + * given class or interface, accounting for implicit/explicit polymorphism settings + * and excluding mapped subclasses/joined-subclasses of other classes in the result. + * @throws MappingException */ String[] getImplementors(String entityName); diff --git a/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java b/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java index 328a6c762544..89636fd24cce 100644 --- a/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java +++ b/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java @@ -10,8 +10,8 @@ /** * A problem occurred accessing a property of an instance of a - * persistent class by reflection, or via CGLIB. There are a - * number of possible underlying causes, including + * persistent class by reflection, or via enhanced entities. + * There are a number of possible underlying causes, including *

    *
  • failure of a security check *
  • an exception occurring inside the getter or setter method diff --git a/hibernate-core/src/main/java/org/hibernate/Query.java b/hibernate-core/src/main/java/org/hibernate/Query.java index 8244ba317305..5fbf556736e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/Query.java @@ -22,6 +22,7 @@ import javax.persistence.TemporalType; import javax.persistence.TypedQuery; +import org.hibernate.engine.spi.RowSelection; import org.hibernate.query.CommonQueryContract; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; @@ -64,6 +65,106 @@ public interface Query extends TypedQuery, CommonQueryContract { */ String getQueryString(); + /** + * "QueryOptions" is a better name, I think, than "RowSelection" -> 6.0 + * + * @todo 6.0 rename RowSelection to QueryOptions + * + * @return Return the encapsulation of this query's options, which includes access to + * firstRow, maxRows, timeout and fetchSize. Important because this gives access to + * those values in their Integer form rather than the primitive form (int) required by JPA. + */ + RowSelection getQueryOptions(); + + /** + * The position of the first query result to be retrieved, previously set by + * {@link #setFirstResult(int)} or {@link #setHibernateFirstResult(int)}. + *

    + * If the value was not initialized by {@link #setFirstResult(int)} or + * {@link #setHibernateFirstResult(int)}, then {@code null} is returned, resulting + * in pagination starting from position 0. + *

    + * If {@link #setHibernateFirstResult(int)} was called with a negative value, then 0 + * is returned. + * + * @return the position of the first query result, or {@code null} if uninitialized. + * + * @see #setFirstResult(int) + * @see #setHibernateFirstResult(int) + * + * @deprecated {@link #getFirstResult()} should be used instead. + */ + @Deprecated + default Integer getHibernateFirstResult() { + return getQueryOptions().getFirstRow(); + } + + /** + * Set the position of the first query result to be retrieved. A negative value will + * result in pagination starting from position 0. + * + * @param firstRow - the position of the first query result + * @return {@code this}, for method chaining + * + * @deprecated {@link #setFirstResult(int)} should be used instead. + */ + @Deprecated + default Query setHibernateFirstResult(int firstRow) { + if ( firstRow < 0 ) { + getQueryOptions().setFirstRow( 0 ); + } + else { + getQueryOptions().setFirstRow( firstRow ); + } + return this; + } + + /** + * The maximum number of query results to be retrieved, previously set by + * {@link #setMaxResults(int)} or {@link #setHibernateMaxResults(int)}. + *

    + * If the value was not initialized by {@link #setMaxResults(int)} or + * {@link #setHibernateMaxResults(int)}, then {@code null} is returned + *

    + * If {@link #setHibernateMaxResults(int)} was called with a value less than + * or equal to 0, the value is considered to be uninitialized, and {@code null} + * is returned, resulting in no limit on the number of results. + * + * @return the maximum number of query results, or {@code null} if uninitialized. + * + * @see #setMaxResults(int) (int) + * @see #setHibernateMaxResults(int) (int) + * + * @deprecated {@link #getMaxResults()} should be used instead. + */ + @Deprecated + default Integer getHibernateMaxResults() { + return getQueryOptions().getMaxRows(); + } + + /** + * Set the maximum number of query results to be retrieved. A value less than + * or equal to 0 is considered uninitialized, resulting in no limit on the number + * of results. + * + * @param maxResults - the maximum number of query results + * @return {@code this}, for method chaining + * + * @deprecated {@link #setMaxResults(int)} should be used instead. + */ + @Deprecated + default Query setHibernateMaxResults(int maxResults) { + // maxResults <= 0 is the same as uninitialized (with no limit), + if ( maxResults <= 0 ) { + // treat zero and negatives specifically as meaning no limit... + getQueryOptions().setMaxRows( null ); + } + else { + getQueryOptions().setMaxRows( maxResults ); + } + return this; + } + /** * Obtain the FlushMode in effect for this query. By default, the query inherits the FlushMode of the Session * from which it originates. @@ -245,7 +346,7 @@ default Query setHibernateFlushMode(FlushMode flushMode) { * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() * * The read-only/modifiable setting has no impact on entities/proxies returned by the - * query that existed in the session beforeQuery the query was executed. + * query that existed in the session before the query was executed. * * @return {@code true} if the entities and proxies loaded by the query will be put * in read-only mode; {@code false} otherwise (they will be modifiable) @@ -272,7 +373,7 @@ default Query setHibernateFlushMode(FlushMode flushMode) { * proxy has, regardless of the session's current setting. * * The read-only/modifiable setting has no impact on entities/proxies - * returned by the query that existed in the session beforeQuery the query was executed. + * returned by the query that existed in the session before the query was executed. * * @return {@code this}, for method chaining * @@ -369,7 +470,7 @@ default Query setHibernateFlushMode(FlushMode flushMode) { /** * Return the query results as an Iterator. If the query - * contains multiple results pre row, the results are returned in + * contains multiple results per row, the results are returned in * an instance of Object[].
    *
    * Entities returned as results are initialized on demand. The first @@ -603,6 +704,7 @@ default Query setHibernateFlushMode(FlushMode flushMode) { * @return {@code this}, for method chaining */ Query setParameterList(String name, Collection values); + Query setParameterList(int position, Collection values); /** * Bind multiple values to a named query parameter. This is useful for binding @@ -615,6 +717,7 @@ default Query setHibernateFlushMode(FlushMode flushMode) { * @return {@code this}, for method chaining */ Query setParameterList(String name, Collection values, Type type); + Query setParameterList(int position, Collection values, Type type); /** * Bind multiple values to a named query parameter. This is useful for binding @@ -627,6 +730,7 @@ default Query setHibernateFlushMode(FlushMode flushMode) { * @return {@code this}, for method chaining */ Query setParameterList(String name, Object[] values, Type type); + Query setParameterList(int position, Object[] values, Type type); /** * Bind multiple values to a named query parameter. The Hibernate type of the parameter is @@ -640,6 +744,7 @@ default Query setHibernateFlushMode(FlushMode flushMode) { * @return {@code this}, for method chaining */ Query setParameterList(String name, Object[] values); + Query setParameterList(int position, Object[] values); /** * Bind the property values of the given bean to named parameters of the query, diff --git a/hibernate-core/src/main/java/org/hibernate/SQLQuery.java b/hibernate-core/src/main/java/org/hibernate/SQLQuery.java index dc9950450581..a4e0a868a68e 100755 --- a/hibernate-core/src/main/java/org/hibernate/SQLQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/SQLQuery.java @@ -65,7 +65,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return this, for method chaining */ - NativeQuery setResultSetMapping(String name); + SQLQuery setResultSetMapping(String name); /** * Is this native-SQL query known to be callable? @@ -90,7 +90,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addScalar(String columnAlias); + SQLQuery addScalar(String columnAlias); /** * Declare a scalar query result. @@ -102,7 +102,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addScalar(String columnAlias, Type type); + SQLQuery addScalar(String columnAlias, Type type); /** * Add a new root return mapping, returning a {@link NativeQuery.RootReturn} to allow further definition. @@ -138,7 +138,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addEntity(String entityName); + SQLQuery addEntity(String entityName); /** * Declare a "root" entity. @@ -148,7 +148,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addEntity(String tableAlias, String entityName); + SQLQuery addEntity(String tableAlias, String entityName); /** * Declare a "root" entity, specifying a lock mode. @@ -159,7 +159,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addEntity(String tableAlias, String entityName, LockMode lockMode); + SQLQuery addEntity(String tableAlias, String entityName, LockMode lockMode); /** * Declare a "root" entity, without specifying an alias. The expectation here is that the table alias is the @@ -169,7 +169,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addEntity(Class entityType); + SQLQuery addEntity(Class entityType); /** * Declare a "root" entity. @@ -179,7 +179,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addEntity(String tableAlias, Class entityType); + SQLQuery addEntity(String tableAlias, Class entityType); /** * Declare a "root" entity, specifying a lock mode. @@ -190,7 +190,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addEntity(String tableAlias, Class entityClass, LockMode lockMode); + SQLQuery addEntity(String tableAlias, Class entityClass, LockMode lockMode); /** * Declare a join fetch result. @@ -214,7 +214,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addJoin(String tableAlias, String path); + SQLQuery addJoin(String tableAlias, String path); /** * Declare a join fetch result. @@ -228,7 +228,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @since 3.6 */ - NativeQuery addJoin(String tableAlias, String ownerTableAlias, String joinPropertyName); + SQLQuery addJoin(String tableAlias, String ownerTableAlias, String joinPropertyName); /** * Declare a join fetch result, specifying a lock mode. @@ -239,7 +239,7 @@ public interface SQLQuery extends Query, SynchronizeableQuery { * * @return {@code this}, for method chaining */ - NativeQuery addJoin(String tableAlias, String path, LockMode lockMode); + SQLQuery addJoin(String tableAlias, String path, LockMode lockMode); /** * Allows access to further control how properties within a root or join fetch are mapped back from the result set. @@ -336,6 +336,15 @@ interface FetchReturn { // overrides + @Override + SQLQuery addSynchronizedQuerySpace(String querySpace); + + @Override + SQLQuery addSynchronizedEntityName(String entityName) throws MappingException; + + @Override + SQLQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; + @Override NativeQuery setHibernateFlushMode(FlushMode flushMode); @@ -441,15 +450,6 @@ interface FetchReturn { @Override NativeQuery setParameter(int position, Date value, TemporalType temporalType); - @Override - NativeQuery addSynchronizedQuerySpace(String querySpace); - - @Override - NativeQuery addSynchronizedEntityName(String entityName) throws MappingException; - - @Override - NativeQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; - @Override NativeQuery setFlushMode(FlushMode flushMode); diff --git a/hibernate-core/src/main/java/org/hibernate/ScrollableResults.java b/hibernate-core/src/main/java/org/hibernate/ScrollableResults.java index 024268419017..c2120e0e1750 100644 --- a/hibernate-core/src/main/java/org/hibernate/ScrollableResults.java +++ b/hibernate-core/src/main/java/org/hibernate/ScrollableResults.java @@ -6,6 +6,7 @@ */ package org.hibernate; +import java.io.Closeable; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Blob; @@ -30,7 +31,7 @@ * * @author Gavin King */ -public interface ScrollableResults extends AutoCloseable { +public interface ScrollableResults extends AutoCloseable, Closeable { /** * Release resources immediately. @@ -75,13 +76,13 @@ public interface ScrollableResults extends AutoCloseable { boolean first(); /** - * Go to a location just beforeQuery first result, This is the location of the cursor on a newly returned + * Go to a location just before first result, This is the location of the cursor on a newly returned * scrollable result. */ void beforeFirst(); /** - * Go to a location just afterQuery the last result. + * Go to a location just after the last result. */ void afterLast(); diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index dafb0fd7b67a..b758a3e2be5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -6,6 +6,7 @@ */ package org.hibernate; +import java.io.Closeable; import java.io.Serializable; import java.sql.Connection; import javax.persistence.EntityManager; @@ -17,6 +18,7 @@ import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; import org.hibernate.jpa.HibernateEntityManager; +import org.hibernate.query.NativeQuery; import org.hibernate.stat.SessionStatistics; /** @@ -73,14 +75,14 @@ *
    * If the Session throws an exception, the transaction must be rolled back * and the session discarded. The internal state of the Session might not - * be consistent with the database afterQuery the exception occurs. + * be consistent with the database after the exception occurs. * * @see SessionFactory * * @author Gavin King * @author Steve Ebersole */ -public interface Session extends SharedSessionContract, EntityManager, HibernateEntityManager, AutoCloseable { +public interface Session extends SharedSessionContract, EntityManager, HibernateEntityManager, AutoCloseable, Closeable { /** * Obtain a {@link Session} builder with the ability to grab certain information from this session. * @@ -90,7 +92,7 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate /** * Force this session to flush. Must be called at the end of a - * unit of work, beforeQuery committing the transaction and closing the + * unit of work, before committing the transaction and closing the * session (depending on {@link #setFlushMode(FlushMode)}, * {@link Transaction#commit()} calls this method). *

    @@ -601,8 +603,8 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate * For example *

      *
    • where a database trigger alters the object state upon insert or update - *
    • afterQuery executing direct SQL (eg. a mass update) in the same session - *
    • afterQuery inserting a Blob or Clob + *
    • after executing direct SQL (eg. a mass update) in the same session + *
    • after inserting a Blob or Clob *
    * * @param object a persistent or detached instance @@ -616,8 +618,8 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate * For example *
      *
    • where a database trigger alters the object state upon insert or update - *
    • afterQuery executing direct SQL (eg. a mass update) in the same session - *
    • afterQuery inserting a Blob or Clob + *
    • after executing direct SQL (eg. a mass update) in the same session + *
    • after inserting a Blob or Clob *
    * * @param entityName a persistent class @@ -681,8 +683,11 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate * @param queryString a Hibernate query fragment. * * @return The query instance for manipulation and execution + * + * @deprecated (since 5.3) with no real replacement. */ - org.hibernate.query.Query createFilter(Object collection, String queryString); + @Deprecated + org.hibernate.Query createFilter(Object collection, String queryString); /** * Completely clear the session. Evict all loaded instances and cancel all pending @@ -837,7 +842,7 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate IdentifierLoadAccess byId(Class entityClass); /** - * Create an {@link NaturalIdLoadAccess} instance to retrieve the specified entity by + * Create a {@link NaturalIdLoadAccess} instance to retrieve the specified entity by * its natural id. * * @param entityName The entity name of the entity type to be retrieved @@ -849,7 +854,7 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate NaturalIdLoadAccess byNaturalId(String entityName); /** - * Create an {@link NaturalIdLoadAccess} instance to retrieve the specified entity by + * Create a {@link NaturalIdLoadAccess} instance to retrieve the specified entity by * its natural id. * * @param entityClass The entity type to be retrieved @@ -861,7 +866,7 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate NaturalIdLoadAccess byNaturalId(Class entityClass); /** - * Create an {@link SimpleNaturalIdLoadAccess} instance to retrieve the specified entity by + * Create a {@link SimpleNaturalIdLoadAccess} instance to retrieve the specified entity by * its natural id. * * @param entityName The entity name of the entity type to be retrieved @@ -874,7 +879,7 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName); /** - * Create an {@link SimpleNaturalIdLoadAccess} instance to retrieve the specified entity by + * Create a {@link SimpleNaturalIdLoadAccess} instance to retrieve the specified entity by * its simple (single attribute) natural id. * * @param entityClass The entity type to be retrieved @@ -1138,9 +1143,6 @@ interface LockRequest { */ void addEventListeners(SessionEventListener... listeners); - @Override - org.hibernate.query.Query createQuery(String queryString); - @Override org.hibernate.query.Query createQuery(String queryString, Class resultType); @@ -1153,5 +1155,9 @@ interface LockRequest { @Override org.hibernate.query.Query createQuery(CriteriaDelete deleteQuery); + org.hibernate.query.Query createNamedQuery(String name, Class resultType); + + @Override + NativeQuery createSQLQuery(String queryString); } diff --git a/hibernate-core/src/main/java/org/hibernate/SessionBuilder.java b/hibernate-core/src/main/java/org/hibernate/SessionBuilder.java index 4d10c885155b..1522d8099c83 100644 --- a/hibernate-core/src/main/java/org/hibernate/SessionBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/SessionBuilder.java @@ -17,6 +17,7 @@ * * @author Steve Ebersole */ +@SuppressWarnings("UnusedReturnValue") public interface SessionBuilder { /** * Opens a session with the specified options. @@ -65,18 +66,6 @@ public interface SessionBuilder { */ T connection(Connection connection); - /** - * Use a specific connection release mode for these session options. - * - * @param connectionReleaseMode The connection release mode to use. - * - * @return {@code this}, for method chaining - * - * @deprecated (since 5.2) use {@link #connectionHandlingMode} instead - */ - @Deprecated - T connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode); - /** * Signifies that the connection release mode from the original session should be used to create the new session. * @@ -97,17 +86,6 @@ public interface SessionBuilder { */ T autoJoinTransactions(boolean autoJoinTransactions); - /** - * Should the session be automatically closed after transaction completion? - * - * @param autoClose Should the session be automatically closed - * - * @return {@code this}, for method chaining - * - * @see javax.persistence.PersistenceContextType - */ - T autoClose(boolean autoClose); - /** * Should the session be automatically cleared on a failed transaction? * @@ -128,27 +106,6 @@ public interface SessionBuilder { */ T flushMode(FlushMode flushMode); - /** - * Should the session be automatically flushed during the "beforeQuery completion" phase of transaction handling. - * - * @param flushBeforeCompletion Should the session be automatically flushed - * - * @return {@code this}, for method chaining - * - * @deprecated (since 5.2) use {@link #flushMode(FlushMode)} instead. - */ - @Deprecated - @SuppressWarnings("unchecked") - default T flushBeforeCompletion(boolean flushBeforeCompletion) { - if ( flushBeforeCompletion ) { - flushMode( FlushMode.ALWAYS ); - } - else { - flushMode( FlushMode.MANUAL ); - } - return (T) this; - } - /** * Define the tenant identifier to be associated with the opened session. * @@ -176,4 +133,68 @@ default T flushBeforeCompletion(boolean flushBeforeCompletion) { T clearEventListeners(); T jdbcTimeZone(TimeZone timeZone); + + /** + * Should {@link org.hibernate.query.Query#setParameter} perform parameter validation + * when the Session is bootstrapped via JPA {@link javax.persistence.EntityManagerFactory} + * + * @param enabled {@code true} indicates the validation should be performed, {@code false} otherwise + *

    + * The default value is {@code true} + * + * @return {@code this}, for method chaining + */ + default T setQueryParameterValidation(boolean enabled) { + return (T) this; + } + + + + /** + * Should the session be automatically closed after transaction completion? + * + * @param autoClose Should the session be automatically closed + * + * @return {@code this}, for method chaining + * + * @see javax.persistence.PersistenceContextType + * + * @deprecated Only integrations can specify autoClosing behavior of individual sessions. See + * {@link org.hibernate.engine.spi.SessionOwner} + */ + @Deprecated + T autoClose(boolean autoClose); + + /** + * Use a specific connection release mode for these session options. + * + * @param connectionReleaseMode The connection release mode to use. + * + * @return {@code this}, for method chaining + * + * @deprecated (since 5.2) use {@link #connectionHandlingMode} instead + */ + @Deprecated + T connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode); + + /** + * Should the session be automatically flushed during the "before completion" phase of transaction handling. + * + * @param flushBeforeCompletion Should the session be automatically flushed + * + * @return {@code this}, for method chaining + * + * @deprecated (since 5.2) use {@link #flushMode(FlushMode)} instead. + */ + @Deprecated + @SuppressWarnings("unchecked") + default T flushBeforeCompletion(boolean flushBeforeCompletion) { + if ( flushBeforeCompletion ) { + flushMode( FlushMode.ALWAYS ); + } + else { + flushMode( FlushMode.MANUAL ); + } + return (T) this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java index 4b97968258a7..4c8a15975a9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java @@ -115,7 +115,7 @@ public interface SessionFactory extends EntityManagerFactory, HibernateEntityMan * connection pools, etc). *

    * It is the responsibility of the application to ensure that there are no - * open {@link Session sessions} beforeQuery calling this method as the impact + * open {@link Session sessions} before calling this method as the impact * on those {@link Session sessions} is indeterminate. *

    * No-ops if already {@link #isClosed closed}. diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionBuilder.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionBuilder.java index 8c3ecb2803d0..541f64a9e013 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionBuilder.java @@ -6,6 +6,8 @@ */ package org.hibernate; +import java.sql.Connection; + /** * Specialized {@link SessionBuilder} with access to stuff from another session. * @@ -90,4 +92,37 @@ default T flushBeforeCompletion() { flushMode(); return (T) this; } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // overrides to maintain binary compatibility + + @Override + T interceptor(Interceptor interceptor); + + @Override + T noInterceptor(); + + @Override + T connection(Connection connection); + + @Override + T connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode); + + @Override + T autoJoinTransactions(boolean autoJoinTransactions); + + @Override + T autoClose(boolean autoClose); + + @Override + default T flushBeforeCompletion(boolean flushBeforeCompletion) { + if ( flushBeforeCompletion ) { + flushMode( FlushMode.ALWAYS ); + } + else { + flushMode( FlushMode.MANUAL ); + } + return (T) this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index e92d7bdb8725..e5e49f95ea53 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -67,6 +67,12 @@ public interface SharedSessionContract extends QueryProducer, Serializable { */ Transaction getTransaction(); + @Override + org.hibernate.query.Query createQuery(String queryString); + + @Override + org.hibernate.query.Query getNamedQuery(String queryName); + /** * Gets a ProcedureCall based on a named template * diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index a0ace4eb2bfc..8a15cf1f7577 100755 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -6,9 +6,12 @@ */ package org.hibernate; +import java.io.Closeable; import java.io.Serializable; import java.sql.Connection; +import org.hibernate.query.NativeQuery; + /** * A command-oriented API for performing bulk operations against a database. *

    @@ -25,7 +28,7 @@ * * @author Gavin King */ -public interface StatelessSession extends SharedSessionContract, AutoCloseable { +public interface StatelessSession extends SharedSessionContract, AutoCloseable, Closeable { /** * Close the stateless session and release the JDBC connection. */ @@ -169,4 +172,7 @@ public interface StatelessSession extends SharedSessionContract, AutoCloseable { */ @Deprecated Connection connection(); + + @Override + NativeQuery createSQLQuery(String queryString); } diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSessionBuilder.java b/hibernate-core/src/main/java/org/hibernate/StatelessSessionBuilder.java index 0cd2a6ee8c20..0654347676cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSessionBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSessionBuilder.java @@ -38,4 +38,18 @@ public interface StatelessSessionBuilder { * @return {@code this}, for method chaining */ T tenantIdentifier(String tenantIdentifier); + + /** + * Should {@link org.hibernate.query.Query#setParameter} perform parameter validation + * when the Session is bootstrapped via JPA {@link javax.persistence.EntityManagerFactory} + * + * @param enabled {@code true} indicates the validation should be performed, {@code false} otherwise + *

    + * The default value is {@code true} + * + * @return {@code this}, for method chaining + */ + default T setQueryParameterValidation(boolean enabled) { + return (T) this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java b/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java index da1206e8d7ca..fb1d4f7ec1a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java @@ -15,10 +15,12 @@ * processed by auto-flush based on the table to which those entities are mapped and which are * determined to have pending state changes. * - * In a similar manner, these query spaces also affect how query result caching can recognize invalidated results. + * In a similar manner, these query spaces also affect how query result caching can recognize + * invalidated results. * * @author Steve Ebersole */ +@SuppressWarnings( { "unused", "UnusedReturnValue", "RedundantSuppression" } ) public interface SynchronizeableQuery { /** * Obtain the list of query spaces the query is synchronized on. @@ -36,6 +38,32 @@ public interface SynchronizeableQuery { */ SynchronizeableQuery addSynchronizedQuerySpace(String querySpace); + /** + * Adds one-or-more synchronized spaces + */ + default SynchronizeableQuery addSynchronizedQuerySpace(String... querySpaces) { + if ( querySpaces != null ) { + for ( int i = 0; i < querySpaces.length; i++ ) { + addSynchronizedQuerySpace( querySpaces[i] ); + } + } + return this; + } + + /** + * Adds a table expression as a query space. + */ + default SynchronizeableQuery addSynchronizedTable(String tableExpression) { + return addSynchronizedQuerySpace( tableExpression ); + } + + /** + * Adds one-or-more synchronized table expressions + */ + default SynchronizeableQuery addSynchronizedTable(String... tableExpressions) { + return addSynchronizedQuerySpace( tableExpressions ); + } + /** * Adds an entity name for (a) auto-flush checking and (b) query result cache invalidation checking. Same as * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity. @@ -48,6 +76,18 @@ public interface SynchronizeableQuery { */ SynchronizeableQuery addSynchronizedEntityName(String entityName) throws MappingException; + /** + * Adds one-or-more entities (by name) whose tables should be added as synchronized spaces + */ + default SynchronizeableQuery addSynchronizedEntityName(String... entityNames) throws MappingException { + if ( entityNames != null ) { + for ( int i = 0; i < entityNames.length; i++ ) { + addSynchronizedEntityName( entityNames[i] ); + } + } + return this; + } + /** * Adds an entity for (a) auto-flush checking and (b) query result cache invalidation checking. Same as * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity. @@ -58,5 +98,18 @@ public interface SynchronizeableQuery { * * @throws MappingException Indicates the given class could not be resolved as an entity */ + @SuppressWarnings( "rawtypes" ) SynchronizeableQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; + + /** + * Adds one-or-more entities (by class) whose tables should be added as synchronized spaces + */ + default SynchronizeableQuery addSynchronizedEntityClass(Class... entityClasses) throws MappingException { + if ( entityClasses != null ) { + for ( int i = 0; i < entityClasses.length; i++ ) { + addSynchronizedEntityClass( entityClasses[i] ); + } + } + return this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/Transaction.java b/hibernate-core/src/main/java/org/hibernate/Transaction.java index 140504314add..6ac446908d92 100644 --- a/hibernate-core/src/main/java/org/hibernate/Transaction.java +++ b/hibernate-core/src/main/java/org/hibernate/Transaction.java @@ -50,7 +50,7 @@ public interface Transaction extends EntityTransaction { /** * Set the transaction timeout for any transaction started by a subsequent call to {@link #begin} on this instance. * - * @param seconds The number of seconds beforeQuery a timeout. + * @param seconds The number of seconds before a timeout. */ void setTimeout(int seconds); diff --git a/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java index 9b94274c4ca6..d269bed1989b 100644 --- a/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java +++ b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java @@ -20,7 +20,7 @@ public class TransientPropertyValueException extends TransientObjectException { private final String propertyName; /** - * Constructs an {@link TransientPropertyValueException} instance. + * Constructs a {@link TransientPropertyValueException} instance. * * @param message - the exception message; * @param transientEntityName - the entity name for the transient entity diff --git a/hibernate-core/src/main/java/org/hibernate/Version.java b/hibernate-core/src/main/java/org/hibernate/Version.java index f65502f77c68..577c65dee543 100644 --- a/hibernate-core/src/main/java/org/hibernate/Version.java +++ b/hibernate-core/src/main/java/org/hibernate/Version.java @@ -7,6 +7,7 @@ package org.hibernate; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.build.AllowSysOut; import org.jboss.logging.Logger; @@ -51,6 +52,7 @@ public static void logVersion() { * * @param args n/a */ + @AllowSysOut public static void main(String[] args) { System.out.println( "Hibernate Core {" + getVersionString() + "}" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java index a7a5507d31a9..6bc20950878c 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java @@ -110,8 +110,8 @@ public NonNullableTransientDependencies findNonNullableTransientEntities() { */ protected final void nullifyTransientReferencesIfNotAlready() { if ( ! areTransientReferencesNullified ) { - new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession() ) - .nullifyTransientReferences( getState(), getPersister().getPropertyTypes() ); + new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() ) + .nullifyTransientReferences( getState() ); new Nullability( getSession() ).checkNullability( getState(), getPersister(), false ); areTransientReferencesNullified = true; } @@ -161,10 +161,10 @@ public void afterDeserialize(SharedSessionContractImplementor session) { } /** - * Handle sending notifications needed for natural-id beforeQuery saving + * Handle sending notifications needed for natural-id before saving */ protected void handleNaturalIdPreSaveNotifications() { - // beforeQuery save, we need to add a local (transactional) natural id cross-reference + // before save, we need to add a local (transactional) natural id cross-reference getSession().getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference( getPersister(), getId(), @@ -175,7 +175,7 @@ protected void handleNaturalIdPreSaveNotifications() { } /** - * Handle sending notifications needed for natural-id afterQuery saving + * Handle sending notifications needed for natural-id after saving * * @param generatedId The generated entity identifier */ @@ -190,7 +190,7 @@ public void handleNaturalIdPostSaveNotifications(Serializable generatedId) { CachedNaturalIdValueSource.INSERT ); } - // afterQuery save, we need to manage the shared cache entries + // after save, we need to manage the shared cache entries getSession().getPersistenceContext().getNaturalIdHelper().manageSharedNaturalIdCrossReference( getPersister(), generatedId, diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java index 103137a70e88..f6eca861399a 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java @@ -16,9 +16,9 @@ import org.hibernate.action.spi.AfterTransactionCompletionProcess; import org.hibernate.action.spi.BeforeTransactionCompletionProcess; import org.hibernate.action.spi.Executable; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -61,11 +61,17 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Quer for ( Queryable persister : affectedQueryables ) { spacesList.addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) ); - if ( persister.hasCache() ) { - entityCleanups.add( new EntityCleanup( persister.getCacheAccessStrategy() ) ); + if ( persister.canWriteToCache() ) { + final EntityDataAccess entityDataAccess = persister.getCacheAccessStrategy(); + if ( entityDataAccess != null ) { + entityCleanups.add( new EntityCleanup( entityDataAccess, session ) ); + } } + if ( persister.hasNaturalIdentifier() && persister.hasNaturalIdCache() ) { - naturalIdCleanups.add( new NaturalIdCleanup( persister.getNaturalIdCacheAccessStrategy() ) ); + naturalIdCleanups.add( + new NaturalIdCleanup( persister.getNaturalIdCacheAccessStrategy(), session ) + ); } final Set roles = factory.getMetamodel().getCollectionRolesByEntityParticipant( persister.getEntityName() ); @@ -73,13 +79,18 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Quer for ( String role : roles ) { final CollectionPersister collectionPersister = factory.getMetamodel().collectionPersister( role ); if ( collectionPersister.hasCache() ) { - collectionCleanups.add( new CollectionCleanup( collectionPersister.getCacheAccessStrategy() ) ); + collectionCleanups.add( + new CollectionCleanup( + collectionPersister.getCacheAccessStrategy(), + session + ) + ); } } } } - this.affectedTableSpaces = spacesList.toArray( new String[ spacesList.size() ] ); + this.affectedTableSpaces = spacesList.toArray( new String[ 0 ] ); } /** @@ -94,7 +105,7 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Quer * @param session The session to which this request is tied. * @param tableSpaces The table spaces. */ - @SuppressWarnings({ "unchecked" }) + @SuppressWarnings( { "unchecked", "rawtypes" } ) public BulkOperationCleanupAction(SharedSessionContractImplementor session, Set tableSpaces) { final LinkedHashSet spacesList = new LinkedHashSet<>(); spacesList.addAll( tableSpaces ); @@ -105,11 +116,11 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Set if ( affectedEntity( tableSpaces, entitySpaces ) ) { spacesList.addAll( Arrays.asList( entitySpaces ) ); - if ( persister.hasCache() ) { - entityCleanups.add( new EntityCleanup( persister.getCacheAccessStrategy() ) ); + if ( persister.canWriteToCache() ) { + entityCleanups.add( new EntityCleanup( persister.getCacheAccessStrategy(), session ) ); } if ( persister.hasNaturalIdentifier() && persister.hasNaturalIdCache() ) { - naturalIdCleanups.add( new NaturalIdCleanup( persister.getNaturalIdCacheAccessStrategy() ) ); + naturalIdCleanups.add( new NaturalIdCleanup( persister.getNaturalIdCacheAccessStrategy(), session ) ); } final Set roles = session.getFactory().getMetamodel().getCollectionRolesByEntityParticipant( persister.getEntityName() ); @@ -118,7 +129,7 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Set final CollectionPersister collectionPersister = factory.getMetamodel().collectionPersister( role ); if ( collectionPersister.hasCache() ) { collectionCleanups.add( - new CollectionCleanup( collectionPersister.getCacheAccessStrategy() ) + new CollectionCleanup( collectionPersister.getCacheAccessStrategy(), session ) ); } } @@ -126,23 +137,26 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Set } } - this.affectedTableSpaces = spacesList.toArray( new String[ spacesList.size() ] ); + this.affectedTableSpaces = spacesList.toArray( new String[ 0 ] ); } /** - * Check to determine whether the table spaces reported by an entity - * persister match against the defined affected table spaces. + * Check whether we should consider an entity as affected by the query. This + * defines inclusion of the entity in the clean-up. * * @param affectedTableSpaces The table spaces reported to be affected by * the query. * @param checkTableSpaces The table spaces (from the entity persister) * to check against the affected table spaces. * - * @return True if there are affected table spaces and any of the incoming - * check table spaces occur in that set. + * @return Whether the entity should be considered affected + * + * @implNote An entity is considered to be affected if either (1) the affected table + * spaces are not known or (2) any of the incoming check table spaces occur + * in that set. */ - private boolean affectedEntity(Set affectedTableSpaces, Serializable[] checkTableSpaces) { + private boolean affectedEntity(Set affectedTableSpaces, Serializable[] checkTableSpaces) { if ( affectedTableSpaces == null || affectedTableSpaces.isEmpty() ) { return true; } @@ -167,25 +181,21 @@ public BeforeTransactionCompletionProcess getBeforeTransactionCompletionProcess( @Override public AfterTransactionCompletionProcess getAfterTransactionCompletionProcess() { - return new AfterTransactionCompletionProcess() { - @Override - public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) { - for ( EntityCleanup cleanup : entityCleanups ) { - cleanup.release(); - } - entityCleanups.clear(); - - for ( NaturalIdCleanup cleanup : naturalIdCleanups ) { - cleanup.release(); + return (success, session) -> { + for ( EntityCleanup cleanup : entityCleanups ) { + cleanup.release(); + } + entityCleanups.clear(); - } - entityCleanups.clear(); + for ( NaturalIdCleanup cleanup : naturalIdCleanups ) { + cleanup.release(); + } + naturalIdCleanups.clear(); - for ( CollectionCleanup cleanup : collectionCleanups ) { - cleanup.release(); - } - collectionCleanups.clear(); + for ( CollectionCleanup cleanup : collectionCleanups ) { + cleanup.release(); } + collectionCleanups.clear(); }; } @@ -200,13 +210,15 @@ public void execute() throws HibernateException { } private static class EntityCleanup implements Serializable { - private final EntityRegionAccessStrategy cacheAccess; + private final EntityDataAccess cacheAccess; private final SoftLock cacheLock; - private EntityCleanup(EntityRegionAccessStrategy cacheAccess) { + private EntityCleanup( + EntityDataAccess cacheAccess, + SharedSessionContractImplementor session) { this.cacheAccess = cacheAccess; this.cacheLock = cacheAccess.lockRegion(); - cacheAccess.removeAll(); + cacheAccess.removeAll( session ); } private void release() { @@ -215,13 +227,15 @@ private void release() { } private static class CollectionCleanup implements Serializable { - private final CollectionRegionAccessStrategy cacheAccess; + private final CollectionDataAccess cacheAccess; private final SoftLock cacheLock; - private CollectionCleanup(CollectionRegionAccessStrategy cacheAccess) { + private CollectionCleanup( + CollectionDataAccess cacheAccess, + SharedSessionContractImplementor session) { this.cacheAccess = cacheAccess; this.cacheLock = cacheAccess.lockRegion(); - cacheAccess.removeAll(); + cacheAccess.removeAll( session ); } private void release() { @@ -230,13 +244,15 @@ private void release() { } private static class NaturalIdCleanup implements Serializable { - private final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy; + private final NaturalIdDataAccess naturalIdCacheAccessStrategy; private final SoftLock cacheLock; - public NaturalIdCleanup(NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy) { + public NaturalIdCleanup( + NaturalIdDataAccess naturalIdCacheAccessStrategy, + SharedSessionContractImplementor session) { this.naturalIdCacheAccessStrategy = naturalIdCacheAccessStrategy; this.cacheLock = naturalIdCacheAccessStrategy.lockRegion(); - naturalIdCacheAccessStrategy.removeAll(); + naturalIdCacheAccessStrategy.removeAll( session ); } private void release() { diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionAction.java index 8dbd8c4156be..e8462168b0f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionAction.java @@ -12,7 +12,7 @@ import org.hibernate.action.spi.BeforeTransactionCompletionProcess; import org.hibernate.action.spi.Executable; import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -54,7 +54,7 @@ protected PersistentCollection getCollection() { } /** - * Reconnect to session afterQuery deserialization... + * Reconnect to session after deserialization... * * @param session The session being deserialized */ @@ -72,11 +72,11 @@ public void afterDeserialize(SharedSessionContractImplementor session) { @Override public final void beforeExecutions() throws CacheException { - // we need to obtain the lock beforeQuery any actions are executed, since this may be an inverse="true" + // we need to obtain the lock before any actions are executed, since this may be an inverse="true" // bidirectional association and it is one of the earlier entity actions which actually updates // the database (this action is responsible for second-level cache invalidation only) if ( persister.hasCache() ) { - final CollectionRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final CollectionDataAccess cache = persister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( key, persister, @@ -129,7 +129,7 @@ protected final SharedSessionContractImplementor getSession() { protected final void evict() throws CacheException { if ( persister.hasCache() ) { - final CollectionRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final CollectionDataAccess cache = persister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( key, persister, @@ -173,7 +173,7 @@ private CacheCleanupProcess(Serializable key, CollectionPersister persister, Sof @Override public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) { - final CollectionRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final CollectionDataAccess cache = persister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( key, persister, diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionRemoveAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionRemoveAction.java index 5e4c88b17f19..2620399529b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionRemoveAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionRemoveAction.java @@ -51,7 +51,7 @@ public CollectionRemoveAction( throw new AssertionFailure("collection == null"); } this.emptySnapshot = emptySnapshot; - // the loaded owner will be set to null afterQuery the collection is removed, + // the loaded owner will be set to null after the collection is removed, // so capture its value as the affected owner so it is accessible to // both pre- and post- events this.affectedOwner = session.getPersistenceContext().getLoadedCollectionOwnerOrNull( collection ); diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java index b8cb3c18ccf3..39264ee6b858 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java @@ -57,8 +57,11 @@ public void execute() throws HibernateException { preUpdate(); if ( !collection.wasInitialized() ) { - if ( !collection.hasQueuedOperations() ) { - throw new AssertionFailure( "no queued adds" ); + // If there were queued operations, they would have been processed + // and cleared by now. + // The collection should still be dirty. + if ( !collection.isDirty() ) { + throw new AssertionFailure( "collection is not dirty" ); } //do nothing - we only need to notify the cache... } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java index 95275cecf84e..35d2ce984cfe 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java @@ -21,6 +21,8 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; +import org.jboss.logging.Logger; + /** * Base class for actions relating to insert/update/delete of an entity * instance. @@ -29,6 +31,7 @@ */ public abstract class EntityAction implements Executable, Serializable, Comparable, AfterTransactionCompletionProcess { + private static final Logger LOG = Logger.getLogger(EntityAction.class); private final String entityName; private final Serializable id; @@ -37,6 +40,8 @@ public abstract class EntityAction private transient SharedSessionContractImplementor session; private transient EntityPersister persister; + private transient boolean veto; + /** * Instantiate an action. * @@ -53,6 +58,14 @@ protected EntityAction(SharedSessionContractImplementor session, Serializable id this.persister = persister; } + public boolean isVeto() { + return veto; + } + + public void setVeto(boolean veto) { + this.veto = veto; + } + @Override public BeforeTransactionCompletionProcess getBeforeTransactionCompletionProcess() { return null; @@ -68,7 +81,7 @@ public AfterTransactionCompletionProcess getAfterTransactionCompletionProcess() protected abstract boolean hasPostCommitEventListeners(); protected boolean needsAfterTransactionCompletion() { - return persister.hasCache() || hasPostCommitEventListeners(); + return persister.canWriteToCache() || hasPostCommitEventListeners(); } /** @@ -156,7 +169,7 @@ public int compareTo(Object other) { } /** - * Reconnect to session afterQuery deserialization... + * Reconnect to session after deserialization... * * @param session The session being deserialized */ diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityActionVetoException.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityActionVetoException.java new file mode 100644 index 000000000000..ad7c6e5c86cd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityActionVetoException.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.action.internal; +import org.hibernate.HibernateException; + +/** + * An exception indicating that an {@link org.hibernate.action.internal.EntityAction} was vetoed. + * + * @author Vlad Mihalcea + */ +public class EntityActionVetoException extends HibernateException { + + private final EntityAction entityAction; + + /** + * Constructs a EntityActionVetoException + * + * @param message Message explaining the exception condition + * @param entityAction The {@link org.hibernate.action.internal.EntityAction} was vetoed that was vetoed. + */ + public EntityActionVetoException(String message, EntityAction entityAction) { + super( message ); + this.entityAction = entityAction; + } + + public EntityAction getEntityAction() { + return entityAction; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java index 33966a4a74a7..666d551c0b32 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java @@ -10,7 +10,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; @@ -60,7 +60,7 @@ public EntityDeleteAction( this.isCascadeDeleteEnabled = isCascadeDeleteEnabled; this.state = state; - // beforeQuery remove we need to remove the local (transactional) natural id cross-reference + // before remove we need to remove the local (transactional) natural id cross-reference naturalIdValues = session.getPersistenceContext().getNaturalIdHelper().removeLocalNaturalIdCrossReference( getPersister(), getId(), @@ -86,8 +86,8 @@ public void execute() throws HibernateException { } final Object ck; - if ( persister.hasCache() ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + if ( persister.canWriteToCache() ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); ck = cache.generateCacheKey( id, persister, session.getFactory(), session.getTenantIdentifier() ); lock = cache.lockItem( session, ck, version ); } @@ -113,7 +113,7 @@ public void execute() throws HibernateException { persistenceContext.removeEntity( entry.getEntityKey() ); persistenceContext.removeProxy( entry.getEntityKey() ); - if ( persister.hasCache() ) { + if ( persister.canWriteToCache() ) { persister.getCacheAccessStrategy().remove( session, ck); } @@ -187,8 +187,8 @@ private void postCommitDelete(boolean success) { @Override public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) throws HibernateException { EntityPersister entityPersister = getPersister(); - if ( entityPersister.hasCache() ) { - EntityRegionAccessStrategy cache = entityPersister.getCacheAccessStrategy(); + if ( entityPersister.canWriteToCache() ) { + EntityDataAccess cache = entityPersister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( getId(), entityPersister, @@ -204,7 +204,7 @@ public void doAfterTransactionCompletion(boolean success, SharedSessionContractI protected boolean hasPostCommitEventListeners() { final EventListenerGroup group = listenerGroup( EventType.POST_COMMIT_DELETE ); for ( PostDeleteEventListener listener : group.listeners() ) { - if ( listener.requiresPostCommitHanding( getPersister() ) ) { + if ( listener.requiresPostCommitHandling( getPersister() ) ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIdentityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIdentityInsertAction.java index 25a176a05d97..9763cc8ba4bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIdentityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIdentityInsertAction.java @@ -72,12 +72,12 @@ public void execute() throws HibernateException { final SharedSessionContractImplementor session = getSession(); final Object instance = getInstance(); - final boolean veto = preInsert(); + setVeto( preInsert() ); // Don't need to lock the cache here, since if someone // else inserted the same pk first, the insert would fail - if ( !veto ) { + if ( !isVeto() ) { generatedId = persister.insert( getState(), instance, session ); if ( persister.hasInsertGeneratedProperties() ) { persister.processInsertGeneratedProperties( generatedId, instance, getState(), session ); @@ -91,7 +91,7 @@ public void execute() throws HibernateException { } - //TODO: this bit actually has to be called afterQuery all cascades! + //TODO: this bit actually has to be called after all cascades! // but since identity insert is called *synchronously*, // instead of asynchronously as other actions, it isn't /*if ( persister.hasCache() && !persister.isCacheInvalidationRequired() ) { @@ -101,8 +101,8 @@ public void execute() throws HibernateException { postInsert(); - if ( session.getFactory().getStatistics().isStatisticsEnabled() && !veto ) { - session.getFactory().getStatisticsImplementor().insertEntity( getPersister().getEntityName() ); + if ( session.getFactory().getStatistics().isStatisticsEnabled() && !isVeto() ) { + session.getFactory().getStatistics().insertEntity( getPersister().getEntityName() ); } markExecuted(); @@ -118,7 +118,7 @@ public boolean needsAfterTransactionCompletion() { protected boolean hasPostCommitEventListeners() { final EventListenerGroup group = listenerGroup( EventType.POST_COMMIT_INSERT ); for ( PostInsertEventListener listener : group.listeners() ) { - if ( listener.requiresPostCommitHanding( getPersister() ) ) { + if ( listener.requiresPostCommitHandling( getPersister() ) ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java index 1beac49178e6..6db4a6e5b231 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java @@ -13,32 +13,35 @@ /** * A BeforeTransactionCompletionProcess impl to verify and increment an entity version as party - * of beforeQuery-transaction-completion processing + * of before-transaction-completion processing * * @author Scott Marlow */ public class EntityIncrementVersionProcess implements BeforeTransactionCompletionProcess { private final Object object; - private final EntityEntry entry; /** * Constructs an EntityIncrementVersionProcess for the given entity. * * @param object The entity instance - * @param entry The entity's EntityEntry reference */ - public EntityIncrementVersionProcess(Object object, EntityEntry entry) { + public EntityIncrementVersionProcess(Object object) { this.object = object; - this.entry = entry; } /** - * Perform whatever processing is encapsulated here beforeQuery completion of the transaction. + * Perform whatever processing is encapsulated here before completion of the transaction. * * @param session The session on which the transaction is preparing to complete. */ @Override public void doBeforeTransactionCompletion(SessionImplementor session) { + final EntityEntry entry = session.getPersistenceContext().getEntry( object ); + // Don't increment version for an entity that is not in the PersistenceContext; + if ( entry == null ) { + return; + } + final EntityPersister persister = entry.getPersister(); final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session ); entry.forceLocked( object, nextVersion ); diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java index 4a2e85ec2336..1d83142def94 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java @@ -10,7 +10,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; @@ -27,6 +27,7 @@ import org.hibernate.event.spi.PreInsertEvent; import org.hibernate.event.spi.PreInsertEventListener; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.stat.internal.StatsHelper; /** * The action for performing an entity insertion, for entities not defined to use IDENTITY generation. @@ -116,13 +117,16 @@ public void execute() throws HibernateException { session ); cacheEntry = persister.getCacheEntryStructure().structure( ce ); - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final EntityDataAccess cache = persister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( id, persister, factory, session.getTenantIdentifier() ); final boolean put = cacheInsert( persister, ck ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().secondLevelCachePut( cache.getRegion().getName() ); + factory.getStatistics().entityCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + cache.getRegion().getName() + ); } } @@ -211,20 +215,22 @@ private boolean preInsert() { public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) throws HibernateException { final EntityPersister persister = getPersister(); if ( success && isCachePutEnabled( persister, getSession() ) ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); - SessionFactoryImplementor sessionFactoryImplementor = session.getFactory(); - final Object ck = cache.generateCacheKey( getId(), persister, sessionFactoryImplementor, session.getTenantIdentifier() ); + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + SessionFactoryImplementor factory = session.getFactory(); + final Object ck = cache.generateCacheKey( getId(), persister, factory, session.getTenantIdentifier() ); final boolean put = cacheAfterInsert( cache, ck ); - if ( put && sessionFactoryImplementor.getStatistics().isStatisticsEnabled() ) { - sessionFactoryImplementor.getStatisticsImplementor() - .secondLevelCachePut( cache.getRegion().getName() ); + if ( put && factory.getStatistics().isStatisticsEnabled() ) { + factory.getStatistics().entityCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + cache.getRegion().getName() + ); } } postCommitInsert( success ); } - private boolean cacheAfterInsert(EntityRegionAccessStrategy cache, Object ck) { + private boolean cacheAfterInsert(EntityDataAccess cache, Object ck) { SharedSessionContractImplementor session = getSession(); final SessionEventListenerManager eventListenerManager = session.getEventListenerManager(); try { @@ -240,7 +246,7 @@ private boolean cacheAfterInsert(EntityRegionAccessStrategy cache, Object ck) { protected boolean hasPostCommitEventListeners() { final EventListenerGroup group = listenerGroup( EventType.POST_COMMIT_INSERT ); for ( PostInsertEventListener listener : group.listeners() ) { - if ( listener.requiresPostCommitHanding( getPersister() ) ) { + if ( listener.requiresPostCommitHandling( getPersister() ) ) { return true; } } @@ -249,7 +255,7 @@ protected boolean hasPostCommitEventListeners() { } private boolean isCachePutEnabled(EntityPersister persister, SharedSessionContractImplementor session) { - return persister.hasCache() + return persister.canWriteToCache() && !persister.isCacheInvalidationRequired() && session.getCacheMode().isPutEnabled(); } 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 f1596a6c9313..fd9655369df8 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 @@ -11,7 +11,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.engine.internal.Versioning; @@ -29,6 +29,7 @@ import org.hibernate.event.spi.PreUpdateEvent; import org.hibernate.event.spi.PreUpdateEventListener; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.stat.internal.StatsHelper; import org.hibernate.type.TypeHelper; /** @@ -127,8 +128,8 @@ public void execute() throws HibernateException { } final Object ck; - if ( persister.hasCache() ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + if ( persister.canWriteToCache() ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); ck = cache.generateCacheKey( id, persister, @@ -184,7 +185,7 @@ public void execute() throws HibernateException { entry.postUpdate( instance, state, nextVersion ); } - if ( persister.hasCache() ) { + if ( persister.canWriteToCache() ) { if ( persister.isCacheInvalidationRequired() || entry.getStatus()!= Status.MANAGED ) { persister.getCacheAccessStrategy().remove( session, ck); } @@ -195,7 +196,10 @@ else if ( session.getCacheMode().isPutEnabled() ) { final boolean put = cacheUpdate( persister, previousVersion, ck ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().secondLevelCachePut( getPersister().getCacheAccessStrategy().getRegion().getName() ); + factory.getStatistics().entityCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + getPersister().getCacheAccessStrategy().getRegion().getName() + ); } } } @@ -299,7 +303,7 @@ private void postCommitUpdate(boolean success) { protected boolean hasPostCommitEventListeners() { final EventListenerGroup group = listenerGroup( EventType.POST_COMMIT_UPDATE ); for ( PostUpdateEventListener listener : group.listeners() ) { - if ( listener.requiresPostCommitHanding( getPersister() ) ) { + if ( listener.requiresPostCommitHandling( getPersister() ) ) { return true; } } @@ -310,8 +314,8 @@ protected boolean hasPostCommitEventListeners() { @Override public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) throws CacheException { final EntityPersister persister = getPersister(); - if ( persister.hasCache() ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + if ( persister.canWriteToCache() ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( getId(), persister, @@ -327,7 +331,10 @@ public void doAfterTransactionCompletion(boolean success, SharedSessionContractI final boolean put = cacheAfterUpdate( cache, ck ); if ( put && getSession().getFactory().getStatistics().isStatisticsEnabled() ) { - getSession().getFactory().getStatistics().secondLevelCachePut( cache.getRegion().getName() ); + session.getFactory().getStatistics().entityCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + getPersister().getCacheAccessStrategy().getRegion().getName() + ); } } else { @@ -337,7 +344,7 @@ public void doAfterTransactionCompletion(boolean success, SharedSessionContractI postCommitUpdate( success ); } - private boolean cacheAfterUpdate(EntityRegionAccessStrategy cache, Object ck) { + private boolean cacheAfterUpdate(EntityDataAccess cache, Object ck) { final SharedSessionContractImplementor session = getSession(); SessionEventListenerManager eventListenerManager = session.getEventListenerManager(); try { diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java index b9ef0b959300..28ba3195e52e 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java @@ -15,34 +15,31 @@ /** * A BeforeTransactionCompletionProcess impl to verify an entity version as part of - * beforeQuery-transaction-completion processing + * before-transaction-completion processing * * @author Scott Marlow */ public class EntityVerifyVersionProcess implements BeforeTransactionCompletionProcess { private final Object object; - private final EntityEntry entry; /** * Constructs an EntityVerifyVersionProcess * * @param object The entity instance - * @param entry The entity's referenced EntityEntry */ - public EntityVerifyVersionProcess(Object object, EntityEntry entry) { + public EntityVerifyVersionProcess(Object object) { this.object = object; - this.entry = entry; } @Override public void doBeforeTransactionCompletion(SessionImplementor session) { - final EntityPersister persister = entry.getPersister(); - - if ( !entry.isExistsInDatabase() ) { - // HHH-9419: We cannot check for a version of an entry we ourselves deleted + final EntityEntry entry = session.getPersistenceContext().getEntry( object ); + // Don't check version for an entity that is not in the PersistenceContext; + if ( entry == null ) { return; } + final EntityPersister persister = entry.getPersister(); final Object latestVersion = persister.getCurrentVersion( entry.getId(), session ); if ( !entry.getVersion().equals( latestVersion ) ) { throw new OptimisticLockException( diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java index 4abf3efb24d3..532b12e377c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java @@ -9,7 +9,9 @@ import java.io.Serializable; import org.hibernate.HibernateException; +import org.hibernate.collection.internal.AbstractPersistentCollection; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.collection.CollectionPersister; @@ -40,6 +42,21 @@ public QueuedOperationCollectionAction( @Override public void execute() throws HibernateException { + // this QueuedOperationCollectionAction has to be executed before any other + // CollectionAction involving the same collection. + getPersister().processQueuedOps( getCollection(), getKey(), getSession() ); + + // TODO: It would be nice if this could be done safely by CollectionPersister#processQueuedOps; + // Can't change the SPI to do this though. + ((AbstractPersistentCollection) getCollection() ).clearOperationQueue(); + + // The other CollectionAction types call CollectionEntry#afterAction, which + // clears the dirty flag. We don't want to call CollectionEntry#afterAction unless + // there is no other CollectionAction that will be executed on the same collection. + final CollectionEntry ce = getSession().getPersistenceContext().getCollectionEntry( getCollection() ); + if ( !ce.isDoremove() && !ce.isDoupdate() && !ce.isDorecreate() ) { + ce.afterAction( getCollection() ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java b/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java index 1d5f1fed732f..7f74080897aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java @@ -37,7 +37,7 @@ * an unsaved transient entity, and the foreign key points to that * unsaved transient entity. * - * These references must be resolved beforeQuery an insert action can be + * These references must be resolved before an insert action can be * executed. * * @author Gail Badner @@ -91,7 +91,7 @@ public Iterable getDependentEntityInsertActions() { * Throws {@link org.hibernate.PropertyValueException} if there are any unresolved * entity insert actions that depend on non-nullable associations with * a transient entity. This method should be called on completion of - * an operation (afterQuery all cascades are completed) that saves an entity. + * an operation (after all cascades are completed) that saves an entity. * * @throws org.hibernate.PropertyValueException if there are any unresolved entity * insert actions; {@link org.hibernate.PropertyValueException#getEntityName()} @@ -117,7 +117,7 @@ public void checkNoUnresolvedActionsAfterOperation() throws PropertyValueExcepti nonNullableTransientDependencies.getNonNullableTransientPropertyPaths( firstTransientDependency ).iterator().next(); throw new TransientPropertyValueException( - "Not-null property references a transient value - transient instance must be saved beforeQuery current operation", + "Not-null property references a transient value - transient instance must be saved before current operation", firstDependentAction.getSession().guessEntityName( firstTransientDependency ), firstDependentAction.getEntityName(), firstPropertyPath @@ -203,7 +203,7 @@ public Set resolveDependentActions(Object managedEnt final Set resolvedActions = new IdentitySet( ); if ( traceEnabled ) { LOG.tracev( - "Unresolved inserts beforeQuery resolving [{0}]: [{1}]", + "Unresolved inserts before resolving [{0}]: [{1}]", MessageHelper.infoString( entityEntry.getEntityName(), entityEntry.getId() ), toString() ); @@ -233,7 +233,7 @@ public Set resolveDependentActions(Object managedEnt } if ( traceEnabled ) { LOG.tracev( - "Unresolved inserts afterQuery resolving [{0}]: [{1}]", + "Unresolved inserts after resolving [{0}]: [{1}]", MessageHelper.infoString( entityEntry.getEntityName(), entityEntry.getId() ), toString() ); diff --git a/hibernate-core/src/main/java/org/hibernate/action/spi/AfterTransactionCompletionProcess.java b/hibernate-core/src/main/java/org/hibernate/action/spi/AfterTransactionCompletionProcess.java index 7d0b9c78a35a..02d3a58a76b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/spi/AfterTransactionCompletionProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/action/spi/AfterTransactionCompletionProcess.java @@ -9,13 +9,13 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; /** - * Contract representing some process that needs to occur during afterQuery transaction completion. + * Contract representing some process that needs to occur during after transaction completion. * * @author Steve Ebersole */ public interface AfterTransactionCompletionProcess { /** - * Perform whatever processing is encapsulated here afterQuery completion of the transaction. + * Perform whatever processing is encapsulated here after completion of the transaction. * * @param success Did the transaction complete successfully? True means it did. * @param session The session on which the transaction is completing. diff --git a/hibernate-core/src/main/java/org/hibernate/action/spi/BeforeTransactionCompletionProcess.java b/hibernate-core/src/main/java/org/hibernate/action/spi/BeforeTransactionCompletionProcess.java index f8e479c408b2..6e7d5f564a40 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/spi/BeforeTransactionCompletionProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/action/spi/BeforeTransactionCompletionProcess.java @@ -9,13 +9,13 @@ import org.hibernate.engine.spi.SessionImplementor; /** - * Contract representing some process that needs to occur during beforeQuery transaction completion. + * Contract representing some process that needs to occur during before transaction completion. * * @author Steve Ebersole */ public interface BeforeTransactionCompletionProcess { /** - * Perform whatever processing is encapsulated here beforeQuery completion of the transaction. + * Perform whatever processing is encapsulated here before completion of the transaction. * * @param session The session on which the transaction is preparing to complete. */ diff --git a/hibernate-core/src/main/java/org/hibernate/action/spi/Executable.java b/hibernate-core/src/main/java/org/hibernate/action/spi/Executable.java index 2ccaec616e96..a0a8180d43ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/spi/Executable.java +++ b/hibernate-core/src/main/java/org/hibernate/action/spi/Executable.java @@ -27,7 +27,7 @@ public interface Executable { Serializable[] getPropertySpaces(); /** - * Called beforeQuery executing any actions. Gives actions a chance to perform any preparation. + * Called before executing any actions. Gives actions a chance to perform any preparation. * * @throws HibernateException Indicates a problem during preparation. */ @@ -41,23 +41,23 @@ public interface Executable { void execute() throws HibernateException; /** - * Get the afterQuery-transaction-completion process, if any, for this action. + * Get the after-transaction-completion process, if any, for this action. * - * @return The afterQuery-transaction-completion process, or null if we have no - * afterQuery-transaction-completion process + * @return The after-transaction-completion process, or null if we have no + * after-transaction-completion process */ AfterTransactionCompletionProcess getAfterTransactionCompletionProcess(); /** - * Get the beforeQuery-transaction-completion process, if any, for this action. + * Get the before-transaction-completion process, if any, for this action. * - * @return The beforeQuery-transaction-completion process, or null if we have no - * beforeQuery-transaction-completion process + * @return The before-transaction-completion process, or null if we have no + * before-transaction-completion process */ BeforeTransactionCompletionProcess getBeforeTransactionCompletionProcess(); /** - * Reconnect to session afterQuery deserialization + * Reconnect to session after deserialization * * @param session The session being deserialized */ diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java b/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java index 9b5405f1de92..b780103ee073 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java @@ -6,10 +6,14 @@ */ package org.hibernate.annotations; -import org.hibernate.tuple.CreationTimestampGeneration; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.CreationTimestampGeneration; /** * Marks a property as the creation timestamp of the containing entity. The property value will be set to the current @@ -38,5 +42,6 @@ */ @ValueGenerationType(generatedBy = CreationTimestampGeneration.class) @Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) public @interface CreationTimestamp { } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/DynamicUpdate.java b/hibernate-core/src/main/java/org/hibernate/annotations/DynamicUpdate.java index 054d1b1f9325..88865a99ca06 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/DynamicUpdate.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/DynamicUpdate.java @@ -16,7 +16,7 @@ * For updating, should this entity use dynamic sql generation where only changed columns get referenced in the * prepared sql statement? *

    - * Note, for re-attachment of detached entities this is not possible without select-beforeQuery-update being enabled. + * Note, for re-attachment of detached entities this is not possible without select-before-update being enabled. * * @author Steve Ebersole * diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Entity.java b/hibernate-core/src/main/java/org/hibernate/annotations/Entity.java index cb879cce282e..900eee730995 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Entity.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Entity.java @@ -43,7 +43,7 @@ @Deprecated boolean dynamicUpdate() default false; /** - * Do a select to retrieve the entity beforeQuery any potential update. + * Do a select to retrieve the entity before any potential update. * @deprecated Use {@link SelectBeforeUpdate} instead */ @Deprecated diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java b/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java index f1eb98e54765..6fb65dd5c942 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java @@ -26,7 +26,7 @@ @Repeatable(NamedNativeQueries.class) public @interface NamedNativeQuery { /** - * The name. It is a named query afterQuery all :) + * The name. It is a named query after all :) */ String name(); @@ -89,4 +89,11 @@ * Whether the results should be read-only. Default is {@code false}. */ boolean readOnly() default false; + + /** + * The query spaces to apply for the query. + * + * @see org.hibernate.SynchronizeableQuery + */ + String[] querySpaces() default {}; } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java b/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java index 4032b8829214..e7ceb14f8956 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java @@ -137,4 +137,17 @@ private QueryHints() { */ public static final String PASS_DISTINCT_THROUGH = "hibernate.query.passDistinctThrough"; + /** + * Hint for specifying query spaces to be applied to a native (SQL) query. + * + * Passed value can be any of:

      + *
    • List of the spaces
    • + *
    • array of the spaces
    • + *
    • String "whitespace"-separated list of the spaces
    • + *
    + * + * @see org.hibernate.SynchronizeableQuery + */ + public static final String NATIVE_SPACES = "org.hibernate.query.native.spaces"; + } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/SelectBeforeUpdate.java b/hibernate-core/src/main/java/org/hibernate/annotations/SelectBeforeUpdate.java index 32168a0144c9..65d26dfcc681 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/SelectBeforeUpdate.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/SelectBeforeUpdate.java @@ -23,8 +23,8 @@ public @interface SelectBeforeUpdate { /** * {@code true} (which is the default when this annotation is present) indicates that - * {@code select-beforeQuery-update} processing should occur. {@code false} indicates - * {@code select-beforeQuery-update} processing should not occur. + * {@code select-before-update} processing should occur. {@code false} indicates + * {@code select-before-update} processing should not occur. */ boolean value() default true; } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java b/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java index b437645f7e01..5e700d472a51 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java @@ -14,7 +14,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** - * Collection sort (in-memory sorting). Different that ordering, which is applied during the SQL select. + * Collection sort (in-memory sorting). Different than ordering, which is applied during the SQL select. * * @author Emmanuel Bernard * diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java b/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java index c1635886da95..7e442f9e1c14 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java @@ -6,10 +6,14 @@ */ package org.hibernate.annotations; -import org.hibernate.tuple.UpdateTimestampGeneration; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.UpdateTimestampGeneration; /** * Marks a property as the update timestamp of the containing entity. The property value will be set to the current VM @@ -38,5 +42,6 @@ */ @ValueGenerationType(generatedBy = UpdateTimestampGeneration.class) @Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) public @interface UpdateTimestamp { } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/AttributeConverterInfo.java b/hibernate-core/src/main/java/org/hibernate/boot/AttributeConverterInfo.java new file mode 100644 index 000000000000..2a0dddb16420 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/AttributeConverterInfo.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.boot; + +import javax.persistence.AttributeConverter; + +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.spi.MetadataBuildingContext; + +/** + * Delayed information about an AttributeConverter. Delayed until we have + * access to {@link org.hibernate.boot.internal.ClassmateContext} during + * the MetadataSources -> Metadata process. + * + * @author Steve Ebersole + */ +public interface AttributeConverterInfo { + Class getConverterClass(); + ConverterDescriptor toConverterDescriptor(MetadataBuildingContext context); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/MetadataBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/MetadataBuilder.java index a5630ea0efe9..111c6ad8047e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/MetadataBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/MetadataBuilder.java @@ -94,20 +94,6 @@ public interface MetadataBuilder { */ MetadataBuilder applyPhysicalNamingStrategy(PhysicalNamingStrategy namingStrategy); - /** - * Defines the Hibernate Commons Annotations ReflectionManager to use - * - * @param reflectionManager The ReflectionManager to use. - * - * @return {@code this}, for method chaining - * - * @deprecated Deprecated (with no replacement) to indicate that this will go away as - * we migrate away from Hibernate Commons Annotations to Jandex for annotation handling - * and XMl->annotation merging. - */ - @Deprecated - MetadataBuilder applyReflectionManager(ReflectionManager reflectionManager); - /** * Specify the second-level cache mode to be used. This is the cache mode in terms of whether or * not to cache. @@ -401,7 +387,18 @@ public interface MetadataBuilder { * @param definition The definition * * @return {@code this} for method chaining + * + * @deprecated (since 5.3) AttributeConverterDefinition forces early + * access to the AttributeConverter instance which precludes the + * possibility to resolve the converter from CDI, etc. Instead use + * one of: + * + * * {@link #applyAttributeConverter(Class)} + * * {@link #applyAttributeConverter(Class, boolean)} + * * {@link #applyAttributeConverter(AttributeConverter)} + * * {@link #applyAttributeConverter(AttributeConverter, boolean)} */ + @Deprecated MetadataBuilder applyAttributeConverter(AttributeConverterDefinition definition); /** @@ -410,8 +407,6 @@ public interface MetadataBuilder { * @param attributeConverterClass The AttributeConverter class. * * @return {@code this} for method chaining - * - * @see org.hibernate.cfg.AttributeConverterDefinition#from(Class) */ MetadataBuilder applyAttributeConverter(Class attributeConverterClass); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java b/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java index 8e8bdea370fd..fcac04652df9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java @@ -39,7 +39,6 @@ import org.hibernate.boot.spi.XmlMappingBinderAccess; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.StringHelper; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.SerializationException; import org.w3c.dom.Document; @@ -60,10 +59,10 @@ public class MetadataSources implements Serializable { private XmlMappingBinderAccess xmlMappingBinderAccess; - private List xmlBindings = new ArrayList(); - private LinkedHashSet> annotatedClasses = new LinkedHashSet>(); - private LinkedHashSet annotatedClassNames = new LinkedHashSet(); - private LinkedHashSet annotatedPackages = new LinkedHashSet(); + private List xmlBindings = new ArrayList<>(); + private LinkedHashSet> annotatedClasses = new LinkedHashSet<>(); + private LinkedHashSet annotatedClassNames = new LinkedHashSet<>(); + private LinkedHashSet annotatedPackages = new LinkedHashSet<>(); public MetadataSources() { this( new BootstrapServiceRegistryBuilder().build() ); @@ -130,7 +129,9 @@ public MetadataBuilder getMetadataBuilder() { * Get a builder for metadata where non-default options can be specified. * * @return The built metadata. + * @deprecated Use {@link #getMetadataBuilder()} instead */ + @Deprecated public MetadataBuilder getMetadataBuilder(StandardServiceRegistry serviceRegistry) { MetadataBuilderImpl defaultBuilder = new MetadataBuilderImpl( this, serviceRegistry ); return getCustomBuilderOrDefault( defaultBuilder ); @@ -151,7 +152,7 @@ private MetadataBuilder getCustomBuilderOrDefault(MetadataBuilderImpl defaultBui final MetadataBuilder returnedBuilder = discoveredBuilderFactory.getMetadataBuilder( this, defaultBuilder ); if ( returnedBuilder != null ) { if ( activeFactoryNames == null ) { - activeFactoryNames = new ArrayList(); + activeFactoryNames = new ArrayList<>(); } activeFactoryNames.add( discoveredBuilderFactory.getClass().getName() ); builder = returnedBuilder; @@ -161,7 +162,7 @@ private MetadataBuilder getCustomBuilderOrDefault(MetadataBuilderImpl defaultBui if ( activeFactoryNames != null && activeFactoryNames.size() > 1 ) { throw new HibernateException( "Multiple active MetadataBuilder definitions were discovered : " + - StringHelper.join( ", ", activeFactoryNames ) + String.join(", ", activeFactoryNames) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java index 4a112466f98a..bdd2f22282b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java @@ -7,6 +7,7 @@ package org.hibernate.boot; import java.util.Map; +import java.util.function.Supplier; import org.hibernate.ConnectionReleaseMode; import org.hibernate.CustomEntityDirtinessStrategy; @@ -17,10 +18,11 @@ import org.hibernate.NullPrecedence; import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; -import org.hibernate.cache.spi.QueryCacheFactory; +import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; @@ -36,6 +38,7 @@ * * @since 5.0 */ +@SuppressWarnings("UnusedReturnValue") public interface SessionFactoryBuilder { /** * Apply a Bean Validation ValidatorFactory to the SessionFactory being built. @@ -139,6 +142,19 @@ public interface SessionFactoryBuilder { */ SessionFactoryBuilder applyStatelessInterceptor(Class statelessInterceptorClass); + /** + * Names a {@link Supplier} instance which is used to retrieve the interceptor to be applied to the SessionFactory, + * which in turn means it will be used by all Sessions unless one is explicitly specified in + * {@link org.hibernate.SessionBuilder#interceptor} + * + * @param statelessInterceptorSupplier {@link Supplier} instance which is used to retrieve the interceptor + * + * @return {@code this}, for method chaining + * + * @see org.hibernate.cfg.AvailableSettings#SESSION_SCOPED_INTERCEPTOR + */ + SessionFactoryBuilder applyStatelessInterceptor(Supplier statelessInterceptorSupplier); + /** * Names a StatementInspector to be applied to the SessionFactory, which in turn means it will be used by all * Sessions unless one is explicitly specified in {@link org.hibernate.SessionBuilder#statementInspector} @@ -287,6 +303,14 @@ SessionFactoryBuilder applyEntityTuplizer( */ SessionFactoryBuilder applyBatchFetchStyle(BatchFetchStyle style); + /** + * Should entity Loaders be generated immediately? Or should the creation + * be delayed until first need? + * + * @see org.hibernate.cfg.AvailableSettings#DELAY_ENTITY_LOADER_CREATIONS + */ + SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay); + /** * Allows specifying a default batch-fetch size for all entities and collections * which do not otherwise specify a batch-fetch size. @@ -426,7 +450,10 @@ SessionFactoryBuilder applyEntityTuplizer( * @return {@code this}, for method chaining * * @see org.hibernate.cfg.AvailableSettings#JPAQL_STRICT_COMPLIANCE + * + * @deprecated Use {@link #enableJpaQueryCompliance} instead */ + @Deprecated SessionFactoryBuilder applyStrictJpaQueryLanguageCompliance(boolean enabled); /** @@ -473,7 +500,7 @@ SessionFactoryBuilder applyEntityTuplizer( * * @see org.hibernate.cfg.AvailableSettings#QUERY_CACHE_FACTORY */ - SessionFactoryBuilder applyQueryCacheFactory(QueryCacheFactory factory); + SessionFactoryBuilder applyTimestampsCacheFactory(TimestampsCacheFactory factory); /** * Apply a prefix to prepended to all cache region names for this SessionFactory. @@ -663,6 +690,13 @@ SessionFactoryBuilder applyEntityTuplizer( @Deprecated SessionFactoryBuilder applyConnectionReleaseMode(ConnectionReleaseMode connectionReleaseMode); + /** + * @see org.hibernate.cfg.AvailableSettings#CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT + */ + default SessionFactoryBuilder applyConnectionProviderDisablesAutoCommit(boolean providerDisablesAutoCommit) { + return this; + } + /** * Should Hibernate apply comments to SQL it generates? * @@ -692,11 +726,33 @@ SessionFactoryBuilder applyEntityTuplizer( /** * Should resources held by {@link javax.persistence.EntityManager} instance be released immediately on close? *

    - * The other option is to release them as part of an afterQuery-transaction callback. + * The other option is to release them as part of an after-transaction callback. * */ SessionFactoryBuilder enableReleaseResourcesOnCloseEnabled(boolean enable); + + /** + * @see JpaCompliance#isJpaQueryComplianceEnabled() + */ + SessionFactoryBuilder enableJpaQueryCompliance(boolean enabled); + + /** + * @see JpaCompliance#isJpaTransactionComplianceEnabled() + */ + SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled); + + /** + * @see JpaCompliance#isJpaListComplianceEnabled() + */ + SessionFactoryBuilder enableJpaListCompliance(boolean enabled); + + /** + * @see JpaCompliance#isJpaClosedComplianceEnabled() + */ + SessionFactoryBuilder enableJpaClosedCompliance(boolean enabled); + + /** * Allows unwrapping this builder as another, more specific type. * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ExplodedArchiveDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ExplodedArchiveDescriptor.java index ba1ec3beb182..61dfe9d3c4bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ExplodedArchiveDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ExplodedArchiveDescriptor.java @@ -145,8 +145,7 @@ public InputStreamAccess getStreamAccess() { } private void processZippedRoot(File rootFile, ArchiveContext context) { - try { - final JarFile jarFile = new JarFile(rootFile); + try (final JarFile jarFile = new JarFile(rootFile)){ final Enumeration entries = jarFile.entries(); while ( entries.hasMoreElements() ) { final ZipEntry zipEntry = entries.nextElement(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/JarFileBasedArchiveDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/JarFileBasedArchiveDescriptor.java index 8ba2f98d54a4..807d922c99af 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/JarFileBasedArchiveDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/JarFileBasedArchiveDescriptor.java @@ -53,97 +53,106 @@ public void visitArchive(ArchiveContext context) { return; } - final Enumeration zipEntries = jarFile.entries(); - while ( zipEntries.hasMoreElements() ) { - final ZipEntry zipEntry = zipEntries.nextElement(); - final String entryName = extractName( zipEntry ); + try { + final Enumeration zipEntries = jarFile.entries(); + while ( zipEntries.hasMoreElements() ) { + final ZipEntry zipEntry = zipEntries.nextElement(); + final String entryName = extractName( zipEntry ); - if ( getEntryBasePrefix() != null && ! entryName.startsWith( getEntryBasePrefix() ) ) { - continue; - } - if ( zipEntry.isDirectory() ) { - continue; - } + if ( getEntryBasePrefix() != null && ! entryName.startsWith( getEntryBasePrefix() ) ) { + continue; + } + if ( zipEntry.isDirectory() ) { + continue; + } - if ( entryName.equals( getEntryBasePrefix() ) ) { - // exact match, might be a nested jar entry (ie from jar:file:..../foo.ear!/bar.jar) - // - // This algorithm assumes that the zipped file is only the URL root (including entry), not - // just any random entry - try (InputStream is = new BufferedInputStream( jarFile.getInputStream( zipEntry ) )) { - final JarInputStream jarInputStream = new JarInputStream( is ); - ZipEntry subZipEntry = jarInputStream.getNextEntry(); - while ( subZipEntry != null ) { - if ( ! subZipEntry.isDirectory() ) { - - final String name = extractName( subZipEntry ); - final String relativeName = extractRelativeName( subZipEntry ); - final InputStreamAccess inputStreamAccess = buildByteBasedInputStreamAccess( name, jarInputStream ); - - final ArchiveEntry entry = new ArchiveEntry() { - @Override - public String getName() { - return name; - } - - @Override - public String getNameWithinArchive() { - return relativeName; - } - - @Override - public InputStreamAccess getStreamAccess() { - return inputStreamAccess; - } - }; - - final ArchiveEntryHandler entryHandler = context.obtainArchiveEntryHandler( entry ); - entryHandler.handleEntry( entry, context ); + if ( entryName.equals( getEntryBasePrefix() ) ) { + // exact match, might be a nested jar entry (ie from jar:file:..../foo.ear!/bar.jar) + // + // This algorithm assumes that the zipped file is only the URL root (including entry), not + // just any random entry + try ( final InputStream is = new BufferedInputStream( jarFile.getInputStream( zipEntry ) ); + final JarInputStream jarInputStream = new JarInputStream( is )) { + ZipEntry subZipEntry = jarInputStream.getNextEntry(); + while ( subZipEntry != null ) { + if ( ! subZipEntry.isDirectory() ) { + + final String name = extractName( subZipEntry ); + final String relativeName = extractRelativeName( subZipEntry ); + final InputStreamAccess inputStreamAccess = buildByteBasedInputStreamAccess( name, jarInputStream ); + + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return name; + } + + @Override + public String getNameWithinArchive() { + return relativeName; + } + + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + + final ArchiveEntryHandler entryHandler = context.obtainArchiveEntryHandler( entry ); + entryHandler.handleEntry( entry, context ); + } + + subZipEntry = jarInputStream.getNextEntry(); } - - subZipEntry = jarInputStream.getNextEntry(); + } + catch (Exception e) { + throw new ArchiveException( "Error accessing JarFile entry [" + zipEntry.getName() + "]", e ); } } - catch (Exception e) { - throw new ArchiveException( "Error accessing JarFile entry [" + zipEntry.getName() + "]", e ); - } - } - else { - final String name = extractName( zipEntry ); - final String relativeName = extractRelativeName( zipEntry ); - final InputStreamAccess inputStreamAccess; - try (InputStream is = jarFile.getInputStream( zipEntry )) { - inputStreamAccess = buildByteBasedInputStreamAccess( name, is ); - } - catch (IOException e) { - throw new ArchiveException( - String.format( - "Unable to access stream from jar file [%s] for entry [%s]", - jarFile.getName(), - zipEntry.getName() - ) - ); - } - - final ArchiveEntry entry = new ArchiveEntry() { - @Override - public String getName() { - return name; + else { + final String name = extractName( zipEntry ); + final String relativeName = extractRelativeName( zipEntry ); + final InputStreamAccess inputStreamAccess; + try (InputStream is = jarFile.getInputStream( zipEntry )) { + inputStreamAccess = buildByteBasedInputStreamAccess( name, is ); } - - @Override - public String getNameWithinArchive() { - return relativeName; + catch (IOException e) { + throw new ArchiveException( + String.format( + "Unable to access stream from jar file [%s] for entry [%s]", + jarFile.getName(), + zipEntry.getName() + ) + ); } - @Override - public InputStreamAccess getStreamAccess() { - return inputStreamAccess; - } - }; + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return name; + } + + @Override + public String getNameWithinArchive() { + return relativeName; + } - final ArchiveEntryHandler entryHandler = context.obtainArchiveEntryHandler( entry ); - entryHandler.handleEntry( entry, context ); + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + + final ArchiveEntryHandler entryHandler = context.obtainArchiveEntryHandler( entry ); + entryHandler.handleEntry( entry, context ); + } + } + } + finally { + try { + jarFile.close(); + } + catch ( Exception ignore ) { } } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/internal/DisabledScanner.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/internal/DisabledScanner.java new file mode 100755 index 000000000000..cb8626c32f7c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/internal/DisabledScanner.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 . + */ +package org.hibernate.boot.archive.scan.internal; + +import java.util.Collections; + +import org.hibernate.boot.archive.scan.spi.ClassDescriptor; +import org.hibernate.boot.archive.scan.spi.MappingFileDescriptor; +import org.hibernate.boot.archive.scan.spi.PackageDescriptor; +import org.hibernate.boot.archive.scan.spi.ScanEnvironment; +import org.hibernate.boot.archive.scan.spi.ScanOptions; +import org.hibernate.boot.archive.scan.spi.ScanParameters; +import org.hibernate.boot.archive.scan.spi.ScanResult; +import org.hibernate.boot.archive.scan.spi.Scanner; + +/** + * Implementation of Scanner that does nothing. Used for optimizing startup + * time when metadata scanning is not needed. + * + * @author Petteri Pitkanen + */ +public class DisabledScanner implements Scanner { + private static final ScanResult emptyScanResult = new ScanResultImpl( + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet() + ); + + @Override + public ScanResult scan(final ScanEnvironment environment, final ScanOptions options, final ScanParameters parameters) { + return emptyScanResult; + } +} 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 735ce56ef9ed..1cc54c140a5d 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,7 +61,7 @@ private ClassFile toClassFile(ArchiveEntry entry) { return new ClassFile( dataInputStream ); } catch (IOException e) { - throw new ArchiveException( "Could not build ClassFile" ); + throw new ArchiveException( "Could not build ClassFile", e ); } finally { try { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java index f2e4f2f696b2..156f334d09ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java @@ -47,7 +47,7 @@ public class LoadedConfig { private List mappingReferences; private Map> eventListenerMap; - private LoadedConfig(String sessionFactoryName) { + public LoadedConfig(String sessionFactoryName) { this.sessionFactoryName = sessionFactoryName; } @@ -259,7 +259,7 @@ public void merge(LoadedConfig incoming) { } @SuppressWarnings("unchecked") - private void addConfigurationValues(Map configurationValues) { + protected void addConfigurationValues(Map configurationValues) { if ( configurationValues == null ) { return; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterDescriptorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterDescriptorImpl.java deleted file mode 100644 index f5a5f12e4da2..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterDescriptorImpl.java +++ /dev/null @@ -1,239 +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.boot.internal; - -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import javax.persistence.AttributeConverter; - -import org.hibernate.AnnotationException; -import org.hibernate.HibernateException; -import org.hibernate.annotations.common.reflection.ReflectionManager; -import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.boot.spi.AttributeConverterDescriptor; -import org.hibernate.boot.spi.MetadataBuildingContext; -import org.hibernate.cfg.AttributeConverterDefinition; -import org.hibernate.cfg.annotations.HCANNHelper; - -import com.fasterxml.classmate.ResolvedType; -import com.fasterxml.classmate.ResolvedTypeWithMembers; -import com.fasterxml.classmate.members.ResolvedField; -import com.fasterxml.classmate.members.ResolvedMember; -import com.fasterxml.classmate.members.ResolvedMethod; - -/** - * The standard AttributeConverterDescriptor implementation - * - * @author Steve Ebersole - */ -public class AttributeConverterDescriptorImpl implements AttributeConverterDescriptor { - private final AttributeConverter attributeConverter; - private final boolean autoApply; - private final ResolvedType domainType; - private final ResolvedType jdbcType; - - - public static AttributeConverterDescriptor create( - AttributeConverterDefinition definition, - ClassmateContext classmateContext) { - final AttributeConverter converter = definition.getAttributeConverter(); - final Class converterClass = converter.getClass(); - - final ResolvedType converterType = classmateContext.getTypeResolver().resolve( converterClass ); - final List converterParamTypes = converterType.typeParametersFor( AttributeConverter.class ); - if ( converterParamTypes == null ) { - throw new AnnotationException( - "Could not extract type parameter information from AttributeConverter implementation [" - + converterClass.getName() + "]" - ); - } - else if ( converterParamTypes.size() != 2 ) { - throw new AnnotationException( - "Unexpected type parameter information for AttributeConverter implementation [" + - converterClass.getName() + "]; expected 2 parameter types, but found " + converterParamTypes.size() - ); - } - - return new AttributeConverterDescriptorImpl( - converter, - definition.isAutoApply(), - converterParamTypes.get( 0 ), - converterParamTypes.get( 1 ) - ); - } - - private AttributeConverterDescriptorImpl( - AttributeConverter attributeConverter, - boolean autoApply, - ResolvedType domainType, - ResolvedType jdbcType) { - this.attributeConverter = attributeConverter; - this.autoApply = autoApply; - this.domainType = domainType; - this.jdbcType = jdbcType; - } - - @Override - public AttributeConverter getAttributeConverter() { - return attributeConverter; - } - - @Override - public Class getDomainType() { - return domainType.getErasedType(); - } - - @Override - public Class getJdbcType() { - return jdbcType.getErasedType(); - } - - @Override - @SuppressWarnings("SimplifiableIfStatement") - public boolean shouldAutoApplyToAttribute(XProperty xProperty, MetadataBuildingContext context) { - if ( !autoApply ) { - return false; - } - - final ResolvedType attributeType = resolveAttributeType( xProperty, context ); - return typesMatch( domainType, attributeType ); - } - - private ResolvedType resolveAttributeType(XProperty xProperty, MetadataBuildingContext context) { - return resolveMember( xProperty, context ).getType(); - } - - private ResolvedMember resolveMember(XProperty xProperty, MetadataBuildingContext buildingContext) { - final ClassmateContext classmateContext = buildingContext.getMetadataCollector().getClassmateContext(); - final ReflectionManager reflectionManager = buildingContext.getBuildingOptions().getReflectionManager(); - - final ResolvedType declaringClassType = classmateContext.getTypeResolver().resolve( - reflectionManager.toClass( xProperty.getDeclaringClass() ) - ); - final ResolvedTypeWithMembers declaringClassWithMembers = classmateContext.getMemberResolver().resolve( - declaringClassType, - null, - null - ); - - final Member member = toMember( xProperty ); - if ( member instanceof Method ) { - for ( ResolvedMethod resolvedMember : declaringClassWithMembers.getMemberMethods() ) { - if ( resolvedMember.getName().equals( member.getName() ) ) { - return resolvedMember; - } - } - } - else if ( member instanceof Field ) { - for ( ResolvedField resolvedMember : declaringClassWithMembers.getMemberFields() ) { - if ( resolvedMember.getName().equals( member.getName() ) ) { - return resolvedMember; - } - } - } - else { - throw new HibernateException( "Unexpected java.lang.reflect.Member type from org.hibernate.annotations.common.reflection.java.JavaXMember : " + member ); - } - - throw new HibernateException( - "Could not locate resolved type information for attribute [" + member.getName() + "] from Classmate" - ); - } - - - private static Member toMember(XProperty xProperty) { - try { - return HCANNHelper.getUnderlyingMember( xProperty ); - } - catch (Exception e) { - throw new HibernateException( - "Could not resolve member signature from XProperty reference", - e - ); - } - } - - private boolean typesMatch(ResolvedType converterDefinedType, ResolvedType checkType) { - if ( !converterDefinedType.getErasedType().isAssignableFrom( checkType.getErasedType() ) ) { - return false; - } - - // if the converter did not define any nested type parameters, then the check above is - // enough for a match - if ( converterDefinedType.getTypeParameters().isEmpty() ) { - return true; - } - - // however, here the converter *did* define nested type parameters, so we'd have a converter defined using something like, e.g., List for its - // domain type. - // - // we need to check those nested types as well - - if ( checkType.getTypeParameters().isEmpty() ) { - // the domain type did not define nested type params. a List would not auto-match a List() - return false; - } - - if ( converterDefinedType.getTypeParameters().size() != checkType.getTypeParameters().size() ) { - // they had different number of type params somehow. - return false; - } - - for ( int i = 0; i < converterDefinedType.getTypeParameters().size(); i++ ) { - if ( !typesMatch( converterDefinedType.getTypeParameters().get( i ), checkType.getTypeParameters().get( i ) ) ) { - return false; - } - } - - return true; - } - - @Override - public boolean shouldAutoApplyToCollectionElement(XProperty xProperty, MetadataBuildingContext context) { - if ( !autoApply ) { - return false; - } - - final ResolvedMember collectionMember = resolveMember( xProperty, context ); - final ResolvedType elementType; - - if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { - elementType = collectionMember.getType().typeParametersFor( Map.class ).get( 1 ); - } - else if ( Collection.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { - elementType = collectionMember.getType().typeParametersFor( Collection.class ).get( 0 ); - } - else { - throw new HibernateException( "Attribute was neither a Collection nor a Map : " + collectionMember.getType().getErasedType() ); - } - - return typesMatch( domainType, elementType ); - } - - @Override - public boolean shouldAutoApplyToMapKey(XProperty xProperty, MetadataBuildingContext context) { - if ( !autoApply ) { - return false; - } - - final ResolvedMember collectionMember = resolveMember( xProperty, context ); - final ResolvedType keyType; - - if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { - keyType = collectionMember.getType().typeParametersFor( Map.class ).get( 0 ); - } - else { - throw new HibernateException( "Attribute was not a Map : " + collectionMember.getType().getErasedType() ); - } - - return typesMatch( domainType, keyType ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterDescriptorNonAutoApplicableImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterDescriptorNonAutoApplicableImpl.java deleted file mode 100644 index 4557fc52ce01..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterDescriptorNonAutoApplicableImpl.java +++ /dev/null @@ -1,199 +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.boot.internal; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.persistence.AttributeConverter; - -import org.hibernate.AnnotationException; -import org.hibernate.AssertionFailure; -import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.boot.spi.AttributeConverterDescriptor; -import org.hibernate.boot.spi.MetadataBuildingContext; - -/** - * Special-use AttributeConverterDescriptor implementation for cases where the converter will never - * be used for auto-apply. - * - * @author Steve Ebersole - */ -public class AttributeConverterDescriptorNonAutoApplicableImpl implements AttributeConverterDescriptor { - private final AttributeConverter converter; - - private Class domainType; - private Class jdbcType; - - public AttributeConverterDescriptorNonAutoApplicableImpl(AttributeConverter converter) { - this.converter = converter; - - final Class attributeConverterClass = converter.getClass(); - final ParameterizedType attributeConverterSignature = extractAttributeConverterParameterizedType( - attributeConverterClass - ); - if ( attributeConverterSignature == null ) { - throw new AssertionFailure( - "Could not extract ParameterizedType representation of AttributeConverter definition " + - "from AttributeConverter implementation class [" + attributeConverterClass.getName() + "]" - ); - } - - if ( attributeConverterSignature.getActualTypeArguments().length < 2 ) { - throw new AnnotationException( - "AttributeConverter [" + attributeConverterClass.getName() - + "] did not retain parameterized type information" - ); - } - - if ( attributeConverterSignature.getActualTypeArguments().length > 2 ) { - throw new AnnotationException( - "AttributeConverter [" + attributeConverterClass.getName() - + "] specified more than 2 parameterized types" - ); - } - - this.domainType = extractClass( attributeConverterSignature.getActualTypeArguments()[0] ); - if ( this.domainType == null ) { - throw new AnnotationException( - "Could not determine domain type from given AttributeConverter [" + - attributeConverterClass.getName() + "]" - ); - } - - this.jdbcType = extractClass(attributeConverterSignature.getActualTypeArguments()[1]); - if ( this.jdbcType == null ) { - throw new AnnotationException( - "Could not determine JDBC type from given AttributeConverter [" + - attributeConverterClass.getName() + "]" - ); - } - } - - private ParameterizedType extractAttributeConverterParameterizedType(Type base) { - if ( base != null ) { - Class clazz = extractClass( base ); - List types = new ArrayList(); - types.add( clazz.getGenericSuperclass() ); - types.addAll( Arrays.asList( clazz.getGenericInterfaces() ) ); - for ( Type type : types ) { - type = resolveType( type, base ); - if ( ParameterizedType.class.isInstance( type ) ) { - final ParameterizedType parameterizedType = (ParameterizedType) type; - if ( AttributeConverter.class.equals( parameterizedType.getRawType() ) ) { - return parameterizedType; - } - } - ParameterizedType parameterizedType = extractAttributeConverterParameterizedType( type ); - if ( parameterizedType != null ) { - return parameterizedType; - } - } - } - return null; - } - - private static Class extractClass(Type type) { - if ( type instanceof Class ) { - return (Class) type; - } - else if ( type instanceof ParameterizedType ) { - return extractClass( ( (ParameterizedType) type ).getRawType() ); - } - return null; - } - - private static Type resolveType(Type target, Type context) { - if ( target instanceof ParameterizedType ) { - return resolveParameterizedType( (ParameterizedType) target, context ); - } - else if ( target instanceof TypeVariable ) { - return resolveTypeVariable( (TypeVariable) target, (ParameterizedType) context ); - } - return target; - } - - private static ParameterizedType resolveParameterizedType(final ParameterizedType parameterizedType, Type context) { - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - - final Type[] resolvedTypeArguments = new Type[actualTypeArguments.length]; - for ( int idx = 0; idx < actualTypeArguments.length; idx++ ) { - resolvedTypeArguments[idx] = resolveType( actualTypeArguments[idx], context ); - } - return new ParameterizedType() { - - @Override - public Type[] getActualTypeArguments() { - return resolvedTypeArguments; - } - - @Override - public Type getRawType() { - return parameterizedType.getRawType(); - } - - @Override - public Type getOwnerType() { - return parameterizedType.getOwnerType(); - } - - }; - } - - private static Type resolveTypeVariable(TypeVariable typeVariable, ParameterizedType context) { - Class clazz = extractClass( context.getRawType() ); - TypeVariable[] typeParameters = clazz.getTypeParameters(); - for ( int idx = 0; idx < typeParameters.length; idx++ ) { - if ( typeVariable.getName().equals( typeParameters[idx].getName() ) ) { - return resolveType( context.getActualTypeArguments()[idx], context ); - } - } - return typeVariable; - } - - private static Class extractType(TypeVariable typeVariable) { - java.lang.reflect.Type[] boundTypes = typeVariable.getBounds(); - if ( boundTypes == null || boundTypes.length != 1 ) { - return null; - } - - return (Class) boundTypes[0]; - } - - @Override - public AttributeConverter getAttributeConverter() { - return converter; - } - - @Override - public Class getDomainType() { - return domainType; - } - - @Override - public Class getJdbcType() { - return jdbcType; - } - - @Override - public boolean shouldAutoApplyToAttribute(XProperty xProperty, MetadataBuildingContext context) { - return false; - } - - @Override - public boolean shouldAutoApplyToCollectionElement(XProperty xProperty, MetadataBuildingContext context) { - return false; - } - - @Override - public boolean shouldAutoApplyToMapKey(XProperty xProperty, MetadataBuildingContext context) { - return false; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterManager.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterManager.java deleted file mode 100644 index e5ca124cd1df..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/AttributeConverterManager.java +++ /dev/null @@ -1,190 +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.boot.internal; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.hibernate.AssertionFailure; -import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.boot.spi.AttributeConverterAutoApplyHandler; -import org.hibernate.boot.spi.AttributeConverterDescriptor; -import org.hibernate.boot.spi.MetadataBuildingContext; -import org.hibernate.internal.util.StringHelper; - -import org.jboss.logging.Logger; - -/** - * @author Steve Ebersole - */ -public class AttributeConverterManager implements AttributeConverterAutoApplyHandler { - private static final Logger log = Logger.getLogger( AttributeConverterManager.class ); - - private Map attributeConverterDescriptorsByClass; - - void addConverter(AttributeConverterDescriptor descriptor) { - if ( attributeConverterDescriptorsByClass == null ) { - attributeConverterDescriptorsByClass = new ConcurrentHashMap(); - } - - final Object old = attributeConverterDescriptorsByClass.put( - descriptor.getAttributeConverter().getClass(), - descriptor - ); - - if ( old != null ) { - throw new AssertionFailure( - String.format( - Locale.ENGLISH, - "AttributeConverter class [%s] registered multiple times", - descriptor.getAttributeConverter().getClass() - ) - ); - } - } - - private Collection converterDescriptors() { - if ( attributeConverterDescriptorsByClass == null ) { - return Collections.emptyList(); - } - return attributeConverterDescriptorsByClass.values(); - } - - @Override - public AttributeConverterDescriptor findAutoApplyConverterForAttribute( - XProperty xProperty, - MetadataBuildingContext context) { - List matched = new ArrayList(); - - for ( AttributeConverterDescriptor descriptor : converterDescriptors() ) { - log.debugf( - "Checking auto-apply AttributeConverter [%s] (type=%s) for match against attribute : %s.%s (type=%s)", - descriptor.toString(), - descriptor.getDomainType().getSimpleName(), - xProperty.getDeclaringClass().getName(), - xProperty.getName(), - xProperty.getType().getName() - ); - - if ( descriptor.shouldAutoApplyToAttribute( xProperty, context ) ) { - matched.add( descriptor ); - } - } - - if ( matched.isEmpty() ) { - return null; - } - - if ( matched.size() == 1 ) { - return matched.get( 0 ); - } - - // otherwise, we had multiple matches - throw new RuntimeException( - String.format( - Locale.ROOT, - "Multiple auto-apply converters matched attribute [%s.%s] : %s", - xProperty.getDeclaringClass().getName(), - xProperty.getName(), - StringHelper.join( matched, RENDERER ) - ) - ); - } - - @Override - public AttributeConverterDescriptor findAutoApplyConverterForCollectionElement( - XProperty xProperty, - MetadataBuildingContext context) { - List matched = new ArrayList(); - - for ( AttributeConverterDescriptor descriptor : converterDescriptors() ) { - log.debugf( - "Checking auto-apply AttributeConverter [%s] (type=%s) for match against collection attribute's element : %s.%s (type=%s)", - descriptor.toString(), - descriptor.getDomainType().getSimpleName(), - xProperty.getDeclaringClass().getName(), - xProperty.getName(), - xProperty.getElementClass().getName() - ); - if ( descriptor.shouldAutoApplyToCollectionElement( xProperty, context ) ) { - matched.add( descriptor ); - } - } - - if ( matched.isEmpty() ) { - return null; - } - - if ( matched.size() == 1 ) { - return matched.get( 0 ); - } - - // otherwise, we had multiple matches - throw new RuntimeException( - String.format( - Locale.ROOT, - "Multiple auto-apply converters matched attribute [%s.%s] : %s", - xProperty.getDeclaringClass().getName(), - xProperty.getName(), - StringHelper.join( matched, RENDERER ) - ) - ); - } - - @Override - public AttributeConverterDescriptor findAutoApplyConverterForMapKey( - XProperty xProperty, - MetadataBuildingContext context) { - List matched = new ArrayList(); - - for ( AttributeConverterDescriptor descriptor : converterDescriptors() ) { - log.debugf( - "Checking auto-apply AttributeConverter [%s] (type=%s) for match against map attribute's key : %s.%s (type=%s)", - descriptor.toString(), - descriptor.getDomainType().getSimpleName(), - xProperty.getDeclaringClass().getName(), - xProperty.getName(), - xProperty.getMapKey().getName() - ); - if ( descriptor.shouldAutoApplyToMapKey( xProperty, context ) ) { - matched.add( descriptor ); - } - } - - if ( matched.isEmpty() ) { - return null; - } - - if ( matched.size() == 1 ) { - return matched.get( 0 ); - } - - // otherwise, we had multiple matches - throw new RuntimeException( - String.format( - Locale.ROOT, - "Multiple auto-apply converters matched attribute [%s.%s] : %s", - xProperty.getDeclaringClass().getName(), - xProperty.getName(), - StringHelper.join( matched, RENDERER ) - ) - ); - } - - private static StringHelper.Renderer RENDERER = new StringHelper.Renderer() { - @Override - public String render(AttributeConverterDescriptor value) { - return value.getAttributeConverter().getClass().getName(); - } - }; - -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/BootstrapContextImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/BootstrapContextImpl.java new file mode 100644 index 000000000000..0004d0499582 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/BootstrapContextImpl.java @@ -0,0 +1,344 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.AssertionFailure; +import org.hibernate.annotations.common.reflection.ClassLoaderDelegate; +import org.hibernate.annotations.common.reflection.ClassLoadingException; +import org.hibernate.annotations.common.reflection.ReflectionManager; +import org.hibernate.annotations.common.reflection.java.JavaReflectionManager; +import org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl; +import org.hibernate.boot.AttributeConverterInfo; +import org.hibernate.boot.CacheRegionDefinition; +import org.hibernate.boot.archive.scan.internal.StandardScanOptions; +import org.hibernate.boot.archive.scan.spi.ScanEnvironment; +import org.hibernate.boot.archive.scan.spi.ScanOptions; +import org.hibernate.boot.archive.scan.spi.Scanner; +import org.hibernate.boot.archive.spi.ArchiveDescriptorFactory; +import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.ClassLoaderAccess; +import org.hibernate.boot.spi.MetadataBuildingOptions; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider; +import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.jpa.internal.MutableJpaComplianceImpl; +import org.hibernate.jpa.spi.MutableJpaCompliance; +import org.hibernate.type.spi.TypeConfiguration; + +import org.jboss.jandex.IndexView; +import org.jboss.logging.Logger; + +import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; + +/** + * @author Andrea Boriero + */ +public class BootstrapContextImpl implements BootstrapContext { + private static final Logger log = Logger.getLogger( BootstrapContextImpl.class ); + + private final StandardServiceRegistry serviceRegistry; + + private final MutableJpaCompliance jpaCompliance; + + private final TypeConfiguration typeConfiguration; + + private final ClassLoaderAccessImpl classLoaderAccess; + + private final JavaReflectionManager hcannReflectionManager; + private final ClassmateContext classmateContext; + private final MetadataBuildingOptions metadataBuildingOptions; + + private boolean isJpaBootstrap; + + private ScanOptions scanOptions; + private ScanEnvironment scanEnvironment; + private Object scannerSetting; + private ArchiveDescriptorFactory archiveDescriptorFactory; + + private IndexView jandexView; + + private HashMap sqlFunctionMap; + private ArrayList auxiliaryDatabaseObjectList; + private HashMap attributeConverterInfoMap; + private ArrayList cacheRegionDefinitions; + + public BootstrapContextImpl( + StandardServiceRegistry serviceRegistry, + MetadataBuildingOptions metadataBuildingOptions) { + this.serviceRegistry = serviceRegistry; + this.classmateContext = new ClassmateContext(); + this.metadataBuildingOptions = metadataBuildingOptions; + + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + this.classLoaderAccess = new ClassLoaderAccessImpl( classLoaderService ); + this.hcannReflectionManager = generateHcannReflectionManager(); + + final StrategySelector strategySelector = serviceRegistry.getService( StrategySelector.class ); + final ConfigurationService configService = serviceRegistry.getService( ConfigurationService.class ); + + this.jpaCompliance = new MutableJpaComplianceImpl( configService.getSettings(), false ); + this.scanOptions = new StandardScanOptions( + (String) configService.getSettings().get( AvailableSettings.SCANNER_DISCOVERY ), + false + ); + + // ScanEnvironment must be set explicitly + this.scannerSetting = configService.getSettings().get( AvailableSettings.SCANNER ); + if ( this.scannerSetting == null ) { + this.scannerSetting = configService.getSettings().get( AvailableSettings.SCANNER_DEPRECATED ); + if ( this.scannerSetting != null ) { + DEPRECATION_LOGGER.logDeprecatedScannerSetting(); + } + } + this.archiveDescriptorFactory = strategySelector.resolveStrategy( + ArchiveDescriptorFactory.class, + configService.getSettings().get( AvailableSettings.SCANNER_ARCHIVE_INTERPRETER ) + ); + this.typeConfiguration = new TypeConfiguration(); + } + + @Override + public StandardServiceRegistry getServiceRegistry() { + return serviceRegistry; + } + + @Override + public MutableJpaCompliance getJpaCompliance() { + return jpaCompliance; + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return typeConfiguration; + } + + @Override + public MetadataBuildingOptions getMetadataBuildingOptions() { + return metadataBuildingOptions; + } + + @Override + public boolean isJpaBootstrap() { + return isJpaBootstrap; + } + + @Override + public void markAsJpaBootstrap() { + isJpaBootstrap = true; + } + + @Override + public ClassLoader getJpaTempClassLoader() { + return classLoaderAccess.getJpaTempClassLoader(); + } + + @Override + public ClassLoaderAccess getClassLoaderAccess() { + return classLoaderAccess; + } + + @Override + public ClassmateContext getClassmateContext() { + return classmateContext; + } + + @Override + public ArchiveDescriptorFactory getArchiveDescriptorFactory() { + return archiveDescriptorFactory; + } + + @Override + public ScanOptions getScanOptions() { + return scanOptions; + } + + @Override + public ScanEnvironment getScanEnvironment() { + return scanEnvironment; + } + + @Override + public Object getScanner() { + return scannerSetting; + } + + @Override + public ReflectionManager getReflectionManager() { + return hcannReflectionManager; + } + + @Override + public IndexView getJandexView() { + return jandexView; + } + + @Override + public Map getSqlFunctions() { + return sqlFunctionMap == null ? Collections.emptyMap() : sqlFunctionMap; + } + + @Override + public Collection getAuxiliaryDatabaseObjectList() { + return auxiliaryDatabaseObjectList == null ? Collections.emptyList() : auxiliaryDatabaseObjectList; + } + + @Override + public Collection getAttributeConverters() { + return attributeConverterInfoMap != null + ? new ArrayList<>( attributeConverterInfoMap.values() ) + : Collections.emptyList(); + } + + @Override + public Collection getCacheRegionDefinitions() { + return cacheRegionDefinitions == null ? Collections.emptyList() : cacheRegionDefinitions; + } + + @Override + public void release() { + classmateContext.release(); + classLoaderAccess.release(); + + scanOptions = null; + scanEnvironment = null; + scannerSetting = null; + archiveDescriptorFactory = null; + jandexView = null; + + if ( sqlFunctionMap != null ) { + sqlFunctionMap.clear(); + } + + if ( auxiliaryDatabaseObjectList != null ) { + auxiliaryDatabaseObjectList.clear(); + } + + if ( attributeConverterInfoMap != null ) { + attributeConverterInfoMap.clear(); + } + + if ( cacheRegionDefinitions != null ) { + cacheRegionDefinitions.clear(); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Mutations + + + public void addAttributeConverterInfo(AttributeConverterInfo info) { + if ( this.attributeConverterInfoMap == null ) { + this.attributeConverterInfoMap = new HashMap<>(); + } + + final Object old = this.attributeConverterInfoMap.put( info.getConverterClass(), info ); + + if ( old != null ) { + throw new AssertionFailure( + String.format( + "AttributeConverter class [%s] registered multiple times", + info.getConverterClass() + ) + ); + } + } + + void injectJpaTempClassLoader(ClassLoader jpaTempClassLoader) { + log.debugf( "Injecting JPA temp ClassLoader [%s] into BootstrapContext; was [%s]", jpaTempClassLoader, this.getJpaTempClassLoader() ); + this.classLoaderAccess.injectTempClassLoader( jpaTempClassLoader ); + } + + void injectScanOptions(ScanOptions scanOptions) { + log.debugf( "Injecting ScanOptions [%s] into BootstrapContext; was [%s]", scanOptions, this.scanOptions ); + this.scanOptions = scanOptions; + } + + void injectScanEnvironment(ScanEnvironment scanEnvironment) { + log.debugf( "Injecting ScanEnvironment [%s] into BootstrapContext; was [%s]", scanEnvironment, this.scanEnvironment ); + this.scanEnvironment = scanEnvironment; + } + + void injectScanner(Scanner scanner) { + log.debugf( "Injecting Scanner [%s] into BootstrapContext; was [%s]", scanner, this.scannerSetting ); + this.scannerSetting = scanner; + } + + void injectArchiveDescriptorFactory(ArchiveDescriptorFactory factory) { + log.debugf( "Injecting ArchiveDescriptorFactory [%s] into BootstrapContext; was [%s]", factory, this.archiveDescriptorFactory ); + this.archiveDescriptorFactory = factory; + } + + void injectJandexView(IndexView jandexView) { + log.debugf( "Injecting Jandex IndexView [%s] into BootstrapContext; was [%s]", jandexView, this.jandexView ); + this.jandexView = jandexView; + } + + public void addSqlFunction(String functionName, SQLFunction function) { + if ( this.sqlFunctionMap == null ) { + this.sqlFunctionMap = new HashMap<>(); + } + this.sqlFunctionMap.put( functionName, function ); + } + + public void addAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabaseObject) { + if ( this.auxiliaryDatabaseObjectList == null ) { + this.auxiliaryDatabaseObjectList = new ArrayList<>(); + } + this.auxiliaryDatabaseObjectList.add( auxiliaryDatabaseObject ); + } + + + public void addCacheRegionDefinition(CacheRegionDefinition cacheRegionDefinition) { + if ( cacheRegionDefinitions == null ) { + cacheRegionDefinitions = new ArrayList<>(); + } + cacheRegionDefinitions.add( cacheRegionDefinition ); + } + + private JavaReflectionManager generateHcannReflectionManager() { + final JavaReflectionManager reflectionManager = new JavaReflectionManager(); + reflectionManager.setMetadataProvider( new JPAMetadataProvider( this ) ); + reflectionManager.injectClassLoaderDelegate( generateHcannClassLoaderDelegate() ); + return reflectionManager; + } + + private ClassLoaderDelegate generateHcannClassLoaderDelegate() { + // class loading here needs to be drastically different for 7.0 + // but luckily 7.0 will do away with HCANN use and be easier to + // implement this. + // + // todo (6.0) : *if possible* make similar change in 6.0 + // possibly using the JPA temp class loader or create our own "throw awy" ClassLoader; + // the trouble there is that we eventually need to load the Class into the real + // ClassLoader prior to use + + final ClassLoaderService classLoaderService = getServiceRegistry().getService( ClassLoaderService.class ); + + return new ClassLoaderDelegate() { + @Override + public Class classForName(String className) throws ClassLoadingException { + try { + return classLoaderService.classForName( className ); + } + catch (org.hibernate.boot.registry.classloading.spi.ClassLoadingException e) { + return StandardClassLoaderDelegateImpl.INSTANCE.classForName( className ); + } + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/ClassLoaderAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/ClassLoaderAccessImpl.java index 031402a7211c..6797a0298c03 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/ClassLoaderAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/ClassLoaderAccessImpl.java @@ -23,8 +23,8 @@ public class ClassLoaderAccessImpl implements ClassLoaderAccess { private static final Logger log = Logger.getLogger( ClassLoaderAccessImpl.class ); - private final ClassLoader jpaTempClassLoader; private final ClassLoaderService classLoaderService; + private ClassLoader jpaTempClassLoader; public ClassLoaderAccessImpl( ClassLoader jpaTempClassLoader, @@ -41,6 +41,11 @@ public ClassLoaderAccessImpl(ClassLoaderService classLoaderService) { this( null, classLoaderService ); } + public void injectTempClassLoader(ClassLoader jpaTempClassLoader) { + log.debugf( "ClassLoaderAccessImpl#injectTempClassLoader(%s) [was %s]", jpaTempClassLoader, this.jpaTempClassLoader ); + this.jpaTempClassLoader = jpaTempClassLoader; + } + @Override @SuppressWarnings("unchecked") public Class classForName(String name) { @@ -84,8 +89,15 @@ private boolean isSafeClass(String name) { } + public ClassLoader getJpaTempClassLoader() { + return jpaTempClassLoader; + } + @Override public URL locateResource(String resourceName) { return classLoaderService.locateResource( resourceName ); } + + public void release() { + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/IdGeneratorInterpreterImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/IdGeneratorInterpreterImpl.java index 4aa37308dd59..2646e7c0e855 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/IdGeneratorInterpreterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/IdGeneratorInterpreterImpl.java @@ -16,6 +16,7 @@ import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.BinderHelper; +import org.hibernate.id.IncrementGenerator; import org.hibernate.id.MultipleHiLoPerTableGenerator; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.id.SequenceHiLoGenerator; @@ -109,13 +110,17 @@ public String determineGeneratorName(GenerationType generationType, GeneratorNam } default: { // AUTO + + if ( "increment".equalsIgnoreCase( context.getGeneratedValueGeneratorName() ) ) { + return IncrementGenerator.class.getName(); + } + final Class javaType = context.getIdType(); if ( UUID.class.isAssignableFrom( javaType ) ) { return UUIDGenerator.class.getName(); } - else { - return "native"; - } + + return "native"; } } } @@ -217,13 +222,17 @@ public String determineGeneratorName(GenerationType generationType, GeneratorNam } default: { // AUTO + + if ( "increment".equalsIgnoreCase( context.getGeneratedValueGeneratorName() ) ) { + return IncrementGenerator.class.getName(); + } + final Class javaType = context.getIdType(); if ( UUID.class.isAssignableFrom( javaType ) ) { return UUIDGenerator.class.getName(); } - else { - return org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName(); - } + + return org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName(); } } } 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 7668b7ef5b7e..815eaa380855 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 @@ -36,6 +36,10 @@ import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; +import org.hibernate.boot.model.convert.internal.AttributeConverterManager; +import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ImplicitForeignKeyNameSource; import org.hibernate.boot.model.naming.ImplicitIndexNameSource; @@ -44,18 +48,22 @@ 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; import org.hibernate.boot.model.source.spi.LocalMetadataBuildingContext; -import org.hibernate.boot.spi.AttributeConverterAutoApplyHandler; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.NaturalIdUniqueKeyBinder; +import org.hibernate.cache.cfg.internal.DomainDataRegionConfigImpl.Builder; +import org.hibernate.cache.spi.access.AccessType; import org.hibernate.cfg.AnnotatedClassType; -import org.hibernate.cfg.AttributeConverterDefinition; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.CopyIdentifierComponentSecondPass; import org.hibernate.cfg.CreateKeySecondPass; import org.hibernate.cfg.FkSecondPass; +import org.hibernate.cfg.IdGeneratorResolverSecondPass; import org.hibernate.cfg.JPAIndexHolder; import org.hibernate.cfg.PkDrivenByDefaultMapsIdSecondPass; import org.hibernate.cfg.PropertyData; @@ -97,7 +105,9 @@ import org.hibernate.mapping.Table; import org.hibernate.mapping.UniqueKey; import org.hibernate.query.spi.NamedQueryRepository; +import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; +import org.hibernate.type.spi.TypeConfiguration; /** * The implementation of the in-flight Metadata collector contract. @@ -111,42 +121,43 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector { private static final CoreMessageLogger log = CoreLogging.messageLogger( InFlightMetadataCollectorImpl.class ); + private final BootstrapContext bootstrapContext; private final MetadataBuildingOptions options; - private final TypeResolver typeResolver; private final AttributeConverterManager attributeConverterManager = new AttributeConverterManager(); - private final ClassmateContext classmateContext = new ClassmateContext(); private final UUID uuid; private final MutableIdentifierGeneratorFactory identifierGeneratorFactory; - private final Map entityBindingMap = new HashMap(); - private final Map collectionBindingMap = new HashMap(); + private final Map entityBindingMap = new HashMap<>(); + private final Map collectionBindingMap = new HashMap<>(); - private final Map typeDefinitionMap = new HashMap(); - private final Map filterDefinitionMap = new HashMap(); - private final Map imports = new HashMap(); + private final Map typeDefinitionMap = new HashMap<>(); + private final Map filterDefinitionMap = new HashMap<>(); + private final Map imports = new HashMap<>(); private Database database; - private final Map namedQueryMap = new HashMap(); - private final Map namedNativeQueryMap = new HashMap(); - private final Map namedProcedureCallMap = new HashMap(); - private final Map sqlResultSetMappingMap = new HashMap(); + private final Map namedQueryMap = new HashMap<>(); + private final Map namedNativeQueryMap = new HashMap<>(); + private final Map namedProcedureCallMap = new HashMap<>(); + private final Map sqlResultSetMappingMap = new HashMap<>(); - private final Map namedEntityGraphMap = new HashMap(); - private final Map fetchProfileMap = new HashMap(); - private final Map idGeneratorDefinitionMap = new HashMap(); + private final Map namedEntityGraphMap = new HashMap<>(); + private final Map fetchProfileMap = new HashMap<>(); + private final Map idGeneratorDefinitionMap = new HashMap<>(); + + private final Map regionConfigBuilders = new ConcurrentHashMap<>(); private Map sqlFunctionMap; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // All the annotation-processing-specific state :( - private final Set defaultIdentifierGeneratorNames = new HashSet(); - private final Set defaultNamedQueryNames = new HashSet(); - private final Set defaultNamedNativeQueryNames = new HashSet(); - private final Set defaultSqlResultSetMappingNames = new HashSet(); - private final Set defaultNamedProcedureNames = new HashSet(); + private final Set defaultIdentifierGeneratorNames = new HashSet<>(); + private final Set defaultNamedQueryNames = new HashSet<>(); + private final Set defaultNamedNativeQueryNames = new HashSet<>(); + private final Set defaultSqlResultSetMappingNames = new HashSet<>(); + private final Set defaultNamedProcedureNames = new HashSet<>(); private Map anyMetaDefs; private Map mappedSuperClasses; private Map> propertiesAnnotatedWithMapsId; @@ -158,27 +169,25 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector private Map> jpaIndexHoldersByTable; public InFlightMetadataCollectorImpl( - MetadataBuildingOptions options, - TypeResolver typeResolver) { + BootstrapContext bootstrapContext, + MetadataBuildingOptions options) { + this.bootstrapContext = bootstrapContext; this.uuid = UUID.randomUUID(); this.options = options; - this.typeResolver = typeResolver; - this.identifierGeneratorFactory = options.getServiceRegistry().getService( MutableIdentifierGeneratorFactory.class ); + this.identifierGeneratorFactory = options.getServiceRegistry() + .getService( MutableIdentifierGeneratorFactory.class ); - for ( Map.Entry sqlFunctionEntry : options.getSqlFunctions().entrySet() ) { + for ( Map.Entry sqlFunctionEntry : bootstrapContext.getSqlFunctions().entrySet() ) { if ( sqlFunctionMap == null ) { // we need this to be a ConcurrentHashMap for the one we ultimately pass along to the SF // but is this the reference that gets passed along? - sqlFunctionMap = new ConcurrentHashMap( 16, .75f, 1 ); + sqlFunctionMap = new ConcurrentHashMap<>( 16, .75f, 1 ); } sqlFunctionMap.put( sqlFunctionEntry.getKey(), sqlFunctionEntry.getValue() ); } - for ( AuxiliaryDatabaseObject auxiliaryDatabaseObject : options.getAuxiliaryDatabaseObjectList() ) { - getDatabase().addAuxiliaryDatabaseObject( auxiliaryDatabaseObject ); - } - + bootstrapContext.getAuxiliaryDatabaseObjectList().forEach( getDatabase()::addAuxiliaryDatabaseObject ); } @Override @@ -192,8 +201,25 @@ public MetadataBuildingOptions getMetadataBuildingOptions() { } @Override + public BootstrapContext getBootstrapContext() { + return bootstrapContext; + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return bootstrapContext.getTypeConfiguration(); + } + + /** + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated public TypeResolver getTypeResolver() { - return typeResolver; + return bootstrapContext.getTypeConfiguration().getTypeResolver(); } @Override @@ -222,7 +248,7 @@ public void validate() throws MappingException { @Override public Set getMappedSuperclassMappingsCopy() { - return new HashSet( mappedSuperClasses.values() ); + return new HashSet<>( mappedSuperClasses.values() ); } @Override @@ -271,8 +297,28 @@ public void addEntityBinding(PersistentClass persistentClass) throws DuplicateMa throw new DuplicateMappingException( DuplicateMappingException.Type.ENTITY, entityName ); } entityBindingMap.put( entityName, persistentClass ); + + final AccessType accessType = AccessType.fromExternalName( persistentClass.getCacheConcurrencyStrategy() ); + if ( accessType != null ) { + if ( persistentClass.isCached() ) { + locateCacheRegionConfigBuilder( persistentClass.getRootClass().getCacheRegionName() ).addEntityConfig( + persistentClass, + accessType + ); + } + + if ( persistentClass.hasNaturalId() && persistentClass instanceof RootClass && persistentClass.getNaturalIdCacheRegionName() != null ) { + locateCacheRegionConfigBuilder( persistentClass.getNaturalIdCacheRegionName() ).addNaturalIdConfig( + (RootClass) persistentClass, + accessType + ); + } + } } + private Builder locateCacheRegionConfigBuilder(String regionName) { + return regionConfigBuilders.computeIfAbsent( regionName, Builder::new ); + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Collection handling @@ -294,6 +340,14 @@ public void addCollectionBinding(Collection collection) throws DuplicateMappingE throw new DuplicateMappingException( DuplicateMappingException.Type.COLLECTION, collectionRole ); } collectionBindingMap.put( collectionRole, collection ); + + final AccessType accessType = AccessType.fromExternalName( collection.getCacheConcurrencyStrategy() ); + if ( accessType != null ) { + locateCacheRegionConfigBuilder( collection.getCacheRegionName() ).addCollectionConfig( + collection, + accessType + ); + } } @@ -337,7 +391,7 @@ private void addTypeDefinition(String registrationKey, TypeDefinition typeDefini @Override public ClassmateContext getClassmateContext() { - return classmateContext; + return bootstrapContext.getClassmateContext(); } @@ -345,22 +399,19 @@ public ClassmateContext getClassmateContext() { // attribute converters @Override - public void addAttributeConverter(AttributeConverterDefinition definition) { + public void addAttributeConverter(Class converterClass) { attributeConverterManager.addConverter( - AttributeConverterDescriptorImpl.create( - definition, - classmateContext - ) + new ClassBasedConverterDescriptor( converterClass, getBootstrapContext().getClassmateContext() ) ); } @Override - public void addAttributeConverter(Class converterClass) { - addAttributeConverter( AttributeConverterDefinition.from( converterClass ) ); + public void addAttributeConverter(ConverterDescriptor descriptor) { + attributeConverterManager.addConverter( descriptor ); } @Override - public AttributeConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() { + public ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() { return attributeConverterManager; } @@ -425,7 +476,7 @@ public IdentifierGeneratorDefinition getIdentifierGenerator(String name) { @Override public java.util.Collection collectTableMappings() { - ArrayList
    tables = new ArrayList
    (); + ArrayList
    tables = new ArrayList<>(); for ( Namespace namespace : getDatabase().getNamespaces() ) { tables.addAll( namespace.getTables() ); } @@ -441,10 +492,14 @@ public void addIdentifierGenerator(IdentifierGeneratorDefinition generator) { if ( defaultIdentifierGeneratorNames.contains( generator.getName() ) ) { return; } - final IdentifierGeneratorDefinition old = idGeneratorDefinitionMap.put( generator.getName(), generator ); - if ( old != null ) { - log.duplicateGeneratorName( old.getName() ); + if ( old != null && !old.equals( generator ) ) { + if ( bootstrapContext.getJpaCompliance().isGlobalGeneratorScopeEnabled() ) { + throw new IllegalArgumentException( "Duplicate generator name " + old.getName() + " you will likely want to set the property " + AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE + " to false " ); + } + else { + log.duplicateGeneratorName( old.getName() ); + } } } @@ -798,8 +853,8 @@ public org.hibernate.type.Type getReferencedPropertyType(String entityName, Stri } - private Map logicalToPhysicalTableNameMap = new HashMap(); - private Map physicalToLogicalTableNameMap = new HashMap(); + private Map logicalToPhysicalTableNameMap = new HashMap<>(); + private Map physicalToLogicalTableNameMap = new HashMap<>(); @Override public void addTableNameBinding(Identifier logicalName, Table table) { @@ -843,8 +898,8 @@ public String getPhysicalTableName(String logicalName) { */ private class TableColumnNameBinding implements Serializable { private final String tableName; - private Map logicalToPhysical = new HashMap(); - private Map physicalToLogical = new HashMap(); + private Map logicalToPhysical = new HashMap<>(); + private Map physicalToLogical = new HashMap<>(); private TableColumnNameBinding(String tableName) { this.tableName = tableName; @@ -913,7 +968,7 @@ public void addColumnNameBinding(Table table, Identifier logicalName, Column col TableColumnNameBinding binding = null; if ( columnNameBindingByTableMap == null ) { - columnNameBindingByTableMap = new HashMap(); + columnNameBindingByTableMap = new HashMap<>(); } else { binding = columnNameBindingByTableMap.get( table ); @@ -1009,7 +1064,7 @@ public void addAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabase getDatabase().addAuxiliaryDatabaseObject( auxiliaryDatabaseObject ); } - private final Map annotatedClassTypeMap = new HashMap(); + private final Map annotatedClassTypeMap = new HashMap<>(); @Override public AnnotatedClassType getClassType(XClass clazz) { @@ -1044,7 +1099,7 @@ else if ( clazz.isAnnotationPresent( javax.persistence.MappedSuperclass.class ) @Override public void addAnyMetaDef(AnyMetaDef defAnn) { if ( anyMetaDefs == null ) { - anyMetaDefs = new HashMap(); + anyMetaDefs = new HashMap<>(); } else { if ( anyMetaDefs.containsKey( defAnn.name() ) ) { @@ -1067,7 +1122,7 @@ public AnyMetaDef getAnyMetaDef(String name) { @Override public void addMappedSuperclass(Class type, MappedSuperclass mappedSuperclass) { if ( mappedSuperClasses == null ) { - mappedSuperClasses = new HashMap(); + mappedSuperClasses = new HashMap<>(); } mappedSuperClasses.put( type, mappedSuperclass ); } @@ -1093,12 +1148,12 @@ public PropertyData getPropertyAnnotatedWithMapsId(XClass entityType, String pro @Override public void addPropertyAnnotatedWithMapsId(XClass entityType, PropertyData property) { if ( propertiesAnnotatedWithMapsId == null ) { - propertiesAnnotatedWithMapsId = new HashMap>(); + propertiesAnnotatedWithMapsId = new HashMap<>(); } Map map = propertiesAnnotatedWithMapsId.get( entityType ); if ( map == null ) { - map = new HashMap(); + map = new HashMap<>(); propertiesAnnotatedWithMapsId.put( entityType, map ); } map.put( property.getProperty().getAnnotation( MapsId.class ).value(), property ); @@ -1107,12 +1162,12 @@ public void addPropertyAnnotatedWithMapsId(XClass entityType, PropertyData prope @Override public void addPropertyAnnotatedWithMapsIdSpecj(XClass entityType, PropertyData property, String mapsIdValue) { if ( propertiesAnnotatedWithMapsId == null ) { - propertiesAnnotatedWithMapsId = new HashMap>(); + propertiesAnnotatedWithMapsId = new HashMap<>(); } Map map = propertiesAnnotatedWithMapsId.get( entityType ); if ( map == null ) { - map = new HashMap(); + map = new HashMap<>(); propertiesAnnotatedWithMapsId.put( entityType, map ); } map.put( mapsIdValue, property ); @@ -1131,12 +1186,12 @@ public PropertyData getPropertyAnnotatedWithIdAndToOne(XClass entityType, String @Override public void addToOneAndIdProperty(XClass entityType, PropertyData property) { if ( propertiesAnnotatedWithIdAndToOne == null ) { - propertiesAnnotatedWithIdAndToOne = new HashMap>(); + propertiesAnnotatedWithIdAndToOne = new HashMap<>(); } Map map = propertiesAnnotatedWithIdAndToOne.get( entityType ); if ( map == null ) { - map = new HashMap(); + map = new HashMap<>(); propertiesAnnotatedWithIdAndToOne.put( entityType, map ); } map.put( property.getPropertyName(), property ); @@ -1145,7 +1200,7 @@ public void addToOneAndIdProperty(XClass entityType, PropertyData property) { @Override public void addMappedBy(String entityName, String propertyName, String inversePropertyName) { if ( mappedByResolver == null ) { - mappedByResolver = new HashMap(); + mappedByResolver = new HashMap<>(); } mappedByResolver.put( entityName + "." + propertyName, inversePropertyName ); } @@ -1161,7 +1216,7 @@ public String getFromMappedBy(String entityName, String propertyName) { @Override public void addPropertyReferencedAssociation(String entityName, String propertyName, String propertyRef) { if ( propertyRefResolver == null ) { - propertyRefResolver = new HashMap(); + propertyRefResolver = new HashMap<>(); } propertyRefResolver.put( entityName + "." + propertyName, propertyRef ); } @@ -1209,7 +1264,7 @@ public void addPropertyReference(String referencedClass, String propertyName) { @Override public void addDelayedPropertyReferenceHandler(DelayedPropertyReferenceHandler handler) { if ( delayedPropertyReferenceHandlers == null ) { - delayedPropertyReferenceHandlers = new HashSet(); + delayedPropertyReferenceHandlers = new HashSet<>(); } delayedPropertyReferenceHandlers.add( handler ); } @@ -1224,7 +1279,7 @@ public void addUniquePropertyReference(String referencedClass, String propertyNa @Override @SuppressWarnings({ "unchecked" }) public void addUniqueConstraints(Table table, List uniqueConstraints) { - List constraintHolders = new ArrayList( + List constraintHolders = new ArrayList<>( CollectionHelper.determineProperSizing( uniqueConstraints.size() ) ); @@ -1250,14 +1305,14 @@ public void addUniqueConstraintHolders(Table table, List List holderList = null; if ( uniqueConstraintHoldersByTable == null ) { - uniqueConstraintHoldersByTable = new HashMap>(); + uniqueConstraintHoldersByTable = new HashMap<>(); } else { holderList = uniqueConstraintHoldersByTable.get( table ); } if ( holderList == null ) { - holderList = new ArrayList(); + holderList = new ArrayList<>(); uniqueConstraintHoldersByTable.put( table, holderList ); } @@ -1269,21 +1324,21 @@ public void addJpaIndexHolders(Table table, List holders) { List holderList = null; if ( jpaIndexHoldersByTable == null ) { - jpaIndexHoldersByTable = new HashMap>(); + jpaIndexHoldersByTable = new HashMap<>(); } else { holderList = jpaIndexHoldersByTable.get( table ); } if ( holderList == null ) { - holderList = new ArrayList(); + holderList = new ArrayList<>(); jpaIndexHoldersByTable.put( table, holderList ); } holderList.addAll( holders ); } - private final Map entityTableXrefMap = new HashMap(); + private final Map entityTableXrefMap = new HashMap<>(); @Override public EntityTableXref getEntityTableXref(String entityName) { @@ -1345,7 +1400,7 @@ public void addSecondaryTable(LocalMetadataBuildingContext buildingContext, Iden if ( secondaryTableJoinMap == null ) { //secondaryTableJoinMap = new HashMap(); //secondaryTableJoinMap.put( logicalName, secondaryTableJoin ); - secondaryTableJoinMap = new HashMap(); + secondaryTableJoinMap = new HashMap<>(); secondaryTableJoinMap.put( logicalName.getCanonicalName(), secondaryTableJoin ); } else { @@ -1366,8 +1421,17 @@ public void addSecondaryTable(LocalMetadataBuildingContext buildingContext, Iden } @Override - public void addSecondaryTable(Identifier logicalName, Join secondaryTableJoin) { - if ( Identifier.areEqual( primaryTableLogicalName, logicalName ) ) { + public void addSecondaryTable(QualifiedTableName logicalQualifiedTableName, Join secondaryTableJoin) { + Identifier logicalName = logicalQualifiedTableName.getTableName(); + if ( Identifier.areEqual( + Identifier.toIdentifier( + new QualifiedTableName( + Identifier.toIdentifier( primaryTable.getCatalog() ), + Identifier.toIdentifier( primaryTable.getSchema() ), + primaryTableLogicalName + ).render() + ), + Identifier.toIdentifier( logicalQualifiedTableName.render() ) ) ) { throw new DuplicateSecondaryTableException( logicalName ); } @@ -1375,7 +1439,7 @@ public void addSecondaryTable(Identifier logicalName, Join secondaryTableJoin) { if ( secondaryTableJoinMap == null ) { //secondaryTableJoinMap = new HashMap(); //secondaryTableJoinMap.put( logicalName, secondaryTableJoin ); - secondaryTableJoinMap = new HashMap(); + secondaryTableJoinMap = new HashMap<>(); secondaryTableJoinMap.put( logicalName.getCanonicalName(), secondaryTableJoin ); } else { @@ -1442,6 +1506,7 @@ public Join locateJoin(Identifier tableName) { } } + private ArrayList idGeneratorResolverSecondPassList; private ArrayList pkDrivenByDefaultMapsIdSecondPassList; private ArrayList setSimpleValueTypeSecondPassList; private ArrayList copyIdentifierComponentSecondPasList; @@ -1460,7 +1525,10 @@ public void addSecondPass(SecondPass secondPass) { @Override public void addSecondPass(SecondPass secondPass, boolean onTopOfTheQueue) { - if ( secondPass instanceof PkDrivenByDefaultMapsIdSecondPass ) { + if ( secondPass instanceof IdGeneratorResolverSecondPass ) { + addIdGeneratorResolverSecondPass( (IdGeneratorResolverSecondPass) secondPass, onTopOfTheQueue ); + } + else if ( secondPass instanceof PkDrivenByDefaultMapsIdSecondPass ) { addPkDrivenByDefaultMapsIdSecondPass( (PkDrivenByDefaultMapsIdSecondPass) secondPass, onTopOfTheQueue ); } else if ( secondPass instanceof SetSimpleValueTypeSecondPass ) { @@ -1487,7 +1555,7 @@ else if ( secondPass instanceof ImplicitColumnNamingSecondPass ) { else { // add to the general SecondPass list if ( generalSecondPassList == null ) { - generalSecondPassList = new ArrayList(); + generalSecondPassList = new ArrayList<>(); } addSecondPass( secondPass, generalSecondPassList, onTopOfTheQueue ); } @@ -1497,7 +1565,7 @@ private void addPkDrivenByDefaultMapsIdSecondPass( PkDrivenByDefaultMapsIdSecondPass secondPass, boolean onTopOfTheQueue) { if ( pkDrivenByDefaultMapsIdSecondPassList == null ) { - pkDrivenByDefaultMapsIdSecondPassList = new ArrayList(); + pkDrivenByDefaultMapsIdSecondPassList = new ArrayList<>(); } addSecondPass( secondPass, pkDrivenByDefaultMapsIdSecondPassList, onTopOfTheQueue ); } @@ -1513,51 +1581,58 @@ private void addSecondPass(T secondPass, ArrayList sec private void addSetSimpleValueTypeSecondPass(SetSimpleValueTypeSecondPass secondPass, boolean onTopOfTheQueue) { if ( setSimpleValueTypeSecondPassList == null ) { - setSimpleValueTypeSecondPassList = new ArrayList(); + setSimpleValueTypeSecondPassList = new ArrayList<>(); } addSecondPass( secondPass, setSimpleValueTypeSecondPassList, onTopOfTheQueue ); } + private void addIdGeneratorResolverSecondPass(IdGeneratorResolverSecondPass secondPass, boolean onTopOfTheQueue) { + if ( idGeneratorResolverSecondPassList == null ) { + idGeneratorResolverSecondPassList = new ArrayList<>(); + } + addSecondPass( secondPass, idGeneratorResolverSecondPassList, onTopOfTheQueue ); + } + private void addCopyIdentifierComponentSecondPass( CopyIdentifierComponentSecondPass secondPass, boolean onTopOfTheQueue) { if ( copyIdentifierComponentSecondPasList == null ) { - copyIdentifierComponentSecondPasList = new ArrayList(); + copyIdentifierComponentSecondPasList = new ArrayList<>(); } addSecondPass( secondPass, copyIdentifierComponentSecondPasList, onTopOfTheQueue ); } private void addFkSecondPass(FkSecondPass secondPass, boolean onTopOfTheQueue) { if ( fkSecondPassList == null ) { - fkSecondPassList = new ArrayList(); + fkSecondPassList = new ArrayList<>(); } addSecondPass( secondPass, fkSecondPassList, onTopOfTheQueue ); } private void addCreateKeySecondPass(CreateKeySecondPass secondPass, boolean onTopOfTheQueue) { if ( createKeySecondPasList == null ) { - createKeySecondPasList = new ArrayList(); + createKeySecondPasList = new ArrayList<>(); } addSecondPass( secondPass, createKeySecondPasList, onTopOfTheQueue ); } private void addSecondaryTableSecondPass(SecondaryTableSecondPass secondPass, boolean onTopOfTheQueue) { if ( secondaryTableSecondPassList == null ) { - secondaryTableSecondPassList = new ArrayList(); + secondaryTableSecondPassList = new ArrayList<>(); } addSecondPass( secondPass, secondaryTableSecondPassList, onTopOfTheQueue ); } private void addQuerySecondPass(QuerySecondPass secondPass, boolean onTopOfTheQueue) { if ( querySecondPassList == null ) { - querySecondPassList = new ArrayList(); + querySecondPassList = new ArrayList<>(); } addSecondPass( secondPass, querySecondPassList, onTopOfTheQueue ); } private void addImplicitColumnNamingSecondPass(ImplicitColumnNamingSecondPass secondPass) { if ( implicitColumnNamingSecondPassList == null ) { - implicitColumnNamingSecondPassList = new ArrayList(); + implicitColumnNamingSecondPassList = new ArrayList<>(); } implicitColumnNamingSecondPassList.add( secondPass ); } @@ -1567,14 +1642,14 @@ private void addImplicitColumnNamingSecondPass(ImplicitColumnNamingSecondPass se /** - * Ugh! But we need this done beforeQuery we ask Envers to produce its entities. + * Ugh! But we need this done before we ask Envers to produce its entities. */ public void processSecondPasses(MetadataBuildingContext buildingContext) { inSecondPass = true; try { + processSecondPasses( idGeneratorResolverSecondPassList ); processSecondPasses( implicitColumnNamingSecondPassList ); - processSecondPasses( pkDrivenByDefaultMapsIdSecondPassList ); processSecondPasses( setSimpleValueTypeSecondPassList ); @@ -1627,8 +1702,8 @@ private void processSecondPasses(ArrayList secondPasses) { private void sortCopyIdentifierComponentSecondPasses() { ArrayList sorted = - new ArrayList( copyIdentifierComponentSecondPasList.size() ); - Set toSort = new HashSet(); + new ArrayList<>( copyIdentifierComponentSecondPasList.size() ); + Set toSort = new HashSet<>(); toSort.addAll( copyIdentifierComponentSecondPasList ); topologicalSort( sorted, toSort ); copyIdentifierComponentSecondPasList = sorted; @@ -1665,15 +1740,15 @@ private void processFkSecondPassesInOrder() { // split FkSecondPass instances into primary key and non primary key FKs. // While doing so build a map of class names to FkSecondPass instances depending on this class. - Map> isADependencyOf = new HashMap>(); - List endOfQueueFkSecondPasses = new ArrayList( fkSecondPassList.size() ); + Map> isADependencyOf = new HashMap<>(); + List endOfQueueFkSecondPasses = new ArrayList<>( fkSecondPassList.size() ); for ( FkSecondPass sp : fkSecondPassList ) { if ( sp.isInPrimaryKey() ) { final String referenceEntityName = sp.getReferencedEntityName(); final PersistentClass classMapping = getEntityBinding( referenceEntityName ); final String dependentTable = classMapping.getTable().getQualifiedTableName().render(); if ( !isADependencyOf.containsKey( dependentTable ) ) { - isADependencyOf.put( dependentTable, new HashSet() ); + isADependencyOf.put( dependentTable, new HashSet<>() ); } isADependencyOf.get( dependentTable ).add( sp ); } @@ -1683,7 +1758,7 @@ private void processFkSecondPassesInOrder() { } // using the isADependencyOf map we order the FkSecondPass recursively instances into the right order for processing - List orderedFkSecondPasses = new ArrayList( fkSecondPassList.size() ); + List orderedFkSecondPasses = new ArrayList<>( fkSecondPassList.size() ); for ( String tableName : isADependencyOf.keySet() ) { buildRecursiveOrderedFkSecondPasses( orderedFkSecondPasses, isADependencyOf, tableName, tableName ); } @@ -1746,7 +1821,7 @@ private void processEndOfQueue(List endOfQueueFkSecondPasses) { boolean stopProcess = false; RuntimeException originalException = null; while ( !stopProcess ) { - List failingSecondPasses = new ArrayList(); + List failingSecondPasses = new ArrayList<>(); for ( FkSecondPass pass : endOfQueueFkSecondPasses ) { try { pass.doSecondPass( getEntityBindingMap() ); @@ -1768,7 +1843,7 @@ private void processEndOfQueue(List endOfQueueFkSecondPasses) { private void secondPassCompileForeignKeys(MetadataBuildingContext buildingContext) { int uniqueInteger = 0; - Set done = new HashSet(); + Set done = new HashSet<>(); for ( Table table : collectTableMappings() ) { table.setUniqueInteger( uniqueInteger++ ); secondPassCompileForeignKeys( table, done, buildingContext ); @@ -1811,61 +1886,53 @@ protected void secondPassCompileForeignKeys( fk.setReferencedTable( referencedClass.getTable() ); - // todo : should we apply a physical naming too? - if ( fk.getName() == null ) { - final Identifier nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy().determineForeignKeyName( - new ImplicitForeignKeyNameSource() { - final List columnNames = extractColumnNames( fk.getColumns() ); - List referencedColumnNames = null; - - @Override - public Identifier getTableName() { - return table.getNameIdentifier(); - } - - @Override - public List getColumnNames() { - return columnNames; - } - - @Override - public Identifier getReferencedTableName() { - return fk.getReferencedTable().getNameIdentifier(); - } - - @Override - public List getReferencedColumnNames() { - if ( referencedColumnNames == null ) { - referencedColumnNames = extractColumnNames( fk.getReferencedColumns() ); - } - return referencedColumnNames; - } - - @Override - public MetadataBuildingContext getBuildingContext() { - return buildingContext; - } - } - ); + Identifier nameIdentifier; - fk.setName( nameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ) ); - } + ImplicitForeignKeyNameSource foreignKeyNameSource = new ImplicitForeignKeyNameSource() { + final List columnNames = extractColumnNames( fk.getColumns() ); + List referencedColumnNames = null; - fk.alignColumns(); - } - } - } + @Override + public Identifier getTableName() { + return table.getNameIdentifier(); + } - private List toIdentifiers(List names) { - if ( names == null || names.isEmpty() ) { - return Collections.emptyList(); - } + @Override + public List getColumnNames() { + return columnNames; + } - final List columnNames = CollectionHelper.arrayList( names.size() ); - for ( String name : names ) { - columnNames.add( getDatabase().toIdentifier( name ) ); + @Override + public Identifier getReferencedTableName() { + return fk.getReferencedTable().getNameIdentifier(); + } + + @Override + public List getReferencedColumnNames() { + if ( referencedColumnNames == null ) { + referencedColumnNames = extractColumnNames( fk.getReferencedColumns() ); + } + return referencedColumnNames; + } + + @Override + public Identifier getUserProvidedIdentifier() { + return fk.getName() != null ? Identifier.toIdentifier( fk.getName() ) : null; + } + + @Override + public MetadataBuildingContext getBuildingContext() { + return buildingContext; + } + }; + + nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy().determineForeignKeyName(foreignKeyNameSource); + + fk.setName( nameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ) ); + + fk.alignColumns(); + } } - return columnNames; } private List toIdentifiers(String[] names) { @@ -1940,8 +2007,8 @@ private void buildUniqueKeyFromColumnNames( final MetadataBuildingContext buildingContext) { int size = columnNames.length; Column[] columns = new Column[size]; - Set unbound = new HashSet(); - Set unboundNoLogical = new HashSet(); + Set unbound = new HashSet<>(); + Set unboundNoLogical = new HashSet<>(); for ( int index = 0; index < size; index++ ) { final String logicalColumnName = columnNames[index]; try { @@ -1959,34 +2026,39 @@ private void buildUniqueKeyFromColumnNames( } } + final String originalKeyName = keyName; + if ( unique ) { - if ( StringHelper.isEmpty( keyName ) ) { - final Identifier keyNameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy().determineUniqueKeyName( - new ImplicitUniqueKeyNameSource() { - @Override - public MetadataBuildingContext getBuildingContext() { - return buildingContext; - } - - @Override - public Identifier getTableName() { - return table.getNameIdentifier(); - } - - private List columnNameIdentifiers; - - @Override - public List getColumnNames() { - // be lazy about building these - if ( columnNameIdentifiers == null ) { - columnNameIdentifiers = toIdentifiers( columnNames ); - } - return columnNameIdentifiers; - } + final Identifier keyNameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy().determineUniqueKeyName( + new ImplicitUniqueKeyNameSource() { + @Override + public MetadataBuildingContext getBuildingContext() { + return buildingContext; + } + + @Override + public Identifier getTableName() { + return table.getNameIdentifier(); + } + + private List columnNameIdentifiers; + + @Override + public List getColumnNames() { + // be lazy about building these + if ( columnNameIdentifiers == null ) { + columnNameIdentifiers = toIdentifiers( columnNames ); } - ); - keyName = keyNameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ); - } + return columnNameIdentifiers; + } + + @Override + public Identifier getUserProvidedIdentifier() { + return originalKeyName != null ? Identifier.toIdentifier( originalKeyName ) : null; + } + } + ); + keyName = keyNameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ); UniqueKey uk = table.getOrCreateUniqueKey( keyName ); for ( int i = 0; i < columns.length; i++ ) { @@ -1999,33 +2071,36 @@ public List getColumnNames() { } } else { - if ( StringHelper.isEmpty( keyName ) ) { - final Identifier keyNameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy().determineIndexName( - new ImplicitIndexNameSource() { - @Override - public MetadataBuildingContext getBuildingContext() { - return buildingContext; - } - - @Override - public Identifier getTableName() { - return table.getNameIdentifier(); - } - - private List columnNameIdentifiers; - - @Override - public List getColumnNames() { - // be lazy about building these - if ( columnNameIdentifiers == null ) { - columnNameIdentifiers = toIdentifiers( columnNames ); - } - return columnNameIdentifiers; - } + final Identifier keyNameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy().determineIndexName( + new ImplicitIndexNameSource() { + @Override + public MetadataBuildingContext getBuildingContext() { + return buildingContext; + } + + @Override + public Identifier getTableName() { + return table.getNameIdentifier(); + } + + private List columnNameIdentifiers; + + @Override + public List getColumnNames() { + // be lazy about building these + if ( columnNameIdentifiers == null ) { + columnNameIdentifiers = toIdentifiers( columnNames ); } - ); - keyName = keyNameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ); - } + return columnNameIdentifiers; + } + + @Override + public Identifier getUserProvidedIdentifier() { + return originalKeyName != null ? Identifier.toIdentifier( originalKeyName ) : null; + } + } + ); + keyName = keyNameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ); Index index = table.getOrCreateIndex( keyName ); for ( int i = 0; i < columns.length; i++ ) { @@ -2096,7 +2171,7 @@ public NaturalIdUniqueKeyBinder locateNaturalIdUniqueKeyBinder(String entityName @Override public void registerNaturalIdUniqueKeyBinder(String entityName, NaturalIdUniqueKeyBinder ukBinder) { if ( naturalIdUniqueKeyBinderMap == null ) { - naturalIdUniqueKeyBinderMap = new HashMap(); + naturalIdUniqueKeyBinderMap = new HashMap<>(); } final NaturalIdUniqueKeyBinder previous = naturalIdUniqueKeyBinderMap.put( entityName, ukBinder ); if ( previous != null ) { @@ -2117,11 +2192,11 @@ private void processNaturalIdUniqueKeyBinders() { } private void processCachingOverrides() { - if ( options.getCacheRegionDefinitions() == null ) { + if ( bootstrapContext.getCacheRegionDefinitions() == null ) { return; } - for ( CacheRegionDefinition cacheRegionDefinition : options.getCacheRegionDefinitions() ) { + for ( CacheRegionDefinition cacheRegionDefinition : bootstrapContext.getCacheRegionDefinitions() ) { if ( cacheRegionDefinition.getRegionType() == CacheRegionDefinition.CacheRegionType.ENTITY ) { final PersistentClass entityBinding = getEntityBinding( cacheRegionDefinition.getRole() ); if ( entityBinding == null ) { @@ -2134,6 +2209,7 @@ private void processCachingOverrides() { "Cache override referenced a non-root entity : " + cacheRegionDefinition.getRole() ); } + entityBinding.setCached( true ); ( (RootClass) entityBinding ).setCacheRegionName( cacheRegionDefinition.getRegion() ); ( (RootClass) entityBinding ).setCacheConcurrencyStrategy( cacheRegionDefinition.getUsage() ); ( (RootClass) entityBinding ).setLazyPropertiesCacheable( cacheRegionDefinition.isCacheLazy() ); @@ -2163,13 +2239,12 @@ public boolean isInSecondPass() { */ public MetadataImpl buildMetadataInstance(MetadataBuildingContext buildingContext) { processSecondPasses( buildingContext ); - processExportableProducers( buildingContext ); + processExportableProducers( ); try { return new MetadataImpl( uuid, options, - typeResolver, identifierGeneratorFactory, entityBindingMap, mappedSuperClasses, @@ -2185,15 +2260,17 @@ public MetadataImpl buildMetadataInstance(MetadataBuildingContext buildingContex sqlResultSetMappingMap, namedEntityGraphMap, sqlFunctionMap, - getDatabase() + regionConfigBuilders.values(), + getDatabase(), + bootstrapContext ); } finally { - classmateContext.release(); + getBootstrapContext().release(); } } - private void processExportableProducers(MetadataBuildingContext buildingContext) { + private void processExportableProducers() { // for now we only handle id generators as ExportableProducers final Dialect dialect = getDatabase().getJdbcEnvironment().getDialect(); 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 4580dc57a50b..1946e5247da4 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 @@ -8,27 +8,20 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import javax.persistence.AttributeConverter; import javax.persistence.SharedCacheMode; -import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.MultiTenancyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.annotations.common.reflection.ClassLoaderDelegate; -import org.hibernate.annotations.common.reflection.ClassLoadingException; import org.hibernate.annotations.common.reflection.ReflectionManager; -import org.hibernate.annotations.common.reflection.java.JavaReflectionManager; -import org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.CacheRegionDefinition; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataSources; -import org.hibernate.boot.archive.scan.internal.StandardScanOptions; import org.hibernate.boot.archive.scan.spi.ScanEnvironment; import org.hibernate.boot.archive.scan.spi.ScanOptions; import org.hibernate.boot.archive.scan.spi.Scanner; @@ -39,9 +32,11 @@ import org.hibernate.boot.model.IdGeneratorStrategyInterpreter; import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.TypeContributor; +import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; +import org.hibernate.boot.model.convert.internal.InstanceBasedConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.naming.ImplicitNamingStrategy; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; -import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.boot.model.process.spi.MetadataBuildingProcess; @@ -52,10 +47,12 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.boot.spi.BasicTypeRegistration; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.JpaOrmXmlPersistenceUnitDefaultAware; import org.hibernate.boot.spi.MappingDefaults; import org.hibernate.boot.spi.MetadataBuilderImplementor; import org.hibernate.boot.spi.MetadataBuilderInitializer; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.MetadataSourcesContributor; @@ -64,7 +61,6 @@ import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.MetadataSourceType; -import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; @@ -73,14 +69,16 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.AbstractStandardBasicType; import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserType; import org.jboss.jandex.IndexView; -import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; - /** * @author Steve Ebersole */ @@ -88,13 +86,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont private static final CoreMessageLogger log = CoreLogging.messageLogger( MetadataBuilderImpl.class ); private final MetadataSources sources; + private final BootstrapContextImpl bootstrapContext; private final MetadataBuildingOptionsImpl options; public MetadataBuilderImpl(MetadataSources sources) { - this( - sources, - getStandardServiceRegistry( sources.getServiceRegistry() ) - ); + this( sources, getStandardServiceRegistry( sources.getServiceRegistry() ) ); } private static StandardServiceRegistry getStandardServiceRegistry(ServiceRegistry serviceRegistry) { @@ -125,6 +121,9 @@ else if ( BootstrapServiceRegistry.class.isInstance( serviceRegistry ) ) { public MetadataBuilderImpl(MetadataSources sources, StandardServiceRegistry serviceRegistry) { this.sources = sources; this.options = new MetadataBuildingOptionsImpl( serviceRegistry ); + this.bootstrapContext = new BootstrapContextImpl( serviceRegistry, options ); + //this is needed only fro implementig deprecated method + options.setBootstrapContext( bootstrapContext ); for ( MetadataSourcesContributor contributor : sources.getServiceRegistry() @@ -178,13 +177,6 @@ public MetadataBuilder applyPhysicalNamingStrategy(PhysicalNamingStrategy naming return this; } - @Override - public MetadataBuilder applyReflectionManager(ReflectionManager reflectionManager) { - this.options.reflectionManager = reflectionManager; - this.options.reflectionManager.injectClassLoaderDelegate( this.options.getHcannClassLoaderDelegate() ); - return this; - } - @Override public MetadataBuilder applySharedCacheMode(SharedCacheMode sharedCacheMode) { this.options.sharedCacheMode = sharedCacheMode; @@ -199,31 +191,31 @@ public MetadataBuilder applyAccessType(AccessType implicitCacheAccessType) { @Override public MetadataBuilder applyIndexView(IndexView jandexView) { - this.options.jandexView = jandexView; + this.bootstrapContext.injectJandexView( jandexView ); return this; } @Override public MetadataBuilder applyScanOptions(ScanOptions scanOptions) { - this.options.scanOptions = scanOptions; + this.bootstrapContext.injectScanOptions( scanOptions ); return this; } @Override public MetadataBuilder applyScanEnvironment(ScanEnvironment scanEnvironment) { - this.options.scanEnvironment = scanEnvironment; + this.bootstrapContext.injectScanEnvironment( scanEnvironment ); return this; } @Override public MetadataBuilder applyScanner(Scanner scanner) { - this.options.scannerSetting = scanner; + this.bootstrapContext.injectScanner( scanner ); return this; } @Override public MetadataBuilder applyArchiveDescriptorFactory(ArchiveDescriptorFactory factory) { - this.options.archiveDescriptorFactory = factory; + this.bootstrapContext.injectArchiveDescriptorFactory( factory ); return this; } @@ -301,18 +293,30 @@ public void contributeType(CompositeUserType type, String[] keys) { options.basicTypeRegistrations.add( new BasicTypeRegistration( type, keys ) ); } + @Override + public void contributeJavaTypeDescriptor(JavaTypeDescriptor descriptor) { + this.bootstrapContext.getTypeConfiguration().getJavaTypeDescriptorRegistry().addDescriptor( descriptor ); + } + + @Override + public void contributeSqlTypeDescriptor(SqlTypeDescriptor descriptor) { + this.bootstrapContext.getTypeConfiguration().getSqlTypeDescriptorRegistry().addDescriptor( descriptor ); + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return bootstrapContext.getTypeConfiguration(); + } + @Override public MetadataBuilder applyCacheRegionDefinition(CacheRegionDefinition cacheRegionDefinition) { - if ( options.cacheRegionDefinitions == null ) { - options.cacheRegionDefinitions = new ArrayList(); - } - options.cacheRegionDefinitions.add( cacheRegionDefinition ); + this.bootstrapContext.addCacheRegionDefinition( cacheRegionDefinition ); return this; } @Override public MetadataBuilder applyTempClassLoader(ClassLoader tempClassLoader) { - options.tempClassLoader = tempClassLoader; + this.bootstrapContext.injectJpaTempClassLoader( tempClassLoader ); return this; } @@ -327,52 +331,109 @@ public MetadataBuilder allowSpecjSyntax() { return this; } - @Override public MetadataBuilder applySqlFunction(String functionName, SQLFunction function) { - if ( this.options.sqlFunctionMap == null ) { - this.options.sqlFunctionMap = new HashMap(); - } - this.options.sqlFunctionMap.put( functionName, function ); + this.bootstrapContext.addSqlFunction( functionName, function ); return this; } @Override public MetadataBuilder applyAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabaseObject) { - if ( this.options.auxiliaryDatabaseObjectList == null ) { - this.options.auxiliaryDatabaseObjectList = new ArrayList(); - } - this.options.auxiliaryDatabaseObjectList.add( auxiliaryDatabaseObject ); + this.bootstrapContext.addAuxiliaryDatabaseObject( auxiliaryDatabaseObject ); return this; } @Override public MetadataBuilder applyAttributeConverter(AttributeConverterDefinition definition) { - this.options.addAttributeConverterDefinition( definition ); + this.bootstrapContext.addAttributeConverterInfo( definition ); return this; } @Override public MetadataBuilder applyAttributeConverter(Class attributeConverterClass) { - applyAttributeConverter( AttributeConverterDefinition.from( attributeConverterClass ) ); + this.bootstrapContext.addAttributeConverterInfo( + new AttributeConverterInfo() { + @Override + public Class getConverterClass() { + return attributeConverterClass; + } + + @Override + public ConverterDescriptor toConverterDescriptor(MetadataBuildingContext context) { + return new ClassBasedConverterDescriptor( + attributeConverterClass, + null, + context.getBootstrapContext().getClassmateContext() + ); + } + } + ); return this; } @Override public MetadataBuilder applyAttributeConverter(Class attributeConverterClass, boolean autoApply) { - applyAttributeConverter( AttributeConverterDefinition.from( attributeConverterClass, autoApply ) ); + this.bootstrapContext.addAttributeConverterInfo( + new AttributeConverterInfo() { + @Override + public Class getConverterClass() { + return attributeConverterClass; + } + + @Override + public ConverterDescriptor toConverterDescriptor(MetadataBuildingContext context) { + return new ClassBasedConverterDescriptor( + attributeConverterClass, + autoApply, + context.getBootstrapContext().getClassmateContext() + ); + } + } + ); return this; } @Override public MetadataBuilder applyAttributeConverter(AttributeConverter attributeConverter) { - applyAttributeConverter( AttributeConverterDefinition.from( attributeConverter ) ); + this.bootstrapContext.addAttributeConverterInfo( + new AttributeConverterInfo() { + @Override + public Class getConverterClass() { + return attributeConverter.getClass(); + } + + @Override + public ConverterDescriptor toConverterDescriptor(MetadataBuildingContext context) { + return new InstanceBasedConverterDescriptor( + attributeConverter, + null, + context.getBootstrapContext().getClassmateContext() + ); + } + } + ); return this; } @Override public MetadataBuilder applyAttributeConverter(AttributeConverter attributeConverter, boolean autoApply) { - applyAttributeConverter( AttributeConverterDefinition.from( attributeConverter, autoApply ) ); + this.bootstrapContext.addAttributeConverterInfo( + new AttributeConverterInfo() { + @Override + public Class getConverterClass() { + return attributeConverter.getClass(); + } + + @Override + public ConverterDescriptor toConverterDescriptor(MetadataBuildingContext context) { + return new InstanceBasedConverterDescriptor( + attributeConverter, + autoApply, + context.getBootstrapContext().getClassmateContext() + ); + } + } + ); return this; } @@ -393,11 +454,6 @@ public MetadataBuilder applyIdGenerationTypeInterpreter(IdGeneratorStrategyInter return this; } -// public MetadataBuilder with(PersistentAttributeMemberResolver resolver) { -// options.persistentAttributeMemberResolver = resolver; -// return this; -// } - @Override @SuppressWarnings("unchecked") public T unwrap(Class type) { @@ -415,7 +471,12 @@ public MetadataImplementor build() { } } - return MetadataBuildingProcess.build( sources, options ); + return MetadataBuildingProcess.build( sources, bootstrapContext, options ); + } + + @Override + public BootstrapContext getBootstrapContext() { + return bootstrapContext; } @Override @@ -534,27 +595,17 @@ public static class MetadataBuildingOptionsImpl implements MetadataBuildingOptions, JpaOrmXmlPersistenceUnitDefaultAware { private final StandardServiceRegistry serviceRegistry; private final MappingDefaultsImpl mappingDefaults; + // todo (6.0) : remove bootstrapContext property along with the deprecated methods + private BootstrapContext bootstrapContext; - private ArrayList basicTypeRegistrations = new ArrayList(); - - private IndexView jandexView; - private ClassLoader tempClassLoader; - - private ScanOptions scanOptions; - private ScanEnvironment scanEnvironment; - private Object scannerSetting; - private ArchiveDescriptorFactory archiveDescriptorFactory; + private ArrayList basicTypeRegistrations = new ArrayList<>(); private ImplicitNamingStrategy implicitNamingStrategy; private PhysicalNamingStrategy physicalNamingStrategy; - private ReflectionManager reflectionManager; - private ClassLoaderDelegate hcannClassLoaderDelegate; - private SharedCacheMode sharedCacheMode; private AccessType defaultCacheAccessType; private MultiTenancyStrategy multiTenancyStrategy; - private ArrayList cacheRegionDefinitions; private boolean explicitDiscriminatorsForJoinedInheritanceSupported; private boolean implicitDiscriminatorsForJoinedInheritanceSupported; private boolean implicitlyForceDiscriminatorInSelect; @@ -562,16 +613,9 @@ public static class MetadataBuildingOptionsImpl private boolean specjProprietarySyntaxEnabled; private ArrayList sourceProcessOrdering; - private HashMap sqlFunctionMap; - private ArrayList auxiliaryDatabaseObjectList; - private HashMap attributeConverterDefinitionsByClass; - private IdGeneratorInterpreterImpl idGenerationTypeInterpreter = new IdGeneratorInterpreterImpl(); - private boolean autoQuoteKeywords; - -// private PersistentAttributeMemberResolver persistentAttributeMemberResolver = -// StandardPersistentAttributeMemberResolver.INSTANCE; + private String schemaCharset; public MetadataBuildingOptionsImpl(StandardServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; @@ -581,25 +625,6 @@ public MetadataBuildingOptionsImpl(StandardServiceRegistry serviceRegistry) { this.mappingDefaults = new MappingDefaultsImpl( serviceRegistry ); -// jandexView = (IndexView) configService.getSettings().get( AvailableSettings.JANDEX_INDEX ); - - this.scanOptions = new StandardScanOptions( - (String) configService.getSettings().get( AvailableSettings.SCANNER_DISCOVERY ), - false - ); - // ScanEnvironment must be set explicitly - this.scannerSetting = configService.getSettings().get( AvailableSettings.SCANNER ); - if ( this.scannerSetting == null ) { - this.scannerSetting = configService.getSettings().get( AvailableSettings.SCANNER_DEPRECATED ); - if ( this.scannerSetting != null ) { - DEPRECATION_LOGGER.logDeprecatedScannerSetting(); - } - } - this.archiveDescriptorFactory = strategySelector.resolveStrategy( - ArchiveDescriptorFactory.class, - configService.getSettings().get( AvailableSettings.SCANNER_ARCHIVE_INTERPRETER ) - ); - this.multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configService.getSettings() ); this.implicitDiscriminatorsForJoinedInheritanceSupported = configService.getSetting( @@ -676,7 +701,7 @@ public AccessType convert(Object value) { configService.getSettings().get( AvailableSettings.IMPLICIT_NAMING_STRATEGY ), new Callable() { @Override - public ImplicitNamingStrategy call() throws Exception { + public ImplicitNamingStrategy call() { return strategySelector.resolveDefaultableStrategy( ImplicitNamingStrategy.class, "default", @@ -712,11 +737,15 @@ public ImplicitNamingStrategy call() throws Exception { false ); - this.reflectionManager = generateDefaultReflectionManager(); + this.schemaCharset = configService.getSetting( + AvailableSettings.HBM2DDL_CHARSET_NAME, + String.class, + null + ); } private ArrayList resolveInitialSourceProcessOrdering(ConfigurationService configService) { - final ArrayList initialSelections = new ArrayList(); + final ArrayList initialSelections = new ArrayList<>(); final String sourceProcessOrderingSetting = configService.getSetting( AvailableSettings.ARTIFACT_PROCESSING_ORDER, @@ -724,7 +753,7 @@ private ArrayList resolveInitialSourceProcessOrdering(Config ); if ( sourceProcessOrderingSetting != null ) { final String[] orderChoices = StringHelper.split( ",; ", sourceProcessOrderingSetting, false ); - initialSelections.addAll( CollectionHelper.arrayList( orderChoices.length ) ); + initialSelections.addAll( CollectionHelper.arrayList( orderChoices.length ) ); for ( String orderChoice : orderChoices ) { initialSelections.add( MetadataSourceType.parsePrecedence( orderChoice ) ); } @@ -737,32 +766,6 @@ private ArrayList resolveInitialSourceProcessOrdering(Config return initialSelections; } - private ReflectionManager generateDefaultReflectionManager() { - final JavaReflectionManager reflectionManager = new JavaReflectionManager(); - reflectionManager.setMetadataProvider( new JPAMetadataProvider( this ) ); - reflectionManager.injectClassLoaderDelegate( getHcannClassLoaderDelegate() ); - return reflectionManager; - } - - public ClassLoaderDelegate getHcannClassLoaderDelegate() { - if ( hcannClassLoaderDelegate == null ) { - hcannClassLoaderDelegate = new ClassLoaderDelegate() { - private final ClassLoaderService classLoaderService = getServiceRegistry().getService( ClassLoaderService.class ); - - @Override - public Class classForName(String className) throws ClassLoadingException { - try { - return classLoaderService.classForName( className ); - } - catch (org.hibernate.boot.registry.classloading.spi.ClassLoadingException e) { - return StandardClassLoaderDelegateImpl.INSTANCE.classForName( className ); - } - } - }; - } - return hcannClassLoaderDelegate; - } - @Override public StandardServiceRegistry getServiceRegistry() { return serviceRegistry; @@ -778,34 +781,39 @@ public List getBasicTypeRegistrations() { return basicTypeRegistrations; } + @Override + public ReflectionManager getReflectionManager() { + return bootstrapContext.getReflectionManager(); + } + @Override public IndexView getJandexView() { - return jandexView; + return bootstrapContext.getJandexView(); } @Override public ScanOptions getScanOptions() { - return scanOptions; + return bootstrapContext.getScanOptions(); } @Override public ScanEnvironment getScanEnvironment() { - return scanEnvironment; + return bootstrapContext.getScanEnvironment(); } @Override public Object getScanner() { - return scannerSetting; + return bootstrapContext.getScanner(); } @Override public ArchiveDescriptorFactory getArchiveDescriptorFactory() { - return archiveDescriptorFactory; + return bootstrapContext.getArchiveDescriptorFactory(); } @Override public ClassLoader getTempClassLoader() { - return tempClassLoader; + return bootstrapContext.getJpaTempClassLoader(); } @Override @@ -818,11 +826,6 @@ public PhysicalNamingStrategy getPhysicalNamingStrategy() { return physicalNamingStrategy; } - @Override - public ReflectionManager getReflectionManager() { - return reflectionManager; - } - @Override public SharedCacheMode getSharedCacheMode() { return sharedCacheMode; @@ -845,7 +848,7 @@ public IdGeneratorStrategyInterpreter getIdGenerationTypeInterpreter() { @Override public List getCacheRegionDefinitions() { - return cacheRegionDefinitions; + return new ArrayList<>( bootstrapContext.getCacheRegionDefinitions() ); } @Override @@ -880,38 +883,22 @@ public List getSourceProcessOrdering() { @Override public Map getSqlFunctions() { - return sqlFunctionMap == null ? Collections.emptyMap() : sqlFunctionMap; + return bootstrapContext.getSqlFunctions(); } @Override public List getAuxiliaryDatabaseObjectList() { - return auxiliaryDatabaseObjectList == null - ? Collections.emptyList() - : auxiliaryDatabaseObjectList; + return new ArrayList<>( bootstrapContext.getAuxiliaryDatabaseObjectList()); } @Override - public List getAttributeConverters() { - return attributeConverterDefinitionsByClass == null - ? Collections.emptyList() - : new ArrayList( attributeConverterDefinitionsByClass.values() ); + public List getAttributeConverters() { + return new ArrayList<>( bootstrapContext.getAttributeConverters() ); } - public void addAttributeConverterDefinition(AttributeConverterDefinition definition) { - if ( this.attributeConverterDefinitionsByClass == null ) { - this.attributeConverterDefinitionsByClass = new HashMap(); - } - - final Object old = this.attributeConverterDefinitionsByClass.put( definition.getAttributeConverter().getClass(), definition ); - - if ( old != null ) { - throw new AssertionFailure( - String.format( - "AttributeConverter class [%s] registered multiple times", - definition.getAttributeConverter().getClass() - ) - ); - } + @Override + public String getSchemaCharset() { + return schemaCharset; } /** @@ -937,9 +924,8 @@ public void apply(JpaOrmXmlPersistenceUnitDefaults jpaOrmXmlPersistenceUnitDefau } } - // @Override -// public PersistentAttributeMemberResolver getPersistentAttributeMemberResolver() { -// return persistentAttributeMemberResolver; -// } + public void setBootstrapContext(BootstrapContextImpl bootstrapContext) { + this.bootstrapContext = bootstrapContext; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java index 412b4dccb79d..afb0b4bcdd1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java @@ -7,6 +7,7 @@ package org.hibernate.boot.internal; import org.hibernate.boot.model.naming.ObjectNameNormalizer; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MappingDefaults; @@ -17,19 +18,19 @@ * @author Steve Ebersole */ public class MetadataBuildingContextRootImpl implements MetadataBuildingContext { + private final BootstrapContext bootstrapContext; private final MetadataBuildingOptions options; private final MappingDefaults mappingDefaults; - private final ClassLoaderAccess classLoaderAccess; private final InFlightMetadataCollector metadataCollector; private final ObjectNameNormalizer objectNameNormalizer; public MetadataBuildingContextRootImpl( + BootstrapContext bootstrapContext, MetadataBuildingOptions options, - ClassLoaderAccess classLoaderAccess, InFlightMetadataCollector metadataCollector) { + this.bootstrapContext = bootstrapContext; this.options = options; this.mappingDefaults = options.getMappingDefaults(); - this.classLoaderAccess = classLoaderAccess; this.metadataCollector = metadataCollector; this.objectNameNormalizer = new ObjectNameNormalizer() { @Override @@ -39,6 +40,11 @@ protected MetadataBuildingContext getBuildingContext() { }; } + @Override + public BootstrapContext getBootstrapContext() { + return bootstrapContext; + } + @Override public MetadataBuildingOptions getBuildingOptions() { return options; @@ -56,7 +62,7 @@ public InFlightMetadataCollector getMetadataCollector() { @Override public ClassLoaderAccess getClassLoaderAccess() { - return classLoaderAccess; + return bootstrapContext.getClassLoaderAccess(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java index e6eb2510b6e6..8a793f6649af 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java @@ -25,9 +25,11 @@ import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryBuilderFactory; +import org.hibernate.cache.cfg.internal.DomainDataRegionConfigImpl; import org.hibernate.cfg.annotations.NamedEntityGraphDefinition; import org.hibernate.cfg.annotations.NamedProcedureCallDefinition; import org.hibernate.dialect.function.SQLFunction; @@ -38,7 +40,6 @@ import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; import org.hibernate.internal.SessionFactoryImpl; -import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Collection; import org.hibernate.mapping.FetchProfile; import org.hibernate.mapping.MappedSuperclass; @@ -47,7 +48,9 @@ import org.hibernate.mapping.Table; import org.hibernate.procedure.ProcedureCallMemento; import org.hibernate.query.spi.NamedQueryRepository; +import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; +import org.hibernate.type.spi.TypeConfiguration; /** * Container for configuration data collected during binding the metamodel. @@ -59,8 +62,8 @@ public class MetadataImpl implements MetadataImplementor, Serializable { private final UUID uuid; private final MetadataBuildingOptions metadataBuildingOptions; + private final BootstrapContext bootstrapContext; - private final TypeResolver typeResolver; private final IdentifierGeneratorFactory identifierGeneratorFactory; private final Map entityBindingMap; @@ -77,12 +80,12 @@ public class MetadataImpl implements MetadataImplementor, Serializable { private final Map sqlResultSetMappingMap; private final Map namedEntityGraphMap; private final Map sqlFunctionMap; + private final java.util.Collection cacheRegionConfigBuilders; private final Database database; - public MetadataImpl( + MetadataImpl( UUID uuid, MetadataBuildingOptions metadataBuildingOptions, - TypeResolver typeResolver, MutableIdentifierGeneratorFactory identifierGeneratorFactory, Map entityBindingMap, Map mappedSuperclassMap, @@ -98,10 +101,11 @@ public MetadataImpl( Map sqlResultSetMappingMap, Map namedEntityGraphMap, Map sqlFunctionMap, - Database database) { + java.util.Collection cacheRegionConfigBuilders, + Database database, + BootstrapContext bootstrapContext) { this.uuid = uuid; this.metadataBuildingOptions = metadataBuildingOptions; - this.typeResolver = typeResolver; this.identifierGeneratorFactory = identifierGeneratorFactory; this.entityBindingMap = entityBindingMap; this.mappedSuperclassMap = mappedSuperclassMap; @@ -117,7 +121,9 @@ public MetadataImpl( this.sqlResultSetMappingMap = sqlResultSetMappingMap; this.namedEntityGraphMap = namedEntityGraphMap; this.sqlFunctionMap = sqlFunctionMap; + this.cacheRegionConfigBuilders = cacheRegionConfigBuilders; this.database = database; + this.bootstrapContext = bootstrapContext; } @Override @@ -126,13 +132,25 @@ public MetadataBuildingOptions getMetadataBuildingOptions() { } @Override + public TypeConfiguration getTypeConfiguration() { + return bootstrapContext.getTypeConfiguration(); + } + + /** + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated public TypeResolver getTypeResolver() { - return typeResolver; + return bootstrapContext.getTypeConfiguration().getTypeResolver(); } @Override public SessionFactoryBuilder getSessionFactoryBuilder() { - final SessionFactoryBuilderImpl defaultBuilder = new SessionFactoryBuilderImpl( this ); + final SessionFactoryBuilderImpl defaultBuilder = new SessionFactoryBuilderImpl( this, bootstrapContext ); final ClassLoaderService cls = metadataBuildingOptions.getServiceRegistry().getService( ClassLoaderService.class ); final java.util.Collection discoveredBuilderFactories = cls.loadJavaServices( SessionFactoryBuilderFactory.class ); @@ -154,7 +172,7 @@ public SessionFactoryBuilder getSessionFactoryBuilder() { if ( activeFactoryNames != null && activeFactoryNames.size() > 1 ) { throw new HibernateException( "Multiple active SessionFactoryBuilderFactory definitions were discovered : " + - StringHelper.join( ", ", activeFactoryNames ) + String.join(", ", activeFactoryNames) ); } @@ -310,7 +328,7 @@ public NamedQueryRepository buildNamedQueryRepository(SessionFactoryImpl session } - public Map buildProcedureCallMementos(SessionFactoryImpl sessionFactory) { + private Map buildProcedureCallMementos(SessionFactoryImpl sessionFactory) { final Map rtn = new HashMap<>(); if ( namedProcedureCallMap != null ) { for ( NamedProcedureCallDefinition procedureCallDefinition : namedProcedureCallMap.values() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java index ac8b4e88b7c5..ffc327962d6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java @@ -6,78 +6,53 @@ */ package org.hibernate.boot.internal; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.TimeZone; +import java.util.function.Supplier; -import org.hibernate.ConnectionAcquisitionMode; import org.hibernate.ConnectionReleaseMode; import org.hibernate.CustomEntityDirtinessStrategy; -import org.hibernate.EmptyInterceptor; import org.hibernate.EntityMode; import org.hibernate.EntityNameResolver; import org.hibernate.Interceptor; import org.hibernate.MultiTenancyStrategy; import org.hibernate.NullPrecedence; -import org.hibernate.SessionEventListener; import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; -import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.TempTableDdlTransactionHandling; -import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryBuilderImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.internal.StandardQueryCacheFactory; -import org.hibernate.cache.spi.QueryCacheFactory; -import org.hibernate.cache.spi.RegionFactory; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; +import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; -import org.hibernate.engine.config.internal.ConfigurationServiceImpl; -import org.hibernate.engine.config.spi.ConfigurationService; -import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.internal.SessionFactoryImpl; -import org.hibernate.internal.log.DeprecationLogger; -import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; -import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; -import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizerFactory; -import org.jboss.logging.Logger; - -import static org.hibernate.cfg.AvailableSettings.*; -import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN; -import static org.hibernate.jpa.AvailableSettings.DISCARD_PC_ON_CLOSE; - /** * @author Gail Badner * @author Steve Ebersole */ -public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplementor, SessionFactoryOptionsState { - private static final Logger log = Logger.getLogger( SessionFactoryBuilderImpl.class ); - +public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplementor { private final MetadataImplementor metadata; - private final SessionFactoryOptionsStateStandardImpl options; + private final BootstrapContext bootstrapContext; + private final SessionFactoryOptionsBuilder optionsBuilder; - public SessionFactoryBuilderImpl(MetadataImplementor metadata) { + public SessionFactoryBuilderImpl(MetadataImplementor metadata, BootstrapContext bootstrapContext) { this.metadata = metadata; - this.options = new SessionFactoryOptionsStateStandardImpl( metadata.getMetadataBuildingOptions().getServiceRegistry() ); + this.bootstrapContext = bootstrapContext; + + this.optionsBuilder = new SessionFactoryOptionsBuilder( + metadata.getMetadataBuildingOptions().getServiceRegistry(), + bootstrapContext + ); if ( metadata.getSqlFunctionMap() != null ) { for ( Map.Entry sqlFunctionEntry : metadata.getSqlFunctionMap().entrySet() ) { @@ -87,128 +62,134 @@ public SessionFactoryBuilderImpl(MetadataImplementor metadata) { } @Override - public SessionFactoryBuilder applyValidatorFactory(Object validatorFactory) { - this.options.validatorFactoryReference = validatorFactory; + public SessionFactoryBuilder applyBeanManager(Object beanManager) { + this.optionsBuilder.applyBeanManager( beanManager ); return this; } @Override - public SessionFactoryBuilder applyBeanManager(Object beanManager) { - this.options.beanManagerReference = beanManager; + public SessionFactoryBuilder applyValidatorFactory(Object validatorFactory) { + this.optionsBuilder.applyValidatorFactory( validatorFactory ); return this; } @Override public SessionFactoryBuilder applyName(String sessionFactoryName) { - this.options.sessionFactoryName = sessionFactoryName; + this.optionsBuilder.applySessionFactoryName( sessionFactoryName ); return this; } @Override public SessionFactoryBuilder applyNameAsJndiName(boolean isJndiName) { - this.options.sessionFactoryNameAlsoJndiName = isJndiName; + this.optionsBuilder.enableSessionFactoryNameAsJndiName( isJndiName ); return this; } @Override public SessionFactoryBuilder applyAutoClosing(boolean enabled) { - this.options.autoCloseSessionEnabled = enabled; + this.optionsBuilder.enableSessionAutoClosing( enabled ); return this; } @Override public SessionFactoryBuilder applyAutoFlushing(boolean enabled) { - this.options.flushBeforeCompletionEnabled = enabled; + this.optionsBuilder.enableSessionAutoFlushing( enabled ); return this; } @Override public SessionFactoryBuilder applyJtaTrackingByThread(boolean enabled) { - this.options.jtaTrackByThread = enabled; + this.optionsBuilder.enableJtaTrackingByThread( enabled ); return this; } @Override public SessionFactoryBuilder applyPreferUserTransactions(boolean preferUserTransactions) { - this.options.preferUserTransaction = preferUserTransactions; + this.optionsBuilder.enablePreferUserTransaction( preferUserTransactions ); return this; } @Override public SessionFactoryBuilder applyStatisticsSupport(boolean enabled) { - this.options.statisticsEnabled = enabled; + this.optionsBuilder.enableStatisticsSupport( enabled ); return this; } @Override public SessionFactoryBuilder addSessionFactoryObservers(SessionFactoryObserver... observers) { - this.options.sessionFactoryObserverList.addAll( Arrays.asList( observers ) ); + this.optionsBuilder.addSessionFactoryObservers( observers ); return this; } @Override public SessionFactoryBuilder applyInterceptor(Interceptor interceptor) { - this.options.interceptor = interceptor; + this.optionsBuilder.applyInterceptor( interceptor ); return this; } @Override public SessionFactoryBuilder applyStatelessInterceptor(Class statelessInterceptorClass) { - this.options.statelessInterceptorClass = statelessInterceptorClass; + this.optionsBuilder.applyStatelessInterceptor( statelessInterceptorClass ); + return this; + } + + @Override + public SessionFactoryBuilder applyStatelessInterceptor(Supplier statelessInterceptorSupplier) { + this.optionsBuilder.applyStatelessInterceptorSupplier( statelessInterceptorSupplier ); return this; } @Override public SessionFactoryBuilder applyStatementInspector(StatementInspector statementInspector) { - this.options.statementInspector = statementInspector; + this.optionsBuilder.applyStatementInspector( statementInspector ); return this; } @Override public SessionFactoryBuilder applyCustomEntityDirtinessStrategy(CustomEntityDirtinessStrategy strategy) { - this.options.customEntityDirtinessStrategy = strategy; + this.optionsBuilder.applyCustomEntityDirtinessStrategy( strategy ); return this; } @Override public SessionFactoryBuilder addEntityNameResolver(EntityNameResolver... entityNameResolvers) { - this.options.entityNameResolvers.addAll( Arrays.asList( entityNameResolvers ) ); + this.optionsBuilder.addEntityNameResolvers( entityNameResolvers ); return this; } @Override public SessionFactoryBuilder applyEntityNotFoundDelegate(EntityNotFoundDelegate entityNotFoundDelegate) { - this.options.entityNotFoundDelegate = entityNotFoundDelegate; + this.optionsBuilder.applyEntityNotFoundDelegate( entityNotFoundDelegate ); return this; } @Override public SessionFactoryBuilder applyIdentifierRollbackSupport(boolean enabled) { - this.options.identifierRollbackEnabled = enabled; + this.optionsBuilder.enableIdentifierRollbackSupport( enabled ); return this; } @Override public SessionFactoryBuilder applyDefaultEntityMode(EntityMode entityMode) { - this.options.defaultEntityMode = entityMode; + this.optionsBuilder.applyDefaultEntityMode( entityMode ); return this; } @Override public SessionFactoryBuilder applyNullabilityChecking(boolean enabled) { - this.options.checkNullability = enabled; + this.optionsBuilder.enableNullabilityChecking( enabled ); return this; } @Override public SessionFactoryBuilder applyLazyInitializationOutsideTransaction(boolean enabled) { - this.options.initializeLazyStateOutsideTransactions = enabled; + this.optionsBuilder.allowLazyInitializationOutsideTransaction( enabled ); return this; } @Override public SessionFactoryBuilder applyEntityTuplizerFactory(EntityTuplizerFactory entityTuplizerFactory) { - this.options.entityTuplizerFactory = entityTuplizerFactory; + this.optionsBuilder.applyEntityTuplizerFactory( entityTuplizerFactory ); return this; } @@ -216,1293 +197,279 @@ public SessionFactoryBuilder applyEntityTuplizerFactory(EntityTuplizerFactory en public SessionFactoryBuilder applyEntityTuplizer( EntityMode entityMode, Class tuplizerClass) { - this.options.entityTuplizerFactory.registerDefaultTuplizerClass( entityMode, tuplizerClass ); + this.optionsBuilder.applyEntityTuplizer( entityMode, tuplizerClass ); return this; } @Override public SessionFactoryBuilder applyMultiTableBulkIdStrategy(MultiTableBulkIdStrategy strategy) { - this.options.multiTableBulkIdStrategy = strategy; + this.optionsBuilder.applyMultiTableBulkIdStrategy( strategy ); return this; } @Override public SessionFactoryBuilder applyTempTableDdlTransactionHandling(TempTableDdlTransactionHandling handling) { - this.options.tempTableDdlTransactionHandling = handling; + this.optionsBuilder.applyTempTableDdlTransactionHandling( handling ); return this; } @Override public SessionFactoryBuilder applyBatchFetchStyle(BatchFetchStyle style) { - this.options.batchFetchStyle = style; + this.optionsBuilder.applyBatchFetchStyle( style ); + return this; + } + + @Override + public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) { + this.optionsBuilder.applyDelayedEntityLoaderCreations( delay ); return this; } @Override public SessionFactoryBuilder applyDefaultBatchFetchSize(int size) { - this.options.defaultBatchFetchSize = size; + this.optionsBuilder.applyDefaultBatchFetchSize( size ); return this; } @Override public SessionFactoryBuilder applyMaximumFetchDepth(int depth) { - this.options.maximumFetchDepth = depth; + this.optionsBuilder.applyMaximumFetchDepth( depth ); return this; } @Override public SessionFactoryBuilder applyDefaultNullPrecedence(NullPrecedence nullPrecedence) { - this.options.defaultNullPrecedence = nullPrecedence; + this.optionsBuilder.applyDefaultNullPrecedence( nullPrecedence ); return this; } @Override public SessionFactoryBuilder applyOrderingOfInserts(boolean enabled) { - this.options.orderInsertsEnabled = enabled; + this.optionsBuilder.enableOrderingOfInserts( enabled ); return this; } @Override public SessionFactoryBuilder applyOrderingOfUpdates(boolean enabled) { - this.options.orderUpdatesEnabled = enabled; + this.optionsBuilder.enableOrderingOfUpdates( enabled ); return this; } @Override public SessionFactoryBuilder applyMultiTenancyStrategy(MultiTenancyStrategy strategy) { - this.options.multiTenancyStrategy = strategy; + this.optionsBuilder.applyMultiTenancyStrategy( strategy ); return this; } @Override public SessionFactoryBuilder applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver resolver) { - this.options.currentTenantIdentifierResolver = resolver; + this.optionsBuilder.applyCurrentTenantIdentifierResolver( resolver ); return this; } @Override - @SuppressWarnings("unchecked") public SessionFactoryBuilder applyQuerySubstitutions(Map substitutions) { - this.options.querySubstitutions.putAll( substitutions ); - return this; - } - - @Override - public SessionFactoryBuilder applyStrictJpaQueryLanguageCompliance(boolean enabled) { - this.options.strictJpaQueryLanguageCompliance = enabled; + this.optionsBuilder.applyQuerySubstitutions( substitutions ); return this; } @Override public SessionFactoryBuilder applyNamedQueryCheckingOnStartup(boolean enabled) { - this.options.namedQueryStartupCheckingEnabled = enabled; + this.optionsBuilder.enableNamedQueryCheckingOnStartup( enabled ); return this; } @Override public SessionFactoryBuilder applySecondLevelCacheSupport(boolean enabled) { - this.options.secondLevelCacheEnabled = enabled; + this.optionsBuilder.enableSecondLevelCacheSupport( enabled ); return this; } @Override public SessionFactoryBuilder applyQueryCacheSupport(boolean enabled) { - this.options.queryCacheEnabled = enabled; + this.optionsBuilder.enableQueryCacheSupport( enabled ); return this; } @Override - public SessionFactoryBuilder applyQueryCacheFactory(QueryCacheFactory factory) { - this.options.queryCacheFactory = factory; + public SessionFactoryBuilder applyTimestampsCacheFactory(TimestampsCacheFactory factory) { + this.optionsBuilder.applyTimestampsCacheFactory( factory ); return this; } @Override public SessionFactoryBuilder applyCacheRegionPrefix(String prefix) { - this.options.cacheRegionPrefix = prefix; + this.optionsBuilder.applyCacheRegionPrefix( prefix ); return this; } @Override public SessionFactoryBuilder applyMinimalPutsForCaching(boolean enabled) { - this.options.minimalPutsEnabled = enabled; + this.optionsBuilder.enableMinimalPuts( enabled ); return this; } @Override public SessionFactoryBuilder applyStructuredCacheEntries(boolean enabled) { - this.options.structuredCacheEntriesEnabled = enabled; + this.optionsBuilder.enabledStructuredCacheEntries( enabled ); return this; } @Override public SessionFactoryBuilder applyDirectReferenceCaching(boolean enabled) { - this.options.directReferenceCacheEntriesEnabled = enabled; + this.optionsBuilder.allowDirectReferenceCacheEntries( enabled ); return this; } @Override public SessionFactoryBuilder applyAutomaticEvictionOfCollectionCaches(boolean enabled) { - this.options.autoEvictCollectionCache = enabled; + this.optionsBuilder.enableAutoEvictCollectionCaches( enabled ); return this; } @Override public SessionFactoryBuilder applyJdbcBatchSize(int size) { - this.options.jdbcBatchSize = size; + this.optionsBuilder.applyJdbcBatchSize( size ); return this; } @Override public SessionFactoryBuilder applyJdbcBatchingForVersionedEntities(boolean enabled) { - this.options.jdbcBatchVersionedData = enabled; + this.optionsBuilder.enableJdbcBatchingForVersionedEntities( enabled ); return this; } @Override public SessionFactoryBuilder applyScrollableResultsSupport(boolean enabled) { - this.options.scrollableResultSetsEnabled = enabled; + this.optionsBuilder.enableScrollableResultSupport( enabled ); return this; } @Override public SessionFactoryBuilder applyResultSetsWrapping(boolean enabled) { - this.options.wrapResultSetsEnabled = enabled; + this.optionsBuilder.enableResultSetWrappingSupport( enabled ); return this; } @Override public SessionFactoryBuilder applyGetGeneratedKeysSupport(boolean enabled) { - this.options.getGeneratedKeysEnabled = enabled; + this.optionsBuilder.enableGeneratedKeysSupport( enabled ); return this; } @Override public SessionFactoryBuilder applyJdbcFetchSize(int size) { - this.options.jdbcFetchSize = size; + this.optionsBuilder.applyJdbcFetchSize( size ); return this; } @Override public SessionFactoryBuilder applyConnectionHandlingMode(PhysicalConnectionHandlingMode connectionHandlingMode) { - this.options.connectionHandlingMode = connectionHandlingMode; + this.optionsBuilder.applyConnectionHandlingMode( connectionHandlingMode ); return this; } @Override public SessionFactoryBuilder applyConnectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) { - if ( this.options.connectionHandlingMode == null ) { - this.options.connectionHandlingMode = PhysicalConnectionHandlingMode.interpret( - ConnectionAcquisitionMode.AS_NEEDED, - connectionReleaseMode - ); - } - else { - this.options.connectionHandlingMode = PhysicalConnectionHandlingMode.interpret( - this.options.connectionHandlingMode.getAcquisitionMode(), - connectionReleaseMode - ); - } + this.optionsBuilder.applyConnectionReleaseMode( connectionReleaseMode ); + return this; + } + + @Override + public SessionFactoryBuilder applyConnectionProviderDisablesAutoCommit(boolean providerDisablesAutoCommit) { + this.optionsBuilder.applyConnectionProviderDisablesAutoCommit( providerDisablesAutoCommit ); return this; } @Override public SessionFactoryBuilder applySqlComments(boolean enabled) { - this.options.commentsEnabled = enabled; + this.optionsBuilder.enableCommentsSupport( enabled ); return this; } @Override public SessionFactoryBuilder applySqlFunction(String registrationName, SQLFunction sqlFunction) { - if ( this.options.sqlFunctions == null ) { - this.options.sqlFunctions = new HashMap(); - } - this.options.sqlFunctions.put( registrationName, sqlFunction ); + this.optionsBuilder.applySqlFunction( registrationName, sqlFunction ); return this; } @Override public SessionFactoryBuilder allowOutOfTransactionUpdateOperations(boolean allow) { - this.options.allowOutOfTransactionUpdateOperations = allow; + this.optionsBuilder.allowOutOfTransactionUpdateOperations( allow ); return this; } @Override public SessionFactoryBuilder enableReleaseResourcesOnCloseEnabled(boolean enable) { - this.options.releaseResourcesOnCloseEnabled = enable; + this.optionsBuilder.enableReleaseResourcesOnClose( enable ); return this; } - @Override - @SuppressWarnings("unchecked") - public T unwrap(Class type) { - return (T) this; - } - - @Override - public SessionFactory build() { - metadata.validate(); - return new SessionFactoryImpl( metadata, buildSessionFactoryOptions() ); - } - - @Override - public void markAsJpaBootstrap() { - this.options.jpaBootstrap = true; - } - - @Override - public void disableRefreshDetachedEntity() { - this.options.allowRefreshDetachedEntity = false; - } - - @Override - public void disableJtaTransactionAccess() { - this.options.jtaTransactionAccessEnabled = false; - } @Override - public SessionFactoryOptions buildSessionFactoryOptions() { - return new SessionFactoryOptionsImpl( this ); - } - - - - // SessionFactoryOptionsState impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public static class SessionFactoryOptionsStateStandardImpl implements SessionFactoryOptionsState { - private final StandardServiceRegistry serviceRegistry; - - // integration - private Object beanManagerReference; - private Object validatorFactoryReference; - - // SessionFactory behavior - private boolean jpaBootstrap; - private String sessionFactoryName; - private boolean sessionFactoryNameAlsoJndiName; - - // Session behavior - private boolean flushBeforeCompletionEnabled; - private boolean autoCloseSessionEnabled; - private boolean jtaTransactionAccessEnabled; - private boolean allowOutOfTransactionUpdateOperations; - private boolean releaseResourcesOnCloseEnabled; - private boolean allowRefreshDetachedEntity; - - // (JTA) transaction handling - private boolean jtaTrackByThread; - private boolean preferUserTransaction; - - // Statistics/Interceptor/observers - private boolean statisticsEnabled; - private Interceptor interceptor; - private Class statelessInterceptorClass; - private StatementInspector statementInspector; - private List sessionFactoryObserverList = new ArrayList<>(); - private BaselineSessionEventsListenerBuilder baselineSessionEventsListenerBuilder; // not exposed on builder atm - - // persistence behavior - private CustomEntityDirtinessStrategy customEntityDirtinessStrategy; - private List entityNameResolvers = new ArrayList<>(); - private EntityNotFoundDelegate entityNotFoundDelegate; - private boolean identifierRollbackEnabled; - private EntityMode defaultEntityMode; - private EntityTuplizerFactory entityTuplizerFactory = new EntityTuplizerFactory(); - private boolean checkNullability; - private boolean initializeLazyStateOutsideTransactions; - private MultiTableBulkIdStrategy multiTableBulkIdStrategy; - private TempTableDdlTransactionHandling tempTableDdlTransactionHandling; - private BatchFetchStyle batchFetchStyle; - private int defaultBatchFetchSize; - private Integer maximumFetchDepth; - private NullPrecedence defaultNullPrecedence; - private boolean orderUpdatesEnabled; - private boolean orderInsertsEnabled; - - - // multi-tenancy - private MultiTenancyStrategy multiTenancyStrategy; - private CurrentTenantIdentifierResolver currentTenantIdentifierResolver; - - // Queries - private Map querySubstitutions; - private boolean strictJpaQueryLanguageCompliance; - private boolean namedQueryStartupCheckingEnabled; - private boolean conventionalJavaConstants; - private final boolean procedureParameterNullPassingEnabled; - private final boolean collectionJoinSubqueryRewriteEnabled; - - // Caching - private boolean secondLevelCacheEnabled; - private boolean queryCacheEnabled; - private QueryCacheFactory queryCacheFactory; - private String cacheRegionPrefix; - private boolean minimalPutsEnabled; - private boolean structuredCacheEntriesEnabled; - private boolean directReferenceCacheEntriesEnabled; - private boolean autoEvictCollectionCache; - - // Schema tooling - private SchemaAutoTooling schemaAutoTooling; - - // JDBC Handling - private boolean getGeneratedKeysEnabled; - private int jdbcBatchSize; - private boolean jdbcBatchVersionedData; - private Integer jdbcFetchSize; - private boolean scrollableResultSetsEnabled; - private boolean commentsEnabled; - private PhysicalConnectionHandlingMode connectionHandlingMode; - private boolean wrapResultSetsEnabled; - private TimeZone jdbcTimeZone; - - private Map sqlFunctions; - - public SessionFactoryOptionsStateStandardImpl(StandardServiceRegistry serviceRegistry) { - this.serviceRegistry = serviceRegistry; - - final StrategySelector strategySelector = serviceRegistry.getService( StrategySelector.class ); - ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class ); - final JdbcServices jdbcServices = serviceRegistry.getService( JdbcServices.class ); - - final Map configurationSettings = new HashMap(); - //noinspection unchecked - configurationSettings.putAll( jdbcServices.getJdbcEnvironment().getDialect().getDefaultProperties() ); - //noinspection unchecked - configurationSettings.putAll( cfgService.getSettings() ); - cfgService = new ConfigurationServiceImpl( configurationSettings ); - ( (ConfigurationServiceImpl) cfgService ).injectServices( (ServiceRegistryImplementor) serviceRegistry ); - - this.beanManagerReference = configurationSettings.get( "javax.persistence.bean.manager" ); - this.validatorFactoryReference = configurationSettings.get( "javax.persistence.validation.factory" ); - - this.sessionFactoryName = (String) configurationSettings.get( SESSION_FACTORY_NAME ); - this.sessionFactoryNameAlsoJndiName = cfgService.getSetting( - SESSION_FACTORY_NAME_IS_JNDI, - BOOLEAN, - true - ); - this.jtaTransactionAccessEnabled = cfgService.getSetting( - ALLOW_JTA_TRANSACTION_ACCESS, - BOOLEAN, - true - ); - - this.allowRefreshDetachedEntity = cfgService.getSetting( - ALLOW_REFRESH_DETACHED_ENTITY, - BOOLEAN, - true - ); - - this.flushBeforeCompletionEnabled = cfgService.getSetting( FLUSH_BEFORE_COMPLETION, BOOLEAN, true ); - this.autoCloseSessionEnabled = cfgService.getSetting( AUTO_CLOSE_SESSION, BOOLEAN, false ); - - this.statisticsEnabled = cfgService.getSetting( GENERATE_STATISTICS, BOOLEAN, false ); - this.interceptor = determineInterceptor( configurationSettings, strategySelector ); - this.statelessInterceptorClass = determineStatelessInterceptorClass( configurationSettings, strategySelector ); - this.statementInspector = strategySelector.resolveStrategy( - StatementInspector.class, - configurationSettings.get( STATEMENT_INSPECTOR ) - ); - - // todo : expose this from builder? - final String autoSessionEventsListenerName = (String) configurationSettings.get( - AUTO_SESSION_EVENTS_LISTENER - ); - final Class autoSessionEventsListener = autoSessionEventsListenerName == null - ? null - : strategySelector.selectStrategyImplementor( SessionEventListener.class, autoSessionEventsListenerName ); - - final boolean logSessionMetrics = cfgService.getSetting( LOG_SESSION_METRICS, BOOLEAN, statisticsEnabled ); - this.baselineSessionEventsListenerBuilder = new BaselineSessionEventsListenerBuilder( logSessionMetrics, autoSessionEventsListener ); - - this.customEntityDirtinessStrategy = strategySelector.resolveDefaultableStrategy( - CustomEntityDirtinessStrategy.class, - configurationSettings.get( CUSTOM_ENTITY_DIRTINESS_STRATEGY ), - DefaultCustomEntityDirtinessStrategy.INSTANCE - ); - - this.entityNotFoundDelegate = StandardEntityNotFoundDelegate.INSTANCE; - this.identifierRollbackEnabled = cfgService.getSetting( USE_IDENTIFIER_ROLLBACK, BOOLEAN, false ); - this.defaultEntityMode = EntityMode.parse( (String) configurationSettings.get( DEFAULT_ENTITY_MODE ) ); - this.checkNullability = cfgService.getSetting( CHECK_NULLABILITY, BOOLEAN, true ); - this.initializeLazyStateOutsideTransactions = cfgService.getSetting( ENABLE_LAZY_LOAD_NO_TRANS, BOOLEAN, false ); - - this.multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configurationSettings ); - this.currentTenantIdentifierResolver = strategySelector.resolveStrategy( - CurrentTenantIdentifierResolver.class, - configurationSettings.get( MULTI_TENANT_IDENTIFIER_RESOLVER ) - ); - - this.multiTableBulkIdStrategy = strategySelector.resolveDefaultableStrategy( - MultiTableBulkIdStrategy.class, - configurationSettings.get( HQL_BULK_ID_STRATEGY ), - jdbcServices.getJdbcEnvironment().getDialect().getDefaultMultiTableBulkIdStrategy() - ); - - this.batchFetchStyle = BatchFetchStyle.interpret( configurationSettings.get( BATCH_FETCH_STYLE ) ); - this.defaultBatchFetchSize = ConfigurationHelper.getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 ); - this.maximumFetchDepth = ConfigurationHelper.getInteger( MAX_FETCH_DEPTH, configurationSettings ); - final String defaultNullPrecedence = ConfigurationHelper.getString( - AvailableSettings.DEFAULT_NULL_ORDERING, configurationSettings, "none", "first", "last" - ); - this.defaultNullPrecedence = NullPrecedence.parse( defaultNullPrecedence ); - this.orderUpdatesEnabled = ConfigurationHelper.getBoolean( ORDER_UPDATES, configurationSettings ); - this.orderInsertsEnabled = ConfigurationHelper.getBoolean( ORDER_INSERTS, configurationSettings ); - - this.jtaTrackByThread = cfgService.getSetting( JTA_TRACK_BY_THREAD, BOOLEAN, true ); - - this.querySubstitutions = ConfigurationHelper.toMap( QUERY_SUBSTITUTIONS, " ,=;:\n\t\r\f", configurationSettings ); - this.strictJpaQueryLanguageCompliance = cfgService.getSetting( JPAQL_STRICT_COMPLIANCE, BOOLEAN, false ); - this.namedQueryStartupCheckingEnabled = cfgService.getSetting( QUERY_STARTUP_CHECKING, BOOLEAN, true ); - this.conventionalJavaConstants = cfgService.getSetting( - CONVENTIONAL_JAVA_CONSTANTS, BOOLEAN, true ); - this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false ); - this.collectionJoinSubqueryRewriteEnabled = cfgService.getSetting( COLLECTION_JOIN_SUBQUERY, BOOLEAN, true ); - - this.secondLevelCacheEnabled = cfgService.getSetting( USE_SECOND_LEVEL_CACHE, BOOLEAN, true ); - this.queryCacheEnabled = cfgService.getSetting( USE_QUERY_CACHE, BOOLEAN, false ); - this.queryCacheFactory = strategySelector.resolveDefaultableStrategy( - QueryCacheFactory.class, - configurationSettings.get( QUERY_CACHE_FACTORY ), - StandardQueryCacheFactory.INSTANCE - ); - this.cacheRegionPrefix = ConfigurationHelper.extractPropertyValue( - CACHE_REGION_PREFIX, - configurationSettings - ); - this.minimalPutsEnabled = cfgService.getSetting( - USE_MINIMAL_PUTS, - BOOLEAN, - serviceRegistry.getService( RegionFactory.class ).isMinimalPutsEnabledByDefault() - ); - this.structuredCacheEntriesEnabled = cfgService.getSetting( USE_STRUCTURED_CACHE, BOOLEAN, false ); - this.directReferenceCacheEntriesEnabled = cfgService.getSetting( USE_DIRECT_REFERENCE_CACHE_ENTRIES,BOOLEAN, false ); - this.autoEvictCollectionCache = cfgService.getSetting( AUTO_EVICT_COLLECTION_CACHE, BOOLEAN, false ); - - try { - this.schemaAutoTooling = SchemaAutoTooling.interpret( (String) configurationSettings.get( AvailableSettings.HBM2DDL_AUTO ) ); - } - catch (Exception e) { - log.warn( e.getMessage() + " Ignoring" ); - } - - - final ExtractedDatabaseMetaData meta = jdbcServices.getExtractedMetaDataSupport(); - - this.tempTableDdlTransactionHandling = TempTableDdlTransactionHandling.NONE; - if ( meta.doesDataDefinitionCauseTransactionCommit() ) { - if ( meta.supportsDataDefinitionInTransaction() ) { - this.tempTableDdlTransactionHandling = TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT; - } - else { - this.tempTableDdlTransactionHandling = TempTableDdlTransactionHandling.ISOLATE; - } - } - - this.jdbcBatchSize = ConfigurationHelper.getInt( STATEMENT_BATCH_SIZE, configurationSettings, 0 ); - if ( !meta.supportsBatchUpdates() ) { - this.jdbcBatchSize = 0; - } - - this.jdbcBatchVersionedData = ConfigurationHelper.getBoolean( BATCH_VERSIONED_DATA, configurationSettings, true ); - this.scrollableResultSetsEnabled = ConfigurationHelper.getBoolean( - USE_SCROLLABLE_RESULTSET, - configurationSettings, - meta.supportsScrollableResults() - ); - this.wrapResultSetsEnabled = ConfigurationHelper.getBoolean( - WRAP_RESULT_SETS, - configurationSettings, - false - ); - this.getGeneratedKeysEnabled = ConfigurationHelper.getBoolean( - USE_GET_GENERATED_KEYS, - configurationSettings, - meta.supportsGetGeneratedKeys() - ); - this.jdbcFetchSize = ConfigurationHelper.getInteger( STATEMENT_FETCH_SIZE, configurationSettings ); - - this.connectionHandlingMode = interpretConnectionHandlingMode( configurationSettings, serviceRegistry ); - - this.commentsEnabled = ConfigurationHelper.getBoolean( USE_SQL_COMMENTS, configurationSettings ); - - this.preferUserTransaction = ConfigurationHelper.getBoolean( PREFER_USER_TRANSACTION, configurationSettings, false ); - - this.allowOutOfTransactionUpdateOperations = ConfigurationHelper.getBoolean( - ALLOW_UPDATE_OUTSIDE_TRANSACTION, - configurationSettings, - false - ); - - this.releaseResourcesOnCloseEnabled = ConfigurationHelper.getBoolean( - DISCARD_PC_ON_CLOSE, - configurationSettings, - false - ); - Object jdbcTimeZoneValue = configurationSettings.get( - JDBC_TIME_ZONE - ); - - if ( jdbcTimeZoneValue instanceof TimeZone ) { - this.jdbcTimeZone = (TimeZone) jdbcTimeZoneValue; - } - else if ( jdbcTimeZoneValue instanceof ZoneId ) { - this.jdbcTimeZone = TimeZone.getTimeZone( (ZoneId) jdbcTimeZoneValue ); - } - else if ( jdbcTimeZoneValue instanceof String ) { - this.jdbcTimeZone = TimeZone.getTimeZone( ZoneId.of((String) jdbcTimeZoneValue) ); - } - else if ( jdbcTimeZoneValue != null ) { - throw new IllegalArgumentException( "Configuration property " + JDBC_TIME_ZONE + " value [" + jdbcTimeZoneValue + "] is not supported!" ); - } - } - - private static Interceptor determineInterceptor(Map configurationSettings, StrategySelector strategySelector) { - Object setting = configurationSettings.get( INTERCEPTOR ); - if ( setting == null ) { - // try the legacy (deprecated) JPA name - setting = configurationSettings.get( org.hibernate.jpa.AvailableSettings.INTERCEPTOR ); - if ( setting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.INTERCEPTOR, - INTERCEPTOR - ); - } - } - - return strategySelector.resolveStrategy( - Interceptor.class, - setting - ); - } - - @SuppressWarnings("unchecked") - private static Class determineStatelessInterceptorClass( - Map configurationSettings, - StrategySelector strategySelector) { - Object setting = configurationSettings.get( SESSION_SCOPED_INTERCEPTOR ); - if ( setting == null ) { - // try the legacy (deprecated) JPA name - setting = configurationSettings.get( org.hibernate.jpa.AvailableSettings.SESSION_INTERCEPTOR ); - if ( setting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.SESSION_INTERCEPTOR, - SESSION_SCOPED_INTERCEPTOR - ); - } - } - - if ( setting == null ) { - return null; - } - else if ( setting instanceof Class ) { - return (Class) setting; - } - else { - return strategySelector.selectStrategyImplementor( - Interceptor.class, - setting.toString() - ); - } - - } - - private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( - Map configurationSettings, - StandardServiceRegistry serviceRegistry) { - final PhysicalConnectionHandlingMode specifiedHandlingMode = PhysicalConnectionHandlingMode.interpret( - configurationSettings.get( CONNECTION_HANDLING ) - ); - - if ( specifiedHandlingMode != null ) { - return specifiedHandlingMode; - } - - - final TransactionCoordinatorBuilder transactionCoordinatorBuilder = serviceRegistry.getService( TransactionCoordinatorBuilder.class ); - - // see if the deprecated ConnectionAcquisitionMode/ConnectionReleaseMode were used.. - final ConnectionAcquisitionMode specifiedAcquisitionMode = ConnectionAcquisitionMode.interpret( - configurationSettings.get( ACQUIRE_CONNECTIONS ) - ); - final ConnectionReleaseMode specifiedReleaseMode = ConnectionReleaseMode.interpret( - configurationSettings.get( RELEASE_CONNECTIONS ) - ); - if ( specifiedAcquisitionMode != null || specifiedReleaseMode != null ) { - return interpretConnectionHandlingMode( specifiedAcquisitionMode, specifiedReleaseMode, configurationSettings, transactionCoordinatorBuilder ); - } - - return transactionCoordinatorBuilder.getDefaultConnectionHandlingMode(); - } - - /** - * @deprecated since 5.2 - */ - @Deprecated - private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( - ConnectionAcquisitionMode specifiedAcquisitionMode, - ConnectionReleaseMode specifiedReleaseMode, - Map configurationSettings, - TransactionCoordinatorBuilder transactionCoordinatorBuilder) { - DeprecationLogger.DEPRECATION_LOGGER.logUseOfDeprecatedConnectionHandlingSettings(); - - final ConnectionAcquisitionMode effectiveAcquisitionMode = specifiedAcquisitionMode == null - ? ConnectionAcquisitionMode.AS_NEEDED - : specifiedAcquisitionMode; - - final ConnectionReleaseMode effectiveReleaseMode; - if ( specifiedReleaseMode == null ) { - // check the actual setting. If we get in here it *should* be "auto" or null - final String releaseModeName = ConfigurationHelper.getString( RELEASE_CONNECTIONS, configurationSettings, "auto" ); - assert "auto".equalsIgnoreCase( releaseModeName ); - // nothing was specified (or someone happened to configure the "magic" value) - if ( effectiveAcquisitionMode == ConnectionAcquisitionMode.IMMEDIATELY ) { - effectiveReleaseMode = ConnectionReleaseMode.ON_CLOSE; - } - else { - effectiveReleaseMode = transactionCoordinatorBuilder.getDefaultConnectionReleaseMode(); - } - } - else { - effectiveReleaseMode = specifiedReleaseMode; - } - - return PhysicalConnectionHandlingMode.interpret( effectiveAcquisitionMode, effectiveReleaseMode ); - } - - @Override - public StandardServiceRegistry getServiceRegistry() { - return serviceRegistry; - } - - @Override - public boolean isJpaBootstrap() { - return jpaBootstrap; - } - - @Override - public boolean isJtaTransactionAccessEnabled() { - return jtaTransactionAccessEnabled; - } - - public boolean isAllowRefreshDetachedEntity() { - return allowRefreshDetachedEntity; - } - - public boolean isAllowOutOfTransactionUpdateOperations() { - return allowOutOfTransactionUpdateOperations; - } - - @Override - public boolean isReleaseResourcesOnCloseEnabled() { - return releaseResourcesOnCloseEnabled; - } - - @Override - public Object getBeanManagerReference() { - return beanManagerReference; - } - - @Override - public Object getValidatorFactoryReference() { - return validatorFactoryReference; - } - - @Override - public String getSessionFactoryName() { - return sessionFactoryName; - } - - @Override - public boolean isSessionFactoryNameAlsoJndiName() { - return sessionFactoryNameAlsoJndiName; - } - - @Override - public boolean isFlushBeforeCompletionEnabled() { - return flushBeforeCompletionEnabled; - } - - @Override - public boolean isAutoCloseSessionEnabled() { - return autoCloseSessionEnabled; - } - - @Override - public boolean isStatisticsEnabled() { - return statisticsEnabled; - } - - @Override - public Interceptor getInterceptor() { - return interceptor == null ? EmptyInterceptor.INSTANCE : interceptor; - } - - @Override - public Class getStatelessInterceptorImplementor() { - return statelessInterceptorClass; - } - - @Override - public StatementInspector getStatementInspector() { - return statementInspector; - } - - @Override - public SessionFactoryObserver[] getSessionFactoryObservers() { - return sessionFactoryObserverList.toArray( new SessionFactoryObserver[ sessionFactoryObserverList.size() ] ); - } - - @Override - public BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder() { - return baselineSessionEventsListenerBuilder; - } - - @Override - public boolean isIdentifierRollbackEnabled() { - return identifierRollbackEnabled; - } - - @Override - public EntityMode getDefaultEntityMode() { - return defaultEntityMode; - } - - @Override - public EntityTuplizerFactory getEntityTuplizerFactory() { - return entityTuplizerFactory; - } - - @Override - public boolean isCheckNullability() { - return checkNullability; - } - - @Override - public boolean isInitializeLazyStateOutsideTransactionsEnabled() { - return initializeLazyStateOutsideTransactions; - } - - @Override - public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy() { - return multiTableBulkIdStrategy; - } - - @Override - public TempTableDdlTransactionHandling getTempTableDdlTransactionHandling() { - return tempTableDdlTransactionHandling; - } - - @Override - public BatchFetchStyle getBatchFetchStyle() { - return batchFetchStyle; - } - - @Override - public int getDefaultBatchFetchSize() { - return defaultBatchFetchSize; - } - - @Override - public Integer getMaximumFetchDepth() { - return maximumFetchDepth; - } - - @Override - public NullPrecedence getDefaultNullPrecedence() { - return defaultNullPrecedence; - } - - @Override - public boolean isOrderUpdatesEnabled() { - return orderUpdatesEnabled; - } - - @Override - public boolean isOrderInsertsEnabled() { - return orderInsertsEnabled; - } - - @Override - public MultiTenancyStrategy getMultiTenancyStrategy() { - return multiTenancyStrategy; - } - - @Override - public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() { - return currentTenantIdentifierResolver; - } - - @Override - public boolean isJtaTrackByThread() { - return jtaTrackByThread; - } - - @Override - public Map getQuerySubstitutions() { - return querySubstitutions; - } - - @Override - public boolean isStrictJpaQueryLanguageCompliance() { - return strictJpaQueryLanguageCompliance; - } - - @Override - public boolean isNamedQueryStartupCheckingEnabled() { - return namedQueryStartupCheckingEnabled; - } - - public boolean isConventionalJavaConstants() { - return conventionalJavaConstants; - } - - @Override - public boolean isProcedureParameterNullPassingEnabled() { - return procedureParameterNullPassingEnabled; - } - - @Override - public boolean isCollectionJoinSubqueryRewriteEnabled() { - return collectionJoinSubqueryRewriteEnabled; - } - - @Override - public boolean isSecondLevelCacheEnabled() { - return secondLevelCacheEnabled; - } - - @Override - public boolean isQueryCacheEnabled() { - return queryCacheEnabled; - } - - @Override - public QueryCacheFactory getQueryCacheFactory() { - return queryCacheFactory; - } - - @Override - public String getCacheRegionPrefix() { - return cacheRegionPrefix; - } - - @Override - public boolean isMinimalPutsEnabled() { - return minimalPutsEnabled; - } - - @Override - public boolean isStructuredCacheEntriesEnabled() { - return structuredCacheEntriesEnabled; - } - - @Override - public boolean isDirectReferenceCacheEntriesEnabled() { - return directReferenceCacheEntriesEnabled; - } - - @Override - public boolean isAutoEvictCollectionCache() { - return autoEvictCollectionCache; - } - - @Override - public SchemaAutoTooling getSchemaAutoTooling() { - return schemaAutoTooling; - } - - @Override - public int getJdbcBatchSize() { - return jdbcBatchSize; - } - - @Override - public boolean isJdbcBatchVersionedData() { - return jdbcBatchVersionedData; - } - - @Override - public boolean isScrollableResultSetsEnabled() { - return scrollableResultSetsEnabled; - } - - @Override - public boolean isWrapResultSetsEnabled() { - return wrapResultSetsEnabled; - } - - @Override - public boolean isGetGeneratedKeysEnabled() { - return getGeneratedKeysEnabled; - } - - @Override - public Integer getJdbcFetchSize() { - return jdbcFetchSize; - } - - @Override - public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() { - return connectionHandlingMode; - } - - @Override - public ConnectionReleaseMode getConnectionReleaseMode() { - return getPhysicalConnectionHandlingMode().getReleaseMode(); - } - - @Override - public boolean isCommentsEnabled() { - return commentsEnabled; - } - - @Override - public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() { - return customEntityDirtinessStrategy; - } - - @Override - public EntityNameResolver[] getEntityNameResolvers() { - return entityNameResolvers.toArray( new EntityNameResolver[ entityNameResolvers.size() ] ); - } - - @Override - public EntityNotFoundDelegate getEntityNotFoundDelegate() { - return entityNotFoundDelegate; - } - - @Override - public Map getCustomSqlFunctionMap() { - return sqlFunctions == null ? Collections.emptyMap() : sqlFunctions; - } - - @Override - public boolean isPreferUserTransaction() { - return this.preferUserTransaction; - } - - @Override - public TimeZone getJdbcTimeZone() { - return this.jdbcTimeZone; - } - } - - @Override - public StandardServiceRegistry getServiceRegistry() { - return options.getServiceRegistry(); - } - - @Override - public boolean isJpaBootstrap() { - return options.isJpaBootstrap(); - } - - @Override - public boolean isJtaTransactionAccessEnabled() { - return options.isJtaTransactionAccessEnabled(); - } - - public boolean isAllowRefreshDetachedEntity() { - return options.isAllowRefreshDetachedEntity(); - } - - public boolean isAllowOutOfTransactionUpdateOperations() { - return options.isAllowOutOfTransactionUpdateOperations(); - } - - @Override - public boolean isReleaseResourcesOnCloseEnabled(){ - return options.releaseResourcesOnCloseEnabled; - } - - @Override - public Object getBeanManagerReference() { - return options.getBeanManagerReference(); - } - - @Override - public Object getValidatorFactoryReference() { - return options.getValidatorFactoryReference(); - } - - @Override - public String getSessionFactoryName() { - return options.getSessionFactoryName(); - } - - @Override - public boolean isSessionFactoryNameAlsoJndiName() { - return options.isSessionFactoryNameAlsoJndiName(); - } - - @Override - public boolean isFlushBeforeCompletionEnabled() { - return options.isFlushBeforeCompletionEnabled(); - } - - @Override - public boolean isAutoCloseSessionEnabled() { - return options.isAutoCloseSessionEnabled(); - } - - @Override - public boolean isStatisticsEnabled() { - return options.isStatisticsEnabled(); - } - - @Override - public Interceptor getInterceptor() { - return options.getInterceptor(); - } - - @Override - public Class getStatelessInterceptorImplementor() { - return options.getStatelessInterceptorImplementor(); - } - - @Override - public StatementInspector getStatementInspector() { - return options.getStatementInspector(); - } - - @Override - public SessionFactoryObserver[] getSessionFactoryObservers() { - return options.getSessionFactoryObservers(); - } - - @Override - public BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder() { - return options.getBaselineSessionEventsListenerBuilder(); - } - - @Override - public boolean isIdentifierRollbackEnabled() { - return options.isIdentifierRollbackEnabled(); - } - - @Override - public EntityMode getDefaultEntityMode() { - return options.getDefaultEntityMode(); - } - - @Override - public EntityTuplizerFactory getEntityTuplizerFactory() { - return options.getEntityTuplizerFactory(); - } - - @Override - public boolean isCheckNullability() { - return options.isCheckNullability(); - } - - @Override - public boolean isInitializeLazyStateOutsideTransactionsEnabled() { - return options.isInitializeLazyStateOutsideTransactionsEnabled(); - } - - @Override - public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy() { - return options.getMultiTableBulkIdStrategy(); - } - - @Override - public TempTableDdlTransactionHandling getTempTableDdlTransactionHandling() { - return options.getTempTableDdlTransactionHandling(); - } - - @Override - public BatchFetchStyle getBatchFetchStyle() { - return options.getBatchFetchStyle(); - } - - @Override - public int getDefaultBatchFetchSize() { - return options.getDefaultBatchFetchSize(); - } - - @Override - public Integer getMaximumFetchDepth() { - return options.getMaximumFetchDepth(); - } - - @Override - public NullPrecedence getDefaultNullPrecedence() { - return options.getDefaultNullPrecedence(); - } - - @Override - public boolean isOrderUpdatesEnabled() { - return options.isOrderUpdatesEnabled(); - } - - @Override - public boolean isOrderInsertsEnabled() { - return options.isOrderInsertsEnabled(); - } - - @Override - public MultiTenancyStrategy getMultiTenancyStrategy() { - return options.getMultiTenancyStrategy(); - } - - @Override - public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() { - return options.getCurrentTenantIdentifierResolver(); - } - - @Override - public boolean isJtaTrackByThread() { - return options.isJtaTrackByThread(); - } - - @Override - public Map getQuerySubstitutions() { - return options.getQuerySubstitutions(); - } - - @Override - public boolean isStrictJpaQueryLanguageCompliance() { - return options.isStrictJpaQueryLanguageCompliance(); - } - - @Override - public boolean isNamedQueryStartupCheckingEnabled() { - return options.isNamedQueryStartupCheckingEnabled(); - } - - public boolean isConventionalJavaConstants() { - return options.isConventionalJavaConstants(); - } - - @Override - public boolean isProcedureParameterNullPassingEnabled() { - return options.isProcedureParameterNullPassingEnabled(); - } - - @Override - public boolean isCollectionJoinSubqueryRewriteEnabled() { - return options.isCollectionJoinSubqueryRewriteEnabled(); - } - - @Override - public boolean isSecondLevelCacheEnabled() { - return options.isSecondLevelCacheEnabled(); - } - - @Override - public boolean isQueryCacheEnabled() { - return options.isQueryCacheEnabled(); - } - - @Override - public QueryCacheFactory getQueryCacheFactory() { - return options.getQueryCacheFactory(); - } - - @Override - public String getCacheRegionPrefix() { - return options.getCacheRegionPrefix(); - } - - @Override - public boolean isMinimalPutsEnabled() { - return options.isMinimalPutsEnabled(); - } - - @Override - public boolean isStructuredCacheEntriesEnabled() { - return options.isStructuredCacheEntriesEnabled(); - } - - @Override - public boolean isDirectReferenceCacheEntriesEnabled() { - return options.isDirectReferenceCacheEntriesEnabled(); - } - - @Override - public boolean isAutoEvictCollectionCache() { - return options.isAutoEvictCollectionCache(); - } - - @Override - public SchemaAutoTooling getSchemaAutoTooling() { - return options.getSchemaAutoTooling(); - } - - @Override - public int getJdbcBatchSize() { - return options.getJdbcBatchSize(); - } - - @Override - public boolean isJdbcBatchVersionedData() { - return options.isJdbcBatchVersionedData(); - } - - @Override - public boolean isScrollableResultSetsEnabled() { - return options.isScrollableResultSetsEnabled(); + public SessionFactoryBuilder applyStrictJpaQueryLanguageCompliance(boolean enabled) { + this.optionsBuilder.enableStrictJpaQueryLanguageCompliance( enabled ); + return this; } @Override - public boolean isWrapResultSetsEnabled() { - return options.isWrapResultSetsEnabled(); + public SessionFactoryBuilder enableJpaQueryCompliance(boolean enabled) { + this.optionsBuilder.enableJpaQueryCompliance( enabled ); + return this; } @Override - public boolean isGetGeneratedKeysEnabled() { - return options.isGetGeneratedKeysEnabled(); + public SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled) { + this.optionsBuilder.enableJpaTransactionCompliance( enabled ); + return this; } @Override - public Integer getJdbcFetchSize() { - return options.getJdbcFetchSize(); + public SessionFactoryBuilder enableJpaListCompliance(boolean enabled) { + this.optionsBuilder.enableJpaListCompliance( enabled ); + return this; } @Override - public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() { - return options.getPhysicalConnectionHandlingMode(); + public SessionFactoryBuilder enableJpaClosedCompliance(boolean enabled) { + this.optionsBuilder.enableJpaClosedCompliance( enabled ); + return this; } @Override - public ConnectionReleaseMode getConnectionReleaseMode() { - return getPhysicalConnectionHandlingMode().getReleaseMode(); + public void markAsJpaBootstrap() { + this.bootstrapContext.markAsJpaBootstrap(); } @Override - public boolean isCommentsEnabled() { - return options.isCommentsEnabled(); + public void disableRefreshDetachedEntity() { + this.optionsBuilder.disableRefreshDetachedEntity(); } @Override - public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() { - return options.getCustomEntityDirtinessStrategy(); + public void disableJtaTransactionAccess() { + this.optionsBuilder.disableJtaTransactionAccess(); } - @Override - public EntityNameResolver[] getEntityNameResolvers() { - return options.getEntityNameResolvers(); + public void enableJdbcStyleParamsZeroBased() { + this.optionsBuilder.enableJdbcStyleParamsZeroBased(); } @Override - public EntityNotFoundDelegate getEntityNotFoundDelegate() { - return options.getEntityNotFoundDelegate(); + @SuppressWarnings("unchecked") + public T unwrap(Class type) { + return (T) this; } @Override - public Map getCustomSqlFunctionMap() { - return options.getCustomSqlFunctionMap(); + public SessionFactory build() { + metadata.validate(); + return new SessionFactoryImpl( bootstrapContext, metadata, buildSessionFactoryOptions() ); } @Override - public boolean isPreferUserTransaction() { - return options.isPreferUserTransaction(); + public SessionFactoryOptions buildSessionFactoryOptions() { + return optionsBuilder.buildOptions(); } - @Override - public TimeZone getJdbcTimeZone() { - return options.getJdbcTimeZone(); - } } 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 new file mode 100644 index 000000000000..de2b94b64573 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -0,0 +1,1342 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.internal; + +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.function.Supplier; + +import org.hibernate.ConnectionAcquisitionMode; +import org.hibernate.ConnectionReleaseMode; +import org.hibernate.CustomEntityDirtinessStrategy; +import org.hibernate.EmptyInterceptor; +import org.hibernate.EntityMode; +import org.hibernate.EntityNameResolver; +import org.hibernate.HibernateException; +import org.hibernate.Interceptor; +import org.hibernate.MultiTenancyStrategy; +import org.hibernate.NullPrecedence; +import org.hibernate.SessionEventListener; +import org.hibernate.SessionFactoryObserver; +import org.hibernate.boot.SchemaAutoTooling; +import org.hibernate.boot.TempTableDdlTransactionHandling; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.internal.NoCachingRegionFactory; +import org.hibernate.cache.internal.StandardTimestampsCacheFactory; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.TimestampsCacheFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; +import org.hibernate.context.spi.CurrentTenantIdentifierResolver; +import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.engine.config.internal.ConfigurationServiceImpl; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.id.uuid.LocalObjectUuidHelper; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.log.DeprecationLogger; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.spi.JpaCompliance; +import org.hibernate.jpa.spi.MutableJpaCompliance; +import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; +import org.hibernate.query.criteria.LiteralHandlingMode; +import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; +import org.hibernate.resource.jdbc.spi.StatementInspector; +import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.tuple.entity.EntityTuplizer; +import org.hibernate.tuple.entity.EntityTuplizerFactory; + +import static org.hibernate.cfg.AvailableSettings.ACQUIRE_CONNECTIONS; +import static org.hibernate.cfg.AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY; +import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS; +import static org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY; +import static org.hibernate.cfg.AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION; +import static org.hibernate.cfg.AvailableSettings.AUTO_CLOSE_SESSION; +import static org.hibernate.cfg.AvailableSettings.AUTO_EVICT_COLLECTION_CACHE; +import static org.hibernate.cfg.AvailableSettings.AUTO_SESSION_EVENTS_LISTENER; +import static org.hibernate.cfg.AvailableSettings.BATCH_FETCH_STYLE; +import static org.hibernate.cfg.AvailableSettings.BATCH_VERSIONED_DATA; +import static org.hibernate.cfg.AvailableSettings.CACHE_REGION_PREFIX; +import static org.hibernate.cfg.AvailableSettings.CHECK_NULLABILITY; +import static org.hibernate.cfg.AvailableSettings.COLLECTION_JOIN_SUBQUERY; +import static org.hibernate.cfg.AvailableSettings.CONNECTION_HANDLING; +import static org.hibernate.cfg.AvailableSettings.CONVENTIONAL_JAVA_CONSTANTS; +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_ENTITY_MODE; +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; +import static org.hibernate.cfg.AvailableSettings.FLUSH_BEFORE_COMPLETION; +import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS; +import static org.hibernate.cfg.AvailableSettings.HQL_BULK_ID_STRATEGY; +import static org.hibernate.cfg.AvailableSettings.IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE; +import static org.hibernate.cfg.AvailableSettings.INTERCEPTOR; +import static org.hibernate.cfg.AvailableSettings.IN_CLAUSE_PARAMETER_PADDING; +import static org.hibernate.cfg.AvailableSettings.JDBC_TIME_ZONE; +import static org.hibernate.cfg.AvailableSettings.JDBC_TYLE_PARAMS_ZERO_BASE; +import static org.hibernate.cfg.AvailableSettings.JTA_TRACK_BY_THREAD; +import static org.hibernate.cfg.AvailableSettings.LOG_SESSION_METRICS; +import static org.hibernate.cfg.AvailableSettings.MAX_FETCH_DEPTH; +import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER; +import static org.hibernate.cfg.AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE; +import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS; +import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES; +import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION; +import static org.hibernate.cfg.AvailableSettings.PROCEDURE_NULL_PARAM_PASSING; +import static org.hibernate.cfg.AvailableSettings.QUERY_CACHE_FACTORY; +import static org.hibernate.cfg.AvailableSettings.QUERY_STARTUP_CHECKING; +import static org.hibernate.cfg.AvailableSettings.QUERY_SUBSTITUTIONS; +import static org.hibernate.cfg.AvailableSettings.RELEASE_CONNECTIONS; +import static org.hibernate.cfg.AvailableSettings.SESSION_FACTORY_NAME; +import static org.hibernate.cfg.AvailableSettings.SESSION_FACTORY_NAME_IS_JNDI; +import static org.hibernate.cfg.AvailableSettings.SESSION_SCOPED_INTERCEPTOR; +import static org.hibernate.cfg.AvailableSettings.STATEMENT_BATCH_SIZE; +import static org.hibernate.cfg.AvailableSettings.STATEMENT_FETCH_SIZE; +import static org.hibernate.cfg.AvailableSettings.STATEMENT_INSPECTOR; +import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES; +import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS; +import static org.hibernate.cfg.AvailableSettings.USE_IDENTIFIER_ROLLBACK; +import static org.hibernate.cfg.AvailableSettings.USE_MINIMAL_PUTS; +import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE; +import static org.hibernate.cfg.AvailableSettings.USE_SCROLLABLE_RESULTSET; +import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE; +import static org.hibernate.cfg.AvailableSettings.USE_SQL_COMMENTS; +import static org.hibernate.cfg.AvailableSettings.USE_STRUCTURED_CACHE; +import static org.hibernate.cfg.AvailableSettings.VALIDATE_QUERY_PARAMETERS; +import static org.hibernate.cfg.AvailableSettings.WRAP_RESULT_SETS; +import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN; +import static org.hibernate.internal.CoreLogging.messageLogger; +import static org.hibernate.jpa.AvailableSettings.DISCARD_PC_ON_CLOSE; + +/** + * In-flight state of {@link org.hibernate.boot.spi.SessionFactoryOptions} + * during {@link org.hibernate.boot.SessionFactoryBuilder} processing. + * + * The intention is that SessionFactoryBuilder internally creates and populates + * this builder, which is then used to construct the SessionFactoryOptions + * as part of building the SessionFactory ({@link org.hibernate.boot.SessionFactoryBuilder#build}) + * + * @author Steve Ebersole + */ +@SuppressWarnings("WeakerAccess") +public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { + private static final CoreMessageLogger log = messageLogger( SessionFactoryOptionsBuilder.class ); + + private final String uuid = LocalObjectUuidHelper.generateLocalObjectUuid(); + private final StandardServiceRegistry serviceRegistry; + + // integration + private Object beanManagerReference; + private Object validatorFactoryReference; + + // SessionFactory behavior + private boolean jpaBootstrap; + private String sessionFactoryName; + private boolean sessionFactoryNameAlsoJndiName; + + // Session behavior + private boolean flushBeforeCompletionEnabled; + private boolean autoCloseSessionEnabled; + private boolean jtaTransactionAccessEnabled; + private boolean allowOutOfTransactionUpdateOperations; + private boolean releaseResourcesOnCloseEnabled; + private boolean allowRefreshDetachedEntity; + + // (JTA) transaction handling + private boolean jtaTrackByThread; + private boolean preferUserTransaction; + + // Statistics/Interceptor/observers + private boolean statisticsEnabled; + private Interceptor interceptor; + private Class statelessInterceptorClass; + private Supplier statelessInterceptorSupplier; + private StatementInspector statementInspector; + private List sessionFactoryObserverList = new ArrayList<>(); + private BaselineSessionEventsListenerBuilder baselineSessionEventsListenerBuilder; // not exposed on builder atm + + // persistence behavior + private CustomEntityDirtinessStrategy customEntityDirtinessStrategy; + private List entityNameResolvers = new ArrayList<>(); + private EntityNotFoundDelegate entityNotFoundDelegate; + private boolean identifierRollbackEnabled; + private EntityMode defaultEntityMode; + private EntityTuplizerFactory entityTuplizerFactory = new EntityTuplizerFactory(); + private boolean checkNullability; + private boolean initializeLazyStateOutsideTransactions; + private MultiTableBulkIdStrategy multiTableBulkIdStrategy; + private TempTableDdlTransactionHandling tempTableDdlTransactionHandling; + private BatchFetchStyle batchFetchStyle; + private boolean delayBatchFetchLoaderCreations; + private int defaultBatchFetchSize; + private Integer maximumFetchDepth; + private NullPrecedence defaultNullPrecedence; + private boolean orderUpdatesEnabled; + private boolean orderInsertsEnabled; + private boolean enhancementAsProxyEnabled; + + + // multi-tenancy + private MultiTenancyStrategy multiTenancyStrategy; + private CurrentTenantIdentifierResolver currentTenantIdentifierResolver; + + // Queries + private Map querySubstitutions; + private boolean namedQueryStartupCheckingEnabled; + private boolean conventionalJavaConstants; + private final boolean procedureParameterNullPassingEnabled; + private final boolean collectionJoinSubqueryRewriteEnabled; + private boolean jdbcStyleParamsZeroBased; + + // Caching + private boolean secondLevelCacheEnabled; + private boolean queryCacheEnabled; + private TimestampsCacheFactory timestampsCacheFactory; + private String cacheRegionPrefix; + private boolean minimalPutsEnabled; + private boolean structuredCacheEntriesEnabled; + private boolean directReferenceCacheEntriesEnabled; + private boolean autoEvictCollectionCache; + + // Schema tooling + private SchemaAutoTooling schemaAutoTooling; + + // JDBC Handling + private boolean getGeneratedKeysEnabled; + private int jdbcBatchSize; + private boolean jdbcBatchVersionedData; + private Integer jdbcFetchSize; + private boolean scrollableResultSetsEnabled; + private boolean commentsEnabled; + private PhysicalConnectionHandlingMode connectionHandlingMode; + private boolean connectionProviderDisablesAutoCommit; + private boolean wrapResultSetsEnabled; + private TimeZone jdbcTimeZone; + private boolean queryParametersValidationEnabled; + private LiteralHandlingMode criteriaLiteralHandlingMode; + private ImmutableEntityUpdateQueryHandlingMode immutableEntityUpdateQueryHandlingMode; + + private Map sqlFunctions; + + private JpaCompliance jpaCompliance; + + private boolean failOnPaginationOverCollectionFetchEnabled; + private boolean inClauseParameterPaddingEnabled; + + private boolean nativeExceptionHandling51Compliance; + + + @SuppressWarnings({"WeakerAccess", "deprecation"}) + public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { + this.serviceRegistry = serviceRegistry; + this.jpaBootstrap = context.isJpaBootstrap(); + + final StrategySelector strategySelector = serviceRegistry.getService( StrategySelector.class ); + ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class ); + final JdbcServices jdbcServices = serviceRegistry.getService( JdbcServices.class ); + + final Map configurationSettings = new HashMap(); + //noinspection unchecked + configurationSettings.putAll( jdbcServices.getJdbcEnvironment().getDialect().getDefaultProperties() ); + //noinspection unchecked + configurationSettings.putAll( cfgService.getSettings() ); + if ( cfgService == null ) { + cfgService = new ConfigurationServiceImpl( configurationSettings ); + ( (ConfigurationServiceImpl) cfgService ).injectServices( (ServiceRegistryImplementor) serviceRegistry ); + } + + this.beanManagerReference = configurationSettings.get( "javax.persistence.bean.manager" ); + this.validatorFactoryReference = configurationSettings.get( "javax.persistence.validation.factory" ); + + this.sessionFactoryName = (String) configurationSettings.get( SESSION_FACTORY_NAME ); + this.sessionFactoryNameAlsoJndiName = cfgService.getSetting( + SESSION_FACTORY_NAME_IS_JNDI, + BOOLEAN, + true + ); + this.jtaTransactionAccessEnabled = cfgService.getSetting( + ALLOW_JTA_TRANSACTION_ACCESS, + BOOLEAN, + true + ); + + this.allowRefreshDetachedEntity = cfgService.getSetting( + ALLOW_REFRESH_DETACHED_ENTITY, + BOOLEAN, + true + ); + + this.flushBeforeCompletionEnabled = cfgService.getSetting( FLUSH_BEFORE_COMPLETION, BOOLEAN, true ); + this.autoCloseSessionEnabled = cfgService.getSetting( AUTO_CLOSE_SESSION, BOOLEAN, false ); + + this.statisticsEnabled = cfgService.getSetting( GENERATE_STATISTICS, BOOLEAN, false ); + this.interceptor = determineInterceptor( configurationSettings, strategySelector ); + this.statelessInterceptorSupplier = determineStatelessInterceptor( configurationSettings, strategySelector ); + this.statementInspector = strategySelector.resolveStrategy( + StatementInspector.class, + configurationSettings.get( STATEMENT_INSPECTOR ) + ); + + // todo : expose this from builder? + final String autoSessionEventsListenerName = (String) configurationSettings.get( + AUTO_SESSION_EVENTS_LISTENER + ); + final Class autoSessionEventsListener = autoSessionEventsListenerName == null + ? null + : strategySelector.selectStrategyImplementor( SessionEventListener.class, autoSessionEventsListenerName ); + + final boolean logSessionMetrics = cfgService.getSetting( LOG_SESSION_METRICS, BOOLEAN, statisticsEnabled ); + this.baselineSessionEventsListenerBuilder = new BaselineSessionEventsListenerBuilder( logSessionMetrics, autoSessionEventsListener ); + + this.customEntityDirtinessStrategy = strategySelector.resolveDefaultableStrategy( + CustomEntityDirtinessStrategy.class, + configurationSettings.get( CUSTOM_ENTITY_DIRTINESS_STRATEGY ), + DefaultCustomEntityDirtinessStrategy.INSTANCE + ); + + this.entityNotFoundDelegate = StandardEntityNotFoundDelegate.INSTANCE; + this.identifierRollbackEnabled = cfgService.getSetting( USE_IDENTIFIER_ROLLBACK, BOOLEAN, false ); + this.defaultEntityMode = EntityMode.parse( (String) configurationSettings.get( DEFAULT_ENTITY_MODE ) ); + this.checkNullability = cfgService.getSetting( CHECK_NULLABILITY, BOOLEAN, true ); + this.initializeLazyStateOutsideTransactions = cfgService.getSetting( ENABLE_LAZY_LOAD_NO_TRANS, BOOLEAN, false ); + + this.multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configurationSettings ); + this.currentTenantIdentifierResolver = strategySelector.resolveStrategy( + CurrentTenantIdentifierResolver.class, + configurationSettings.get( MULTI_TENANT_IDENTIFIER_RESOLVER ) + ); + + this.multiTableBulkIdStrategy = strategySelector.resolveDefaultableStrategy( + MultiTableBulkIdStrategy.class, + configurationSettings.get( HQL_BULK_ID_STRATEGY ), + jdbcServices.getJdbcEnvironment().getDialect().getDefaultMultiTableBulkIdStrategy() + ); + + this.batchFetchStyle = BatchFetchStyle.interpret( configurationSettings.get( BATCH_FETCH_STYLE ) ); + this.delayBatchFetchLoaderCreations = cfgService.getSetting( DELAY_ENTITY_LOADER_CREATIONS, BOOLEAN, true ); + this.defaultBatchFetchSize = ConfigurationHelper.getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 ); + this.maximumFetchDepth = ConfigurationHelper.getInteger( MAX_FETCH_DEPTH, configurationSettings ); + final String defaultNullPrecedence = ConfigurationHelper.getString( + AvailableSettings.DEFAULT_NULL_ORDERING, configurationSettings, "none", "first", "last" + ); + this.defaultNullPrecedence = NullPrecedence.parse( defaultNullPrecedence ); + this.orderUpdatesEnabled = ConfigurationHelper.getBoolean( ORDER_UPDATES, configurationSettings ); + this.orderInsertsEnabled = ConfigurationHelper.getBoolean( ORDER_INSERTS, configurationSettings ); + this.enhancementAsProxyEnabled = ConfigurationHelper.getBoolean( ALLOW_ENHANCEMENT_AS_PROXY, configurationSettings ); + + this.jtaTrackByThread = cfgService.getSetting( JTA_TRACK_BY_THREAD, BOOLEAN, true ); + + this.querySubstitutions = ConfigurationHelper.toMap( QUERY_SUBSTITUTIONS, " ,=;:\n\t\r\f", configurationSettings ); + this.namedQueryStartupCheckingEnabled = cfgService.getSetting( QUERY_STARTUP_CHECKING, BOOLEAN, true ); + this.conventionalJavaConstants = cfgService.getSetting( + CONVENTIONAL_JAVA_CONSTANTS, BOOLEAN, true ); + this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false ); + this.collectionJoinSubqueryRewriteEnabled = cfgService.getSetting( COLLECTION_JOIN_SUBQUERY, BOOLEAN, true ); + + final RegionFactory regionFactory = serviceRegistry.getService( RegionFactory.class ); + if ( !NoCachingRegionFactory.class.isInstance( regionFactory ) ) { + this.secondLevelCacheEnabled = cfgService.getSetting( USE_SECOND_LEVEL_CACHE, BOOLEAN, true ); + this.queryCacheEnabled = cfgService.getSetting( USE_QUERY_CACHE, BOOLEAN, false ); + this.timestampsCacheFactory = strategySelector.resolveDefaultableStrategy( + TimestampsCacheFactory.class, + configurationSettings.get( QUERY_CACHE_FACTORY ), + StandardTimestampsCacheFactory.INSTANCE + ); + this.cacheRegionPrefix = ConfigurationHelper.extractPropertyValue( + CACHE_REGION_PREFIX, + configurationSettings + ); + this.minimalPutsEnabled = cfgService.getSetting( + USE_MINIMAL_PUTS, + BOOLEAN, + regionFactory.isMinimalPutsEnabledByDefault() + ); + this.structuredCacheEntriesEnabled = cfgService.getSetting( USE_STRUCTURED_CACHE, BOOLEAN, false ); + this.directReferenceCacheEntriesEnabled = cfgService.getSetting( + USE_DIRECT_REFERENCE_CACHE_ENTRIES, + BOOLEAN, + false + ); + this.autoEvictCollectionCache = cfgService.getSetting( AUTO_EVICT_COLLECTION_CACHE, BOOLEAN, false ); + } + else { + this.secondLevelCacheEnabled = false; + this.queryCacheEnabled = false; + this.timestampsCacheFactory = null; + this.cacheRegionPrefix = null; + this.minimalPutsEnabled = false; + this.structuredCacheEntriesEnabled = false; + this.directReferenceCacheEntriesEnabled = false; + this.autoEvictCollectionCache = false; + } + + try { + this.schemaAutoTooling = SchemaAutoTooling.interpret( (String) configurationSettings.get( AvailableSettings.HBM2DDL_AUTO ) ); + } + catch (Exception e) { + log.warn( e.getMessage() + " Ignoring" ); + } + + + final ExtractedDatabaseMetaData meta = jdbcServices.getExtractedMetaDataSupport(); + + this.tempTableDdlTransactionHandling = TempTableDdlTransactionHandling.NONE; + if ( meta.doesDataDefinitionCauseTransactionCommit() ) { + if ( meta.supportsDataDefinitionInTransaction() ) { + this.tempTableDdlTransactionHandling = TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT; + } + else { + this.tempTableDdlTransactionHandling = TempTableDdlTransactionHandling.ISOLATE; + } + } + + this.jdbcBatchSize = ConfigurationHelper.getInt( STATEMENT_BATCH_SIZE, configurationSettings, 0 ); + if ( !meta.supportsBatchUpdates() ) { + this.jdbcBatchSize = 0; + } + + this.jdbcBatchVersionedData = ConfigurationHelper.getBoolean( BATCH_VERSIONED_DATA, configurationSettings, true ); + this.scrollableResultSetsEnabled = ConfigurationHelper.getBoolean( + USE_SCROLLABLE_RESULTSET, + configurationSettings, + meta.supportsScrollableResults() + ); + this.wrapResultSetsEnabled = ConfigurationHelper.getBoolean( + WRAP_RESULT_SETS, + configurationSettings, + false + ); + this.getGeneratedKeysEnabled = ConfigurationHelper.getBoolean( + USE_GET_GENERATED_KEYS, + configurationSettings, + meta.supportsGetGeneratedKeys() + ); + this.jdbcFetchSize = ConfigurationHelper.getInteger( STATEMENT_FETCH_SIZE, configurationSettings ); + + this.connectionHandlingMode = interpretConnectionHandlingMode( configurationSettings, serviceRegistry ); + this.connectionProviderDisablesAutoCommit = ConfigurationHelper.getBoolean( + AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, + configurationSettings, + false + ); + + this.commentsEnabled = ConfigurationHelper.getBoolean( USE_SQL_COMMENTS, configurationSettings ); + + this.preferUserTransaction = ConfigurationHelper.getBoolean( PREFER_USER_TRANSACTION, configurationSettings, false ); + + this.allowOutOfTransactionUpdateOperations = ConfigurationHelper.getBoolean( + ALLOW_UPDATE_OUTSIDE_TRANSACTION, + configurationSettings, + false + ); + + this.releaseResourcesOnCloseEnabled = ConfigurationHelper.getBoolean( + DISCARD_PC_ON_CLOSE, + configurationSettings, + false + ); + Object jdbcTimeZoneValue = configurationSettings.get( + JDBC_TIME_ZONE + ); + + if ( jdbcTimeZoneValue instanceof TimeZone ) { + this.jdbcTimeZone = (TimeZone) jdbcTimeZoneValue; + } + else if ( jdbcTimeZoneValue instanceof ZoneId ) { + this.jdbcTimeZone = TimeZone.getTimeZone( (ZoneId) jdbcTimeZoneValue ); + } + else if ( jdbcTimeZoneValue instanceof String ) { + this.jdbcTimeZone = TimeZone.getTimeZone( ZoneId.of((String) jdbcTimeZoneValue) ); + } + else if ( jdbcTimeZoneValue != null ) { + throw new IllegalArgumentException( "Configuration property " + JDBC_TIME_ZONE + " value [" + jdbcTimeZoneValue + "] is not supported!" ); + } + + this.queryParametersValidationEnabled = ConfigurationHelper.getBoolean( + VALIDATE_QUERY_PARAMETERS, + configurationSettings, + true + ); + + this.criteriaLiteralHandlingMode = LiteralHandlingMode.interpret( + configurationSettings.get( CRITERIA_LITERAL_HANDLING_MODE ) + ); + + this.jdbcStyleParamsZeroBased = ConfigurationHelper.getBoolean( + JDBC_TYLE_PARAMS_ZERO_BASE, + configurationSettings, + false + ); + + // added the boolean parameter in case we want to define some form of "all" as discussed + this.jpaCompliance = context.getJpaCompliance(); + + this.failOnPaginationOverCollectionFetchEnabled = ConfigurationHelper.getBoolean( + FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH, + configurationSettings, + false + ); + + this.immutableEntityUpdateQueryHandlingMode = ImmutableEntityUpdateQueryHandlingMode.interpret( + configurationSettings.get( IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE ) + ); + + this.inClauseParameterPaddingEnabled = ConfigurationHelper.getBoolean( + IN_CLAUSE_PARAMETER_PADDING, + configurationSettings, + false + ); + + this.nativeExceptionHandling51Compliance = ConfigurationHelper.getBoolean( + NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE, + configurationSettings, + false + ); + if ( context.isJpaBootstrap() && nativeExceptionHandling51Compliance ) { + log.nativeExceptionHandling51ComplianceJpaBootstrapping(); + this.nativeExceptionHandling51Compliance = false; + } + } + + @SuppressWarnings("deprecation") + private static Interceptor determineInterceptor(Map configurationSettings, StrategySelector strategySelector) { + Object setting = configurationSettings.get( INTERCEPTOR ); + if ( setting == null ) { + // try the legacy (deprecated) JPA name + setting = configurationSettings.get( org.hibernate.jpa.AvailableSettings.INTERCEPTOR ); + if ( setting != null ) { + DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( + org.hibernate.jpa.AvailableSettings.INTERCEPTOR, + INTERCEPTOR + ); + } + } + + return strategySelector.resolveStrategy( + Interceptor.class, + setting + ); + } + + @SuppressWarnings({"unchecked", "deprecation"}) + private static Supplier determineStatelessInterceptor( + Map configurationSettings, + StrategySelector strategySelector) { + Object setting = configurationSettings.get( SESSION_SCOPED_INTERCEPTOR ); + if ( setting == null ) { + // try the legacy (deprecated) JPA name + setting = configurationSettings.get( org.hibernate.jpa.AvailableSettings.SESSION_INTERCEPTOR ); + if ( setting != null ) { + DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( + org.hibernate.jpa.AvailableSettings.SESSION_INTERCEPTOR, + SESSION_SCOPED_INTERCEPTOR + ); + } + } + + if ( setting == null ) { + return null; + } + else if ( setting instanceof Supplier ) { + return (Supplier) setting; + } + else if ( setting instanceof Class ) { + Class clazz = (Class) setting; + return interceptorSupplier( clazz ); + } + else { + return interceptorSupplier( + strategySelector.selectStrategyImplementor( + Interceptor.class, + setting.toString() + ) + ); + } + } + + + private static Supplier interceptorSupplier(Class clazz) { + return () -> { + try { + return clazz.newInstance(); + } + catch (InstantiationException | IllegalAccessException e) { + throw new HibernateException( "Could not supply session-scoped SessionFactory Interceptor", e ); + } + }; + } + + @SuppressWarnings("deprecation") + private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( + Map configurationSettings, + StandardServiceRegistry serviceRegistry) { + final PhysicalConnectionHandlingMode specifiedHandlingMode = PhysicalConnectionHandlingMode.interpret( + configurationSettings.get( CONNECTION_HANDLING ) + ); + + if ( specifiedHandlingMode != null ) { + return specifiedHandlingMode; + } + + + final TransactionCoordinatorBuilder transactionCoordinatorBuilder = serviceRegistry.getService( TransactionCoordinatorBuilder.class ); + + // see if the deprecated ConnectionAcquisitionMode/ConnectionReleaseMode were used.. + final ConnectionAcquisitionMode specifiedAcquisitionMode = ConnectionAcquisitionMode.interpret( + configurationSettings.get( ACQUIRE_CONNECTIONS ) + ); + final ConnectionReleaseMode specifiedReleaseMode = ConnectionReleaseMode.interpret( + configurationSettings.get( RELEASE_CONNECTIONS ) + ); + if ( specifiedAcquisitionMode != null || specifiedReleaseMode != null ) { + return interpretConnectionHandlingMode( specifiedAcquisitionMode, specifiedReleaseMode, configurationSettings, transactionCoordinatorBuilder ); + } + + return transactionCoordinatorBuilder.getDefaultConnectionHandlingMode(); + } + + @SuppressWarnings("deprecation") + private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( + ConnectionAcquisitionMode specifiedAcquisitionMode, + ConnectionReleaseMode specifiedReleaseMode, + Map configurationSettings, + TransactionCoordinatorBuilder transactionCoordinatorBuilder) { + DeprecationLogger.DEPRECATION_LOGGER.logUseOfDeprecatedConnectionHandlingSettings(); + + final ConnectionAcquisitionMode effectiveAcquisitionMode = specifiedAcquisitionMode == null + ? ConnectionAcquisitionMode.AS_NEEDED + : specifiedAcquisitionMode; + + final ConnectionReleaseMode effectiveReleaseMode; + if ( specifiedReleaseMode == null ) { + // check the actual setting. If we get in here it *should* be "auto" or null + final String releaseModeName = ConfigurationHelper.getString( RELEASE_CONNECTIONS, configurationSettings, "auto" ); + assert "auto".equalsIgnoreCase( releaseModeName ); + // nothing was specified (or someone happened to configure the "magic" value) + if ( effectiveAcquisitionMode == ConnectionAcquisitionMode.IMMEDIATELY ) { + effectiveReleaseMode = ConnectionReleaseMode.ON_CLOSE; + } + else { + effectiveReleaseMode = transactionCoordinatorBuilder.getDefaultConnectionReleaseMode(); + } + } + else { + effectiveReleaseMode = specifiedReleaseMode; + } + + return PhysicalConnectionHandlingMode.interpret( effectiveAcquisitionMode, effectiveReleaseMode ); + } + + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SessionFactoryOptionsState + + @Override + public String getUuid() { + return this.uuid; + } + + @Override + public StandardServiceRegistry getServiceRegistry() { + return serviceRegistry; + } + + @Override + public boolean isJpaBootstrap() { + return jpaBootstrap; + } + + @Override + public boolean isJtaTransactionAccessEnabled() { + return jtaTransactionAccessEnabled; + } + + @Override + public boolean isAllowRefreshDetachedEntity() { + return allowRefreshDetachedEntity; + } + + @Override + public boolean isAllowOutOfTransactionUpdateOperations() { + return allowOutOfTransactionUpdateOperations; + } + + + @Override + public boolean isReleaseResourcesOnCloseEnabled() { + return releaseResourcesOnCloseEnabled; + } + + @Override + public Object getBeanManagerReference() { + return beanManagerReference; + } + + @Override + public Object getValidatorFactoryReference() { + return validatorFactoryReference; + } + + @Override + public String getSessionFactoryName() { + return sessionFactoryName; + } + + @Override + public boolean isSessionFactoryNameAlsoJndiName() { + return sessionFactoryNameAlsoJndiName; + } + + @Override + public boolean isFlushBeforeCompletionEnabled() { + return flushBeforeCompletionEnabled; + } + + @Override + public boolean isAutoCloseSessionEnabled() { + return autoCloseSessionEnabled; + } + + @Override + public boolean isStatisticsEnabled() { + return statisticsEnabled; + } + + @Override + public Interceptor getInterceptor() { + return interceptor == null ? EmptyInterceptor.INSTANCE : interceptor; + } + + @Override + public Class getStatelessInterceptorImplementor() { + return statelessInterceptorClass; + } + + @Override + public Supplier getStatelessInterceptorImplementorSupplier() { + return statelessInterceptorSupplier; + } + + @Override + public StatementInspector getStatementInspector() { + return statementInspector; + } + + @Override + public SessionFactoryObserver[] getSessionFactoryObservers() { + return sessionFactoryObserverList.toArray( new SessionFactoryObserver[ sessionFactoryObserverList.size() ] ); + } + + @Override + public BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder() { + return baselineSessionEventsListenerBuilder; + } + + @Override + public boolean isIdentifierRollbackEnabled() { + return identifierRollbackEnabled; + } + + @Override + public EntityMode getDefaultEntityMode() { + return defaultEntityMode; + } + + @Override + public EntityTuplizerFactory getEntityTuplizerFactory() { + return entityTuplizerFactory; + } + + @Override + public boolean isCheckNullability() { + return checkNullability; + } + + @Override + public boolean isInitializeLazyStateOutsideTransactionsEnabled() { + return initializeLazyStateOutsideTransactions; + } + + @Override + public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy() { + return multiTableBulkIdStrategy; + } + + @Override + public TempTableDdlTransactionHandling getTempTableDdlTransactionHandling() { + return tempTableDdlTransactionHandling; + } + + @Override + public BatchFetchStyle getBatchFetchStyle() { + return batchFetchStyle; + } + + @Override + public boolean isDelayBatchFetchLoaderCreationsEnabled() { + return delayBatchFetchLoaderCreations; + } + + @Override + public int getDefaultBatchFetchSize() { + return defaultBatchFetchSize; + } + + @Override + public Integer getMaximumFetchDepth() { + return maximumFetchDepth; + } + + @Override + public NullPrecedence getDefaultNullPrecedence() { + return defaultNullPrecedence; + } + + @Override + public boolean isOrderUpdatesEnabled() { + return orderUpdatesEnabled; + } + + @Override + public boolean isOrderInsertsEnabled() { + return orderInsertsEnabled; + } + + @Override + public MultiTenancyStrategy getMultiTenancyStrategy() { + return multiTenancyStrategy; + } + + @Override + public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() { + return currentTenantIdentifierResolver; + } + + @Override + public boolean isJtaTrackByThread() { + return jtaTrackByThread; + } + + @Override + public Map getQuerySubstitutions() { + return querySubstitutions; + } + + @Override + public boolean isNamedQueryStartupCheckingEnabled() { + return namedQueryStartupCheckingEnabled; + } + + @Override + public boolean isConventionalJavaConstants() { + return conventionalJavaConstants; + } + + @Override + public boolean isProcedureParameterNullPassingEnabled() { + return procedureParameterNullPassingEnabled; + } + + @Override + public boolean isCollectionJoinSubqueryRewriteEnabled() { + return collectionJoinSubqueryRewriteEnabled; + } + + @Override + public boolean isSecondLevelCacheEnabled() { + return secondLevelCacheEnabled; + } + + @Override + public boolean isQueryCacheEnabled() { + return queryCacheEnabled; + } + + @Override + public TimestampsCacheFactory getTimestampsCacheFactory() { + return timestampsCacheFactory; + } + + @Override + public String getCacheRegionPrefix() { + return cacheRegionPrefix; + } + + @Override + public boolean isMinimalPutsEnabled() { + return minimalPutsEnabled; + } + + @Override + public boolean isStructuredCacheEntriesEnabled() { + return structuredCacheEntriesEnabled; + } + + @Override + public boolean isDirectReferenceCacheEntriesEnabled() { + return directReferenceCacheEntriesEnabled; + } + + @Override + public boolean isAutoEvictCollectionCache() { + return autoEvictCollectionCache; + } + + @Override + public SchemaAutoTooling getSchemaAutoTooling() { + return schemaAutoTooling; + } + + @Override + public int getJdbcBatchSize() { + return jdbcBatchSize; + } + + @Override + public boolean isJdbcBatchVersionedData() { + return jdbcBatchVersionedData; + } + + @Override + public boolean isScrollableResultSetsEnabled() { + return scrollableResultSetsEnabled; + } + + @Override + public boolean isWrapResultSetsEnabled() { + return wrapResultSetsEnabled; + } + + @Override + public boolean isGetGeneratedKeysEnabled() { + return getGeneratedKeysEnabled; + } + + @Override + public Integer getJdbcFetchSize() { + return jdbcFetchSize; + } + + @Override + public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() { + return connectionHandlingMode; + } + + @Override + public void setCheckNullability(boolean enabled) { + this.checkNullability = enabled; + } + + @Override + public ConnectionReleaseMode getConnectionReleaseMode() { + return getPhysicalConnectionHandlingMode().getReleaseMode(); + } + + @Override + public boolean doesConnectionProviderDisableAutoCommit() { + return connectionProviderDisablesAutoCommit; + } + + @Override + public boolean isCommentsEnabled() { + return commentsEnabled; + } + + @Override + public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() { + return customEntityDirtinessStrategy; + } + + @Override + public EntityNameResolver[] getEntityNameResolvers() { + return entityNameResolvers.toArray( new EntityNameResolver[ entityNameResolvers.size() ] ); + } + + @Override + public EntityNotFoundDelegate getEntityNotFoundDelegate() { + return entityNotFoundDelegate; + } + + @Override + public Map getCustomSqlFunctionMap() { + return sqlFunctions == null ? Collections.emptyMap() : sqlFunctions; + } + + @Override + public boolean isPreferUserTransaction() { + return this.preferUserTransaction; + } + + @Override + public TimeZone getJdbcTimeZone() { + return this.jdbcTimeZone; + } + + @Override + public boolean isQueryParametersValidationEnabled() { + return this.queryParametersValidationEnabled; + } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return this.criteriaLiteralHandlingMode; + } + + @Override + public ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandlingMode() { + return immutableEntityUpdateQueryHandlingMode; + } + + @Override + public boolean jdbcStyleParamsZeroBased() { + return this.jdbcStyleParamsZeroBased; + } + + @Override + public boolean isFailOnPaginationOverCollectionFetchEnabled() { + return this.failOnPaginationOverCollectionFetchEnabled; + } + + @Override + public boolean inClauseParameterPaddingEnabled() { + return this.inClauseParameterPaddingEnabled; + } + + @Override + public JpaCompliance getJpaCompliance() { + return jpaCompliance; + } + + @Override + public boolean nativeExceptionHandling51Compliance() { + return nativeExceptionHandling51Compliance; + } + + @Override + public boolean isEnhancementAsProxyEnabled() { + return enhancementAsProxyEnabled; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // In-flight mutation access + + public void applyBeanManager(Object beanManager) { + this.beanManagerReference = beanManager; + } + + public void applyValidatorFactory(Object validatorFactory) { + this.validatorFactoryReference = validatorFactory; + } + + public void applySessionFactoryName(String sessionFactoryName) { + this.sessionFactoryName = sessionFactoryName; + } + + public void enableSessionFactoryNameAsJndiName(boolean isJndiName) { + this.sessionFactoryNameAlsoJndiName = isJndiName; + } + + public void enableSessionAutoClosing(boolean autoClosingEnabled) { + this.autoCloseSessionEnabled = autoClosingEnabled; + } + + public void enableSessionAutoFlushing(boolean flushBeforeCompletionEnabled) { + this.flushBeforeCompletionEnabled = flushBeforeCompletionEnabled; + } + + public void enableJtaTrackingByThread(boolean enabled) { + this.jtaTrackByThread = enabled; + } + + public void enablePreferUserTransaction(boolean preferUserTransaction) { + this.preferUserTransaction = preferUserTransaction; + } + + public void enableStatisticsSupport(boolean enabled) { + this.statisticsEnabled = enabled; + } + + public void addSessionFactoryObservers(SessionFactoryObserver... observers) { + Collections.addAll( this.sessionFactoryObserverList, observers ); + } + + public void applyInterceptor(Interceptor interceptor) { + this.interceptor = interceptor; + } + + public void applyStatelessInterceptor(Class statelessInterceptorClass) { + this.statelessInterceptorClass = statelessInterceptorClass; + } + + public void applyStatelessInterceptorSupplier(Supplier statelessInterceptorSupplier) { + this.statelessInterceptorSupplier = statelessInterceptorSupplier; + } + + public void applyStatementInspector(StatementInspector statementInspector) { + this.statementInspector = statementInspector; + } + + public void applyCustomEntityDirtinessStrategy(CustomEntityDirtinessStrategy strategy) { + this.customEntityDirtinessStrategy = strategy; + } + + public void addEntityNameResolvers(EntityNameResolver... entityNameResolvers) { + Collections.addAll( this.entityNameResolvers, entityNameResolvers ); + } + + public void applyEntityNotFoundDelegate(EntityNotFoundDelegate entityNotFoundDelegate) { + this.entityNotFoundDelegate = entityNotFoundDelegate; + } + + public void enableIdentifierRollbackSupport(boolean enabled) { + this.identifierRollbackEnabled = enabled; + } + + public void applyDefaultEntityMode(EntityMode entityMode) { + this.defaultEntityMode = entityMode; + } + + public void enableNullabilityChecking(boolean enabled) { + this.checkNullability = enabled; + } + + public void allowLazyInitializationOutsideTransaction(boolean enabled) { + this.initializeLazyStateOutsideTransactions = enabled; + } + + public void applyEntityTuplizerFactory(EntityTuplizerFactory entityTuplizerFactory) { + this.entityTuplizerFactory = entityTuplizerFactory; + } + + public void applyEntityTuplizer(EntityMode entityMode, Class tuplizerClass) { + this.entityTuplizerFactory.registerDefaultTuplizerClass( entityMode, tuplizerClass ); + } + + public void applyMultiTableBulkIdStrategy(MultiTableBulkIdStrategy strategy) { + this.multiTableBulkIdStrategy = strategy; + } + + public void applyTempTableDdlTransactionHandling(TempTableDdlTransactionHandling handling) { + this.tempTableDdlTransactionHandling = handling; + } + + public void applyBatchFetchStyle(BatchFetchStyle style) { + this.batchFetchStyle = style; + } + + public void applyDelayedEntityLoaderCreations(boolean delay) { + this.delayBatchFetchLoaderCreations = delay; + } + + public void applyDefaultBatchFetchSize(int size) { + this.defaultBatchFetchSize = size; + } + + public void applyMaximumFetchDepth(int depth) { + this.maximumFetchDepth = depth; + } + + public void applyDefaultNullPrecedence(NullPrecedence nullPrecedence) { + this.defaultNullPrecedence = nullPrecedence; + } + + public void enableOrderingOfInserts(boolean enabled) { + this.orderInsertsEnabled = enabled; + } + + public void enableOrderingOfUpdates(boolean enabled) { + this.orderUpdatesEnabled = enabled; + } + + public void applyMultiTenancyStrategy(MultiTenancyStrategy strategy) { + this.multiTenancyStrategy = strategy; + } + + public void applyCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver resolver) { + this.currentTenantIdentifierResolver = resolver; + } + + @SuppressWarnings("unchecked") + public void applyQuerySubstitutions(Map substitutions) { + this.querySubstitutions.putAll( substitutions ); + } + + public void enableNamedQueryCheckingOnStartup(boolean enabled) { + this.namedQueryStartupCheckingEnabled = enabled; + } + + public void enableSecondLevelCacheSupport(boolean enabled) { + this.secondLevelCacheEnabled = enabled; + } + + public void enableQueryCacheSupport(boolean enabled) { + this.queryCacheEnabled = enabled; + } + + public void applyTimestampsCacheFactory(TimestampsCacheFactory factory) { + this.timestampsCacheFactory = factory; + } + + public void applyCacheRegionPrefix(String prefix) { + this.cacheRegionPrefix = prefix; + } + + public void enableMinimalPuts(boolean enabled) { + this.minimalPutsEnabled = enabled; + } + + public void enabledStructuredCacheEntries(boolean enabled) { + this.structuredCacheEntriesEnabled = enabled; + } + + public void allowDirectReferenceCacheEntries(boolean enabled) { + this.directReferenceCacheEntriesEnabled = enabled; + } + + public void enableAutoEvictCollectionCaches(boolean enabled) { + this.autoEvictCollectionCache = enabled; + } + + public void applyJdbcBatchSize(int size) { + this.jdbcBatchSize = size; + } + + public void enableJdbcBatchingForVersionedEntities(boolean enabled) { + this.jdbcBatchVersionedData = enabled; + } + + public void enableScrollableResultSupport(boolean enabled) { + this.scrollableResultSetsEnabled = enabled; + } + + public void enableResultSetWrappingSupport(boolean enabled) { + this.wrapResultSetsEnabled = enabled; + } + + public void enableGeneratedKeysSupport(boolean enabled) { + this.getGeneratedKeysEnabled = enabled; + } + + public void applyJdbcFetchSize(int size) { + this.jdbcFetchSize = size; + } + + public void applyConnectionHandlingMode(PhysicalConnectionHandlingMode mode) { + this.connectionHandlingMode = mode; + } + + public void applyConnectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) { + if ( this.connectionHandlingMode == null ) { + this.connectionHandlingMode = PhysicalConnectionHandlingMode.interpret( + ConnectionAcquisitionMode.AS_NEEDED, + connectionReleaseMode + ); + } + else { + this.connectionHandlingMode = PhysicalConnectionHandlingMode.interpret( + this.connectionHandlingMode.getAcquisitionMode(), + connectionReleaseMode + ); + } + } + + public void applyConnectionProviderDisablesAutoCommit(boolean providerDisablesAutoCommit) { + this.connectionProviderDisablesAutoCommit = providerDisablesAutoCommit; + } + + public void enableCommentsSupport(boolean enabled) { + this.commentsEnabled = enabled; + } + + public void applySqlFunction(String registrationName, SQLFunction sqlFunction) { + if ( this.sqlFunctions == null ) { + this.sqlFunctions = new HashMap<>(); + } + this.sqlFunctions.put( registrationName, sqlFunction ); + } + + public void allowOutOfTransactionUpdateOperations(boolean allow) { + this.allowOutOfTransactionUpdateOperations = allow; + } + + public void enableReleaseResourcesOnClose(boolean enable) { + this.releaseResourcesOnCloseEnabled = enable; + } + + public void enableStrictJpaQueryLanguageCompliance(boolean enabled) { + enableJpaQueryCompliance( enabled ); + } + + public void enableJpaQueryCompliance(boolean enabled) { + mutableJpaCompliance().setQueryCompliance( enabled ); + } + + private MutableJpaCompliance mutableJpaCompliance() { + if ( ! MutableJpaCompliance.class.isInstance( this.jpaCompliance ) ) { + throw new IllegalStateException( "JpaCompliance is no longer mutable" ); + } + + return (MutableJpaCompliance) this.jpaCompliance; + } + + public void enableJpaTransactionCompliance(boolean enabled) { + mutableJpaCompliance().setTransactionCompliance( enabled ); + } + + public void enableJpaListCompliance(boolean enabled) { + mutableJpaCompliance().setListCompliance( enabled ); + } + + public void enableJpaClosedCompliance(boolean enabled) { + mutableJpaCompliance().setClosedCompliance( enabled ); + } + + public void enableJpaProxyCompliance(boolean enabled) { + mutableJpaCompliance().setProxyCompliance( enabled ); + } + + public void enableJpaCachingCompliance(boolean enabled) { + mutableJpaCompliance().setCachingCompliance( enabled ); + } + + public void disableRefreshDetachedEntity() { + this.allowRefreshDetachedEntity = false; + } + + public void disableJtaTransactionAccess() { + this.jtaTransactionAccessEnabled = false; + } + + public void enableJdbcStyleParamsZeroBased() { + this.jdbcStyleParamsZeroBased = true; + } + + public SessionFactoryOptions buildOptions() { + if ( MutableJpaCompliance.class.isInstance( this.jpaCompliance ) ) { + this.jpaCompliance = mutableJpaCompliance().immutableCopy(); + } + + return this; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java deleted file mode 100644 index 40ead867bab7..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java +++ /dev/null @@ -1,530 +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.boot.internal; - -import java.util.Map; -import java.util.TimeZone; - -import org.hibernate.ConnectionReleaseMode; -import org.hibernate.CustomEntityDirtinessStrategy; -import org.hibernate.EntityMode; -import org.hibernate.EntityNameResolver; -import org.hibernate.Interceptor; -import org.hibernate.MultiTenancyStrategy; -import org.hibernate.NullPrecedence; -import org.hibernate.SessionFactoryObserver; -import org.hibernate.boot.SchemaAutoTooling; -import org.hibernate.boot.TempTableDdlTransactionHandling; -import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.spi.QueryCacheFactory; -import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; -import org.hibernate.context.spi.CurrentTenantIdentifierResolver; -import org.hibernate.dialect.function.SQLFunction; -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.loader.BatchFetchStyle; -import org.hibernate.proxy.EntityNotFoundDelegate; -import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; -import org.hibernate.resource.jdbc.spi.StatementInspector; -import org.hibernate.tuple.entity.EntityTuplizerFactory; - -/** - * Standard implementation of SessionFactoryOptions - * - * @author Steve Ebersole - */ -public class SessionFactoryOptionsImpl implements SessionFactoryOptions { - private final StandardServiceRegistry serviceRegistry; - - // integration - private final Object beanManagerReference; - private final Object validatorFactoryReference; - - // SessionFactory behavior - private boolean jpaBootstrap; - private final String sessionFactoryName; - private final boolean sessionFactoryNameAlsoJndiName; - - // Session behavior - private final boolean flushBeforeCompletionEnabled; - private final boolean autoCloseSessionEnabled; - private boolean jtaTransactionAccessEnabled; - private boolean allowRefreshDetachedEntity; - - private boolean allowOutOfTransactionUpdateOperations; - private boolean releaseResourcesOnCloseEnabled; - - // transaction handling - private final boolean jtaTrackByThread; - private final boolean preferUserTransaction; - - // Statistics/Interceptor/observers - private final boolean statisticsEnabled; - private final Interceptor interceptor; - private Class statelessInterceptorClass; - private final StatementInspector statementInspector; - private final SessionFactoryObserver[] sessionFactoryObserverList; - private final BaselineSessionEventsListenerBuilder baselineSessionEventsListenerBuilder; // not exposed on builder atm - - // persistence behavior - private final CustomEntityDirtinessStrategy customEntityDirtinessStrategy; - private final EntityNameResolver[] entityNameResolvers; - private final EntityNotFoundDelegate entityNotFoundDelegate; - private final boolean identifierRollbackEnabled; - private final EntityMode defaultEntityMode; - private final EntityTuplizerFactory entityTuplizerFactory; - private boolean checkNullability; - private final boolean initializeLazyStateOutsideTransactions; - private final MultiTableBulkIdStrategy multiTableBulkIdStrategy; - private final TempTableDdlTransactionHandling tempTableDdlTransactionHandling; - private final BatchFetchStyle batchFetchStyle; - private final int defaultBatchFetchSize; - private final Integer maximumFetchDepth; - private final NullPrecedence defaultNullPrecedence; - private final boolean orderUpdatesEnabled; - private final boolean orderInsertsEnabled; - - // multi-tenancy - private final MultiTenancyStrategy multiTenancyStrategy; - private final CurrentTenantIdentifierResolver currentTenantIdentifierResolver; - - // Queries - private final Map querySubstitutions; - private final boolean strictJpaQueryLanguageCompliance; - private final boolean namedQueryStartupCheckingEnabled; - private final boolean conventionalJavaConstants; - private final boolean procedureParameterNullPassingEnabled; - private final boolean collectionJoinSubqueryRewriteEnabled; - - // Caching - private final boolean secondLevelCacheEnabled; - private final boolean queryCacheEnabled; - private final QueryCacheFactory queryCacheFactory; - private final String cacheRegionPrefix; - private final boolean minimalPutsEnabled; - private final boolean structuredCacheEntriesEnabled; - private final boolean directReferenceCacheEntriesEnabled; - private final boolean autoEvictCollectionCache; - - // Schema tooling - private final SchemaAutoTooling schemaAutoTooling; - - // JDBC Handling - private final boolean getGeneratedKeysEnabled; - private final int jdbcBatchSize; - private final boolean jdbcBatchVersionedData; - private final Integer jdbcFetchSize; - private final boolean scrollableResultSetsEnabled; - private final boolean commentsEnabled; - private final PhysicalConnectionHandlingMode physicalConnectionHandlingMode; - private final boolean wrapResultSetsEnabled; - private final TimeZone jdbcTimeZone; - - private final Map sqlFunctions; - - public SessionFactoryOptionsImpl(SessionFactoryOptionsState state) { - this.serviceRegistry = state.getServiceRegistry(); - - this.beanManagerReference = state.getBeanManagerReference(); - this.validatorFactoryReference = state.getValidatorFactoryReference(); - - this.jpaBootstrap = state.isJpaBootstrap(); - this.jtaTransactionAccessEnabled = state.isJtaTransactionAccessEnabled(); - this.allowRefreshDetachedEntity = state.isAllowRefreshDetachedEntity(); - this.allowOutOfTransactionUpdateOperations = state.isAllowOutOfTransactionUpdateOperations(); - this.sessionFactoryName = state.getSessionFactoryName(); - this.sessionFactoryNameAlsoJndiName = state.isSessionFactoryNameAlsoJndiName(); - - this.flushBeforeCompletionEnabled = state.isFlushBeforeCompletionEnabled(); - this.autoCloseSessionEnabled = state.isAutoCloseSessionEnabled(); - this.releaseResourcesOnCloseEnabled = state.isReleaseResourcesOnCloseEnabled(); - - this.jtaTrackByThread = state.isJtaTrackByThread(); - this.preferUserTransaction = state.isPreferUserTransaction(); - - this.statisticsEnabled = state.isStatisticsEnabled(); - this.interceptor = state.getInterceptor(); - this.statelessInterceptorClass = state.getStatelessInterceptorImplementor(); - this.statementInspector = state.getStatementInspector(); - this.sessionFactoryObserverList = state.getSessionFactoryObservers(); - this.baselineSessionEventsListenerBuilder = state.getBaselineSessionEventsListenerBuilder(); - - this.customEntityDirtinessStrategy = state.getCustomEntityDirtinessStrategy(); - this.entityNameResolvers = state.getEntityNameResolvers(); - this.entityNotFoundDelegate = state.getEntityNotFoundDelegate(); - this.identifierRollbackEnabled = state.isIdentifierRollbackEnabled(); - this.defaultEntityMode = state.getDefaultEntityMode(); - this.entityTuplizerFactory = state.getEntityTuplizerFactory(); - this.checkNullability = state.isCheckNullability(); - this.initializeLazyStateOutsideTransactions = state.isInitializeLazyStateOutsideTransactionsEnabled(); - this.multiTableBulkIdStrategy = state.getMultiTableBulkIdStrategy(); - this.tempTableDdlTransactionHandling = state.getTempTableDdlTransactionHandling(); - this.batchFetchStyle = state.getBatchFetchStyle(); - this.defaultBatchFetchSize = state.getDefaultBatchFetchSize(); - this.maximumFetchDepth = state.getMaximumFetchDepth(); - this.defaultNullPrecedence = state.getDefaultNullPrecedence(); - this.orderUpdatesEnabled = state.isOrderUpdatesEnabled(); - this.orderInsertsEnabled = state.isOrderInsertsEnabled(); - - this.multiTenancyStrategy = state.getMultiTenancyStrategy(); - this.currentTenantIdentifierResolver = state.getCurrentTenantIdentifierResolver(); - - this.querySubstitutions = state.getQuerySubstitutions(); - this.strictJpaQueryLanguageCompliance = state.isStrictJpaQueryLanguageCompliance(); - this.namedQueryStartupCheckingEnabled = state.isNamedQueryStartupCheckingEnabled(); - this.conventionalJavaConstants = state.isConventionalJavaConstants(); - this.procedureParameterNullPassingEnabled = state.isProcedureParameterNullPassingEnabled(); - this.collectionJoinSubqueryRewriteEnabled = state.isCollectionJoinSubqueryRewriteEnabled(); - - this.secondLevelCacheEnabled = state.isSecondLevelCacheEnabled(); - this.queryCacheEnabled = state.isQueryCacheEnabled(); - this.queryCacheFactory = state.getQueryCacheFactory(); - this.cacheRegionPrefix = state.getCacheRegionPrefix(); - this.minimalPutsEnabled = state.isMinimalPutsEnabled(); - this.structuredCacheEntriesEnabled = state.isStructuredCacheEntriesEnabled(); - this.directReferenceCacheEntriesEnabled = state.isDirectReferenceCacheEntriesEnabled(); - this.autoEvictCollectionCache = state.isAutoEvictCollectionCache(); - - this.schemaAutoTooling = state.getSchemaAutoTooling(); - this.physicalConnectionHandlingMode = state.getPhysicalConnectionHandlingMode(); - this.getGeneratedKeysEnabled = state.isGetGeneratedKeysEnabled(); - this.jdbcBatchSize = state.getJdbcBatchSize(); - this.jdbcBatchVersionedData = state.isJdbcBatchVersionedData(); - this.jdbcFetchSize = state.getJdbcFetchSize(); - this.scrollableResultSetsEnabled = state.isScrollableResultSetsEnabled(); - this.wrapResultSetsEnabled = state.isWrapResultSetsEnabled(); - this.commentsEnabled = state.isCommentsEnabled(); - - this.sqlFunctions = state.getCustomSqlFunctionMap(); - - this.jdbcTimeZone = state.getJdbcTimeZone(); - } - - @Override - public StandardServiceRegistry getServiceRegistry() { - return serviceRegistry; - } - - @Override - public boolean isJpaBootstrap() { - return jpaBootstrap; - } - - @Override - public boolean isJtaTransactionAccessEnabled() { - return jtaTransactionAccessEnabled; - } - - @Override - public boolean isAllowRefreshDetachedEntity() { - return allowRefreshDetachedEntity; - } - - @Override - public Object getBeanManagerReference() { - return beanManagerReference; - } - - @Override - public Object getValidatorFactoryReference() { - return validatorFactoryReference; - } - - @Override - public String getSessionFactoryName() { - return sessionFactoryName; - } - - @Override - public boolean isSessionFactoryNameAlsoJndiName() { - return sessionFactoryNameAlsoJndiName; - } - - @Override - public boolean isFlushBeforeCompletionEnabled() { - return flushBeforeCompletionEnabled; - } - - @Override - public boolean isAutoCloseSessionEnabled() { - return autoCloseSessionEnabled; - } - - @Override - public boolean isStatisticsEnabled() { - return statisticsEnabled; - } - - @Override - public Interceptor getInterceptor() { - return interceptor; - } - - @Override - public Class getStatelessInterceptorImplementor() { - return statelessInterceptorClass; - } - - @Override - public StatementInspector getStatementInspector() { - return statementInspector; - } - - @Override - public BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder() { - return baselineSessionEventsListenerBuilder; - } - - @Override - public SessionFactoryObserver[] getSessionFactoryObservers() { - return sessionFactoryObserverList; - } - - @Override - public boolean isIdentifierRollbackEnabled() { - return identifierRollbackEnabled; - } - - @Override - public EntityMode getDefaultEntityMode() { - return defaultEntityMode; - } - - public EntityTuplizerFactory getEntityTuplizerFactory() { - return entityTuplizerFactory; - } - - @Override - public boolean isCheckNullability() { - return checkNullability; - } - - @Override - public boolean isInitializeLazyStateOutsideTransactionsEnabled() { - return initializeLazyStateOutsideTransactions; - } - - @Override - public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy() { - return multiTableBulkIdStrategy; - } - - @Override - public TempTableDdlTransactionHandling getTempTableDdlTransactionHandling() { - return tempTableDdlTransactionHandling; - } - - @Override - public BatchFetchStyle getBatchFetchStyle() { - return batchFetchStyle; - } - - @Override - public int getDefaultBatchFetchSize() { - return defaultBatchFetchSize; - } - - @Override - public Integer getMaximumFetchDepth() { - return maximumFetchDepth; - } - - @Override - public NullPrecedence getDefaultNullPrecedence() { - return defaultNullPrecedence; - } - - @Override - public boolean isOrderUpdatesEnabled() { - return orderUpdatesEnabled; - } - - @Override - public boolean isOrderInsertsEnabled() { - return orderInsertsEnabled; - } - - @Override - public MultiTenancyStrategy getMultiTenancyStrategy() { - return multiTenancyStrategy; - } - - @Override - public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() { - return currentTenantIdentifierResolver; - } - - @Override - public boolean isJtaTrackByThread() { - return jtaTrackByThread; - } - - @Override - public Map getQuerySubstitutions() { - return querySubstitutions; - } - - @Override - public boolean isStrictJpaQueryLanguageCompliance() { - return strictJpaQueryLanguageCompliance; - } - - @Override - public boolean isNamedQueryStartupCheckingEnabled() { - return namedQueryStartupCheckingEnabled; - } - - @Override - public boolean isConventionalJavaConstants() { - return conventionalJavaConstants; - } - - @Override - public boolean isProcedureParameterNullPassingEnabled() { - return procedureParameterNullPassingEnabled; - } - - @Override - public boolean isCollectionJoinSubqueryRewriteEnabled() { - return collectionJoinSubqueryRewriteEnabled; - } - - @Override - public boolean isAllowOutOfTransactionUpdateOperations() { - return allowOutOfTransactionUpdateOperations; - } - - @Override - public boolean isReleaseResourcesOnCloseEnabled() { - return releaseResourcesOnCloseEnabled; - } - - @Override - public boolean isSecondLevelCacheEnabled() { - return secondLevelCacheEnabled; - } - - @Override - public boolean isQueryCacheEnabled() { - return queryCacheEnabled; - } - - @Override - public QueryCacheFactory getQueryCacheFactory() { - return queryCacheFactory; - } - - @Override - public String getCacheRegionPrefix() { - return cacheRegionPrefix; - } - - @Override - public boolean isMinimalPutsEnabled() { - return minimalPutsEnabled; - } - - @Override - public boolean isStructuredCacheEntriesEnabled() { - return structuredCacheEntriesEnabled; - } - - @Override - public boolean isDirectReferenceCacheEntriesEnabled() { - return directReferenceCacheEntriesEnabled; - } - - public boolean isAutoEvictCollectionCache() { - return autoEvictCollectionCache; - } - - @Override - public SchemaAutoTooling getSchemaAutoTooling() { - return schemaAutoTooling; - } - - @Override - public int getJdbcBatchSize() { - return jdbcBatchSize; - } - - @Override - public boolean isJdbcBatchVersionedData() { - return jdbcBatchVersionedData; - } - - @Override - public boolean isScrollableResultSetsEnabled() { - return scrollableResultSetsEnabled; - } - - @Override - public boolean isWrapResultSetsEnabled() { - return wrapResultSetsEnabled; - } - - @Override - public boolean isGetGeneratedKeysEnabled() { - return getGeneratedKeysEnabled; - } - - @Override - public Integer getJdbcFetchSize() { - return jdbcFetchSize; - } - - @Override - public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() { - return physicalConnectionHandlingMode; - } - - @Override - public ConnectionReleaseMode getConnectionReleaseMode() { - return physicalConnectionHandlingMode.getReleaseMode(); - } - - @Override - public boolean isCommentsEnabled() { - return commentsEnabled; - } - - @Override - public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() { - return customEntityDirtinessStrategy; - } - - - @Override - public EntityNameResolver[] getEntityNameResolvers() { - return entityNameResolvers; - } - - @Override - public EntityNotFoundDelegate getEntityNotFoundDelegate() { - return entityNotFoundDelegate; - } - - @Override - public Map getCustomSqlFunctionMap() { - return sqlFunctions; - } - - @Override - public void setCheckNullability(boolean enabled) { - this.checkNullability = enabled; - } - - @Override - public boolean isPreferUserTransaction() { - return preferUserTransaction; - } - - @Override - public TimeZone getJdbcTimeZone() { - return jdbcTimeZone; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java deleted file mode 100644 index 31ff57edaecc..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java +++ /dev/null @@ -1,177 +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.boot.internal; - -import java.util.Map; -import java.util.TimeZone; - -import org.hibernate.ConnectionReleaseMode; -import org.hibernate.CustomEntityDirtinessStrategy; -import org.hibernate.EntityMode; -import org.hibernate.EntityNameResolver; -import org.hibernate.Interceptor; -import org.hibernate.MultiTenancyStrategy; -import org.hibernate.NullPrecedence; -import org.hibernate.SessionFactoryObserver; -import org.hibernate.boot.SchemaAutoTooling; -import org.hibernate.boot.TempTableDdlTransactionHandling; -import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.spi.QueryCacheFactory; -import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; -import org.hibernate.context.spi.CurrentTenantIdentifierResolver; -import org.hibernate.dialect.function.SQLFunction; -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.loader.BatchFetchStyle; -import org.hibernate.proxy.EntityNotFoundDelegate; -import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; -import org.hibernate.resource.jdbc.spi.StatementInspector; -import org.hibernate.tuple.entity.EntityTuplizerFactory; - -/** - * Sort of a mutable SessionFactoryOptions used during SessionFactoryBuilder calls. - * - * @author Steve Ebersole - */ -public interface SessionFactoryOptionsState { - StandardServiceRegistry getServiceRegistry(); - - /** - * @deprecated (since 5.2) see {@link SessionFactoryOptions#isJpaBootstrap} for details - * on deprecation and intention/use. - */ - @Deprecated - boolean isJpaBootstrap(); - - boolean isJtaTransactionAccessEnabled(); - - boolean isAllowRefreshDetachedEntity(); - - boolean isAllowOutOfTransactionUpdateOperations(); - - boolean isReleaseResourcesOnCloseEnabled(); - - Object getBeanManagerReference(); - - Object getValidatorFactoryReference(); - - String getSessionFactoryName(); - - boolean isSessionFactoryNameAlsoJndiName(); - - boolean isFlushBeforeCompletionEnabled(); - - boolean isAutoCloseSessionEnabled(); - - boolean isStatisticsEnabled(); - - Interceptor getInterceptor(); - - Class getStatelessInterceptorImplementor(); - - StatementInspector getStatementInspector(); - - SessionFactoryObserver[] getSessionFactoryObservers(); - - BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder(); - - boolean isIdentifierRollbackEnabled(); - - EntityMode getDefaultEntityMode(); - - EntityTuplizerFactory getEntityTuplizerFactory(); - - boolean isCheckNullability(); - - boolean isInitializeLazyStateOutsideTransactionsEnabled(); - - MultiTableBulkIdStrategy getMultiTableBulkIdStrategy(); - - TempTableDdlTransactionHandling getTempTableDdlTransactionHandling(); - - BatchFetchStyle getBatchFetchStyle(); - - int getDefaultBatchFetchSize(); - - Integer getMaximumFetchDepth(); - - NullPrecedence getDefaultNullPrecedence(); - - boolean isOrderUpdatesEnabled(); - - boolean isOrderInsertsEnabled(); - - MultiTenancyStrategy getMultiTenancyStrategy(); - - CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver(); - - boolean isJtaTrackByThread(); - - Map getQuerySubstitutions(); - - boolean isStrictJpaQueryLanguageCompliance(); - - boolean isNamedQueryStartupCheckingEnabled(); - - boolean isConventionalJavaConstants(); - - boolean isProcedureParameterNullPassingEnabled(); - - boolean isCollectionJoinSubqueryRewriteEnabled(); - - boolean isSecondLevelCacheEnabled(); - - boolean isQueryCacheEnabled(); - - QueryCacheFactory getQueryCacheFactory(); - - String getCacheRegionPrefix(); - - boolean isMinimalPutsEnabled(); - - boolean isStructuredCacheEntriesEnabled(); - - boolean isDirectReferenceCacheEntriesEnabled(); - - boolean isAutoEvictCollectionCache(); - - SchemaAutoTooling getSchemaAutoTooling(); - - int getJdbcBatchSize(); - - boolean isJdbcBatchVersionedData(); - - boolean isScrollableResultSetsEnabled(); - - boolean isWrapResultSetsEnabled(); - - boolean isGetGeneratedKeysEnabled(); - - Integer getJdbcFetchSize(); - - PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode(); - - /** - * @deprecated Use {@link #getPhysicalConnectionHandlingMode()} instead - */ - @Deprecated - ConnectionReleaseMode getConnectionReleaseMode(); - - boolean isCommentsEnabled(); - - CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy(); - - EntityNameResolver[] getEntityNameResolvers(); - - EntityNotFoundDelegate getEntityNotFoundDelegate(); - - Map getCustomSqlFunctionMap(); - - boolean isPreferUserTransaction(); - - TimeZone getJdbcTimeZone(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/Origin.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/Origin.java index 5c10eaa30545..8406be0c3061 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/Origin.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/Origin.java @@ -8,8 +8,8 @@ import java.io.Serializable; import java.util.Locale; +import java.util.Objects; -import org.hibernate.internal.util.compare.EqualsHelper; /** * Describes the origin of an xml document @@ -57,7 +57,7 @@ public boolean equals(Object o) { final Origin other = (Origin) o; return type == other.type - && EqualsHelper.equals( name, other.name ); + && Objects.equals( name, other.name ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java index af2051d7520b..8dd1e96b1ea0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java @@ -20,9 +20,9 @@ import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping; import org.hibernate.boot.jaxb.internal.stax.HbmEventReader; import org.hibernate.boot.jaxb.internal.stax.JpaOrmXmlEventReader; -import org.hibernate.boot.jaxb.internal.stax.LocalSchema; import org.hibernate.boot.jaxb.spi.Binding; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.xsd.MappingXsdSupport; import org.hibernate.internal.util.config.ConfigurationException; import org.jboss.logging.Logger; @@ -38,10 +38,11 @@ public class MappingBinder extends AbstractBinder { private static final Logger log = Logger.getLogger( MappingBinder.class ); private final XMLEventFactory xmlEventFactory = XMLEventFactory.newInstance(); + private JAXBContext hbmJaxbContext; public MappingBinder(ClassLoaderService classLoaderService) { - super( classLoaderService ); + this( classLoaderService, true ); } public MappingBinder(ClassLoaderService classLoaderService, boolean validateXml) { @@ -58,8 +59,8 @@ protected Binding doBind( log.debugf( "Performing JAXB binding of hbm.xml document : %s", origin.toString() ); XMLEventReader hbmReader = new HbmEventReader( staxEventReader, xmlEventFactory ); - JaxbHbmHibernateMapping hbmBindings = jaxb( hbmReader, LocalSchema.HBM.getSchema(), hbmJaxbContext(), origin ); - return new Binding( hbmBindings, origin ); + JaxbHbmHibernateMapping hbmBindings = jaxb( hbmReader, MappingXsdSupport.INSTANCE.hbmXsd().getSchema(), hbmJaxbContext(), origin ); + return new Binding<>( hbmBindings, origin ); } else { // final XMLEventReader reader = new JpaOrmXmlEventReader( staxEventReader ); @@ -67,7 +68,7 @@ protected Binding doBind( try { final XMLEventReader reader = new JpaOrmXmlEventReader( staxEventReader, xmlEventFactory ); - return new Binding( toDom4jDocument( reader, origin), origin ); + return new Binding<>( toDom4jDocument( reader, origin ), origin ); } catch (JpaOrmXmlEventReader.BadVersionException e) { throw new UnsupportedOrmXsdVersionException( e.getRequestedVersion(), origin ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/BufferedXMLEventReader.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/BufferedXMLEventReader.java index 5bfc4e50d5a4..54769e9ae612 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/BufferedXMLEventReader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/BufferedXMLEventReader.java @@ -162,7 +162,7 @@ public int bufferSize() { } /** - * If reading from the buffer afterQuery a {@link #reset()} call an {@link IllegalStateException} will be thrown. + * If reading from the buffer after a {@link #reset()} call an {@link IllegalStateException} will be thrown. */ @Override public void remove() { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/FilteringXMLEventReader.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/FilteringXMLEventReader.java index cecabe7871ea..b59e0738554d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/FilteringXMLEventReader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/FilteringXMLEventReader.java @@ -19,7 +19,7 @@ /** * Base class for {@link javax.xml.stream.XMLEventReader}s that want to modify or remove events from the reader stream. * If a {@link javax.xml.stream.events.StartElement} event is removed the subclass's {@link #filterEvent(javax.xml.stream.events.XMLEvent, boolean)} will - * not see any events until afterQuery the matching {@link javax.xml.stream.events.EndElement} event. + * not see any events until after the matching {@link javax.xml.stream.events.EndElement} event. * * Note, copied from the uPortal project by permission of author. See * https://github.com/Jasig/uPortal/blob/master/uportal-war/src/main/java/org/jasig/portal/xml/stream/FilteringXMLEventReader.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/HbmEventReader.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/HbmEventReader.java index 98d2d757c9c9..72e9649cefa9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/HbmEventReader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/HbmEventReader.java @@ -19,6 +19,8 @@ import javax.xml.stream.events.XMLEvent; import javax.xml.stream.util.EventReaderDelegate; +import org.hibernate.boot.xsd.MappingXsdSupport; + /** * A StAX EventReader for {@code hbm.xml} files to add namespaces in documents * not containing namespaces. @@ -65,7 +67,7 @@ private StartElement applyNamespace(StartElement startElement) { if ( "".equals( startElement.getName().getNamespaceURI() ) ) { // add the default namespace mapping - targetNamespaces.add( xmlEventFactory.createNamespace( LocalSchema.HBM.getNamespaceUri() ) ); + targetNamespaces.add( xmlEventFactory.createNamespace( MappingXsdSupport.INSTANCE.hbmXsd().getNamespaceUri() ) ); } // transfer any namespaces directly, unless it is in the "to map" list in which case @@ -75,7 +77,7 @@ private StartElement applyNamespace(StartElement startElement) { Namespace namespace = originalNamespaces.next(); if ( NAMESPACE_URIS_TO_MAP.contains( namespace.getNamespaceURI() ) ) { // this is a namespace "to map" so map it - namespace = xmlEventFactory.createNamespace( namespace.getPrefix(), LocalSchema.HBM.getNamespaceUri() ); + namespace = xmlEventFactory.createNamespace( namespace.getPrefix(), MappingXsdSupport.INSTANCE.hbmXsd().getNamespaceUri() ); } targetNamespaces.add( namespace ); } @@ -84,7 +86,7 @@ private StartElement applyNamespace(StartElement startElement) { // so that the event we ask it to generate for us has the same location info xmlEventFactory.setLocation( startElement.getLocation() ); return xmlEventFactory.createStartElement( - new QName( LocalSchema.HBM.getNamespaceUri(), startElement.getName().getLocalPart() ), + new QName( MappingXsdSupport.INSTANCE.hbmXsd().getNamespaceUri(), startElement.getName().getLocalPart() ), startElement.getAttributes(), targetNamespaces.iterator() ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/JpaOrmXmlEventReader.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/JpaOrmXmlEventReader.java index 17b0aadb57e2..b862c4bfac4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/JpaOrmXmlEventReader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/JpaOrmXmlEventReader.java @@ -21,6 +21,9 @@ import javax.xml.stream.events.XMLEvent; import javax.xml.stream.util.EventReaderDelegate; +import org.hibernate.boot.xsd.LocalXsdResolver; +import org.hibernate.boot.xsd.MappingXsdSupport; + /** * A JPA {@code orm.xml} specific StAX EVentReader to handle a few oddities. * @@ -44,9 +47,6 @@ public class JpaOrmXmlEventReader extends EventReaderDelegate { private static final String ROOT_ELEMENT_NAME = "entity-mappings"; private static final String VERSION_ATTRIBUTE_NAME = "version"; - private static final String DEFAULT_VERSION = "2.1"; - private static final List VALID_VERSIONS = Arrays.asList( "1.0", "2.0", "2.1" ); - private final XMLEventFactory xmlEventFactory; public JpaOrmXmlEventReader(XMLEventReader reader) { @@ -88,14 +88,14 @@ private StartElement wrap(StartElement startElement) { // so that the event we ask it to generate for us has the same location info xmlEventFactory.setLocation( startElement.getLocation() ); return xmlEventFactory.createStartElement( - new QName( LocalSchema.ORM.getNamespaceUri(), startElement.getName().getLocalPart() ), + new QName( MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri(), startElement.getName().getLocalPart() ), newElementAttributeList.iterator(), newNamespaceList.iterator() ); } private List mapAttributes(StartElement startElement) { - final List mappedAttributes = new ArrayList(); + final List mappedAttributes = new ArrayList<>(); Iterator existingAttributesIterator = existingXmlAttributesIterator( startElement ); while ( existingAttributesIterator.hasNext() ) { @@ -125,11 +125,11 @@ private Attribute mapAttribute(StartElement startElement, Attribute originalAttr if ( VERSION_ATTRIBUTE_NAME.equals( originalAttribute.getName().getLocalPart() ) ) { final String specifiedVersion = originalAttribute.getValue(); - if ( !VALID_VERSIONS.contains( specifiedVersion ) ) { + if ( !LocalXsdResolver.isValidJpaVersion( specifiedVersion ) ) { throw new BadVersionException( specifiedVersion ); } - return xmlEventFactory.createAttribute( VERSION_ATTRIBUTE_NAME, DEFAULT_VERSION ); + return xmlEventFactory.createAttribute( VERSION_ATTRIBUTE_NAME, LocalXsdResolver.latestJpaVerison() ); } } @@ -156,7 +156,7 @@ private List mapNamespaces(Iterator originalNamespaceItera } if ( mappedNamespaces.isEmpty() ) { - mappedNamespaces.add( xmlEventFactory.createNamespace( LocalSchema.ORM.getNamespaceUri() ) ); + mappedNamespaces.add( xmlEventFactory.createNamespace( MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri() ) ); } return mappedNamespaces; @@ -170,7 +170,7 @@ private Iterator existingXmlNamespacesIterator(StartElement startElem private Namespace mapNamespace(Namespace originalNamespace) { if ( NAMESPACE_URIS_TO_MAP.contains( originalNamespace.getNamespaceURI() ) ) { // this is a namespace "to map" so map it - return xmlEventFactory.createNamespace( originalNamespace.getPrefix(), LocalSchema.ORM.getNamespaceUri() ); + return xmlEventFactory.createNamespace( originalNamespace.getPrefix(), MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri() ); } return originalNamespace; @@ -183,7 +183,7 @@ private XMLEvent wrap(EndElement endElement) { // so that the event we ask it to generate for us has the same location info xmlEventFactory.setLocation( endElement.getLocation() ); return xmlEventFactory.createEndElement( - new QName( LocalSchema.ORM.getNamespaceUri(), endElement.getName().getLocalPart() ), + new QName( MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri(), endElement.getName().getLocalPart() ), targetNamespaces.iterator() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalSchema.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalSchema.java index c1067758b7bf..0897b2bece59 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalSchema.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalSchema.java @@ -18,7 +18,11 @@ /** * @author Steve Ebersole + * + * @deprecated No longer used locally. See {@link org.hibernate.boot.xsd.MappingXsdSupport} + * and {@link org.hibernate.boot.xsd.ConfigXsdSupport} */ +@Deprecated public enum LocalSchema { ORM( "http://www.hibernate.org/xsd/orm/mapping", 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 537c348ffccb..89a749a2d154 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java @@ -42,6 +42,12 @@ public Object resolveEntity(String publicID, String systemID, String baseURI, St else if ( JPA_XSD_MAPPING.matches( namespace ) ) { return openUrlStream( JPA_XSD_MAPPING.getMappedLocalUrl() ); } + else if ( PERSISTENCE_ORM_XSD_MAPPING.matches( namespace ) ) { + return openUrlStream( PERSISTENCE_ORM_XSD_MAPPING.getMappedLocalUrl() ); + } + else if ( PERSISTENCE_ORM_XSD_MAPPING2.matches( namespace ) ) { + return openUrlStream( PERSISTENCE_ORM_XSD_MAPPING2.getMappedLocalUrl() ); + } else if ( HBM_XSD_MAPPING.matches( namespace ) ) { return openUrlStream( HBM_XSD_MAPPING.getMappedLocalUrl() ); } @@ -152,7 +158,23 @@ private InputStream resolveInLocalNamespace(String path) { "http://xmlns.jcp.org/xml/ns/persistence/orm", "org/hibernate/jpa/orm_2_1.xsd" ); + + /** + * Maps the namespace for the orm.xml xsd for Jakarta Persistence 2.2 + */ + public static final NamespaceSchemaMapping PERSISTENCE_ORM_XSD_MAPPING = new NamespaceSchemaMapping( + "http://xmlns.jcp.org/xml/ns/persistence/orm", + "org/hibernate/jpa/orm_2_2.xsd" + ); + /** + * Maps the namespace for the orm.xml xsd for Jakarta Persistence 3.0 + */ + public static final NamespaceSchemaMapping PERSISTENCE_ORM_XSD_MAPPING2 = new NamespaceSchemaMapping( + "https://jakarta.ee/xml/ns/persistence/orm", + "org/hibernate/jpa/orm_3_0.xsd" + ); + public static final NamespaceSchemaMapping HBM_XSD_MAPPING = new NamespaceSchemaMapping( "http://www.hibernate.org/xsd/orm/hbm", "org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd" diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/SupportedOrmXsdVersion.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/SupportedOrmXsdVersion.java deleted file mode 100644 index 2759d34d6a6f..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/SupportedOrmXsdVersion.java +++ /dev/null @@ -1,69 +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.boot.jaxb.internal.stax; - -import java.net.URL; -import javax.xml.validation.Schema; - -import org.hibernate.boot.jaxb.Origin; - -/** - * @author Steve Ebersole - * - * @deprecated since 5.0; no longer used internally. - */ -@Deprecated -public enum SupportedOrmXsdVersion { - ORM_1_0( "org/hibernate/jpa/orm_1_0.xsd" ), - ORM_2_0( "org/hibernate/jpa/orm_2_0.xsd" ), - ORM_2_1( "org/hibernate/jpa/orm_2_1.xsd" ), - ORM_2_1_0( "org/hibernate/xsd/mapping/mapping-2.1.0.xsd" ), - HBM_4_0( "org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd" ); - - private final String schemaResourceName; - - SupportedOrmXsdVersion(String schemaResourceName) { - this.schemaResourceName = schemaResourceName; - } - - public static SupportedOrmXsdVersion parse(String name, Origin origin) { - if ( "1.0".equals( name ) ) { - return ORM_1_0; - } - else if ( "2.0".equals( name ) ) { - return ORM_2_0; - } - else if ( "2.1".equals( name ) ) { - return ORM_2_1; - } - else if ( "2.1.0".equals( name ) ) { - return ORM_2_1_0; - } - else if ( "4.0".equals( name ) ) { - return HBM_4_0; - } - throw new UnsupportedOrmXsdVersionException( name, origin ); - } - - private URL schemaUrl; - - public URL getSchemaUrl() { - if ( schemaUrl == null ) { - schemaUrl = LocalSchemaLocator.resolveLocalSchemaUrl( schemaResourceName ); - } - return schemaUrl; - } - - private Schema schema; - - public Schema getSchema() { - if ( schema == null ) { - schema = LocalSchemaLocator.resolveLocalSchema( getSchemaUrl() ); - } - return schema; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/UnsupportedOrmXsdVersionException.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/UnsupportedOrmXsdVersionException.java deleted file mode 100644 index 02b3142d8084..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/UnsupportedOrmXsdVersionException.java +++ /dev/null @@ -1,29 +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.boot.jaxb.internal.stax; - -import org.hibernate.HibernateException; -import org.hibernate.boot.jaxb.Origin; - -/** - * @author Steve Ebersole - * - * @deprecated Use {@link org.hibernate.boot.UnsupportedOrmXsdVersionException} instead - */ -@Deprecated -public class UnsupportedOrmXsdVersionException extends HibernateException { - public UnsupportedOrmXsdVersionException(String requestedVersion, Origin origin) { - super( - String.format( - "Encountered unsupported orm.xml xsd version [%s] in mapping document [type=%s, name=%s]", - requestedVersion, - origin.getType(), - origin.getName() - ) - ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/IdGeneratorStrategyInterpreter.java b/hibernate-core/src/main/java/org/hibernate/boot/model/IdGeneratorStrategyInterpreter.java index 9d5efb8a2d4b..81ea7c213626 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/IdGeneratorStrategyInterpreter.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/IdGeneratorStrategyInterpreter.java @@ -6,6 +6,7 @@ */ package org.hibernate.boot.model; +import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.SequenceGenerator; import javax.persistence.TableGenerator; @@ -16,8 +17,17 @@ * @author Steve Ebersole */ public interface IdGeneratorStrategyInterpreter { - public static interface GeneratorNameDeterminationContext { - public Class getIdType(); + interface GeneratorNameDeterminationContext { + /** + * The Java type of the attribute defining the id whose value is to + * be generated. + */ + Class getIdType(); + + /** + * The {@link GeneratedValue#generator()} name. + */ + String getGeneratedValueGeneratorName(); } /** @@ -25,8 +35,18 @@ public static interface GeneratorNameDeterminationContext { * GenerationType, returning {@code null} to indicate that this interpreter * did not have a match and that any additional resolutions should be performed. * + * @apiNote Not really a great name as it is a bit confusing. What is really + * being resolved here is the name of the + * {@link org.hibernate.id.IdentifierGenerator} to use. This is (generally) + * different than the {@link GeneratedValue#generator()} name for the + * {@link GeneratedValue} that is the source of the passed {@link GenerationType}. + * For implementations that need it, the {@link GeneratedValue#generator()} + * is passed as part of the `context`. + * * @param generationType The {@link javax.persistence.GeneratedValue#strategy} value * @param context The context for resolution (method parameter object) + * + * @return The {@link org.hibernate.id.IdentifierGenerator} name (FQN, short name, etc) */ String determineGeneratorName(GenerationType generationType, GeneratorNameDeterminationContext context); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributions.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributions.java index e6096ba1c75e..8f2dcbfa9483 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributions.java @@ -7,6 +7,11 @@ package org.hibernate.boot.model; import org.hibernate.type.BasicType; +import org.hibernate.type.StandardBasicTypeTemplate; +import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserType; @@ -16,11 +21,56 @@ * @author Steve Ebersole */ public interface TypeContributions { + TypeConfiguration getTypeConfiguration(); + + /** + * Add the JavaTypeDescriptor to the {@link TypeConfiguration}'s + * {@link JavaTypeDescriptorRegistry} + */ + void contributeJavaTypeDescriptor(JavaTypeDescriptor descriptor); + + /** + * Add the JavaTypeDescriptor to the {@link TypeConfiguration}'s + * {@link JavaTypeDescriptorRegistry} + */ + void contributeSqlTypeDescriptor(SqlTypeDescriptor descriptor); + void contributeType(BasicType type); + /** + * @deprecated (since 5.3) Use {@link #contributeType(BasicType)} instead. Basic + * types will be defined and handled much differently in 6.0 based on a combination + * of {@link JavaTypeDescriptor}, {@link SqlTypeDescriptor} and a concept of a "value + * converter" (a JPA AttributeConverter, an enum value resolver, etc). To get as + * close as possible in 5.3 use existing {@link JavaTypeDescriptor} and + * {@link SqlTypeDescriptor} implementations (or write your own for custom types) + * and use {@link StandardBasicTypeTemplate} to combine those with + * registration keys and call {@link #contributeType(BasicType)} instead + */ + @Deprecated void contributeType(BasicType type, String... keys); + /** + * @deprecated (since 5.3) Use {@link #contributeType(BasicType)} instead. + * {@link UserType}, as currently defined, will be done very differently in 6.0. + * In most cases a {@link UserType} can be simply replaced with proper + * {@link JavaTypeDescriptor}. To get as close as possible to 6.0 in 5.3 use + * existing {@link JavaTypeDescriptor} and {@link SqlTypeDescriptor} + * implementations (or write your own for custom impls) and use + * {@link StandardBasicTypeTemplate} to combine those with registration keys + * and call {@link #contributeType(BasicType)} instead + */ + @Deprecated void contributeType(UserType type, String... keys); + /** + * @deprecated (since 5.3) Use {@link #contributeType(BasicType)} instead. + * {@link CompositeUserType}, as currently defined, will be done very differently + * in 6.0. {@link CompositeUserType} should be replaced with a normal Hibernate + * component or JPA embeddable (different names, same thing. This embeddable + * may contain, in turn, custom types that should be handled as described on these + * methods + */ + @Deprecated void contributeType(CompositeUserType type, String... keys); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java index 04d674048e0f..5128b6bdfe92 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java @@ -23,5 +23,5 @@ public interface TypeContributor { * @param typeContributions The callback for adding contributed types * @param serviceRegistry The service registry */ - public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry); + void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java index ae80865f7bc1..5c4922e58a25 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java @@ -11,10 +11,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Properties; -import org.hibernate.internal.util.compare.EqualsHelper; - /** * Models the information pertaining to a custom type definition supplied by the user. Used * to delay instantiation of the actual {@link org.hibernate.type.Type} instance. @@ -110,10 +109,10 @@ public boolean equals(Object o) { } final TypeDefinition that = (TypeDefinition) o; - return EqualsHelper.equals( this.name, that.name ) - && EqualsHelper.equals( this.typeImplementorClass, that.typeImplementorClass ) + return Objects.equals( this.name, that.name ) + && Objects.equals( this.typeImplementorClass, that.typeImplementorClass ) && Arrays.equals( this.registrationKeys, that.registrationKeys ) - && EqualsHelper.equals( this.parameters, that.parameters ); + && Objects.equals( this.parameters, that.parameters ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AbstractConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AbstractConverterDescriptor.java new file mode 100644 index 000000000000..a3551982ee36 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AbstractConverterDescriptor.java @@ -0,0 +1,115 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.model.convert.internal; + +import java.util.List; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.hibernate.AnnotationException; +import org.hibernate.boot.internal.ClassmateContext; +import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; +import org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl; +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; +import org.hibernate.resource.beans.spi.ManagedBean; + +import com.fasterxml.classmate.ResolvedType; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractConverterDescriptor implements ConverterDescriptor { + private final Class converterClass; + + private final ResolvedType domainType; + private final ResolvedType jdbcType; + + private final AutoApplicableConverterDescriptor autoApplicableDescriptor; + + @SuppressWarnings("WeakerAccess") + public AbstractConverterDescriptor( + Class converterClass, + Boolean forceAutoApply, + ClassmateContext classmateContext) { + this.converterClass = converterClass; + + final ResolvedType converterType = classmateContext.getTypeResolver().resolve( converterClass ); + final List converterParamTypes = converterType.typeParametersFor( AttributeConverter.class ); + if ( converterParamTypes == null ) { + throw new AnnotationException( + "Could not extract type parameter information from AttributeConverter implementation [" + + converterClass.getName() + "]" + ); + } + else if ( converterParamTypes.size() != 2 ) { + throw new AnnotationException( + "Unexpected type parameter information for AttributeConverter implementation [" + + converterClass.getName() + "]; expected 2 parameter types, but found " + converterParamTypes.size() + ); + } + + this.domainType = converterParamTypes.get( 0 ); + this.jdbcType = converterParamTypes.get( 1 ); + + this.autoApplicableDescriptor = resolveAutoApplicableDescriptor( converterClass, forceAutoApply ); + } + + private AutoApplicableConverterDescriptor resolveAutoApplicableDescriptor( + Class converterClass, + Boolean forceAutoApply) { + final boolean autoApply; + + if ( forceAutoApply != null ) { + // if the caller explicitly specified whether to auto-apply, honor that + autoApply = forceAutoApply; + } + else { + // otherwise, look at the converter's @Converter annotation + final Converter annotation = converterClass.getAnnotation( Converter.class ); + autoApply = annotation != null && annotation.autoApply(); + } + + return autoApply + ? new AutoApplicableConverterDescriptorStandardImpl( this ) + : AutoApplicableConverterDescriptorBypassedImpl.INSTANCE; + } + + @Override + public Class getAttributeConverterClass() { + return converterClass; + } + + @Override + public ResolvedType getDomainValueResolvedType() { + return domainType; + } + + @Override + public ResolvedType getRelationalValueResolvedType() { + return jdbcType; + } + + @Override + public AutoApplicableConverterDescriptor getAutoApplyDescriptor() { + return autoApplicableDescriptor; + } + + @Override + @SuppressWarnings("unchecked") + public JpaAttributeConverter createJpaAttributeConverter(JpaAttributeConverterCreationContext context) { + return new JpaAttributeConverterImpl( + createManagedBean( context ), + context.getJavaTypeDescriptorRegistry().getDescriptor( getAttributeConverterClass() ), + context.getJavaTypeDescriptorRegistry().getDescriptor( getDomainValueResolvedType().getErasedType() ), + context.getJavaTypeDescriptorRegistry().getDescriptor( getRelationalValueResolvedType().getErasedType() ) + ); + } + + protected abstract ManagedBean createManagedBean(JpaAttributeConverterCreationContext context); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java new file mode 100644 index 000000000000..577e04f70f37 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java @@ -0,0 +1,159 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.convert.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import org.hibernate.AssertionFailure; +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.internal.util.StringHelper; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public class AttributeConverterManager implements ConverterAutoApplyHandler { + private static final Logger log = Logger.getLogger( AttributeConverterManager.class ); + + private Map attributeConverterDescriptorsByClass; + + public void addConverter(ConverterDescriptor descriptor) { + if ( attributeConverterDescriptorsByClass == null ) { + attributeConverterDescriptorsByClass = new ConcurrentHashMap<>(); + } + + final Object old = attributeConverterDescriptorsByClass.put( + descriptor.getAttributeConverterClass(), + descriptor + ); + + if ( old != null ) { + throw new AssertionFailure( + String.format( + Locale.ENGLISH, + "AttributeConverter class [%s] registered multiple times", + descriptor.getAttributeConverterClass() + ) + ); + } + } + + private Collection converterDescriptors() { + if ( attributeConverterDescriptorsByClass == null ) { + return Collections.emptyList(); + } + return attributeConverterDescriptorsByClass.values(); + } + + enum ConversionSite { + ATTRIBUTE( "basic attribute" ), + COLLECTION_ELEMENT( "collection attribute's element" ), + MAP_KEY( "map attribute's key" ); + + private final String siteDescriptor; + + ConversionSite(String siteDescriptor) { + this.siteDescriptor = siteDescriptor; + } + + public String getSiteDescriptor() { + return siteDescriptor; + } + } + @Override + public ConverterDescriptor findAutoApplyConverterForAttribute( + XProperty xProperty, + MetadataBuildingContext context) { + return locateMatchingConverter( + xProperty, + ConversionSite.ATTRIBUTE, + (autoApplyDescriptor) -> autoApplyDescriptor.getAutoAppliedConverterDescriptorForAttribute( xProperty, context ) + ); + } + + private static StringHelper.Renderer RENDERER = value -> value.getAttributeConverterClass().getName(); + + private ConverterDescriptor locateMatchingConverter( + XProperty xProperty, + ConversionSite conversionSite, + Function matcher) { + List matches = new ArrayList<>(); + + for ( ConverterDescriptor descriptor : converterDescriptors() ) { + log.debugf( + "Checking auto-apply AttributeConverter [%s] (domain-type=%s) for match against %s : %s.%s (type=%s)", + descriptor.getAttributeConverterClass().getName(), + descriptor.getDomainValueResolvedType().getSignature(), + conversionSite.getSiteDescriptor(), + xProperty.getDeclaringClass().getName(), + xProperty.getName(), + xProperty.getType().getName() + ); + + final ConverterDescriptor match = matcher.apply( descriptor.getAutoApplyDescriptor() ); + + if ( match != null ) { + matches.add( descriptor ); + } + } + + if ( matches.isEmpty() ) { + return null; + } + + if ( matches.size() == 1 ) { + return matches.get( 0 ); + } + + // otherwise, we had multiple matches + throw new RuntimeException( + String.format( + Locale.ROOT, + "Multiple auto-apply converters matched %s [%s.%s] : %s", + conversionSite.getSiteDescriptor(), + xProperty.getDeclaringClass().getName(), + xProperty.getName(), + StringHelper.join( matches, RENDERER ) + ) + ); + } + + @Override + public ConverterDescriptor findAutoApplyConverterForCollectionElement( + XProperty xProperty, + MetadataBuildingContext context) { + return locateMatchingConverter( + xProperty, + ConversionSite.COLLECTION_ELEMENT, + (autoApplyDescriptor) -> autoApplyDescriptor.getAutoAppliedConverterDescriptorForCollectionElement( xProperty, context ) + ); + } + + @Override + public ConverterDescriptor findAutoApplyConverterForMapKey( + XProperty xProperty, + MetadataBuildingContext context) { + return locateMatchingConverter( + xProperty, + ConversionSite.MAP_KEY, + (autoApplyDescriptor) -> autoApplyDescriptor.getAutoAppliedConverterDescriptorForMapKey( xProperty, context ) + ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorBypassedImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorBypassedImpl.java new file mode 100644 index 000000000000..24b889f04391 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorBypassedImpl.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.boot.model.convert.internal; + +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.spi.MetadataBuildingContext; + +/** + * An implementation of AutoApplicableConverterDescriptor that always reports + * no auto-apply match + * + * @author Steve Ebersole + */ +public class AutoApplicableConverterDescriptorBypassedImpl implements AutoApplicableConverterDescriptor { + /** + * Singleton access + */ + public static final AutoApplicableConverterDescriptorBypassedImpl INSTANCE = new AutoApplicableConverterDescriptorBypassedImpl(); + + private AutoApplicableConverterDescriptorBypassedImpl() { + } + + @Override + public ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute( + XProperty xProperty, + MetadataBuildingContext context) { + return null; + } + + @Override + public ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement( + XProperty xProperty, + MetadataBuildingContext context) { + return null; + } + + @Override + public ConverterDescriptor getAutoAppliedConverterDescriptorForMapKey( + XProperty xProperty, + MetadataBuildingContext context) { + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java new file mode 100644 index 000000000000..5f04d936ce00 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java @@ -0,0 +1,183 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.model.convert.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +import org.hibernate.HibernateException; +import org.hibernate.annotations.common.reflection.ReflectionManager; +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.internal.ClassmateContext; +import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.cfg.annotations.HCANNHelper; + +import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.classmate.ResolvedTypeWithMembers; +import com.fasterxml.classmate.members.ResolvedField; +import com.fasterxml.classmate.members.ResolvedMember; +import com.fasterxml.classmate.members.ResolvedMethod; + +/** + * Standard implementation of AutoApplicableConverterDescriptor + * + * @author Steve Ebersole + */ +public class AutoApplicableConverterDescriptorStandardImpl implements AutoApplicableConverterDescriptor { + private final ConverterDescriptor linkedConverterDescriptor; + + public AutoApplicableConverterDescriptorStandardImpl(ConverterDescriptor linkedConverterDescriptor) { + this.linkedConverterDescriptor = linkedConverterDescriptor; + } + + @Override + public ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute( + XProperty xProperty, + MetadataBuildingContext context) { + final ResolvedType attributeType = resolveAttributeType( xProperty, context ); + + return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), attributeType ) + ? linkedConverterDescriptor + : null; + } + + @Override + public ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement( + XProperty xProperty, + MetadataBuildingContext context) { + final ResolvedMember collectionMember = resolveMember( xProperty, context ); + + final ResolvedType elementType; + if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { + elementType = collectionMember.getType().typeParametersFor( Map.class ).get( 1 ); + } + else if ( Collection.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { + elementType = collectionMember.getType().typeParametersFor( Collection.class ).get( 0 ); + } + else { + throw new HibernateException( "Attribute was neither a Collection nor a Map : " + collectionMember.getType().getErasedType() ); + } + + return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), elementType ) + ? linkedConverterDescriptor + : null; + } + + @Override + public ConverterDescriptor getAutoAppliedConverterDescriptorForMapKey( + XProperty xProperty, + MetadataBuildingContext context) { + + final ResolvedMember collectionMember = resolveMember( xProperty, context ); + final ResolvedType keyType; + + if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { + keyType = collectionMember.getType().typeParametersFor( Map.class ).get( 0 ); + } + else { + throw new HibernateException( "Attribute was not a Map : " + collectionMember.getType().getErasedType() ); + } + + return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), keyType ) + ? linkedConverterDescriptor + : null; + } + + private ResolvedType resolveAttributeType(XProperty xProperty, MetadataBuildingContext context) { + return resolveMember( xProperty, context ).getType(); + } + + private ResolvedMember resolveMember(XProperty xProperty, MetadataBuildingContext buildingContext) { + final ClassmateContext classmateContext = buildingContext.getBootstrapContext().getClassmateContext(); + final ReflectionManager reflectionManager = buildingContext.getBootstrapContext().getReflectionManager(); + + final ResolvedType declaringClassType = classmateContext.getTypeResolver().resolve( + reflectionManager.toClass( xProperty.getDeclaringClass() ) + ); + final ResolvedTypeWithMembers declaringClassWithMembers = classmateContext.getMemberResolver().resolve( + declaringClassType, + null, + null + ); + + final Member member = toMember( xProperty ); + if ( member instanceof Method ) { + for ( ResolvedMethod resolvedMember : declaringClassWithMembers.getMemberMethods() ) { + if ( resolvedMember.getName().equals( member.getName() ) ) { + return resolvedMember; + } + } + } + else if ( member instanceof Field ) { + for ( ResolvedField resolvedMember : declaringClassWithMembers.getMemberFields() ) { + if ( resolvedMember.getName().equals( member.getName() ) ) { + return resolvedMember; + } + } + } + else { + throw new HibernateException( "Unexpected java.lang.reflect.Member type from org.hibernate.annotations.common.reflection.java.JavaXMember : " + member ); + } + + throw new HibernateException( + "Could not locate resolved type information for attribute [" + member.getName() + "] from Classmate" + ); + } + + + private static Member toMember(XProperty xProperty) { + try { + return HCANNHelper.getUnderlyingMember( xProperty ); + } + catch (Exception e) { + throw new HibernateException( + "Could not resolve member signature from XProperty reference", + e + ); + } + } + + private boolean typesMatch(ResolvedType converterDefinedType, ResolvedType checkType) { + if ( !converterDefinedType.getErasedType().isAssignableFrom( checkType.getErasedType() ) ) { + return false; + } + + // if the converter did not define any nested type parameters, then the check above is + // enough for a match + if ( converterDefinedType.getTypeParameters().isEmpty() ) { + return true; + } + + // however, here the converter *did* define nested type parameters, so we'd have a converter defined using something like, e.g., List for its + // domain type. + // + // we need to check those nested types as well + + if ( checkType.getTypeParameters().isEmpty() ) { + // the domain type did not define nested type params. a List would not auto-match a List() + return false; + } + + if ( converterDefinedType.getTypeParameters().size() != checkType.getTypeParameters().size() ) { + // they had different number of type params somehow. + return false; + } + + for ( int i = 0; i < converterDefinedType.getTypeParameters().size(); i++ ) { + if ( !typesMatch( converterDefinedType.getTypeParameters().get( i ), checkType.getTypeParameters().get( i ) ) ) { + return false; + } + } + + return true; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ClassBasedConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ClassBasedConverterDescriptor.java new file mode 100644 index 000000000000..d498085fa032 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ClassBasedConverterDescriptor.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.model.convert.internal; + +import javax.persistence.AttributeConverter; + +import org.hibernate.boot.internal.ClassmateContext; +import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; +import org.hibernate.resource.beans.spi.ManagedBean; + +/** + * ConverterDescriptor implementation for cases where we know the + * AttributeConverter Class. This is the normal case. + * + * @author Steve Ebersole + */ +public class ClassBasedConverterDescriptor extends AbstractConverterDescriptor { + public ClassBasedConverterDescriptor( + Class converterClass, + ClassmateContext classmateContext) { + super( converterClass, null, classmateContext ); + } + + public ClassBasedConverterDescriptor( + Class converterClass, + Boolean forceAutoApply, + ClassmateContext classmateContext) { + super( converterClass, forceAutoApply, classmateContext ); + } + + @SuppressWarnings("unchecked") + @Override + protected ManagedBean createManagedBean(JpaAttributeConverterCreationContext context) { + return context.getManagedBeanRegistry().getBean( getAttributeConverterClass() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/InstanceBasedConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/InstanceBasedConverterDescriptor.java new file mode 100644 index 000000000000..fc85ce809272 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/InstanceBasedConverterDescriptor.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.boot.model.convert.internal; + +import javax.persistence.AttributeConverter; + +import org.hibernate.boot.internal.ClassmateContext; +import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; +import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.resource.beans.spi.ProvidedInstanceManagedBeanImpl; + +/** + * ConverterDescriptor implementation for cases where we are handed + * the AttributeConverter instance to use. + * + * @author Steve Ebersole + */ +public class InstanceBasedConverterDescriptor extends AbstractConverterDescriptor { + private final AttributeConverter converterInstance; + + public InstanceBasedConverterDescriptor( + AttributeConverter converterInstance, + ClassmateContext classmateContext) { + this( converterInstance, null, classmateContext ); + } + + public InstanceBasedConverterDescriptor( + AttributeConverter converterInstance, + Boolean forceAutoApply, + ClassmateContext classmateContext) { + super( converterInstance.getClass(), forceAutoApply, classmateContext ); + this.converterInstance = converterInstance; + } + + @Override + @SuppressWarnings("unchecked") + protected ManagedBean createManagedBean(JpaAttributeConverterCreationContext context) { + return new ProvidedInstanceManagedBeanImpl( converterInstance ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/package-info.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/package-info.java new file mode 100644 index 000000000000..aa9910407bab --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/package-info.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 defining boot-time handling of JPA's + * {@link javax.persistence.AttributeConverter} + * + * The general paradigm is that handling converters is split into a + * boot-time piece and a run-time piece. This package defines the + * boot-time piece. Specifically the boot-time representation of a + * converter is {@link org.hibernate.boot.model.convert.spi.ConverterDescriptor}. + * Another important aspect is managing the resolution of auto-applied + * converters which is handled by coordination between + * {@link org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor} + * and {@link org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler} + * + * The run-time piece is defined by + * {@link org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter}. + * The bridge from boot-time to run-time is defined by + * {@link org.hibernate.boot.model.convert.spi.ConverterDescriptor#createJpaAttributeConverter}. + * This process also incorporates integration with + * {@link org.hibernate.resource.beans.spi.ManagedBeanRegistry} for resolving + * converters from DI containers (if configured) + */ +package org.hibernate.boot.model.convert; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/AutoApplicableConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/AutoApplicableConverterDescriptor.java new file mode 100644 index 000000000000..7e3346824a46 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/AutoApplicableConverterDescriptor.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.model.convert.spi; + +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.spi.MetadataBuildingContext; + +/** + * Contract for handling auto-apply checks for JPA AttributeConverters + * + * @author Steve Ebersole + */ +public interface AutoApplicableConverterDescriptor { + ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute(XProperty xProperty, MetadataBuildingContext context); + ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement(XProperty xProperty, MetadataBuildingContext context); + ConverterDescriptor getAutoAppliedConverterDescriptorForMapKey(XProperty xProperty, MetadataBuildingContext context); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterAutoApplyHandler.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterAutoApplyHandler.java new file mode 100644 index 000000000000..1c95c961c54a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterAutoApplyHandler.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.boot.model.convert.spi; + +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.spi.MetadataBuildingContext; + +/** + * @author Steve Ebersole + */ +public interface ConverterAutoApplyHandler { + ConverterDescriptor findAutoApplyConverterForAttribute(XProperty xProperty, MetadataBuildingContext context); + ConverterDescriptor findAutoApplyConverterForCollectionElement(XProperty xProperty, MetadataBuildingContext context); + ConverterDescriptor findAutoApplyConverterForMapKey(XProperty xProperty, MetadataBuildingContext context); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterDescriptor.java new file mode 100644 index 000000000000..3c58cd41a857 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterDescriptor.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.boot.model.convert.spi; + +import javax.persistence.AttributeConverter; + +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; + +import com.fasterxml.classmate.ResolvedType; + +/** + * Boot-time descriptor of a JPA AttributeConverter + * + * @author Steve Ebersole + */ +public interface ConverterDescriptor { + /** + * The AttributeConverter class + */ + Class getAttributeConverterClass(); + + /** + * The resolved Classmate type descriptor for the conversion's domain type + */ + ResolvedType getDomainValueResolvedType(); + + /** + * The resolved Classmate type descriptor for the conversion's relational type + */ + ResolvedType getRelationalValueResolvedType(); + + /** + * Get the auto-apply checker for this converter. Should never return `null` - prefer + * {@link org.hibernate.boot.model.convert.internal.AutoApplicableConverterDescriptorBypassedImpl#INSTANCE} + * instead. + */ + AutoApplicableConverterDescriptor getAutoApplyDescriptor(); + + /** + * Factory for the runtime representation of the converter + */ + JpaAttributeConverter createJpaAttributeConverter(JpaAttributeConverterCreationContext context); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/JpaAttributeConverterCreationContext.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/JpaAttributeConverterCreationContext.java new file mode 100644 index 000000000000..03a9bd34ef89 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/JpaAttributeConverterCreationContext.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.boot.model.convert.spi; + +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry; + +/** + * Access to information that implementors of + * {@link ConverterDescriptor#createJpaAttributeConverter} might + * need + * + * @author Steve Ebersole + */ +public interface JpaAttributeConverterCreationContext { + ManagedBeanRegistry getManagedBeanRegistry(); + JavaTypeDescriptorRegistry getJavaTypeDescriptorRegistry(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java index 6f60bb8fb062..6d5eae1a96df 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java @@ -14,19 +14,28 @@ * @author Andrea Boriero */ public class DatabaseIdentifier extends Identifier { + /** * Constructs a datatabase identifier instance. + * It is assumed that text is unquoted. * * @param text The identifier text. */ - public DatabaseIdentifier(String text) { - super( text, false ); + protected DatabaseIdentifier(String text) { + super( text ); } public static DatabaseIdentifier toIdentifier(String text) { if ( StringHelper.isEmpty( text ) ) { return null; } - return new DatabaseIdentifier( text ); + else if ( isQuoted( text ) ) { + // exclude the quotes from text + final String unquotedtext = text.substring( 1, text.length() - 1 ); + return new DatabaseIdentifier( unquotedtext ); + } + else { + return new DatabaseIdentifier( text ); + } } } 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 b63a6fe6ab91..fafcd35635f3 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 @@ -113,6 +113,16 @@ public Identifier(String text, boolean quoted) { this.isQuoted = quoted; } + /** + * Constructs an unquoted identifier instance. + * + * @param text The identifier text. + */ + protected Identifier(String text) { + this.text = text; + this.isQuoted = false; + } + /** * Get the identifiers name (text) * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitConstraintNameSource.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitConstraintNameSource.java index 1a8d9d5f44d5..4aa6971b8e3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitConstraintNameSource.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitConstraintNameSource.java @@ -16,4 +16,5 @@ public interface ImplicitConstraintNameSource extends ImplicitNameSource { public Identifier getTableName(); public List getColumnNames(); + public Identifier getUserProvidedIdentifier(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategy.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategy.java index 5aaee800961f..57eea2b2be56 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategy.java @@ -66,7 +66,7 @@ public interface ImplicitNamingStrategy { public Identifier determineJoinTableName(ImplicitJoinTableNameSource source); /** - * Determine the name of an collection join table given the source naming + * Determine the name of a collection join table given the source naming * information, when a name is not explicitly given. * * @param source The source information diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java index a6b0996a5c02..5418de9433e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java @@ -198,8 +198,10 @@ public Identifier determineListIndexColumnName(ImplicitIndexColumnNameSource sou @Override public Identifier determineForeignKeyName(ImplicitForeignKeyNameSource source) { - return toIdentifier( - NamingHelper.INSTANCE.generateHashedFkName( + Identifier userProvidedIdentifier = source.getUserProvidedIdentifier(); + source.getBuildingContext().getBuildingOptions().getSchemaCharset(); + return userProvidedIdentifier != null ? userProvidedIdentifier : toIdentifier( + NamingHelper.withCharset( source.getBuildingContext().getBuildingOptions().getSchemaCharset() ).generateHashedFkName( "FK", source.getTableName(), source.getReferencedTableName(), @@ -211,8 +213,9 @@ public Identifier determineForeignKeyName(ImplicitForeignKeyNameSource source) { @Override public Identifier determineUniqueKeyName(ImplicitUniqueKeyNameSource source) { - return toIdentifier( - NamingHelper.INSTANCE.generateHashedConstraintName( + Identifier userProvidedIdentifier = source.getUserProvidedIdentifier(); + return userProvidedIdentifier != null ? userProvidedIdentifier : toIdentifier( + NamingHelper.withCharset( source.getBuildingContext().getBuildingOptions().getSchemaCharset() ).generateHashedConstraintName( "UK", source.getTableName(), source.getColumnNames() @@ -223,8 +226,9 @@ public Identifier determineUniqueKeyName(ImplicitUniqueKeyNameSource source) { @Override public Identifier determineIndexName(ImplicitIndexNameSource source) { - return toIdentifier( - NamingHelper.INSTANCE.generateHashedConstraintName( + Identifier userProvidedIdentifier = source.getUserProvidedIdentifier(); + return userProvidedIdentifier != null ? userProvidedIdentifier : toIdentifier( + NamingHelper.withCharset( source.getBuildingContext().getBuildingOptions().getSchemaCharset() ).generateHashedConstraintName( "IDX", source.getTableName(), source.getColumnNames() diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java index 9edf9ce6570e..4b83b2c6ba90 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java @@ -6,7 +6,9 @@ */ package org.hibernate.boot.model.naming; +import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -24,6 +26,20 @@ public class NamingHelper { */ public static final NamingHelper INSTANCE = new NamingHelper(); + public static NamingHelper withCharset(String charset) { + return new NamingHelper(charset); + } + + private final String charset; + + public NamingHelper() { + this(null); + } + + private NamingHelper(String charset) { + this.charset = charset; + } + /** * If a foreign-key is not explicitly named, this is called to generate * a unique hash using the table and column names. @@ -146,7 +162,7 @@ public String hashedName(String s) { try { MessageDigest md = MessageDigest.getInstance( "MD5" ); md.reset(); - md.update( s.getBytes() ); + md.update( charset != null ? s.getBytes( charset ) : s.getBytes() ); byte[] digest = md.digest(); BigInteger bigInt = new BigInteger( 1, digest ); // By converting to base 35 (full alphanumeric), we guarantee @@ -154,7 +170,7 @@ public String hashedName(String s) { // character identifier restriction enforced by a few dialects. return bigInt.toString( 35 ); } - catch ( NoSuchAlgorithmException e ) { + catch ( NoSuchAlgorithmException|UnsupportedEncodingException e ) { throw new HibernateException( "Unable to generate a hashed name!", e ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ManagedResourcesImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ManagedResourcesImpl.java index 26fc4e249bb6..c912c9556c52 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ManagedResourcesImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ManagedResourcesImpl.java @@ -10,15 +10,16 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.jaxb.spi.Binding; import org.hibernate.boot.model.process.spi.ManagedResources; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.cfg.AttributeConverterDefinition; @@ -26,17 +27,15 @@ * @author Steve Ebersole */ public class ManagedResourcesImpl implements ManagedResources { - private Map attributeConverterDefinitionMap = new HashMap(); + private Map attributeConverterInfoMap = new HashMap<>(); private Set annotatedClassReferences = new LinkedHashSet(); private Set annotatedClassNames = new LinkedHashSet(); private Set annotatedPackageNames = new LinkedHashSet(); private List mappingFileBindings = new ArrayList(); - public static ManagedResourcesImpl baseline(MetadataSources sources, MetadataBuildingOptions metadataBuildingOptions) { + public static ManagedResourcesImpl baseline(MetadataSources sources, BootstrapContext bootstrapContext) { final ManagedResourcesImpl impl = new ManagedResourcesImpl(); - for ( AttributeConverterDefinition attributeConverterDefinition : metadataBuildingOptions.getAttributeConverters() ) { - impl.addAttributeConverterDefinition( attributeConverterDefinition ); - } + bootstrapContext.getAttributeConverters().forEach( impl::addAttributeConverterDefinition ); impl.annotatedClassReferences.addAll( sources.getAnnotatedClasses() ); impl.annotatedClassNames.addAll( sources.getAnnotatedClassNames() ); impl.annotatedPackageNames.addAll( sources.getAnnotatedPackages() ); @@ -48,8 +47,8 @@ private ManagedResourcesImpl() { } @Override - public Collection getAttributeConverterDefinitions() { - return Collections.unmodifiableCollection( attributeConverterDefinitionMap.values() ); + public Collection getAttributeConverterDefinitions() { + return Collections.unmodifiableCollection( attributeConverterInfoMap.values() ); } @Override @@ -76,11 +75,8 @@ public Collection getXmlMappingBindings() { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // package private - void addAttributeConverterDefinition(AttributeConverterDefinition attributeConverterDefinition) { - attributeConverterDefinitionMap.put( - attributeConverterDefinition.getAttributeConverter().getClass(), - attributeConverterDefinition - ); + void addAttributeConverterDefinition(AttributeConverterInfo converterInfo) { + attributeConverterInfoMap.put( converterInfo.getConverterClass(), converterInfo ); } void addAnnotatedClassReference(Class annotatedClassReference) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java index 3c8b7f15add9..bd8b525e646d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java @@ -30,6 +30,7 @@ import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.XmlMappingBinderAccess; @@ -57,36 +58,36 @@ private ScanningCoordinator() { public void coordinateScan( ManagedResourcesImpl managedResources, - MetadataBuildingOptions options, + BootstrapContext bootstrapContext, XmlMappingBinderAccess xmlMappingBinderAccess) { - if ( options.getScanEnvironment() == null ) { + if ( bootstrapContext.getScanEnvironment() == null ) { return; } - final ClassLoaderService classLoaderService = options.getServiceRegistry().getService( ClassLoaderService.class ); + final ClassLoaderService classLoaderService = bootstrapContext.getServiceRegistry().getService( ClassLoaderService.class ); final ClassLoaderAccess classLoaderAccess = new ClassLoaderAccessImpl( - options.getTempClassLoader(), + bootstrapContext.getJpaTempClassLoader(), classLoaderService ); // NOTE : the idea with JandexInitializer/JandexInitManager was to allow adding classes // to the index as we discovered them via scanning and . Currently - final Scanner scanner = buildScanner( options, classLoaderAccess ); + final Scanner scanner = buildScanner( bootstrapContext, classLoaderAccess ); final ScanResult scanResult = scanner.scan( - options.getScanEnvironment(), - options.getScanOptions(), + bootstrapContext.getScanEnvironment(), + bootstrapContext.getScanOptions(), StandardScanParameters.INSTANCE ); - applyScanResultsToManagedResources( managedResources, scanResult, options, xmlMappingBinderAccess ); + applyScanResultsToManagedResources( managedResources, scanResult, bootstrapContext, xmlMappingBinderAccess ); } private static final Class[] SINGLE_ARG = new Class[] { ArchiveDescriptorFactory.class }; @SuppressWarnings("unchecked") - private static Scanner buildScanner(MetadataBuildingOptions options, ClassLoaderAccess classLoaderAccess) { - final Object scannerSetting = options.getScanner(); - final ArchiveDescriptorFactory archiveDescriptorFactory = options.getArchiveDescriptorFactory(); + private static Scanner buildScanner(BootstrapContext bootstrapContext, ClassLoaderAccess classLoaderAccess) { + final Object scannerSetting = bootstrapContext.getScanner(); + final ArchiveDescriptorFactory archiveDescriptorFactory = bootstrapContext.getArchiveDescriptorFactory(); if ( scannerSetting == null ) { // No custom Scanner specified, use the StandardScanner @@ -186,11 +187,11 @@ private static Scanner buildScanner(MetadataBuildingOptions options, ClassLoader public void applyScanResultsToManagedResources( ManagedResourcesImpl managedResources, ScanResult scanResult, - MetadataBuildingOptions options, + BootstrapContext bootstrapContext, XmlMappingBinderAccess xmlMappingBinderAccess) { - final ScanEnvironment scanEnvironment = options.getScanEnvironment(); - final ServiceRegistry serviceRegistry = options.getServiceRegistry(); + final ScanEnvironment scanEnvironment = bootstrapContext.getScanEnvironment(); + final ServiceRegistry serviceRegistry = bootstrapContext.getServiceRegistry(); final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); @@ -271,7 +272,7 @@ else if ( classDescriptor.getCategorization() == ClassDescriptor.Categorization. log.debugf( "Unable to resolve class [%s] named in persistence unit [%s]", unresolvedListedClassName, - scanEnvironment.getRootUrl().toExternalForm() + scanEnvironment.getRootUrl() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/ManagedResources.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/ManagedResources.java index e39e4371e746..df4a69ee7117 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/ManagedResources.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/ManagedResources.java @@ -8,6 +8,7 @@ import java.util.Collection; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.jaxb.spi.Binding; import org.hibernate.cfg.AttributeConverterDefinition; @@ -31,7 +32,7 @@ public interface ManagedResources { * * @return The AttributeConverter definitions. */ - Collection getAttributeConverterDefinitions(); + Collection getAttributeConverterDefinitions(); /** * Informational access to any entity and component classes in the user domain model known by Class diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java index 2a1e1138f0ca..8d686c5644a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java @@ -10,8 +10,8 @@ import java.util.HashSet; import java.util.Set; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.MetadataSources; -import org.hibernate.boot.internal.ClassLoaderAccessImpl; import org.hibernate.boot.internal.InFlightMetadataCollectorImpl; import org.hibernate.boot.internal.MetadataBuildingContextRootImpl; import org.hibernate.boot.jaxb.internal.MappingBinder; @@ -29,18 +29,18 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.spi.AdditionalJaxbMappingProducer; import org.hibernate.boot.spi.BasicTypeRegistration; -import org.hibernate.boot.spi.ClassLoaderAccess; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataContributor; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.MetadataSourceType; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.type.BasicType; import org.hibernate.type.BasicTypeRegistry; -import org.hibernate.type.TypeFactory; -import org.hibernate.type.TypeResolver; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserType; @@ -79,23 +79,28 @@ public class MetadataBuildingProcess { */ public static MetadataImplementor build( final MetadataSources sources, + final BootstrapContext bootstrapContext, final MetadataBuildingOptions options) { - return complete( prepare( sources, options ), options ); + return complete( prepare( sources, bootstrapContext ), bootstrapContext, options ); } /** * First step of 2-phase for MetadataSources->Metadata process * * @param sources The MetadataSources - * @param options The building options + * @param bootstrapContext The bootstrapContext * * @return Token/memento representing all known users resources (classes, packages, mapping files, etc). */ public static ManagedResources prepare( final MetadataSources sources, - final MetadataBuildingOptions options) { - final ManagedResourcesImpl managedResources = ManagedResourcesImpl.baseline( sources, options ); - ScanningCoordinator.INSTANCE.coordinateScan( managedResources, options, sources.getXmlMappingBinderAccess() ); + final BootstrapContext bootstrapContext) { + final ManagedResourcesImpl managedResources = ManagedResourcesImpl.baseline( sources, bootstrapContext ); + ScanningCoordinator.INSTANCE.coordinateScan( + managedResources, + bootstrapContext, + sources.getXmlMappingBinderAccess() + ); return managedResources; } @@ -107,35 +112,39 @@ public static ManagedResources prepare( * * @return Token/memento representing all known users resources (classes, packages, mapping files, etc). */ - public static MetadataImplementor complete(final ManagedResources managedResources, final MetadataBuildingOptions options) { - final BasicTypeRegistry basicTypeRegistry = handleTypes( options ); - + public static MetadataImplementor complete( + final ManagedResources managedResources, + final BootstrapContext bootstrapContext, + final MetadataBuildingOptions options) { final InFlightMetadataCollectorImpl metadataCollector = new InFlightMetadataCollectorImpl( - options, - new TypeResolver( basicTypeRegistry, new TypeFactory() ) + bootstrapContext, + options ); - for ( AttributeConverterDefinition attributeConverterDefinition : managedResources.getAttributeConverterDefinitions() ) { - metadataCollector.addAttributeConverter( attributeConverterDefinition ); - } - final ClassLoaderService classLoaderService = options.getServiceRegistry().getService( ClassLoaderService.class ); + handleTypes( bootstrapContext, options ); - final ClassLoaderAccess classLoaderAccess = new ClassLoaderAccessImpl( - options.getTempClassLoader(), - classLoaderService - ); + final ClassLoaderService classLoaderService = options.getServiceRegistry().getService( ClassLoaderService.class ); final MetadataBuildingContextRootImpl rootMetadataBuildingContext = new MetadataBuildingContextRootImpl( + bootstrapContext, options, - classLoaderAccess, metadataCollector ); - final IndexView jandexView = options.getJandexView(); + for ( AttributeConverterInfo converterInfo : managedResources.getAttributeConverterDefinitions() ) { + metadataCollector.addAttributeConverter( + converterInfo.toConverterDescriptor( rootMetadataBuildingContext ) + ); + } + + bootstrapContext.getTypeConfiguration().scope( rootMetadataBuildingContext ); + + + final IndexView jandexView = bootstrapContext.getJandexView(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Set up the processors and start binding - // NOTE : this becomes even more simplified afterQuery we move purely + // NOTE : this becomes even more simplified after we move purely // to unified model final MetadataSourceProcessor processor = new MetadataSourceProcessor() { @@ -260,7 +269,7 @@ public void finishUp() { processor.processFilterDefinitions(); processor.processFetchProfiles(); - final Set processedEntityNames = new HashSet(); + final Set processedEntityNames = new HashSet<>(); processor.prepareForEntityHierarchyProcessing(); processor.processEntityHierarchies( processedEntityNames ); processor.postProcessEntityHierarchies(); @@ -305,6 +314,7 @@ public void finishUp() { return metadataCollector.buildMetadataInstance( rootMetadataBuildingContext ); } +// todo (7.0) : buildJandexInitializer // private static JandexInitManager buildJandexInitializer( // MetadataBuildingOptions options, // ClassLoaderAccess classLoaderAccess) { @@ -317,35 +327,49 @@ public void finishUp() { // return new JandexInitManager( options.getJandexView(), classLoaderAccess, autoIndexMembers ); // } - - - - private static BasicTypeRegistry handleTypes(MetadataBuildingOptions options) { + private static void handleTypes(BootstrapContext bootstrapContext, MetadataBuildingOptions options) { final ClassLoaderService classLoaderService = options.getServiceRegistry().getService( ClassLoaderService.class ); - // ultimately this needs to change a little bit to account for HHH-7792 - final BasicTypeRegistry basicTypeRegistry = new BasicTypeRegistry(); - final TypeContributions typeContributions = new TypeContributions() { @Override public void contributeType(org.hibernate.type.BasicType type) { - basicTypeRegistry.register( type ); + getBasicTypeRegistry().register( type ); } @Override public void contributeType(BasicType type, String... keys) { - basicTypeRegistry.register( type, keys ); + getBasicTypeRegistry().register( type, keys ); } @Override public void contributeType(UserType type, String[] keys) { - basicTypeRegistry.register( type, keys ); + getBasicTypeRegistry().register( type, keys ); } @Override public void contributeType(CompositeUserType type, String[] keys) { - basicTypeRegistry.register( type, keys ); + getBasicTypeRegistry().register( type, keys ); + } + + @Override + public void contributeJavaTypeDescriptor(JavaTypeDescriptor descriptor) { + bootstrapContext.getTypeConfiguration().getJavaTypeDescriptorRegistry().addDescriptor( descriptor ); + } + + @Override + public void contributeSqlTypeDescriptor(SqlTypeDescriptor descriptor) { + bootstrapContext.getTypeConfiguration().getSqlTypeDescriptorRegistry().addDescriptor( descriptor ); } + + @Override + public TypeConfiguration getTypeConfiguration() { + return bootstrapContext.getTypeConfiguration(); + } + + final BasicTypeRegistry getBasicTypeRegistry() { + return bootstrapContext.getTypeConfiguration().getBasicTypeRegistry(); + } + }; // add Dialect contributed types @@ -359,13 +383,10 @@ public void contributeType(CompositeUserType type, String[] keys) { // add explicit application registered types for ( BasicTypeRegistration basicTypeRegistration : options.getBasicTypeRegistrations() ) { - basicTypeRegistry.register( + bootstrapContext.getTypeConfiguration().getBasicTypeRegistry().register( basicTypeRegistration.getBasicType(), basicTypeRegistration.getRegistrationKeys() ); } - - return basicTypeRegistry; } - } 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 bbc5740cb961..7434c0772afd 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 @@ -27,15 +27,15 @@ public interface AuxiliaryDatabaseObject extends Exportable, Serializable { public boolean appliesToDialect(Dialect dialect); /** - * Defines a simple precedence. Should creation of this auxiliary object happen beforeQuery creation of - * tables? If {@code true}, the auxiliary object creation will happen afterQuery any explicit schema creations - * but beforeQuery table/sequence creations; if {@code false}, the auxiliary object creation will happen afterQuery - * explicit schema creations and afterQuery table/sequence creations. + * Defines a simple precedence. Should creation of this auxiliary object happen before creation of + * tables? If {@code true}, the auxiliary object creation will happen after any explicit schema creations + * but before table/sequence creations; if {@code false}, the auxiliary object creation will happen after + * explicit schema creations and after table/sequence creations. * * This precedence is automatically inverted for dropping. * - * @return {@code true} indicates this object should be created beforeQuery tables; {@code false} indicates - * it should be created afterQuery. + * @return {@code true} indicates this object should be created before tables; {@code false} indicates + * it should be created after. */ public boolean beforeTablesOnCreation(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java index 887300e3d6a2..9d9eb11c575c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java @@ -8,6 +8,8 @@ import java.util.Set; +import org.hibernate.boot.model.naming.Identifier; + /** * Mainly this is used to support legacy sequence exporting. * @@ -42,6 +44,10 @@ public NamedAuxiliaryDatabaseObject( @Override public String getExportIdentifier() { - return name; + return new QualifiedNameImpl( + Identifier.toIdentifier( getCatalogName() ), + Identifier.toIdentifier( getSchemaName() ), + Identifier.toIdentifier( name ) + ).render(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java index 34e2cf259edb..68b3d676adde 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java @@ -8,13 +8,13 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; import org.hibernate.HibernateException; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.compare.EqualsHelper; import org.hibernate.mapping.DenormalizedTable; import org.hibernate.mapping.Table; @@ -41,7 +41,7 @@ public Namespace(Database database, Name name) { database.getPhysicalNamingStrategy() .toPhysicalCatalogName( name.getCatalog(), database.getJdbcEnvironment() ), database.getPhysicalNamingStrategy() - .toPhysicalCatalogName( name.getSchema(), database.getJdbcEnvironment() ) + .toPhysicalSchemaName( name.getSchema(), database.getJdbcEnvironment() ) ); log.debugf( @@ -145,7 +145,7 @@ public boolean equals(Object o) { } final Namespace that = (Namespace) o; - return EqualsHelper.equals( this.name, that.name ); + return Objects.equals( this.name, that.name ); } @Override @@ -190,8 +190,8 @@ public boolean equals(Object o) { final Name that = (Name) o; - return EqualsHelper.equals( this.catalog, that.catalog ) - && EqualsHelper.equals( this.schema, that.schema ); + return Objects.equals( this.catalog, that.catalog ) + && Objects.equals( this.schema, that.schema ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java index b642e00ca686..9aabf934ff13 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java @@ -6,10 +6,11 @@ */ package org.hibernate.boot.model.relational; +import java.util.Objects; + import org.hibernate.HibernateException; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.IllegalIdentifierException; -import org.hibernate.internal.util.compare.EqualsHelper; /** * Parses a qualified name. @@ -86,9 +87,9 @@ public boolean equals(Object o) { NameParts that = (NameParts) o; - return EqualsHelper.equals( this.getCatalogName(), that.getCatalogName() ) - && EqualsHelper.equals( this.getSchemaName(), that.getSchemaName() ) - && EqualsHelper.equals( this.getObjectName(), that.getObjectName() ); + return Objects.equals( this.getCatalogName(), that.getCatalogName() ) + && Objects.equals( this.getSchemaName(), that.getSchemaName() ) + && Objects.equals( this.getObjectName(), that.getObjectName() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Sequence.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Sequence.java index 046489f87e54..044a6153c9d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Sequence.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Sequence.java @@ -66,7 +66,7 @@ public void validate(int initialValue, int incrementSize) { if ( this.initialValue != initialValue ) { throw new HibernateException( String.format( - "Multiple references to database sequence [%s] were encountered attempting to" + + "Multiple references to database sequence [%s] were encountered attempting to " + "set conflicting values for 'initial value'. Found [%s] and [%s]", exportIdentifier, this.initialValue, @@ -77,7 +77,7 @@ public void validate(int initialValue, int incrementSize) { if ( this.incrementSize != incrementSize ) { throw new HibernateException( String.format( - "Multiple references to database sequence [%s] were encountered attempting to" + + "Multiple references to database sequence [%s] were encountered attempting to " + "set conflicting values for 'increment size'. Found [%s] and [%s]", exportIdentifier, this.incrementSize, 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 edcaa565fc55..df59ef7466f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java @@ -93,6 +93,14 @@ public String[] sqlDropStrings(Dialect dialect) { return copy; } + protected String getCatalogName() { + return catalogName; + } + + protected String getSchemaName() { + return schemaName; + } + private String injectCatalogAndSchema(String ddlString) { String rtn = StringHelper.replace( ddlString, CATALOG_NAME_PLACEHOLDER, catalogName == null ? "" : catalogName ); rtn = StringHelper.replace( rtn, SCHEMA_NAME_PLACEHOLDER, schemaName == null ? "" : schemaName ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java index b47b0ab9e2a4..2aff8fbe38d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java @@ -22,15 +22,16 @@ import org.hibernate.annotations.common.reflection.MetadataProviderInjector; import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XClass; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.internal.MetadataBuildingContextRootImpl; import org.hibernate.boot.jaxb.spi.Binding; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.process.spi.ManagedResources; import org.hibernate.boot.model.source.spi.MetadataSourceProcessor; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.spi.JpaOrmXmlPersistenceUnitDefaultAware; import org.hibernate.boot.spi.JpaOrmXmlPersistenceUnitDefaultAware.JpaOrmXmlPersistenceUnitDefaults; import org.hibernate.cfg.AnnotationBinder; -import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.InheritanceState; import org.hibernate.cfg.annotations.reflection.AttributeConverterDefinitionCollector; import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider; @@ -66,7 +67,7 @@ public AnnotationMetadataSourceProcessorImpl( this.rootMetadataBuildingContext = rootMetadataBuildingContext; this.jandexView = jandexView; - this.reflectionManager = rootMetadataBuildingContext.getBuildingOptions().getReflectionManager(); + this.reflectionManager = rootMetadataBuildingContext.getBootstrapContext().getReflectionManager(); if ( CollectionHelper.isNotEmpty( managedResources.getAnnotatedPackageNames() ) ) { annotatedPackages.addAll( managedResources.getAnnotatedPackageNames() ); @@ -317,8 +318,15 @@ public AttributeConverterManager(MetadataBuildingContextRootImpl rootMetadataBui } @Override - public void addAttributeConverter(AttributeConverterDefinition definition) { - rootMetadataBuildingContext.getMetadataCollector().addAttributeConverter( definition ); + public void addAttributeConverter(AttributeConverterInfo info) { + rootMetadataBuildingContext.getMetadataCollector().addAttributeConverter( + info.toConverterDescriptor( rootMetadataBuildingContext ) + ); + } + + @Override + public void addAttributeConverter(ConverterDescriptor descriptor) { + rootMetadataBuildingContext.getMetadataCollector().addAttributeConverter( descriptor ); } public void addAttributeConverter(Class converterClass) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java index 6199fea5ccd1..cfb2826243b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java @@ -11,11 +11,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import org.hibernate.EntityMode; -import org.hibernate.boot.MappingException; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.hbm.spi.EntityInfo; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmEntityBaseDefinition; @@ -46,7 +44,6 @@ import org.hibernate.boot.model.source.spi.ToolingHintContext; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.internal.util.compare.EqualsHelper; /** * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractPluralAttributeSourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractPluralAttributeSourceImpl.java index 084192ec8fd6..88905125d65d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractPluralAttributeSourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractPluralAttributeSourceImpl.java @@ -6,9 +6,13 @@ */ package org.hibernate.boot.model.source.internal.hbm; +import java.util.Optional; + import org.hibernate.AssertionFailure; import org.hibernate.boot.MappingException; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmFilterType; +import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmManyToOneType; +import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmRootEntityType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmSynchronizeType; import org.hibernate.boot.jaxb.hbm.spi.PluralAttributeInfo; import org.hibernate.boot.model.Caching; @@ -66,11 +70,36 @@ protected AbstractPluralAttributeSourceImpl( this.attributeRole = container.getAttributeRoleBase().append( pluralAttributeJaxbMapping.getName() ); this.attributePath = container.getAttributePathBase().append( pluralAttributeJaxbMapping.getName() ); - this.keySource = new PluralAttributeKeySourceImpl( - sourceMappingDocument(), - pluralAttributeJaxbMapping.getKey(), - container - ); + Optional jaxbHbmManyToOneTypeOptional = Optional.empty(); + + if ( pluralAttributeJaxbMapping.isInverse() && pluralAttributeJaxbMapping.getOneToMany() != null ) { + String childClass = pluralAttributeJaxbMapping.getOneToMany().getClazz(); + + if ( childClass != null ) { + jaxbHbmManyToOneTypeOptional = mappingDocument.getDocumentRoot().getClazz() + .stream() + .filter( (JaxbHbmRootEntityType entityType) -> childClass.equals( entityType.getName() ) ) + .flatMap( jaxbHbmRootEntityType -> jaxbHbmRootEntityType.getAttributes().stream() ) + .filter( + attribute -> attribute instanceof JaxbHbmManyToOneType && + ( (JaxbHbmManyToOneType) attribute ).getPropertyRef() != null ) + .map( JaxbHbmManyToOneType.class::cast ) + .findFirst(); + } + } + + this.keySource = jaxbHbmManyToOneTypeOptional + .map( jaxbHbmManyToOneType -> new PluralAttributeKeySourceImpl( + sourceMappingDocument(), + jaxbHbmManyToOneType, + container + ) ).orElseGet( () -> new PluralAttributeKeySourceImpl( + sourceMappingDocument(), + pluralAttributeJaxbMapping.isInverse() ? + pluralAttributeJaxbMapping.getKey() : + pluralAttributeJaxbMapping.getKey(), + container + ) ); this.typeInformation = new HibernateTypeSourceImpl( pluralAttributeJaxbMapping.getCollectionType() ); @@ -99,7 +128,7 @@ private static String[] extractSynchronizedTableNames(PluralAttributeInfo plural return new String[0]; } - final String[] names = new String[ pluralAttributeElement.getSynchronize().size() ]; + final String[] names = new String[pluralAttributeElement.getSynchronize().size()]; int i = 0; for ( JaxbHbmSynchronizeType jaxbHbmSynchronizeType : pluralAttributeElement.getSynchronize() ) { names[i++] = jaxbHbmSynchronizeType.getTable(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityHierarchyBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityHierarchyBuilder.java index 3b9a7bfc7fe2..45e7cfc06dc3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityHierarchyBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityHierarchyBuilder.java @@ -52,7 +52,7 @@ public EntityHierarchyBuilder() { } /** - * To be called afterQuery all mapping documents have been processed (via {@link #indexMappingDocument}) + * To be called after all mapping documents have been processed (via {@link #indexMappingDocument}) * * @return The built hierarchies * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java index 6569d928abae..ecdbe1d3f5f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java @@ -14,6 +14,7 @@ import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmFilterAliasMappingType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmFilterType; import org.hibernate.boot.model.source.spi.FilterSource; +import org.hibernate.internal.util.NullnessHelper; import org.hibernate.internal.util.StringHelper; /** @@ -63,7 +64,7 @@ else if ( StringHelper.isNotEmpty( aliasMapping.getEntity() ) ) { } } - this.condition = Helper.coalesce( conditionContent, conditionAttribute ); + this.condition = NullnessHelper.coalesce( conditionContent, conditionAttribute ); this.autoAliasInjection = StringHelper.isNotEmpty( explicitAutoAliasInjectionSetting ) ? Boolean.valueOf( explicitAutoAliasInjectionSetting ) : true; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java index aef4d52a5599..50a55e4e5266 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java @@ -116,40 +116,13 @@ public static Map extractParameters(List params = new HashMap(); + final HashMap params = new HashMap<>(); for ( JaxbHbmConfigParameterType paramElement : xmlParamElements ) { params.put( paramElement.getName(), paramElement.getValue() ); } return params; } - /** - * Operates like SQL coalesce expression, except empty strings are treated as null. Return the first non-empty value - * - * @param values The list of values. - * @param Generic type of values to coalesce - * - * @return The first non-empty value, or null if all values were empty - */ - public static T coalesce(T... values) { - if ( values == null ) { - return null; - } - for ( T value : values ) { - if ( value != null ) { - if ( String.class.isInstance( value ) ) { - if ( StringHelper.isNotEmpty( (String) value ) ) { - return value; - } - } - else { - return value; - } - } - } - return null; - } - static ToolingHintContext collectToolingHints( ToolingHintContext baseline, ToolingHintContainer toolingHintContainer) { @@ -200,8 +173,8 @@ public static TableSpecificationSource createTableSource( return createTableSource( mappingDocument, entityElement, inLineViewNameInferrer, null, null, null ); } - public static interface InLineViewNameInferrer { - public String inferInLineViewName(); + public interface InLineViewNameInferrer { + String inferInLineViewName(); } public static TableSpecificationSource createTableSource( @@ -258,7 +231,7 @@ public static Class reflectedPropertyClass( MetadataBuildingContext buildingContext, String attributeOwnerClassName, String attributeName) { - final Class attributeOwnerClass = buildingContext.getClassLoaderAccess().classForName( attributeOwnerClassName ); + final Class attributeOwnerClass = buildingContext.getBootstrapContext().getClassLoaderAccess().classForName( attributeOwnerClassName ); return reflectedPropertyClass( buildingContext, attributeOwnerClass, @@ -270,20 +243,6 @@ public static Class reflectedPropertyClass( MetadataBuildingContext buildingContext, Class attributeOwnerClass, final String attributeName) { -// return BeanInfoHelper.visitBeanInfo( -// attributeOwnerClass, -// new BeanInfoHelper.ReturningBeanInfoDelegate() { -// @Override -// public Class processBeanInfo(BeanInfo beanInfo) throws Exception { -// for ( PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors() ) { -// if ( propertyDescriptor.getName().equals( attributeName ) ) { -// return propertyDescriptor.getPropertyType(); -// } -// } -// return null; -// } -// } -// ); return ReflectHelper.reflectedPropertyClass( attributeOwnerClass, attributeName ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java index cf52d4f5caa3..4cf5d29e7cd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java @@ -24,6 +24,7 @@ import org.hibernate.boot.model.source.internal.OverriddenMappingDefaults; import org.hibernate.boot.model.source.spi.MetadataSourceProcessor; import org.hibernate.boot.model.source.spi.ToolingHintContext; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MappingDefaults; @@ -122,6 +123,11 @@ public Origin getOrigin() { return origin; } + @Override + public BootstrapContext getBootstrapContext() { + return rootBuildingContext.getBootstrapContext(); + } + @Override public MetadataBuildingOptions getBuildingOptions() { return rootBuildingContext.getBuildingOptions(); 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 ccaabcf4a369..2c7ec3ee1f53 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 @@ -12,6 +12,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Properties; import org.hibernate.AssertionFailure; @@ -91,10 +92,12 @@ import org.hibernate.boot.spi.InFlightMetadataCollector.EntityTableXref; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.NaturalIdUniqueKeyBinder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.FkSecondPass; import org.hibernate.cfg.SecondPass; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.id.PersistentIdentifierGenerator; @@ -103,7 +106,7 @@ import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.internal.util.compare.EqualsHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.loader.PropertyPath; import org.hibernate.mapping.Any; import org.hibernate.mapping.Array; @@ -140,8 +143,14 @@ import org.hibernate.mapping.Value; import org.hibernate.tuple.GeneratedValueGeneration; import org.hibernate.tuple.GenerationTiming; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.BasicType; +import org.hibernate.type.BlobType; +import org.hibernate.type.ClobType; import org.hibernate.type.DiscriminatorType; import org.hibernate.type.ForeignKeyDirection; +import org.hibernate.type.NClobType; +import org.hibernate.type.TypeResolver; /** * Responsible for coordinating the binding of all information inside entity tags ({@code }, etc). @@ -331,7 +340,7 @@ private void applyCaching(MappingDocument mappingDocument, Caching caching, Root } rootEntityDescriptor.setCacheRegionName( caching.getRegion() ); rootEntityDescriptor.setLazyPropertiesCacheable( caching.isCacheLazyProperties() ); - rootEntityDescriptor.setCachingExplicitlyRequested( caching.getRequested() != TruthValue.UNKNOWN ); + rootEntityDescriptor.setCached( caching.getRequested() != TruthValue.UNKNOWN ); } private void bindEntityIdentifier( @@ -444,7 +453,9 @@ else if ( entitySource.isLazy() ) { if ( StringHelper.isNotEmpty( entitySource.getCustomPersisterClassName() ) ) { try { entityDescriptor.setEntityPersisterClass( - sourceDocument.getClassLoaderAccess().classForName( entitySource.getCustomPersisterClassName() ) + sourceDocument.getBootstrapContext() + .getClassLoaderAccess() + .classForName( entitySource.getCustomPersisterClassName() ) ); } catch (ClassLoadingException e) { @@ -603,7 +614,7 @@ private void bindJoinedSubclassEntity( // KEY final SimpleValue keyBinding = new DependantValue( - mappingDocument.getMetadataCollector(), + mappingDocument, primaryTable, entityDescriptor.getIdentifier() ); @@ -695,7 +706,7 @@ private void bindSimpleEntityIdentifier( final IdentifierSourceSimple idSource = (IdentifierSourceSimple) hierarchySource.getIdentifierSource(); final SimpleValue idValue = new SimpleValue( - sourceDocument.getMetadataCollector(), + sourceDocument, rootEntityDescriptor.getTable() ); rootEntityDescriptor.setIdentifier( idValue ); @@ -835,7 +846,7 @@ private void bindAggregatedCompositeEntityIdentifier( final IdentifierSourceAggregatedComposite identifierSource = (IdentifierSourceAggregatedComposite) hierarchySource.getIdentifierSource(); - final Component cid = new Component( mappingDocument.getMetadataCollector(), rootEntityDescriptor ); + final Component cid = new Component( mappingDocument, rootEntityDescriptor ); cid.setKey( true ); rootEntityDescriptor.setIdentifier( cid ); @@ -883,7 +894,7 @@ private void bindNonAggregatedCompositeEntityIdentifier( final IdentifierSourceNonAggregatedComposite identifierSource = (IdentifierSourceNonAggregatedComposite) hierarchySource.getIdentifierSource(); - final Component cid = new Component( mappingDocument.getMetadataCollector(), rootEntityDescriptor ); + final Component cid = new Component( mappingDocument, rootEntityDescriptor ); cid.setKey( true ); rootEntityDescriptor.setIdentifier( cid ); @@ -906,7 +917,7 @@ private void bindNonAggregatedCompositeEntityIdentifier( // we also need to bind the "id mapper". ugh, terrible name. Basically we need to // create a virtual (embedded) composite for the non-aggregated attributes on the entity // itself. - final Component mapper = new Component( mappingDocument.getMetadataCollector(), rootEntityDescriptor ); + final Component mapper = new Component( mappingDocument, rootEntityDescriptor ); bindComponent( mappingDocument, hierarchySource.getRoot().getAttributeRoleBase().append( ID_MAPPER_PATH_PART ).getFullPath(), @@ -990,7 +1001,7 @@ private void bindEntityVersion( final VersionAttributeSource versionAttributeSource = hierarchySource.getVersionAttributeSource(); final SimpleValue versionValue = new SimpleValue( - sourceDocument.getMetadataCollector(), + sourceDocument, rootEntityDescriptor.getTable() ); @@ -1043,6 +1054,7 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { } rootEntityDescriptor.setVersion( prop ); + rootEntityDescriptor.setDeclaredVersion( prop ); rootEntityDescriptor.addProperty( prop ); } @@ -1051,7 +1063,7 @@ private void bindEntityDiscriminator( final EntityHierarchySourceImpl hierarchySource, RootClass rootEntityDescriptor) { final SimpleValue discriminatorValue = new SimpleValue( - sourceDocument.getMetadataCollector(), + sourceDocument, rootEntityDescriptor.getTable() ); rootEntityDescriptor.setDiscriminator( discriminatorValue ); @@ -1152,7 +1164,7 @@ private void bindAllEntityAttributes( final Property attribute = createBasicAttribute( mappingDocument, basicAttributeSource, - new SimpleValue( mappingDocument.getMetadataCollector(), table ), + new SimpleValue( mappingDocument, table ), entityDescriptor.getClassName() ); @@ -1187,7 +1199,7 @@ else if ( SingularAttributeSourceEmbedded.class.isInstance( attributeSource ) ) final Property attribute = createEmbeddedAttribute( mappingDocument, (SingularAttributeSourceEmbedded) attributeSource, - new Component( mappingDocument.getMetadataCollector(), table, entityDescriptor ), + new Component( mappingDocument, table, entityDescriptor ), entityDescriptor.getClassName() ); @@ -1222,7 +1234,7 @@ else if ( SingularAttributeSourceManyToOne.class.isInstance( attributeSource ) ) final Property attribute = createManyToOneAttribute( mappingDocument, manyToOneAttributeSource, - new ManyToOne( mappingDocument.getMetadataCollector(), table ), + new ManyToOne( mappingDocument, table ), entityDescriptor.getClassName() ); @@ -1245,7 +1257,7 @@ else if ( SingularAttributeSourceOneToOne.class.isInstance( attributeSource ) ) final Property attribute = createOneToOneAttribute( mappingDocument, oneToOneAttributeSource, - new OneToOne( mappingDocument.getMetadataCollector(), table, entityDescriptor ), + new OneToOne( mappingDocument, table, entityDescriptor ), entityDescriptor.getClassName() ); entityDescriptor.addProperty( attribute ); @@ -1279,7 +1291,7 @@ else if ( SingularAttributeSourceAny.class.isInstance( attributeSource ) ) { final Property attribute = createAnyAssociationAttribute( mappingDocument, anyAttributeSource, - new Any( mappingDocument.getMetadataCollector(), table ), + new Any( mappingDocument, table ), entityDescriptor.getEntityName() ); @@ -1337,7 +1349,7 @@ private Property createPluralAttribute( final Collection collectionBinding; if ( attributeSource instanceof PluralAttributeSourceListImpl ) { - collectionBinding = new org.hibernate.mapping.List( sourceDocument.getMetadataCollector(), entityDescriptor ); + collectionBinding = new org.hibernate.mapping.List( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); registerSecondPass( @@ -1350,7 +1362,7 @@ private Property createPluralAttribute( ); } else if ( attributeSource instanceof PluralAttributeSourceSetImpl ) { - collectionBinding = new Set( sourceDocument.getMetadataCollector(), entityDescriptor ); + collectionBinding = new Set( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); registerSecondPass( @@ -1359,7 +1371,7 @@ else if ( attributeSource instanceof PluralAttributeSourceSetImpl ) { ); } else if ( attributeSource instanceof PluralAttributeSourceMapImpl ) { - collectionBinding = new org.hibernate.mapping.Map( sourceDocument.getMetadataCollector(), entityDescriptor ); + collectionBinding = new org.hibernate.mapping.Map( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); registerSecondPass( @@ -1372,7 +1384,7 @@ else if ( attributeSource instanceof PluralAttributeSourceMapImpl ) { ); } else if ( attributeSource instanceof PluralAttributeSourceBagImpl ) { - collectionBinding = new Bag( sourceDocument.getMetadataCollector(), entityDescriptor ); + collectionBinding = new Bag( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); registerSecondPass( @@ -1381,7 +1393,7 @@ else if ( attributeSource instanceof PluralAttributeSourceBagImpl ) { ); } else if ( attributeSource instanceof PluralAttributeSourceIdBagImpl ) { - collectionBinding = new IdentifierBag( sourceDocument.getMetadataCollector(), entityDescriptor ); + collectionBinding = new IdentifierBag( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); registerSecondPass( @@ -1391,7 +1403,7 @@ else if ( attributeSource instanceof PluralAttributeSourceIdBagImpl ) { } else if ( attributeSource instanceof PluralAttributeSourceArrayImpl ) { final PluralAttributeSourceArray arraySource = (PluralAttributeSourceArray) attributeSource; - collectionBinding = new Array( sourceDocument.getMetadataCollector(), entityDescriptor ); + collectionBinding = new Array( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); ( (Array) collectionBinding ).setElementClassName( @@ -1408,7 +1420,7 @@ else if ( attributeSource instanceof PluralAttributeSourceArrayImpl ) { ); } else if ( attributeSource instanceof PluralAttributeSourcePrimitiveArrayImpl ) { - collectionBinding = new PrimitiveArray( sourceDocument.getMetadataCollector(), entityDescriptor ); + collectionBinding = new PrimitiveArray( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); registerSecondPass( @@ -1447,7 +1459,7 @@ private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttri if ( source.getCustomPersisterClassName() != null ) { binding.setCollectionPersisterClass( - mappingDocument.getClassLoaderAccess().classForName( + mappingDocument.getBootstrapContext().getClassLoaderAccess().classForName( mappingDocument.qualifyClassName( source.getCustomPersisterClassName() ) ) ); @@ -1518,7 +1530,6 @@ private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttri binding.getSynchronizedTables().add( name ); } - binding.setWhere( source.getWhere() ); binding.setLoaderName( source.getCustomLoaderName() ); if ( source.getCustomSqlInsert() != null ) { binding.setCustomSQLInsert( @@ -1674,7 +1685,7 @@ else if ( SingularAttributeSourceAny.class.isInstance( attributeSource ) ) { continue; } - if ( EqualsHelper.equals( tableName, determinedName ) ) { + if ( Objects.equals( tableName, determinedName ) ) { continue; } @@ -1705,7 +1716,7 @@ private Identifier determineTable( for ( RelationalValueSource relationalValueSource : relationalValueSources ) { // We need to get the containing table name for both columns and formulas, // particularly when a column/formula is for a property on a secondary table. - if ( EqualsHelper.equals( tableName, relationalValueSource.getContainingTableName() ) ) { + if ( Objects.equals( tableName, relationalValueSource.getContainingTableName() ) ) { continue; } @@ -1787,7 +1798,7 @@ private void bindSecondaryTable( } final SimpleValue keyBinding = new DependantValue( - mappingDocument.getMetadataCollector(), + mappingDocument, secondaryTable, persistentClass.getIdentifier() ); @@ -1912,6 +1923,8 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { attributeSource.getAttributeRole() ); + resolveLob( attributeSource, value ); + // // this is done here 'cos we might only know the type here (ugly!) // // TODO: improve this a lot: // if ( value instanceof ToOne ) { @@ -1935,6 +1948,7 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { Property property = new Property(); property.setValue( value ); + property.setLob( value.isLob() ); bindProperty( sourceDocument, attributeSource, @@ -1944,6 +1958,46 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { return property; } + private void resolveLob(final SingularAttributeSourceBasic attributeSource, SimpleValue value) { + // Resolves whether the property is LOB based on the type attribute on the attribute property source. + // Essentially this expects the type to map to a CLOB/NCLOB/BLOB sql type internally and compares. + if ( !value.isLob() && value.getTypeName() != null ) { + final TypeResolver typeResolver = attributeSource.getBuildingContext().getMetadataCollector().getTypeResolver(); + final BasicType basicType = typeResolver.basic( value.getTypeName() ); + if ( basicType instanceof AbstractSingleColumnStandardBasicType ) { + if ( isLob( ( (AbstractSingleColumnStandardBasicType) basicType ).getSqlTypeDescriptor().getSqlType(), null ) ) { + value.makeLob(); + } + } + } + + // If the prior check didn't set the lob flag, this will inspect the column sql-type attribute value and + // if this maps to CLOB/NCLOB/BLOB then the value will be marked as lob. + if ( !value.isLob() ) { + for ( RelationalValueSource relationalValueSource : attributeSource.getRelationalValueSources() ) { + if ( ColumnSource.class.isInstance( relationalValueSource ) ) { + if ( isLob( null, ( (ColumnSource) relationalValueSource ).getSqlType() ) ) { + value.makeLob(); + } + } + } + } + } + + private static boolean isLob(Integer sqlType, String sqlTypeName) { + if ( sqlType != null ) { + return ClobType.INSTANCE.getSqlTypeDescriptor().getSqlType() == sqlType || + BlobType.INSTANCE.getSqlTypeDescriptor().getSqlType() == sqlType || + NClobType.INSTANCE.getSqlTypeDescriptor().getSqlType() == sqlType; + } + else if ( sqlTypeName != null ) { + return ClobType.INSTANCE.getName().equalsIgnoreCase( sqlTypeName ) || + BlobType.INSTANCE.getName().equalsIgnoreCase( sqlTypeName ) || + NClobType.INSTANCE.getName().equalsIgnoreCase( sqlTypeName ); + } + return false; + } + private Property createOneToOneAttribute( MappingDocument sourceDocument, SingularAttributeSourceOneToOne oneToOneSource, @@ -2674,7 +2728,7 @@ private void bindAllCompositeAttributes( attribute = createBasicAttribute( sourceDocument, (SingularAttributeSourceBasic) attributeSource, - new SimpleValue( sourceDocument.getMetadataCollector(), component.getTable() ), + new SimpleValue( sourceDocument, component.getTable() ), component.getComponentClassName() ); } @@ -2682,7 +2736,7 @@ else if ( SingularAttributeSourceEmbedded.class.isInstance( attributeSource ) ) attribute = createEmbeddedAttribute( sourceDocument, (SingularAttributeSourceEmbedded) attributeSource, - new Component( sourceDocument.getMetadataCollector(), component ), + new Component( sourceDocument, component ), component.getComponentClassName() ); } @@ -2690,7 +2744,7 @@ else if ( SingularAttributeSourceManyToOne.class.isInstance( attributeSource ) ) attribute = createManyToOneAttribute( sourceDocument, (SingularAttributeSourceManyToOne) attributeSource, - new ManyToOne( sourceDocument.getMetadataCollector(), component.getTable() ), + new ManyToOne( sourceDocument, component.getTable() ), component.getComponentClassName() ); } @@ -2698,7 +2752,7 @@ else if ( SingularAttributeSourceOneToOne.class.isInstance( attributeSource ) ) attribute = createOneToOneAttribute( sourceDocument, (SingularAttributeSourceOneToOne) attributeSource, - new OneToOne( sourceDocument.getMetadataCollector(), component.getTable(), component.getOwner() ), + new OneToOne( sourceDocument, component.getTable(), component.getOwner() ), component.getComponentClassName() ); } @@ -2706,7 +2760,7 @@ else if ( SingularAttributeSourceAny.class.isInstance( attributeSource ) ) { attribute = createAnyAssociationAttribute( sourceDocument, (SingularAttributeSourceAny) attributeSource, - new Any( sourceDocument.getMetadataCollector(), component.getTable() ), + new Any( sourceDocument, component.getTable() ), component.getComponentClassName() ); } @@ -2784,20 +2838,6 @@ private static TypeResolution resolveType( typeParameters.putAll( typeDefinition.getParameters() ); } } -// else { -// final BasicType basicType = sourceDocument.getMetadataCollector().getTypeResolver().basic( typeName ); -// if ( basicType == null ) { -// throw new MappingException( -// String.format( -// Locale.ENGLISH, -// "Mapping named an explicit type [%s] which could not be resolved", -// typeName -// ), -// sourceDocument.getOrigin() -// ); -// } -// } - // parameters on the property mapping should override parameters in the type-def if ( typeSource.getParameters() != null ) { typeParameters.putAll( typeSource.getParameters() ); @@ -3270,7 +3310,7 @@ protected void bindCollectionKey() { keyVal = (KeyValue) getCollectionBinding().getOwner().getRecursiveProperty( propRef ).getValue(); } final DependantValue key = new DependantValue( - mappingDocument.getMetadataCollector(), + mappingDocument, getCollectionBinding().getCollectionTable(), keyVal ); @@ -3294,59 +3334,6 @@ protected void bindCollectionKey() { new RelationalObjectBinder.ColumnNamingDelegate() { @Override public Identifier determineImplicitName(final LocalMetadataBuildingContext context) { - // another case where HbmBinder was not adjusted to make use of NamingStrategy#foreignKeyColumnName - // when that was added in developing annotation binding :( -// return implicitNamingStrategy.determineJoinColumnName( -// new ImplicitJoinColumnNameSource() { -// private EntityNamingSourceImpl entityNamingSource; -// private Identifier referencedColumnName; -// -// @Override -// public Nature getNature() { -// return implicitNamingNature; -// } -// -// @Override -// public EntityNaming getEntityNaming() { -// if ( entityNamingSource == null ) { -// entityNamingSource = new EntityNamingSourceImpl( -// getCollectionBinding().getOwner().getEntityName(), -// getCollectionBinding().getOwner().getClassName(), -// getCollectionBinding().getOwner().getJpaEntityName() -// ); -// } -// return entityNamingSource; -// } -// -// @Override -// public AttributePath getAttributePath() { -// return getPluralAttributeSource().getAttributePath(); -// } -// -// @Override -// public Identifier getReferencedTableName() { -// return getCollectionBinding().getCollectionTable().getNameIdentifier(); -// } -// -// @Override -// public Identifier getReferencedColumnName() { -// if ( referencedColumnName == null ) { -// final Iterator selectableItr = keyVal.getColumnIterator(); -// // assume there is just one, and that its a column... -// final Column column = (Column) selectableItr.next(); -// referencedColumnName = getMappingDocument().getMetadataCollector() -// .getDatabase() -// .toIdentifier( column.getQuotedName() ); -// } -// return referencedColumnName; -// } -// -// @Override -// public MetadataBuildingContext getBuildingContext() { -// return context; -// } -// } -// ); return context.getMetadataCollector().getDatabase().toIdentifier( Collection.DEFAULT_KEY_COLUMN_NAME ); } } @@ -3364,7 +3351,7 @@ protected void bindCollectionIdentifier() { if ( idSource != null ) { final IdentifierCollection idBagBinding = (IdentifierCollection) getCollectionBinding(); final SimpleValue idBinding = new SimpleValue( - mappingDocument.getMetadataCollector(), + mappingDocument, idBagBinding.getCollectionTable() ); @@ -3402,11 +3389,16 @@ protected void bindCollectionIndex() { } protected void bindCollectionElement() { + log.debugf( + "Binding [%s] element type for a [%s]", + getPluralAttributeSource().getElementSource().getNature(), + getPluralAttributeSource().getNature() + ); if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceBasic ) { final PluralAttributeElementSourceBasic elementSource = (PluralAttributeElementSourceBasic) getPluralAttributeSource().getElementSource(); final SimpleValue elementBinding = new SimpleValue( - getMappingDocument().getMetadataCollector(), + getMappingDocument(), getCollectionBinding().getCollectionTable() ); @@ -3424,21 +3416,22 @@ protected void bindCollectionElement() { new RelationalObjectBinder.ColumnNamingDelegate() { @Override public Identifier determineImplicitName(LocalMetadataBuildingContext context) { -// return implicitNamingStrategy.determineBasicColumnName( -// elementSource -// ); return context.getMetadataCollector().getDatabase().toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); } } ); getCollectionBinding().setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (the table containing the basic elements) + // This "where" clause comes from the collection mapping; e.g., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceEmbedded ) { final PluralAttributeElementSourceEmbedded elementSource = (PluralAttributeElementSourceEmbedded) getPluralAttributeSource().getElementSource(); final Component elementBinding = new Component( - getMappingDocument().getMetadataCollector(), + getMappingDocument(), getCollectionBinding() ); @@ -3454,28 +3447,51 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu ); getCollectionBinding().setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (the table containing the embeddable elements) + // This "where" clause comes from the collection mapping; e.g., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceOneToMany ) { final PluralAttributeElementSourceOneToMany elementSource = (PluralAttributeElementSourceOneToMany) getPluralAttributeSource().getElementSource(); final OneToMany elementBinding = new OneToMany( - getMappingDocument().getMetadataCollector(), + getMappingDocument(), getCollectionBinding().getOwner() ); collectionBinding.setElement( elementBinding ); final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() .getEntityBinding( elementSource.getReferencedEntityName() ); + + if ( useEntityWhereClauseForCollections() ) { + // For a one-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table: + // 1) from the associated entity mapping; i.e., + // 2) from the collection mapping; e.g., + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the associated entity table for a one-to-many association). + collectionBinding.setWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + getPluralAttributeSource().getWhere() + ) + ); + } + else { + // ignore entity's where clause + collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); + } + elementBinding.setReferencedEntityName( referencedEntityBinding.getEntityName() ); elementBinding.setAssociatedClass( referencedEntityBinding ); - elementBinding.setIgnoreNotFound( elementSource.isIgnoreNotFound() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceManyToMany ) { final PluralAttributeElementSourceManyToMany elementSource = (PluralAttributeElementSourceManyToMany) getPluralAttributeSource().getElementSource(); final ManyToOne elementBinding = new ManyToOne( - getMappingDocument().getMetadataCollector(), + getMappingDocument(), getCollectionBinding().getCollectionTable() ); @@ -3487,88 +3503,17 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu new RelationalObjectBinder.ColumnNamingDelegate() { @Override public Identifier determineImplicitName(final LocalMetadataBuildingContext context) { -// return implicitNamingStrategy.determineJoinColumnName( -// new ImplicitJoinColumnNameSource() { -// private final PersistentClass pc = mappingDocument.getMetadataCollector() -// .getEntityBinding( elementSource.getReferencedEntityName() ); -// private final EntityNaming referencedEntityNaming = new EntityNamingSourceImpl( -// pc -// ); -// private Identifier referencedTableName; -// private Identifier referencedColumnName; -// -// @Override -// public Nature getNature() { -// return Nature.ENTITY_COLLECTION; -// } -// -// @Override -// public EntityNaming getEntityNaming() { -// return referencedEntityNaming; -// } -// -// @Override -// public AttributePath getAttributePath() { -// // this is the mapped-by attribute, which we do not -// // know here -// return null; -// } -// -// @Override -// public Identifier getReferencedTableName() { -// if ( referencedTableName == null ) { -// resolveTableAndColumn(); -// } -// return referencedTableName; -// } -// -// private void resolveTableAndColumn() { -// final Iterator itr; -// -// if ( elementSource.getReferencedEntityAttributeName() == null ) { -// // refers to PK -// referencedTableName = pc.getIdentifier() -// .getTable() -// .getNameIdentifier(); -// itr = pc.getIdentifier().getColumnIterator(); -// } -// else { -// // refers to an attribute's column(s) -// final Property referencedAttribute = pc.getProperty( elementSource.getReferencedEntityAttributeName() ); -// referencedTableName = referencedAttribute.getValue() -// .getTable() -// .getNameIdentifier(); -// itr = referencedAttribute.getValue().getColumnIterator(); -// } -// -// // assume one and only one... -// referencedColumnName = context.getMetadataCollector() -// .getDatabase() -// .getJdbcEnvironment() -// .getIdentifierHelper() -// .toIdentifier( ( (Column) itr.next() ).getQuotedName() ); -// } -// -// @Override -// public Identifier getReferencedColumnName() { -// if ( referencedColumnName == null ) { -// resolveTableAndColumn(); -// } -// return referencedColumnName; -// } -// -// @Override -// public MetadataBuildingContext getBuildingContext() { -// return context; -// } -// } -// ); - return context.getMetadataCollector().getDatabase().toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); + return context.getMetadataCollector() + .getDatabase() + .toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); } } ); - elementBinding.setLazy( elementSource.getFetchCharacteristics().getFetchTiming() != FetchTiming.IMMEDIATE ); + elementBinding.setLazy( + elementSource.getFetchCharacteristics() + .getFetchTiming() != FetchTiming.IMMEDIATE + ); elementBinding.setFetchMode( elementSource.getFetchCharacteristics().getFetchStyle() == FetchStyle.SELECT ? FetchMode.SELECT @@ -3588,7 +3533,34 @@ public Identifier determineImplicitName(final LocalMetadataBuildingContext conte getCollectionBinding().setElement( elementBinding ); - getCollectionBinding().setManyToManyWhere( elementSource.getWhere() ); + final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector().getEntityBinding( + elementSource.getReferencedEntityName() + ); + + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-many association). + // This "where" clause comes from the collection mapping; e.g., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); + + if ( useEntityWhereClauseForCollections() ) { + // For a many-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table (not the join table): + // 1) from the associated entity mapping; i.e., + // 2) from the many-to-many mapping; i.e + // Collection#setManytoManyWhere is used to set the "where" clause that applies to + // to the many-to-many associated entity table (not the join table). + getCollectionBinding().setManyToManyWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + elementSource.getWhere() + ) + ); + } + else { + // ignore entity's where clause + getCollectionBinding().setManyToManyWhere( elementSource.getWhere() ); + } + getCollectionBinding().setManyToManyOrdering( elementSource.getOrder() ); if ( !CollectionHelper.isEmpty( elementSource.getFilterSources() ) @@ -3650,7 +3622,7 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu final PluralAttributeElementSourceManyToAny elementSource = (PluralAttributeElementSourceManyToAny) getPluralAttributeSource().getElementSource(); final Any elementBinding = new Any( - getMappingDocument().getMetadataCollector(), + getMappingDocument(), getCollectionBinding().getCollectionTable() ); bindAny( @@ -3662,10 +3634,26 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu ); getCollectionBinding().setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-any association). + // This "where" clause comes from the collection mapping; e.g., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } } } + private boolean useEntityWhereClauseForCollections() { + return ConfigurationHelper.getBoolean( + AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + metadataBuildingContext + .getBuildingOptions() + .getServiceRegistry() + .getService( ConfigurationService.class ) + .getSettings(), + true + ); + } + private class PluralAttributeListSecondPass extends AbstractPluralAttributeSecondPass { public PluralAttributeListSecondPass( MappingDocument sourceDocument, @@ -3895,7 +3883,7 @@ public void bindListOrArrayIndex( (PluralAttributeSequentialIndexSource) attributeSource.getIndexSource(); final SimpleValue indexBinding = new SimpleValue( - mappingDocument.getMetadataCollector(), + mappingDocument, collectionBinding.getCollectionTable() ); @@ -3942,7 +3930,7 @@ private void bindMapKey( final PluralAttributeMapKeySourceBasic mapKeySource = (PluralAttributeMapKeySourceBasic) pluralAttributeSource.getIndexSource(); final SimpleValue value = new SimpleValue( - mappingDocument.getMetadataCollector(), + mappingDocument, collectionBinding.getCollectionTable() ); bindSimpleValueType( @@ -3977,7 +3965,7 @@ else if ( pluralAttributeSource.getIndexSource() instanceof PluralAttributeMapKe final PluralAttributeMapKeySourceEmbedded mapKeySource = (PluralAttributeMapKeySourceEmbedded) pluralAttributeSource.getIndexSource(); final Component componentBinding = new Component( - mappingDocument.getMetadataCollector(), + mappingDocument, collectionBinding ); bindComponent( @@ -3995,7 +3983,7 @@ else if ( pluralAttributeSource.getIndexSource() instanceof PluralAttributeMapKe final PluralAttributeMapKeyManyToManySource mapKeySource = (PluralAttributeMapKeyManyToManySource) pluralAttributeSource.getIndexSource(); final ManyToOne mapKeyBinding = new ManyToOne( - mappingDocument.getMetadataCollector(), + mappingDocument, collectionBinding.getCollectionTable() ); @@ -4031,7 +4019,7 @@ else if ( pluralAttributeSource.getIndexSource() instanceof PluralAttributeMapKe final PluralAttributeMapKeyManyToAnySource mapKeySource = (PluralAttributeMapKeyManyToAnySource) pluralAttributeSource.getIndexSource(); final Any mapKeyBinding = new Any( - mappingDocument.getMetadataCollector(), + mappingDocument, collectionBinding.getCollectionTable() ); bindAny( @@ -4116,8 +4104,6 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { // implicit naming. If we get here, we assume that there is only a single // column making up the FK - final String referencedEntityAttributeName = manyToOneSource.getReferencedEntityAttributeName(); - final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() .getEntityBinding( referencedEntityName ); @@ -4128,36 +4114,6 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { ); } - final EntityNaming entityNaming = new EntityNamingSourceImpl( referencedEntityBinding ); - - final Identifier referencedTableName; - final Identifier referencedColumnName; - - if ( referencedEntityAttributeName == null ) { - referencedTableName = referencedEntityBinding.getTable().getNameIdentifier(); - final Column referencedColumn = referencedEntityBinding.getTable() - .getPrimaryKey() - .getColumn( 0 ); - referencedColumnName = mappingDocument.getMetadataCollector() - .getDatabase() - .getJdbcEnvironment() - .getIdentifierHelper() - .toIdentifier( referencedColumn.getQuotedName() ); - } - else { - final Property referencedProperty = referencedEntityBinding.getReferencedProperty( - referencedEntityAttributeName - ); - final SimpleValue value = (SimpleValue) referencedProperty.getValue(); - referencedTableName = value.getTable().getNameIdentifier(); - final Column referencedColumn = (Column) value.getColumnIterator().next(); - referencedColumnName = mappingDocument.getMetadataCollector() - .getDatabase() - .getJdbcEnvironment() - .getIdentifierHelper() - .toIdentifier( referencedColumn.getQuotedName() ); - } - relationalObjectBinder.bindColumnsAndFormulas( mappingDocument, manyToOneSource.getRelationalValueSources(), @@ -4166,45 +4122,6 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { new RelationalObjectBinder.ColumnNamingDelegate() { @Override public Identifier determineImplicitName(final LocalMetadataBuildingContext context) { - // NOTE : This sucks!!! The problem is that the legacy HBMBinder routed this - // through the legacy NamingStrategy#propertyToColumName. - // - // Basically, when developing the AnnotationBinder and - // NamingStrategy#foreignKeyColumnName HbmBinder was never updated to - // utilize that new method. -// return implicitNamingStrategy.determineJoinColumnName( -// new ImplicitJoinColumnNameSource() { -// @Override -// public Nature getNature() { -// return Nature.ENTITY; -// } -// -// @Override -// public EntityNaming getEntityNaming() { -// return entityNaming; -// } -// -// @Override -// public AttributePath getAttributePath() { -// return manyToOneSource.getAttributePath(); -// } -// -// @Override -// public Identifier getReferencedTableName() { -// return referencedTableName; -// } -// -// @Override -// public Identifier getReferencedColumnName() { -// return referencedColumnName; -// } -// -// @Override -// public MetadataBuildingContext getBuildingContext() { -// return context; -// } -// } -// ); return implicitNamingStrategy.determineBasicColumnName( new ImplicitBasicColumnNameSource() { @Override @@ -4345,6 +4262,11 @@ public List getColumnNames() { public MetadataBuildingContext getBuildingContext() { return mappingDocument; } + + @Override + public Identifier getUserProvidedIdentifier() { + return uk.getName() != null ? Identifier.toIdentifier( uk.getName() ) : null; + } } ); uk.setName( ukName.render( mappingDocument.getMetadataCollector().getDatabase().getDialect() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/NamedQueryBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/NamedQueryBinder.java index 64e54f9c8c6d..d0990c57bd21 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/NamedQueryBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/NamedQueryBinder.java @@ -156,7 +156,7 @@ public static void processNamedNativeQuery( // returns it defines. But binding for those entities may have not been // completed yet. For "normal" ResultSet mappings, this is already handled by // the fact that MetadataSourceProcessor#processResultSetMappings() is called - // afterQuery all entity hierarchies have been processed. However, here we are in + // after all entity hierarchies have been processed. However, here we are in // the middle of processing named-queries (either top-level or entity-level) // and have no guarantee that any entity bindings we may need here are bound. // So we add the second-pass to bind the implicit resultSet mapping. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/PluralAttributeKeySourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/PluralAttributeKeySourceImpl.java index d25898145de8..30caba025bce 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/PluralAttributeKeySourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/PluralAttributeKeySourceImpl.java @@ -9,6 +9,7 @@ import java.util.List; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmKeyType; +import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmManyToOneType; import org.hibernate.boot.model.source.spi.AttributeSourceContainer; import org.hibernate.boot.model.source.spi.PluralAttributeKeySource; import org.hibernate.boot.model.source.spi.RelationalValueSource; @@ -71,6 +72,47 @@ public List getColumnOrFormulaElements() { ); } + public PluralAttributeKeySourceImpl( + MappingDocument mappingDocument, + final JaxbHbmManyToOneType jaxbKey, + final AttributeSourceContainer container) { + super( mappingDocument ); + + this.explicitFkName = StringHelper.nullIfEmpty( jaxbKey.getForeignKey() ); + this.referencedPropertyName = StringHelper.nullIfEmpty( jaxbKey.getPropertyRef() ); + this.cascadeDeletesAtFkLevel = jaxbKey.getOnDelete() != null + && "cascade".equals( jaxbKey.getOnDelete().value() ); + this.nullable = jaxbKey.isNotNull() == null || !jaxbKey.isNotNull(); + this.updateable = jaxbKey.isUpdate(); + + this.valueSources = RelationalValueSourceHelper.buildValueSources( + sourceMappingDocument(), + null, // todo : collection table name + new RelationalValueSourceHelper.AbstractColumnsAndFormulasSource() { + @Override + public XmlElementMetadata getSourceType() { + return XmlElementMetadata.KEY; + } + + @Override + public String getSourceName() { + return null; + } + + @Override + public String getColumnAttribute() { + return StringHelper.nullIfEmpty( jaxbKey.getColumnAttribute() ); + } + + @Override + public List getColumnOrFormulaElements() { + return jaxbKey.getColumnOrFormula(); + } + + } + ); + } + @Override public String getExplicitForeignKeyName() { return explicitFkName; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java index 63ba3d7da8a9..fc27ce7ee364 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java @@ -156,7 +156,7 @@ public BootstrapServiceRegistryBuilder withStrategySelectors(StrategyRegistratio /** * Applies one or more strategy selectors announced as available by the passed announcer. * - * @param strategyRegistrationProvider An provider for one or more available selectors + * @param strategyRegistrationProvider A provider for one or more available selectors * * @return {@code this}, for method chaining * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java index 302b9f4763c5..62c739345207 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java @@ -36,6 +36,35 @@ * @see org.hibernate.boot.registry.BootstrapServiceRegistryBuilder */ public class StandardServiceRegistryBuilder { + /** + * Intended only for use from {@link org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl}. + * + * Creates a StandardServiceRegistryBuilder specific to the needs of JPA bootstrapping. + * Specifically we ignore properties found in `cfg.xml` files in terms of adding them to + * the builder immediately. EntityManagerFactoryBuilderImpl handles collecting these + * properties itself. + */ + public static StandardServiceRegistryBuilder forJpa(BootstrapServiceRegistry bootstrapServiceRegistry) { + final LoadedConfig loadedConfig = new LoadedConfig( null ) { + @Override + protected void addConfigurationValues(Map configurationValues) { + // here, do nothing + } + }; + return new StandardServiceRegistryBuilder( + bootstrapServiceRegistry, + new HashMap(), + loadedConfig + ) { + @Override + public StandardServiceRegistryBuilder configure(LoadedConfig loadedConfig) { + getAggregatedCfgXml().merge( loadedConfig ); + // super also collects the properties - here we skip that part + return this; + } + }; + } + /** * The default resource name for a hibernate configuration xml file. */ @@ -43,7 +72,7 @@ public class StandardServiceRegistryBuilder { private final Map settings; private final List initiators = standardInitiatorList(); - private final List providedServices = new ArrayList(); + private final List providedServices = new ArrayList<>(); private boolean autoCloseRegistry = true; @@ -64,7 +93,22 @@ public StandardServiceRegistryBuilder() { * @param bootstrapServiceRegistry Provided bootstrap registry to use. */ public StandardServiceRegistryBuilder(BootstrapServiceRegistry bootstrapServiceRegistry) { - this( bootstrapServiceRegistry, LoadedConfig.baseline() ); + this( bootstrapServiceRegistry, LoadedConfig.baseline() ); + } + + /** + * Intended for use exclusively from JPA boot-strapping. + * + * @see #forJpa + */ + private StandardServiceRegistryBuilder( + BootstrapServiceRegistry bootstrapServiceRegistry, + Map settings, + LoadedConfig loadedConfig) { + this.bootstrapServiceRegistry = bootstrapServiceRegistry; + this.configLoader = new ConfigLoader( bootstrapServiceRegistry ); + this.settings = settings; + this.aggregatedCfgXml = loadedConfig; } /** @@ -72,13 +116,19 @@ public StandardServiceRegistryBuilder(BootstrapServiceRegistry bootstrapServiceR * * @param bootstrapServiceRegistry Provided bootstrap registry to use. */ - public StandardServiceRegistryBuilder(BootstrapServiceRegistry bootstrapServiceRegistry, LoadedConfig loadedConfigBaseline) { + public StandardServiceRegistryBuilder( + BootstrapServiceRegistry bootstrapServiceRegistry, + LoadedConfig loadedConfigBaseline) { this.settings = Environment.getProperties(); this.bootstrapServiceRegistry = bootstrapServiceRegistry; this.configLoader = new ConfigLoader( bootstrapServiceRegistry ); this.aggregatedCfgXml = loadedConfigBaseline; } + public ConfigLoader getConfigLoader() { + return configLoader; + } + /** * Intended for internal testing use only!! */ @@ -92,18 +142,19 @@ public LoadedConfig getAggregatedCfgXml() { * @return List of standard initiators */ private static List standardInitiatorList() { - final List initiators = new ArrayList(); + final List initiators = new ArrayList<>( StandardServiceInitiators.LIST.size() ); initiators.addAll( StandardServiceInitiators.LIST ); return initiators; } + @SuppressWarnings("unused") public BootstrapServiceRegistry getBootstrapServiceRegistry() { return bootstrapServiceRegistry; } /** * Read settings from a {@link java.util.Properties} file by resource name. - * + *

    * Differs from {@link #configure()} and {@link #configure(String)} in that here we expect to read a * {@link java.util.Properties} file while for {@link #configure} we read the XML variant. * @@ -114,7 +165,7 @@ public BootstrapServiceRegistry getBootstrapServiceRegistry() { * @see #configure() * @see #configure(String) */ - @SuppressWarnings( {"unchecked"}) + @SuppressWarnings({"unchecked"}) public StandardServiceRegistryBuilder loadProperties(String resourceName) { settings.putAll( configLoader.loadProperties( resourceName ) ); return this; @@ -122,7 +173,7 @@ public StandardServiceRegistryBuilder loadProperties(String resourceName) { /** * Read settings from a {@link java.util.Properties} file by File reference - * + *

    * Differs from {@link #configure()} and {@link #configure(String)} in that here we expect to read a * {@link java.util.Properties} file while for {@link #configure} we read the XML variant. * @@ -133,7 +184,7 @@ public StandardServiceRegistryBuilder loadProperties(String resourceName) { * @see #configure() * @see #configure(String) */ - @SuppressWarnings( {"unchecked"}) + @SuppressWarnings({"unchecked"}) public StandardServiceRegistryBuilder loadProperties(File file) { settings.putAll( configLoader.loadProperties( file ) ); return this; @@ -171,7 +222,7 @@ public StandardServiceRegistryBuilder configure(URL url) { return configure( configLoader.loadConfigXmlUrl( url ) ); } - @SuppressWarnings( {"unchecked"}) + @SuppressWarnings({"unchecked"}) public StandardServiceRegistryBuilder configure(LoadedConfig loadedConfig) { aggregatedCfgXml.merge( loadedConfig ); settings.putAll( loadedConfig.getConfigurationValues() ); @@ -187,7 +238,7 @@ public StandardServiceRegistryBuilder configure(LoadedConfig loadedConfig) { * * @return this, for method chaining */ - @SuppressWarnings( {"unchecked", "UnusedDeclaration"}) + @SuppressWarnings({"unchecked", "UnusedDeclaration"}) public StandardServiceRegistryBuilder applySetting(String settingName, Object value) { settings.put( settingName, value ); return this; @@ -200,12 +251,16 @@ public StandardServiceRegistryBuilder applySetting(String settingName, Object va * * @return this, for method chaining */ - @SuppressWarnings( {"unchecked", "UnusedDeclaration"}) + @SuppressWarnings({"unchecked", "UnusedDeclaration"}) public StandardServiceRegistryBuilder applySettings(Map settings) { this.settings.putAll( settings ); return this; } + public void clearSettings() { + settings.clear(); + } + /** * Adds a service initiator. * @@ -213,7 +268,7 @@ public StandardServiceRegistryBuilder applySettings(Map settings) { * * @return this, for method chaining */ - @SuppressWarnings( {"UnusedDeclaration"}) + @SuppressWarnings({"UnusedDeclaration"}) public StandardServiceRegistryBuilder addInitiator(StandardServiceInitiator initiator) { initiators.add( initiator ); return this; @@ -227,7 +282,7 @@ public StandardServiceRegistryBuilder addInitiator(StandardServiceInitiator init * * @return this, for method chaining */ - @SuppressWarnings( {"unchecked"}) + @SuppressWarnings({"unchecked"}) public StandardServiceRegistryBuilder addService(final Class serviceRole, final Service service) { providedServices.add( new ProvidedService( serviceRole, service ) ); return this; @@ -289,9 +344,11 @@ public StandardServiceRegistry build() { @SuppressWarnings("deprecation") private void applyServiceContributingIntegrators() { - for ( Integrator integrator : bootstrapServiceRegistry.getService( IntegratorService.class ).getIntegrators() ) { + for ( Integrator integrator : bootstrapServiceRegistry.getService( IntegratorService.class ) + .getIntegrators() ) { if ( org.hibernate.integrator.spi.ServiceContributingIntegrator.class.isInstance( integrator ) ) { - org.hibernate.integrator.spi.ServiceContributingIntegrator.class.cast( integrator ).prepareServices( this ); + org.hibernate.integrator.spi.ServiceContributingIntegrator.class.cast( integrator ).prepareServices( + this ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedClassLoader.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedClassLoader.java new file mode 100644 index 000000000000..e92ae1b9b4f4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedClassLoader.java @@ -0,0 +1,231 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.registry.classloading.internal; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashSet; + +public class AggregatedClassLoader extends ClassLoader { + private final ClassLoader[] individualClassLoaders; + private final TcclLookupPrecedence tcclLookupPrecedence; + + public AggregatedClassLoader(final LinkedHashSet orderedClassLoaderSet, TcclLookupPrecedence precedence) { + super( null ); + individualClassLoaders = orderedClassLoaderSet.toArray( new ClassLoader[orderedClassLoaderSet.size()] ); + tcclLookupPrecedence = precedence; + } + + private Iterator newClassLoaderIterator() { + final ClassLoader threadClassLoader = locateTCCL(); + if ( tcclLookupPrecedence == TcclLookupPrecedence.NEVER || threadClassLoader == null ) { + return newTcclNeverIterator(); + } + else if ( tcclLookupPrecedence == TcclLookupPrecedence.AFTER ) { + return newTcclAfterIterator(threadClassLoader); + } + else if ( tcclLookupPrecedence == TcclLookupPrecedence.BEFORE ) { + return newTcclBeforeIterator(threadClassLoader); + } + else { + throw new RuntimeException( "Unknown precedence: "+tcclLookupPrecedence ); + } + } + + private Iterator newTcclBeforeIterator(final ClassLoader threadContextClassLoader) { + final ClassLoader systemClassLoader = locateSystemClassLoader(); + return new Iterator() { + private int currentIndex = 0; + private boolean tcCLReturned = false; + private boolean sysCLReturned = false; + + @Override + public boolean hasNext() { + if ( !tcCLReturned ) { + return true; + } + else if ( currentIndex < individualClassLoaders.length ) { + return true; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + return true; + } + + return false; + } + + @Override + public ClassLoader next() { + if ( !tcCLReturned ) { + tcCLReturned = true; + return threadContextClassLoader; + } + else if ( currentIndex < individualClassLoaders.length ) { + currentIndex += 1; + return individualClassLoaders[ currentIndex - 1 ]; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + sysCLReturned = true; + return systemClassLoader; + } + throw new IllegalStateException( "No more item" ); + } + }; + } + + private Iterator newTcclAfterIterator(final ClassLoader threadContextClassLoader) { + final ClassLoader systemClassLoader = locateSystemClassLoader(); + return new Iterator() { + private int currentIndex = 0; + private boolean tcCLReturned = false; + private boolean sysCLReturned = false; + + @Override + public boolean hasNext() { + if ( currentIndex < individualClassLoaders.length ) { + return true; + } + else if ( !tcCLReturned ) { + return true; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + return true; + } + + return false; + } + + @Override + public ClassLoader next() { + if ( currentIndex < individualClassLoaders.length ) { + currentIndex += 1; + return individualClassLoaders[ currentIndex - 1 ]; + } + else if ( !tcCLReturned ) { + tcCLReturned = true; + return threadContextClassLoader; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + sysCLReturned = true; + return systemClassLoader; + } + throw new IllegalStateException( "No more item" ); + } + }; + } + + private Iterator newTcclNeverIterator() { + final ClassLoader systemClassLoader = locateSystemClassLoader(); + return new Iterator() { + private int currentIndex = 0; + private boolean sysCLReturned = false; + + @Override + public boolean hasNext() { + if ( currentIndex < individualClassLoaders.length ) { + return true; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + return true; + } + + return false; + } + + @Override + public ClassLoader next() { + if ( currentIndex < individualClassLoaders.length ) { + currentIndex += 1; + return individualClassLoaders[ currentIndex - 1 ]; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + sysCLReturned = true; + return systemClassLoader; + } + throw new IllegalStateException( "No more item" ); + } + }; + } + + @Override + public Enumeration getResources(String name) throws IOException { + final LinkedHashSet resourceUrls = new LinkedHashSet(); + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); + final Enumeration urls = classLoader.getResources( name ); + while ( urls.hasMoreElements() ) { + resourceUrls.add( urls.nextElement() ); + } + } + + return new Enumeration() { + final Iterator resourceUrlIterator = resourceUrls.iterator(); + + @Override + public boolean hasMoreElements() { + return resourceUrlIterator.hasNext(); + } + + @Override + public URL nextElement() { + return resourceUrlIterator.next(); + } + }; + } + + @Override + protected URL findResource(String name) { + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); + final URL resource = classLoader.getResource( name ); + if ( resource != null ) { + return resource; + } + } + return super.findResource( name ); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); + try { + return classLoader.loadClass( name ); + } + catch (Exception ignore) { + } + catch (LinkageError ignore) { + } + } + + throw new ClassNotFoundException( "Could not load requested class : " + name ); + } + + private static ClassLoader locateSystemClassLoader() { + try { + return ClassLoader.getSystemClassLoader(); + } + catch (Exception e) { + return null; + } + } + + private static ClassLoader locateTCCL() { + try { + return Thread.currentThread().getContextClassLoader(); + } + catch (Exception e) { + return null; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java index b27105716084..7208caabd4ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java @@ -6,16 +6,16 @@ */ package org.hibernate.boot.registry.classloading.internal; -import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -81,7 +81,11 @@ public ClassLoaderServiceImpl(Collection providedClassLoaders, Tccl orderedClassLoaderSet.add( ClassLoaderServiceImpl.class.getClassLoader() ); // now build the aggregated class loader... - this.aggregatedClassLoader = new AggregatedClassLoader( orderedClassLoaderSet,lookupPrecedence ); + this.aggregatedClassLoader = AccessController.doPrivileged( new PrivilegedAction() { + public AggregatedClassLoader run() { + return new AggregatedClassLoader( orderedClassLoaderSet, lookupPrecedence ); + } + } ); } /** @@ -120,224 +124,6 @@ private static void addIfSet(List providedClassLoaders, String name } } - private static ClassLoader locateSystemClassLoader() { - try { - return ClassLoader.getSystemClassLoader(); - } - catch (Exception e) { - return null; - } - } - - private static ClassLoader locateTCCL() { - try { - return Thread.currentThread().getContextClassLoader(); - } - catch (Exception e) { - return null; - } - } - - private static class AggregatedClassLoader extends ClassLoader { - private final ClassLoader[] individualClassLoaders; - private final TcclLookupPrecedence tcclLookupPrecedence; - - private AggregatedClassLoader(final LinkedHashSet orderedClassLoaderSet, TcclLookupPrecedence precedence) { - super( null ); - individualClassLoaders = orderedClassLoaderSet.toArray( new ClassLoader[orderedClassLoaderSet.size()] ); - tcclLookupPrecedence = precedence; - } - - private Iterator newClassLoaderIterator() { - final ClassLoader threadClassLoader = locateTCCL(); - if ( tcclLookupPrecedence == TcclLookupPrecedence.NEVER || threadClassLoader == null ) { - return newTcclNeverIterator(); - } - else if ( tcclLookupPrecedence == TcclLookupPrecedence.AFTER ) { - return newTcclAfterIterator(threadClassLoader); - } - else if ( tcclLookupPrecedence == TcclLookupPrecedence.BEFORE ) { - return newTcclBeforeIterator(threadClassLoader); - } - else { - throw new RuntimeException( "Unknown precedence: "+tcclLookupPrecedence ); - } - } - - private Iterator newTcclBeforeIterator(final ClassLoader threadContextClassLoader) { - final ClassLoader systemClassLoader = locateSystemClassLoader(); - return new Iterator() { - private int currentIndex = 0; - private boolean tcCLReturned = false; - private boolean sysCLReturned = false; - - @Override - public boolean hasNext() { - if ( !tcCLReturned ) { - return true; - } - else if ( currentIndex < individualClassLoaders.length ) { - return true; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - return true; - } - - return false; - } - - @Override - public ClassLoader next() { - if ( !tcCLReturned ) { - tcCLReturned = true; - return threadContextClassLoader; - } - else if ( currentIndex < individualClassLoaders.length ) { - currentIndex += 1; - return individualClassLoaders[ currentIndex - 1 ]; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - sysCLReturned = true; - return systemClassLoader; - } - throw new IllegalStateException( "No more item" ); - } - }; - } - - private Iterator newTcclAfterIterator(final ClassLoader threadContextClassLoader) { - final ClassLoader systemClassLoader = locateSystemClassLoader(); - return new Iterator() { - private int currentIndex = 0; - private boolean tcCLReturned = false; - private boolean sysCLReturned = false; - - @Override - public boolean hasNext() { - if ( currentIndex < individualClassLoaders.length ) { - return true; - } - else if ( !tcCLReturned ) { - return true; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - return true; - } - - return false; - } - - @Override - public ClassLoader next() { - if ( currentIndex < individualClassLoaders.length ) { - currentIndex += 1; - return individualClassLoaders[ currentIndex - 1 ]; - } - else if ( !tcCLReturned ) { - tcCLReturned = true; - return threadContextClassLoader; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - sysCLReturned = true; - return systemClassLoader; - } - throw new IllegalStateException( "No more item" ); - } - }; - } - - private Iterator newTcclNeverIterator() { - final ClassLoader systemClassLoader = locateSystemClassLoader(); - return new Iterator() { - private int currentIndex = 0; - private boolean sysCLReturned = false; - - @Override - public boolean hasNext() { - if ( currentIndex < individualClassLoaders.length ) { - return true; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - return true; - } - - return false; - } - - @Override - public ClassLoader next() { - if ( currentIndex < individualClassLoaders.length ) { - currentIndex += 1; - return individualClassLoaders[ currentIndex - 1 ]; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - sysCLReturned = true; - return systemClassLoader; - } - throw new IllegalStateException( "No more item" ); - } - }; - } - - @Override - public Enumeration getResources(String name) throws IOException { - final LinkedHashSet resourceUrls = new LinkedHashSet(); - final Iterator clIterator = newClassLoaderIterator(); - while ( clIterator.hasNext() ) { - final ClassLoader classLoader = clIterator.next(); - final Enumeration urls = classLoader.getResources( name ); - while ( urls.hasMoreElements() ) { - resourceUrls.add( urls.nextElement() ); - } - } - - return new Enumeration() { - final Iterator resourceUrlIterator = resourceUrls.iterator(); - - @Override - public boolean hasMoreElements() { - return resourceUrlIterator.hasNext(); - } - - @Override - public URL nextElement() { - return resourceUrlIterator.next(); - } - }; - } - - @Override - protected URL findResource(String name) { - final Iterator clIterator = newClassLoaderIterator(); - while ( clIterator.hasNext() ) { - final ClassLoader classLoader = clIterator.next(); - final URL resource = classLoader.getResource( name ); - if ( resource != null ) { - return resource; - } - } - return super.findResource( name ); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - final Iterator clIterator = newClassLoaderIterator(); - while ( clIterator.hasNext() ) { - final ClassLoader classLoader = clIterator.next(); - try { - return classLoader.loadClass( name ); - } - catch (Exception ignore) { - } - catch (LinkageError ignore) { - } - } - - throw new ClassNotFoundException( "Could not load requested class : " + name ); - } - - } - @Override @SuppressWarnings({"unchecked"}) public Class classForName(String className) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/internal/StandardServiceRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/internal/StandardServiceRegistryImpl.java index ca74699daab4..3ee764ab36b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/internal/StandardServiceRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/internal/StandardServiceRegistryImpl.java @@ -71,14 +71,20 @@ public StandardServiceRegistryImpl( this.configurationValues = configurationValues; - // process initiators - for ( ServiceInitiator initiator : serviceInitiators ) { - createServiceBinding( initiator ); - } + try { + // process initiators + for ( ServiceInitiator initiator : serviceInitiators ) { + createServiceBinding( initiator ); + } - // then, explicitly provided service instances - for ( ProvidedService providedService : providedServices ) { - createServiceBinding( providedService ); + // then, explicitly provided service instances + for ( ProvidedService providedService : providedServices ) { + createServiceBinding( providedService ); + } + } + catch (RuntimeException e) { + visitServiceBindings( binding -> binding.getLifecycleOwner().stopService( binding ) ); + throw e; } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index 53878363aa7b..086a79204286 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -26,6 +26,7 @@ import org.hibernate.dialect.CUBRIDDialect; import org.hibernate.dialect.Cache71Dialect; import org.hibernate.dialect.DB2390Dialect; +import org.hibernate.dialect.DB2390V8Dialect; import org.hibernate.dialect.DB2400Dialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyTenFiveDialect; @@ -35,6 +36,8 @@ import org.hibernate.dialect.FirebirdDialect; import org.hibernate.dialect.FrontBaseDialect; import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.HANAColumnStoreDialect; +import org.hibernate.dialect.HANARowStoreDialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.InformixDialect; import org.hibernate.dialect.Ingres10Dialect; @@ -44,7 +47,9 @@ import org.hibernate.dialect.JDataStoreDialect; import org.hibernate.dialect.MckoiDialect; import org.hibernate.dialect.MimerSQLDialect; +import org.hibernate.dialect.MySQL57Dialect; import org.hibernate.dialect.MySQL57InnoDBDialect; +import org.hibernate.dialect.MySQL8Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQL5InnoDBDialect; import org.hibernate.dialect.Oracle10gDialect; @@ -76,9 +81,11 @@ import org.hibernate.engine.transaction.jta.platform.internal.OC4JJtaPlatform; import org.hibernate.engine.transaction.jta.platform.internal.OrionJtaPlatform; import org.hibernate.engine.transaction.jta.platform.internal.ResinJtaPlatform; +import org.hibernate.engine.transaction.jta.platform.internal.SapNetWeaverJtaPlatform; import org.hibernate.engine.transaction.jta.platform.internal.SunOneJtaPlatform; import org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform; import org.hibernate.engine.transaction.jta.platform.internal.WebSphereJtaPlatform; +import org.hibernate.engine.transaction.jta.platform.internal.WebSphereLibertyJtaPlatform; import org.hibernate.engine.transaction.jta.platform.internal.WeblogicJtaPlatform; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.event.internal.EntityCopyAllowedLoggedObserver; @@ -193,6 +200,7 @@ private void addDialects(StrategySelectorImpl strategySelector) { addDialect( strategySelector, CUBRIDDialect.class ); addDialect( strategySelector, DB2Dialect.class ); addDialect( strategySelector, DB2390Dialect.class ); + addDialect( strategySelector, DB2390V8Dialect.class ); addDialect( strategySelector, DB2400Dialect.class ); addDialect( strategySelector, DerbyTenFiveDialect.class ); addDialect( strategySelector, DerbyTenSixDialect.class ); @@ -200,6 +208,8 @@ private void addDialects(StrategySelectorImpl strategySelector) { addDialect( strategySelector, FirebirdDialect.class ); addDialect( strategySelector, FrontBaseDialect.class ); addDialect( strategySelector, H2Dialect.class ); + addDialect( strategySelector, HANAColumnStoreDialect.class ); + addDialect( strategySelector, HANARowStoreDialect.class ); addDialect( strategySelector, HSQLDialect.class ); addDialect( strategySelector, InformixDialect.class ); addDialect( strategySelector, IngresDialect.class ); @@ -212,6 +222,8 @@ private void addDialects(StrategySelectorImpl strategySelector) { addDialect( strategySelector, MySQL5Dialect.class ); addDialect( strategySelector, MySQL5InnoDBDialect.class ); addDialect( strategySelector, MySQL57InnoDBDialect.class ); + addDialect( strategySelector, MySQL57Dialect.class ); + addDialect( strategySelector, MySQL8Dialect.class ); addDialect( strategySelector, Oracle8iDialect.class ); addDialect( strategySelector, Oracle9iDialect.class ); addDialect( strategySelector, Oracle10gDialect.class ); @@ -312,6 +324,13 @@ private void addJtaPlatforms(StrategySelectorImpl strategySelector) { "org.hibernate.service.jta.platform.internal.ResinJtaPlatform" ); + addJtaPlatforms( + strategySelector, + SapNetWeaverJtaPlatform.class, + "SapNetWeaver", + "org.hibernate.service.jta.platform.internal.SapNetWeaverJtaPlatform" + ); + addJtaPlatforms( strategySelector, SunOneJtaPlatform.class, @@ -325,6 +344,13 @@ private void addJtaPlatforms(StrategySelectorImpl strategySelector) { "Weblogic", "org.hibernate.service.jta.platform.internal.WeblogicJtaPlatform" ); + + addJtaPlatforms( + strategySelector, + WebSphereLibertyJtaPlatform.class, + "WebSphereLiberty", + "org.hibernate.engine.transaction.jta.platform.internal.WebSphereLibertyJtaPlatform" + ); addJtaPlatforms( strategySelector, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java index 11af4a2ada32..be2a450fa079 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java @@ -6,6 +6,9 @@ */ package org.hibernate.boot.registry.selector.internal; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.concurrent.Callable; @@ -63,25 +66,29 @@ public void registerStrategyImplementor(Class strategy, String name, Clas final Class old = namedStrategyImplementorMap.put( name, implementation ); if ( old == null ) { - log.trace( - String.format( - "Registering named strategy selector [%s] : [%s] -> [%s]", - strategy.getName(), - name, - implementation.getName() - ) - ); + if ( log.isTraceEnabled() ) { + log.trace( + String.format( + "Registering named strategy selector [%s] : [%s] -> [%s]", + strategy.getName(), + name, + implementation.getName() + ) + ); + } } else { - log.debug( - String.format( - "Registering named strategy selector [%s] : [%s] -> [%s] (replacing [%s])", - strategy.getName(), - name, - implementation.getName(), - old.getName() - ) - ); + if ( log.isDebugEnabled() ) { + log.debug( + String.format( + "Registering named strategy selector [%s] : [%s] -> [%s] (replacing [%s])", + strategy.getName(), + name, + implementation.getName(), + old.getName() + ) + ); + } } } @@ -101,7 +108,7 @@ public void unRegisterStrategyImplementor(Class strategy, Class Class selectStrategyImplementor(Class strategy, Strin } catch (ClassLoadingException e) { throw new StrategySelectionException( - "Unable to resolve name [" + name + "] as strategy [" + strategy.getName() + "]" + "Unable to resolve name [" + name + "] as strategy [" + strategy.getName() + "]", + e ); } } @@ -166,6 +174,16 @@ public T resolveStrategy( ); } + @Override + @SuppressWarnings("unchecked") + public Collection getRegisteredStrategyImplementors(Class strategy) { + final Map registrations = namedStrategyImplementorByStrategyMap.get( strategy ); + if ( registrations == null ) { + return Collections.emptySet(); + } + return new HashSet( registrations.values() ); + } + @SuppressWarnings("unchecked") @Override public T resolveStrategy( diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/StrategySelector.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/StrategySelector.java index 8ac40c3cff17..2ea4daa80a93 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/StrategySelector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/StrategySelector.java @@ -6,6 +6,7 @@ */ package org.hibernate.boot.registry.selector.spi; +import java.util.Collection; import java.util.concurrent.Callable; import org.hibernate.service.Service; @@ -139,4 +140,13 @@ public interface StrategySelector extends Service { T resolveStrategy(Class strategy, Object strategyReference, Callable defaultResolver, StrategyCreator creator); T resolveStrategy(Class strategy, Object strategyReference, T defaultValue, StrategyCreator creator); + + /** + * Retrieve all of the registered implementors of the given strategy. Useful + * to allow defaulting the choice to the single registered implementor when + * only one is registered + * + * @return The implementors. Should never return {@code null} + */ + Collection> getRegisteredStrategyImplementors(Class strategy); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java index bcf13d1cf9db..5fdbc749f0c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java @@ -17,6 +17,7 @@ import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.relational.Database; +import org.hibernate.cache.cfg.internal.DomainDataRegionConfigImpl; import org.hibernate.cfg.annotations.NamedEntityGraphDefinition; import org.hibernate.cfg.annotations.NamedProcedureCallDefinition; import org.hibernate.dialect.function.SQLFunction; @@ -33,6 +34,7 @@ import org.hibernate.query.spi.NamedQueryRepository; import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; +import org.hibernate.type.spi.TypeConfiguration; /** * Convenience base class for custom implementors of {@link MetadataImplementor} using delegation. @@ -48,6 +50,10 @@ public AbstractDelegatingMetadata(MetadataImplementor delegate) { this.delegate = delegate; } + protected MetadataImplementor delegate() { + return delegate; + } + @Override public IdentifierGeneratorFactory getIdentifierGeneratorFactory() { return delegate.getIdentifierGeneratorFactory(); @@ -204,6 +210,18 @@ public MetadataBuildingOptions getMetadataBuildingOptions() { } @Override + public TypeConfiguration getTypeConfiguration() { + return delegate.getTypeConfiguration(); + } + + /** + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated public TypeResolver getTypeResolver() { return delegate.getTypeResolver(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuilderImplementor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuilderImplementor.java index c13752507903..cbceca638693 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuilderImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuilderImplementor.java @@ -11,6 +11,7 @@ import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.boot.CacheRegionDefinition; +import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.archive.scan.spi.ScanEnvironment; import org.hibernate.boot.archive.scan.spi.ScanOptions; @@ -40,14 +41,24 @@ * to a specialization of {@link MetadataBuilderImplementor} */ @SuppressWarnings("unused") -public abstract class AbstractDelegatingMetadataBuilderImplementor> implements MetadataBuilderImplementor { +public abstract class AbstractDelegatingMetadataBuilderImplementor implements MetadataBuilderImplementor { private final MetadataBuilderImplementor delegate; + /** + * Kept for compatibility reason but should be removed as soon as possible. + * + * @deprecated use {@link #delegate()} instead + */ + @Deprecated public MetadataBuilderImplementor getDelegate() { return delegate; } + protected MetadataBuilderImplementor delegate() { + return delegate; + } + public AbstractDelegatingMetadataBuilderImplementor(MetadataBuilderImplementor delegate) { this.delegate = delegate; } @@ -83,12 +94,6 @@ public MetadataBuilder applyPhysicalNamingStrategy(PhysicalNamingStrategy naming return getThis(); } - @Override - public MetadataBuilder applyReflectionManager(ReflectionManager reflectionManager) { - delegate.applyReflectionManager( reflectionManager ); - return getThis(); - } - @Override public MetadataBuilder applySharedCacheMode(SharedCacheMode cacheMode) { delegate.applySharedCacheMode( cacheMode ); @@ -256,4 +261,19 @@ public MetadataBuilder applyIdGenerationTypeInterpreter(IdGeneratorStrategyInter delegate.applyIdGenerationTypeInterpreter( interpreter ); return getThis(); } + + @Override + public M unwrap(Class type) { + return delegate.unwrap( type ); + } + + @Override + public MetadataBuildingOptions getMetadataBuildingOptions() { + return delegate.getMetadataBuildingOptions(); + } + + @Override + public Metadata build() { + return delegate.build(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java index 8bc60dcc4a2b..c6b885d7ac11 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java @@ -13,6 +13,7 @@ import org.hibernate.HibernateException; import org.hibernate.MultiTenancyStrategy; import org.hibernate.annotations.common.reflection.ReflectionManager; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.CacheRegionDefinition; import org.hibernate.boot.archive.scan.spi.ScanEnvironment; import org.hibernate.boot.archive.scan.spi.ScanOptions; @@ -23,7 +24,6 @@ import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.MetadataSourceType; import org.hibernate.dialect.function.SQLFunction; @@ -44,6 +44,10 @@ public AbstractDelegatingMetadataBuildingOptions(MetadataBuildingOptions delegat this.delegate = delegate; } + protected MetadataBuildingOptions delegate() { + return delegate; + } + @Override public StandardServiceRegistry getServiceRegistry() { return delegate.getServiceRegistry(); @@ -170,7 +174,7 @@ public List getAuxiliaryDatabaseObjectList() { } @Override - public List getAttributeConverters() { + public List getAttributeConverters() { return delegate.getAttributeConverters(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java index fa125f9f528b..a7061209476c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java @@ -7,6 +7,7 @@ package org.hibernate.boot.spi; import java.util.Map; +import java.util.function.Supplier; import org.hibernate.ConnectionReleaseMode; import org.hibernate.CustomEntityDirtinessStrategy; @@ -15,15 +16,17 @@ import org.hibernate.Interceptor; import org.hibernate.MultiTenancyStrategy; import org.hibernate.NullPrecedence; +import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.TempTableDdlTransactionHandling; -import org.hibernate.cache.spi.QueryCacheFactory; +import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -33,10 +36,11 @@ * * @author Steve Ebersole * @author Gunnar Morling + * @author Guillaume Smet * @param The type of a specific sub-class; Allows sub-classes to narrow down the return-type of the contract methods * to a specialization of {@link SessionFactoryBuilder} */ -public abstract class AbstractDelegatingSessionFactoryBuilder> implements SessionFactoryBuilder { +public abstract class AbstractDelegatingSessionFactoryBuilder implements SessionFactoryBuilder { private final SessionFactoryBuilder delegate; public AbstractDelegatingSessionFactoryBuilder(SessionFactoryBuilder delegate) { @@ -50,6 +54,10 @@ public AbstractDelegatingSessionFactoryBuilder(SessionFactoryBuilder delegate) { */ protected abstract T getThis(); + protected SessionFactoryBuilder delegate() { + return delegate; + } + @Override public T applyValidatorFactory(Object validatorFactory) { delegate.applyValidatorFactory( validatorFactory ); @@ -185,6 +193,12 @@ public T applyBatchFetchStyle(BatchFetchStyle style) { return getThis(); } + @Override + public SessionFactoryBuilder applyDelayedEntityLoaderCreations(boolean delay) { + delegate.applyDelayedEntityLoaderCreations( delay ); + return getThis(); + } + @Override public T applyDefaultBatchFetchSize(int size) { delegate.applyDefaultBatchFetchSize( size ); @@ -271,8 +285,8 @@ public T applyQueryCacheSupport(boolean enabled) { } @Override - public T applyQueryCacheFactory(QueryCacheFactory factory) { - delegate.applyQueryCacheFactory( factory ); + public SessionFactoryBuilder applyTimestampsCacheFactory(TimestampsCacheFactory factory) { + delegate.applyTimestampsCacheFactory( factory ); return getThis(); } @@ -342,12 +356,19 @@ public T applyJdbcFetchSize(int size) { return getThis(); } + @SuppressWarnings("deprecation") @Override public T applyConnectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) { delegate.applyConnectionReleaseMode( connectionReleaseMode ); return getThis(); } + @Override + public SessionFactoryBuilder applyConnectionProviderDisablesAutoCommit(boolean providerDisablesAutoCommit) { + delegate.applyConnectionProviderDisablesAutoCommit( providerDisablesAutoCommit ); + return getThis(); + } + @Override public T applySqlComments(boolean enabled) { delegate.applySqlComments( enabled ); @@ -369,14 +390,61 @@ public T allowOutOfTransactionUpdateOperations(boolean allow) { } @Override - public SessionFactoryBuilder enableReleaseResourcesOnCloseEnabled(boolean enable) { + public T enableReleaseResourcesOnCloseEnabled(boolean enable) { delegate.enableReleaseResourcesOnCloseEnabled( enable ); return getThis(); } + @Override + public SessionFactoryBuilder enableJpaQueryCompliance(boolean enabled) { + delegate.enableJpaQueryCompliance( enabled ); + return getThis(); + } + + @Override + public SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled) { + delegate.enableJpaTransactionCompliance( enabled ); + return getThis(); + } + + @Override + public SessionFactoryBuilder enableJpaListCompliance(boolean enabled) { + delegate.enableJpaListCompliance( enabled ); + return getThis(); + } + + @Override + public SessionFactoryBuilder enableJpaClosedCompliance(boolean enabled) { + delegate.enableJpaClosedCompliance( enabled ); + return getThis(); + } + @Override @SuppressWarnings("unchecked") public S unwrap(Class type) { return (S) this; } + + @Override + public T applyStatelessInterceptor(Supplier statelessInterceptorSupplier) { + delegate.applyStatelessInterceptor(statelessInterceptorSupplier); + return getThis(); + } + + @Override + public T applyStatelessInterceptor(Class statelessInterceptorClass) { + delegate.applyStatelessInterceptor(statelessInterceptorClass); + return getThis(); + } + + @Override + public T applyConnectionHandlingMode(PhysicalConnectionHandlingMode connectionHandlingMode) { + delegate.applyConnectionHandlingMode( connectionHandlingMode ); + return getThis(); + } + + @Override + public SessionFactory build() { + return delegate.build(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilderImplementor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilderImplementor.java new file mode 100644 index 000000000000..9e0d6edf7120 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilderImplementor.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 . + */ +package org.hibernate.boot.spi; + +/** + * Convenience base class for custom implementors of {@link SessionFactoryBuilderImplementor}, using delegation + * + * @author Guillaume Smet + * @param The type of a specific sub-class; Allows sub-classes to narrow down the return-type of the contract methods + * to a specialization of {@link SessionFactoryBuilderImplementor} + */ +public abstract class AbstractDelegatingSessionFactoryBuilderImplementor + extends AbstractDelegatingSessionFactoryBuilder implements SessionFactoryBuilderImplementor { + + public AbstractDelegatingSessionFactoryBuilderImplementor(SessionFactoryBuilderImplementor delegate) { + super( delegate ); + } + + @Override + protected SessionFactoryBuilderImplementor delegate() { + return (SessionFactoryBuilderImplementor) super.delegate(); + } + + @SuppressWarnings("deprecation") + @Override + public void markAsJpaBootstrap() { + delegate().markAsJpaBootstrap(); + } + + @Override + public void disableJtaTransactionAccess() { + delegate().disableJtaTransactionAccess(); + } + + @Override + public void enableJdbcStyleParamsZeroBased() { + delegate().enableJdbcStyleParamsZeroBased(); + } + + @Override + public SessionFactoryOptions buildSessionFactoryOptions() { + return delegate().buildSessionFactoryOptions(); + } +} 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 89f420493194..f1f9acd23566 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 @@ -8,6 +8,7 @@ import java.util.Map; import java.util.TimeZone; +import java.util.function.Supplier; import org.hibernate.ConnectionReleaseMode; import org.hibernate.CustomEntityDirtinessStrategy; @@ -20,13 +21,16 @@ import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.cache.spi.QueryCacheFactory; +import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -44,6 +48,15 @@ public AbstractDelegatingSessionFactoryOptions(SessionFactoryOptions delegate) { this.delegate = delegate; } + protected SessionFactoryOptions delegate() { + return delegate; + } + + @Override + public String getUuid() { + return delegate().getUuid(); + } + @Override public StandardServiceRegistry getServiceRegistry() { return delegate.getServiceRegistry(); @@ -159,6 +172,11 @@ public BatchFetchStyle getBatchFetchStyle() { return delegate.getBatchFetchStyle(); } + @Override + public boolean isDelayBatchFetchLoaderCreationsEnabled() { + return delegate.isDelayBatchFetchLoaderCreationsEnabled(); + } + @Override public int getDefaultBatchFetchSize() { return delegate.getDefaultBatchFetchSize(); @@ -204,11 +222,6 @@ public Map getQuerySubstitutions() { return delegate.getQuerySubstitutions(); } - @Override - public boolean isStrictJpaQueryLanguageCompliance() { - return delegate.isStrictJpaQueryLanguageCompliance(); - } - @Override public boolean isNamedQueryStartupCheckingEnabled() { return delegate.isNamedQueryStartupCheckingEnabled(); @@ -250,8 +263,8 @@ public boolean isQueryCacheEnabled() { } @Override - public QueryCacheFactory getQueryCacheFactory() { - return delegate.getQueryCacheFactory(); + public TimestampsCacheFactory getTimestampsCacheFactory() { + return delegate.getTimestampsCacheFactory(); } @Override @@ -319,6 +332,11 @@ public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() { return delegate.getPhysicalConnectionHandlingMode(); } + @Override + public boolean doesConnectionProviderDisableAutoCommit() { + return delegate.doesConnectionProviderDisableAutoCommit(); + } + @Override @SuppressWarnings("deprecation") public ConnectionReleaseMode getConnectionReleaseMode() { @@ -365,8 +383,58 @@ public Class getStatelessInterceptorImplementor() { return delegate.getStatelessInterceptorImplementor(); } + @Override + public Supplier getStatelessInterceptorImplementorSupplier() { + return delegate.getStatelessInterceptorImplementorSupplier(); + } + @Override public TimeZone getJdbcTimeZone() { return delegate.getJdbcTimeZone(); } + + @Override + public boolean isQueryParametersValidationEnabled() { + return delegate.isQueryParametersValidationEnabled(); + } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return delegate.getCriteriaLiteralHandlingMode(); + } + + @Override + public boolean jdbcStyleParamsZeroBased() { + return delegate.jdbcStyleParamsZeroBased(); + } + + @Override + public JpaCompliance getJpaCompliance() { + return delegate.getJpaCompliance(); + } + + @Override + public boolean isFailOnPaginationOverCollectionFetchEnabled() { + return delegate.isFailOnPaginationOverCollectionFetchEnabled(); + } + + @Override + public ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandlingMode() { + return delegate.getImmutableEntityUpdateQueryHandlingMode(); + } + + @Override + public boolean inClauseParameterPaddingEnabled() { + return delegate.inClauseParameterPaddingEnabled(); + } + + @Override + public boolean nativeExceptionHandling51Compliance() { + return delegate.nativeExceptionHandling51Compliance(); + } + + @Override + public boolean isEnhancementAsProxyEnabled() { + return delegate.isEnhancementAsProxyEnabled(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AttributeConverterAutoApplyHandler.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AttributeConverterAutoApplyHandler.java deleted file mode 100644 index 723a8bcea9d8..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AttributeConverterAutoApplyHandler.java +++ /dev/null @@ -1,18 +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.boot.spi; - -import org.hibernate.annotations.common.reflection.XProperty; - -/** - * @author Steve Ebersole - */ -public interface AttributeConverterAutoApplyHandler { - AttributeConverterDescriptor findAutoApplyConverterForAttribute(XProperty xProperty, MetadataBuildingContext context); - AttributeConverterDescriptor findAutoApplyConverterForCollectionElement(XProperty xProperty, MetadataBuildingContext context); - AttributeConverterDescriptor findAutoApplyConverterForMapKey(XProperty xProperty, MetadataBuildingContext context); -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AttributeConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AttributeConverterDescriptor.java deleted file mode 100644 index 0a41c9469dff..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AttributeConverterDescriptor.java +++ /dev/null @@ -1,27 +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.boot.spi; - -import javax.persistence.AttributeConverter; - -import org.hibernate.annotations.common.reflection.XProperty; - -/** - * Internal descriptor for an AttributeConverter implementation. - * - * @author Steve Ebersole - */ -public interface AttributeConverterDescriptor { - AttributeConverter getAttributeConverter(); - Class getDomainType(); - Class getJdbcType(); - - boolean shouldAutoApplyToAttribute(XProperty xProperty, MetadataBuildingContext context); - boolean shouldAutoApplyToCollectionElement(XProperty xProperty, MetadataBuildingContext context); - boolean shouldAutoApplyToMapKey(XProperty xProperty, MetadataBuildingContext context); - -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java new file mode 100644 index 000000000000..75eb9219e646 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java @@ -0,0 +1,172 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi; + +import java.util.Collection; +import java.util.Map; + +import org.hibernate.annotations.common.reflection.ReflectionManager; +import org.hibernate.boot.AttributeConverterInfo; +import org.hibernate.boot.CacheRegionDefinition; +import org.hibernate.boot.archive.scan.spi.ScanEnvironment; +import org.hibernate.boot.archive.scan.spi.ScanOptions; +import org.hibernate.boot.archive.spi.ArchiveDescriptorFactory; +import org.hibernate.boot.internal.ClassmateContext; +import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.jpa.spi.MutableJpaCompliance; +import org.hibernate.type.spi.TypeConfiguration; + +import org.jboss.jandex.IndexView; + +/** + * Defines a context for things generally available to the process of + * bootstrapping a SessionFactory that are expected to be released after + * the SessionFactory is built. + * + * @author Steve Ebersole + */ +public interface BootstrapContext { + StandardServiceRegistry getServiceRegistry(); + + MutableJpaCompliance getJpaCompliance(); + + TypeConfiguration getTypeConfiguration(); + + MetadataBuildingOptions getMetadataBuildingOptions(); + + boolean isJpaBootstrap(); + + /** + * Indicates that bootstrap was initiated from JPA bootstrapping. Internally {@code false} is + * the assumed value. We only need to call this to mark that as true. + */ + void markAsJpaBootstrap(); + + /** + * Access the temporary ClassLoader passed to us as defined by + * {@link javax.persistence.spi.PersistenceUnitInfo#getNewTempClassLoader()}, if any. + * + * @return The tempo ClassLoader + */ + ClassLoader getJpaTempClassLoader(); + + ClassLoaderAccess getClassLoaderAccess(); + + /** + * Access to the shared Classmate objects used throughout Hibernate's + * bootstrap process. + * + * @return Access to the shared Classmate delegates. + */ + ClassmateContext getClassmateContext(); + + /** + * Access to the ArchiveDescriptorFactory to be used for scanning + * + * @return The ArchiveDescriptorFactory + */ + ArchiveDescriptorFactory getArchiveDescriptorFactory(); + + /** + * Access to the options to be used for scanning + * + * @return The scan options + */ + ScanOptions getScanOptions(); + + /** + * Access to the environment for scanning. Consider this temporary; see discussion on + * {@link ScanEnvironment} + * + * @return The scan environment + */ + ScanEnvironment getScanEnvironment(); + + /** + * Access to the Scanner to be used for scanning. Can be:

      + *
    • A Scanner instance
    • + *
    • A Class reference to the Scanner implementor
    • + *
    • A String naming the Scanner implementor
    • + *
    + * + * @return The scanner + */ + Object getScanner(); + + /** + * Retrieve the Hibernate Commons Annotations ReflectionManager to use. + * + * @return The Hibernate Commons Annotations ReflectionManager to use. + * + * @deprecated Deprecated (with no replacement) to indicate that this will go away as + * we migrate away from Hibernate Commons Annotations to Jandex for annotation handling + * and XMl->annotation merging. + */ + @Deprecated + ReflectionManager getReflectionManager(); + + /** + * Access to the Jandex index passed by call to + * {@link org.hibernate.boot.MetadataBuilder#applyIndexView(org.jboss.jandex.IndexView)}, if any. + *

    + * Note that Jandex is currently not used. See https://github.com/hibernate/hibernate-orm/wiki/Roadmap7.0 + * + * @return The Jandex index + */ + IndexView getJandexView(); + + /** + * Access to any SQL functions explicitly registered with the MetadataBuilder. This + * does not include Dialect defined functions, etc. + *

    + * Should never return {@code null} + * + * @return The SQLFunctions registered through MetadataBuilder + */ + Map getSqlFunctions(); + + /** + * Access to any AuxiliaryDatabaseObject explicitly registered with the MetadataBuilder. This + * does not include AuxiliaryDatabaseObject defined in mappings. + *

    + * Should never return {@code null} + * + * @return The AuxiliaryDatabaseObject registered through MetadataBuilder + */ + Collection getAuxiliaryDatabaseObjectList(); + + /** + * Access to collected AttributeConverter definitions. + *

    + * Should never return {@code null} + * + * @return The AttributeConverterInfo registered through MetadataBuilder + */ + Collection getAttributeConverters(); + + /** + * Access to all explicit cache region mappings. + *

    + * Should never return {@code null} + * + * @return Explicit cache region mappings + */ + Collection getCacheRegionDefinitions(); + + /** + * Releases the "bootstrap only" resources held by this BootstrapContext. + *

    + * Only one call to this method is supported, after we have completed the process of + * building the (non-inflight) Metadata impl. We may want to delay this until we + * get into SF building. Not sure yet. + * + * @todo verify this ^^ + */ + void release(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java index 1b9a89fe020c..b15b422a4010 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java @@ -20,9 +20,13 @@ import org.hibernate.boot.internal.ClassmateContext; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; +import org.hibernate.boot.model.convert.internal.InstanceBasedConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.model.source.spi.LocalMetadataBuildingContext; import org.hibernate.cfg.AnnotatedClassType; import org.hibernate.cfg.AttributeConverterDefinition; @@ -44,7 +48,6 @@ import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; -import org.hibernate.type.TypeResolver; /** * An in-flight representation of Metadata while Metadata is being built. @@ -54,6 +57,7 @@ * @since 5.0 */ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor { + BootstrapContext getBootstrapContext(); /** * Add the PersistentClass for an entity mapping. @@ -202,20 +206,36 @@ Table addDenormalizedTable( void addFetchProfile(FetchProfile profile); - TypeResolver getTypeResolver(); - - Database getDatabase(); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // make sure these are account for better in metamodel void addIdentifierGenerator(IdentifierGeneratorDefinition generatorDefinition); - void addAttributeConverter(AttributeConverterDefinition converter); + /** + * @deprecated AttributeConverterDefinition forces early resolution of the + * AttributeConverter instance, which precludes resolution of the converter + * from {@link org.hibernate.resource.beans.spi.ManagedBeanRegistry} (CDI, etc). + * Instead one of: + * * {@link #addAttributeConverter(ConverterDescriptor)} + * * {@link #addAttributeConverter(Class)} + * * {@link #addAttributeConverter(Class)} + */ + @Deprecated + default void addAttributeConverter(AttributeConverterDefinition converter) { + addAttributeConverter( + new InstanceBasedConverterDescriptor( + converter.getAttributeConverter(), + getBootstrapContext().getClassmateContext() + ) + ); + } + + void addAttributeConverter(ConverterDescriptor descriptor); + void addAttributeConverter(Class converterClass); - AttributeConverterAutoApplyHandler getAttributeConverterAutoApplyHandler(); + ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -278,6 +298,15 @@ void addTableNameBinding( NaturalIdUniqueKeyBinder locateNaturalIdUniqueKeyBinder(String entityName); void registerNaturalIdUniqueKeyBinder(String entityName, NaturalIdUniqueKeyBinder ukBinder); + /** + * Access to the shared Classmate objects used throughout Hibernate's + * bootstrap process. + * + * @return Access to the shared Classmate delegates. + * + * @deprecated Use {@link BootstrapContext#getClassmateContext()} instead. + */ + @Deprecated ClassmateContext getClassmateContext(); interface DelayedPropertyReferenceHandler extends Serializable { @@ -300,7 +329,7 @@ interface DelayedPropertyReferenceHandler extends Serializable { interface EntityTableXref { void addSecondaryTable(LocalMetadataBuildingContext buildingContext, Identifier logicalName, Join secondaryTableJoin); - void addSecondaryTable(Identifier logicalName, Join secondaryTableJoin); + void addSecondaryTable(QualifiedTableName logicalName, Join secondaryTableJoin); Table resolveTable(Identifier tableName); Table getPrimaryTable(); Join locateJoin(Identifier tableName); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderContributor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderContributor.java new file mode 100644 index 000000000000..8eecb2dc5110 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderContributor.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 . + */ +package org.hibernate.boot.spi; + +import org.hibernate.boot.MetadataBuilder; + +/** + * A bootstrap process hook for contributing settings to {@link MetadataBuilder}. + * + * @author Vlad Mihalcea + * + * @since 5.3 + */ +public interface MetadataBuilderContributor { + /** + * Perform the process of contributing to MetadataSources. + * + * @param metadataBuilder The {@link MetadataBuilder}, to which to contribute. + */ + void contribute(MetadataBuilder metadataBuilder); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderImplementor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderImplementor.java index aba824aee5e5..25958fe786d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderImplementor.java @@ -14,6 +14,8 @@ * @author Steve Ebersole */ public interface MetadataBuilderImplementor extends MetadataBuilder { + BootstrapContext getBootstrapContext(); + /** * Get the options being collected on this MetadataBuilder that will ultimately be used in * building the Metadata. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderInitializer.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderInitializer.java index 77527ca40420..6be8c66fe730 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuilderInitializer.java @@ -11,7 +11,7 @@ /** * Contract for contributing to the initialization of MetadataBuilder. Called - * immediately afterQuery any configuration settings have been applied from + * immediately after any configuration settings have been applied from * {@link org.hibernate.engine.config.spi.ConfigurationService}. Any values specified * here override those. Any values set here can still be overridden explicitly by the user * via the exposed config methods of {@link MetadataBuilder} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingContext.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingContext.java index 174c08eaa57f..12fcc57d9368 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingContext.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingContext.java @@ -19,38 +19,42 @@ * @since 5.0 */ public interface MetadataBuildingContext { + BootstrapContext getBootstrapContext(); /** * Access to the options specified by the {@link org.hibernate.boot.MetadataBuilder} * * @return The options */ - public MetadataBuildingOptions getBuildingOptions(); + MetadataBuildingOptions getBuildingOptions(); /** * Access to mapping defaults in effect for this context * * @return The mapping defaults. */ - public MappingDefaults getMappingDefaults(); + MappingDefaults getMappingDefaults(); /** * Access to the collector of metadata as we build it. * * @return The metadata collector. */ - public InFlightMetadataCollector getMetadataCollector(); + InFlightMetadataCollector getMetadataCollector(); /** * Provides access to ClassLoader services when needed during binding * * @return The ClassLoaderAccess + * + * @deprecated Use {@link BootstrapContext#getClassLoaderAccess()}} instead. */ - public ClassLoaderAccess getClassLoaderAccess(); + @Deprecated + ClassLoaderAccess getClassLoaderAccess(); /** * Not sure how I feel about this exposed here * * @return */ - public ObjectNameNormalizer getObjectNameNormalizer(); + ObjectNameNormalizer getObjectNameNormalizer(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java index ab2ce2ebac43..db8a735aabde 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java @@ -12,6 +12,7 @@ import org.hibernate.MultiTenancyStrategy; import org.hibernate.annotations.common.reflection.ReflectionManager; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.CacheRegionDefinition; import org.hibernate.boot.archive.scan.spi.ScanEnvironment; import org.hibernate.boot.archive.scan.spi.ScanOptions; @@ -22,7 +23,6 @@ import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.MetadataSourceType; import org.hibernate.dialect.function.SQLFunction; @@ -64,19 +64,39 @@ public interface MetadataBuildingOptions { */ List getBasicTypeRegistrations(); + /** + * Retrieve the Hibernate Commons Annotations ReflectionManager to use. + * + * @return The Hibernate Commons Annotations ReflectionManager to use. + * + * @deprecated Use {@link BootstrapContext#getReflectionManager()} instead, + * The plan is to remove first {@link MetadataBuildingOptions#getReflectionManager()} + * keeping {@link BootstrapContext#getReflectionManager()} till the migration from + * Hibernate Commons Annotations to Jandex. + * + */ + @Deprecated + ReflectionManager getReflectionManager(); + /** * Access to the Jandex index passed by call to * {@link org.hibernate.boot.MetadataBuilder#applyIndexView(org.jboss.jandex.IndexView)}, if any. * * @return The Jandex index + * + * @deprecated Use {@link BootstrapContext#getJandexView()} instead. */ + @Deprecated IndexView getJandexView(); /** * Access to the options to be used for scanning * * @return The scan options + * + * @deprecated Use {@link BootstrapContext#getScanOptions()} instead. */ + @Deprecated ScanOptions getScanOptions(); /** @@ -84,7 +104,10 @@ public interface MetadataBuildingOptions { * {@link ScanEnvironment} * * @return The scan environment + * + * @deprecated Use {@link BootstrapContext#getScanEnvironment()} instead. */ + @Deprecated ScanEnvironment getScanEnvironment(); /** @@ -95,14 +118,20 @@ public interface MetadataBuildingOptions { * * * @return The scanner + * + * @deprecated Use {@link BootstrapContext#getScanner()} instead. */ + @Deprecated Object getScanner(); /** * Access to the ArchiveDescriptorFactory to be used for scanning * * @return The ArchiveDescriptorFactory + * + * @deprecated Use {@link BootstrapContext#getArchiveDescriptorFactory()} instead. */ + @Deprecated ArchiveDescriptorFactory getArchiveDescriptorFactory(); /** @@ -110,13 +139,15 @@ public interface MetadataBuildingOptions { * {@link javax.persistence.spi.PersistenceUnitInfo#getNewTempClassLoader()}, if any. * * @return The tempo ClassLoader + * + * @deprecated Use {@link BootstrapContext#getJpaTempClassLoader()} instead. */ + @Deprecated ClassLoader getTempClassLoader(); ImplicitNamingStrategy getImplicitNamingStrategy(); - PhysicalNamingStrategy getPhysicalNamingStrategy(); - ReflectionManager getReflectionManager(); + PhysicalNamingStrategy getPhysicalNamingStrategy(); /** * Access to the SharedCacheMode for determining whether we should perform second level @@ -146,7 +177,10 @@ public interface MetadataBuildingOptions { * Access to all explicit cache region mappings. * * @return Explicit cache region mappings. + * + * @deprecated Use {@link BootstrapContext#getClassmateContext()} instead. */ + @Deprecated List getCacheRegionDefinitions(); /** @@ -203,12 +237,19 @@ public interface MetadataBuildingOptions { */ List getSourceProcessOrdering(); + default String getSchemaCharset() { + return null; + } + /** * Access to any SQL functions explicitly registered with the MetadataBuilder. This * does not include Dialect defined functions, etc. * * @return The SQLFunctions registered through MetadataBuilder + * + * @deprecated Use {@link BootstrapContext#getSqlFunctions()} instead. */ + @Deprecated Map getSqlFunctions(); /** @@ -216,15 +257,19 @@ public interface MetadataBuildingOptions { * does not include AuxiliaryDatabaseObject defined in mappings. * * @return The AuxiliaryDatabaseObject registered through MetadataBuilder + * + * @deprecated Use {@link BootstrapContext#getAuxiliaryDatabaseObjectList()} instead. */ + @Deprecated List getAuxiliaryDatabaseObjectList(); - List getAttributeConverters(); - -// /** -// * Obtain the selected strategy for resolving members identifying persistent attributes -// * -// * @return The select resolver strategy -// */ -// PersistentAttributeMemberResolver getPersistentAttributeMemberResolver(); + /** + * Access to collected AttributeConverter definitions. + * + * @return The AttributeConverterInfo registered through MetadataBuilder + * + * @deprecated Use {@link BootstrapContext#getAttributeConverters()} instead + */ + @Deprecated + List getAttributeConverters(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataContributor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataContributor.java index d0b1ea56970f..0c07666632ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataContributor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataContributor.java @@ -11,8 +11,8 @@ /** * Contract for contributing to Metadata (InFlightMetadataCollector). * - * This hook occurs just afterQuery all processing of all {@link org.hibernate.boot.MetadataSources}, - * and just beforeQuery {@link org.hibernate.boot.spi.AdditionalJaxbMappingProducer}. + * This hook occurs just after all processing of all {@link org.hibernate.boot.MetadataSources}, + * and just before {@link org.hibernate.boot.spi.AdditionalJaxbMappingProducer}. * * @author Steve Ebersole * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java index e05329c28092..0bd1a400c3cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java @@ -6,15 +6,19 @@ */ package org.hibernate.boot.spi; +import java.util.Collection; import java.util.Set; import org.hibernate.MappingException; import org.hibernate.boot.Metadata; +import org.hibernate.cache.cfg.internal.DomainDataRegionConfigImpl; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.query.spi.NamedQueryRepository; +import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; +import org.hibernate.type.spi.TypeConfiguration; /** * The SPI-level Metadata contract. @@ -33,6 +37,21 @@ public interface MetadataImplementor extends Metadata, Mapping { */ MetadataBuildingOptions getMetadataBuildingOptions(); + /** + * Access to the TypeConfiguration + * + * @return Access to the TypeConfiguration + */ + TypeConfiguration getTypeConfiguration(); + + /** + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated TypeResolver getTypeResolver(); NamedQueryRepository buildNamedQueryRepository(SessionFactoryImpl sessionFactory); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryBuilderImplementor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryBuilderImplementor.java index ba9f8c920f10..62dd63af1d59 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryBuilderImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryBuilderImplementor.java @@ -24,6 +24,8 @@ public interface SessionFactoryBuilderImplementor extends SessionFactoryBuilder * directly into Hibernate contracts (SessionFactory, Session); intended to provide * transition help in cases where we need to know the difference in JPA/native use for * various reasons. + * Use {@link BootstrapContext#markAsJpaBootstrap()} + * */ @Deprecated void markAsJpaBootstrap(); @@ -33,6 +35,11 @@ public interface SessionFactoryBuilderImplementor extends SessionFactoryBuilder default void disableRefreshDetachedEntity() { } + /** + * @see org.hibernate.cfg.AvailableSettings#JDBC_TYLE_PARAMS_ZERO_BASE + */ + void enableJdbcStyleParamsZeroBased(); + /** * Build the SessionFactoryOptions that will ultimately be passed to SessionFactoryImpl constructor. * 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 e669e03a3471..8e92af0def06 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 @@ -8,11 +8,13 @@ import java.util.Map; import java.util.TimeZone; +import java.util.function.Supplier; import org.hibernate.ConnectionReleaseMode; import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.EntityMode; import org.hibernate.EntityNameResolver; +import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.MultiTenancyStrategy; import org.hibernate.NullPrecedence; @@ -20,13 +22,17 @@ import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.cache.spi.QueryCacheFactory; +import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -37,6 +43,20 @@ * @since 5.0 */ public interface SessionFactoryOptions { + /** + * Get the UUID unique to this SessionFactoryOptions. Will be the + * same value available as {@link SessionFactoryImplementor#getUuid()}. + * + * @apiNote The value is generated as a {@link java.util.UUID}, but kept + * as a String. + * + * @return The UUID for this SessionFactory. + * + * @see org.hibernate.internal.SessionFactoryRegistry#getSessionFactory + * @see SessionFactoryImplementor#getUuid + */ + String getUuid(); + /** * The service registry to use in building the factory. * @@ -49,14 +69,12 @@ public interface SessionFactoryOptions { Object getValidatorFactoryReference(); /** - * @deprecated (since 5.2) In fact added in 5.2 as part of consolidating JPA support - * directly into Hibernate contracts (SessionFactory, Session); intended to provide - * transition help in cases where we need to know the difference in JPA/native use for - * various reasons. + * Was building of the SessionFactory initiated through JPA bootstrapping, or + * through Hibernate's native bootstrapping? * - * @see SessionFactoryBuilderImplementor#markAsJpaBootstrap + * @return {@code true} indicates the SessionFactory was built through JPA + * bootstrapping; {@code false} indicates it was built through native bootstrapping. */ - @Deprecated boolean isJpaBootstrap(); boolean isJtaTransactionAccessEnabled(); @@ -100,9 +118,27 @@ default boolean isAllowRefreshDetachedEntity() { * Get the interceptor to use by default for all sessions opened from this factory. * * @return The interceptor to use factory wide. May be {@code null} + * @deprecated use {@link #getStatelessInterceptorImplementorSupplier()} instead. */ + @Deprecated Class getStatelessInterceptorImplementor(); + /** + * Get the interceptor to use by default for all sessions opened from this factory. + * + * @return The interceptor to use factory wide. May be {@code null} + */ + default Supplier getStatelessInterceptorImplementorSupplier() { + return () -> { + try { + return getStatelessInterceptorImplementor().newInstance(); + } + catch (InstantiationException | IllegalAccessException e) { + throw new HibernateException( "Could not supply session-scoped SessionFactory Interceptor", e ); + } + }; + } + StatementInspector getStatementInspector(); SessionFactoryObserver[] getSessionFactoryObservers(); @@ -125,6 +161,8 @@ default boolean isAllowRefreshDetachedEntity() { BatchFetchStyle getBatchFetchStyle(); + boolean isDelayBatchFetchLoaderCreationsEnabled(); + int getDefaultBatchFetchSize(); Integer getMaximumFetchDepth(); @@ -143,7 +181,14 @@ default boolean isAllowRefreshDetachedEntity() { Map getQuerySubstitutions(); - boolean isStrictJpaQueryLanguageCompliance(); + /** + * @deprecated Use {@link JpaCompliance#isJpaQueryComplianceEnabled()} instead + * via {@link #getJpaCompliance()} + */ + @Deprecated + default boolean isStrictJpaQueryLanguageCompliance() { + return getJpaCompliance().isJpaQueryComplianceEnabled(); + } boolean isNamedQueryStartupCheckingEnabled(); @@ -153,7 +198,7 @@ default boolean isAllowRefreshDetachedEntity() { boolean isQueryCacheEnabled(); - QueryCacheFactory getQueryCacheFactory(); + TimestampsCacheFactory getTimestampsCacheFactory(); String getCacheRegionPrefix(); @@ -181,6 +226,10 @@ default boolean isAllowRefreshDetachedEntity() { PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode(); + default boolean doesConnectionProviderDisableAutoCommit() { + return false; + } + /** * @deprecated Use {@link #getPhysicalConnectionHandlingMode()} instead */ @@ -215,4 +264,37 @@ default boolean isAllowRefreshDetachedEntity() { boolean isReleaseResourcesOnCloseEnabled(); TimeZone getJdbcTimeZone(); + + default boolean isQueryParametersValidationEnabled(){ + return isJpaBootstrap(); + } + + default LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return LiteralHandlingMode.AUTO; + } + + boolean jdbcStyleParamsZeroBased(); + + JpaCompliance getJpaCompliance(); + + boolean isFailOnPaginationOverCollectionFetchEnabled(); + + default ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandlingMode() { + return ImmutableEntityUpdateQueryHandlingMode.WARNING; + } + + default boolean inClauseParameterPaddingEnabled() { + return false; + } + + default boolean nativeExceptionHandling51Compliance() { + return false; + } + + /** + * Can bytecode-enhanced entity classes be used as a "proxy"? + */ + default boolean isEnhancementAsProxyEnabled() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/ConfigXsdSupport.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/ConfigXsdSupport.java new file mode 100644 index 000000000000..69c5214f87fc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/ConfigXsdSupport.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.xsd; + +import org.jboss.logging.Logger; + +/** + * Support for XSD handling related to Hibernate's `cfg.xml` and + * JPA's `persistence.xml`. + * + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class ConfigXsdSupport { + private static final Logger log = Logger.getLogger( ConfigXsdSupport.class ); + + /** + * Singleton access + */ + public static final ConfigXsdSupport INSTANCE = new ConfigXsdSupport(); + + private ConfigXsdSupport() { + //Do not construct new instances + } + + private final XsdDescriptor jpa10 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/persistence_1_0.xsd", + "1.0", + "http://java.sun.com/xml/ns/persistence" + ); + + private final XsdDescriptor jpa20 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/persistence_2_0.xsd", + "2.0" , + "http://java.sun.com/xml/ns/persistence" + ); + + private final XsdDescriptor jpa21 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/persistence_2_1.xsd", + "2.1", + "http://xmlns.jcp.org/xml/ns/persistence" + ); + + private final XsdDescriptor jpa22 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/persistence_2_2.xsd", + "2.2" , + "http://xmlns.jcp.org/xml/ns/persistence" + ); + + private final XsdDescriptor jpa30 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/persistence_3_0.xsd", + "3.0", + "https://jakarta.ee/xml/ns/persistence" + ); + + private final XsdDescriptor cfgXml = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/xsd/cfg/legacy-configuration-4.0.xsd", + "4.0" , + "http://www.hibernate.org/xsd/orm/cfg" + ); + + public XsdDescriptor latestJpaDescriptor() { + return jpa22; + } + + public XsdDescriptor jpaXsd(String version) { + switch ( version ) { + case "1.0": { + return jpa10; + } + case "2.0": { + return jpa20; + } + case "2.1": { + return jpa21; + } + case "2.2": { + return jpa22; + } + case "3.0": { + return jpa30; + } + default: { + throw new IllegalArgumentException( "Unrecognized JPA persistence.xml XSD version : `" + version + "`" ); + } + } + } + + public XsdDescriptor cfgXsd() { + return cfgXml; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/LocalXsdResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/LocalXsdResolver.java new file mode 100644 index 000000000000..333bb475f2a1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/LocalXsdResolver.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.xsd; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import javax.xml.XMLConstants; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.hibernate.internal.util.xml.XsdException; + +import org.jboss.logging.Logger; + +import org.xml.sax.SAXException; + +/** + * When Hibernate loads an XSD we fully expect that to be resolved from our + * jar file via ClassLoader resource look-up. This class simplifies + * the steps needed to achieve those goals explicitly using its own + * ClassLoader for the look-ups. + * + * @author Steve Ebersole + */ +@SuppressWarnings("WeakerAccess") +public class LocalXsdResolver { + private static final Logger log = Logger.getLogger( LocalXsdResolver.class ); + + private static final List VALID_JPA_VERSIONS = Arrays.asList( "1.0", "2.0", "2.1", "2.2", "3.0" ); + + public static String latestJpaVerison() { + return "2.2"; + } + + public static boolean isValidJpaVersion(String version) { + return VALID_JPA_VERSIONS.contains( version ); + } + + public static URL resolveLocalXsdUrl(String resourceName) { + try { + final URL url = LocalXsdResolver.class.getClassLoader().getResource( resourceName ); + if ( url != null ) { + return url; + } + } + catch (Exception ignore) { + } + + if ( resourceName.startsWith( "/" ) ) { + resourceName = resourceName.substring( 1 ); + + try { + final URL url = LocalXsdResolver.class.getClassLoader().getResource( resourceName ); + if ( url != null ) { + return url; + } + } + catch (Exception ignore) { + } + } + + // Last: we try name as a URL + try { + return new URL( resourceName ); + } + catch (Exception ignore) { + } + + return null; + } + + + public static Schema resolveLocalXsdSchema(String schemaResourceName) { + final URL url = resolveLocalXsdUrl( schemaResourceName ); + if ( url == null ) { + throw new XsdException( "Unable to locate schema [" + schemaResourceName + "] via classpath", schemaResourceName ); + } + try { + InputStream schemaStream = url.openStream(); + try { + StreamSource source = new StreamSource( url.openStream() ); + SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ); + return schemaFactory.newSchema( source ); + } + catch ( SAXException | IOException e ) { + throw new XsdException( "Unable to load schema [" + schemaResourceName + "]", e, schemaResourceName ); + } + finally { + try { + schemaStream.close(); + } + catch ( IOException e ) { + log.debugf( "Problem closing schema stream [%s]", e.toString() ); + } + } + } + catch ( IOException e ) { + throw new XsdException( "Stream error handling schema url [" + url.toExternalForm() + "]", schemaResourceName ); + } + } + + public static XsdDescriptor buildXsdDescriptor(String resourceName, String version, String namespaceUri) { + return new XsdDescriptor( resourceName, resolveLocalXsdSchema( resourceName ), version, namespaceUri ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/MappingXsdSupport.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/MappingXsdSupport.java new file mode 100644 index 000000000000..33a371927088 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/MappingXsdSupport.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.boot.xsd; + +/** + * Support for XSD handling related to Hibernate's `hbm.xml` and + * JPA's `orm.xml`. + * + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class MappingXsdSupport { + + /** + * Singleton access + */ + public static final MappingXsdSupport INSTANCE = new MappingXsdSupport(); + + private final XsdDescriptor jpa10 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/orm_1_0.xsd", + "1.0", + "http://java.sun.com/xml/ns/persistence/orm" + ); + + private final XsdDescriptor jpa20 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/orm_2_0.xsd", + "2.0", + "http://java.sun.com/xml/ns/persistence/orm" + ); + + private final XsdDescriptor jpa21 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/orm_2_1.xsd", + "2.1", + "http://xmlns.jcp.org/xml/ns/persistence" + ); + + private final XsdDescriptor jpa22 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/orm_2_2.xsd", + "2.2", + "http://xmlns.jcp.org/xml/ns/persistence" + ); + + private final XsdDescriptor jpa30 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/orm_3_0.xsd", + "3.0", + "https://jakarta.ee/xml/ns/persistence/orm" + ); + + private final XsdDescriptor hbmXml = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd", + "4.0", + "http://www.hibernate.org/xsd/orm/hbm" + ); + + private MappingXsdSupport() { + //Do not construct new instances + } + + public XsdDescriptor latestJpaDescriptor() { + return jpa22; + } + + public XsdDescriptor jpaXsd(String version) { + switch ( version ) { + case "1.0": { + return jpa10; + } + case "2.0": { + return jpa20; + } + case "2.1": { + return jpa21; + } + case "2.2": { + return jpa22; + } + case "3.0:": { + return jpa30; + } + default: { + throw new IllegalArgumentException( "Unrecognized JPA orm.xml XSD version : `" + version + "`" ); + } + } + } + + public XsdDescriptor hbmXsd() { + return hbmXml; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/XsdDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/XsdDescriptor.java new file mode 100644 index 000000000000..e68529dac559 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/XsdDescriptor.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.boot.xsd; + +import javax.xml.validation.Schema; + +/** + * Representation of a locally resolved XSD + * + * @author Steve Ebersole + */ +public final class XsdDescriptor { + private final String localResourceName; + private final String namespaceUri; + private final String version; + private final Schema schema; + + XsdDescriptor(String localResourceName, Schema schema, String version, String namespaceUri) { + this.localResourceName = localResourceName; + this.schema = schema; + this.version = version; + this.namespaceUri = namespaceUri; + } + + public String getLocalResourceName() { + return localResourceName; + } + + public String getNamespaceUri() { + return namespaceUri; + } + + public String getVersion() { + return version; + } + + public Schema getSchema() { + return schema; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/package-info.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/package-info.java new file mode 100644 index 000000000000..b6be8bfa1133 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/package-info.java @@ -0,0 +1,12 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +/** + * Support for XSD handling, specifically for ORM mappings (Hibernate's `hbm.xml` and + * JPA's `orm.xml`) and config files (Hibernate's `cfg.xml` and JPA's `persistence.xml`) + */ +package org.hibernate.boot.xsd; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java new file mode 100644 index 000000000000..203d7d2929bf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java @@ -0,0 +1,22 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public interface BytecodeLogger extends BasicLogger { + String NAME = "org.hibernate.orm.bytecode"; + + Logger LOGGER = Logger.getLogger( NAME ); + + boolean TRACE_ENABLED = LOGGER.isTraceEnabled(); + boolean DEBUG_ENABLED = LOGGER.isDebugEnabled(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java index 8792cac4a181..9e120435a85b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java @@ -8,6 +8,9 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; +import java.util.Optional; + import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.ManyToMany; @@ -15,6 +18,7 @@ import javax.persistence.OneToMany; import javax.persistence.OneToOne; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.internal.CoreLogging; @@ -34,14 +38,14 @@ import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; -class BiDirectionalAssociationHandler implements Implementation { +final class BiDirectionalAssociationHandler implements Implementation { private static final CoreMessageLogger log = CoreLogging.messageLogger( BiDirectionalAssociationHandler.class ); static Implementation wrap( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, - FieldDescription persistentField, + AnnotatedFieldDescription persistentField, Implementation implementation) { if ( !enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) { return implementation; @@ -67,17 +71,17 @@ static Implementation wrap( .getType() .asErasure(); - if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToOne.class ) ) { + if ( persistentField.hasAnnotation( OneToOne.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( CodeTemplates.OneToOneHandler.class ) .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToMany.class ) ) { + if ( persistentField.hasAnnotation( OneToMany.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( persistentField.getType().asErasure().isAssignableTo( Map.class ) ? CodeTemplates.OneToManyOnMapHandler.class @@ -85,15 +89,15 @@ static Implementation wrap( .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToOne.class ) ) { + if ( persistentField.hasAnnotation( ManyToOne.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( CodeTemplates.ManyToOneHandler.class ) .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToMany.class ) ) { + if ( persistentField.hasAnnotation( ManyToMany.class ) ) { if ( persistentField.getType().asErasure().isAssignableTo( Map.class ) || targetType.isAssignableTo( Map.class ) ) { log.infof( @@ -105,7 +109,7 @@ static Implementation wrap( } implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( CodeTemplates.ManyToManyHandler.class ) .wrap( implementation ); @@ -114,12 +118,12 @@ static Implementation wrap( return new BiDirectionalAssociationHandler( implementation, targetEntity, targetType, mappedBy ); } - public static TypeDescription getTargetEntityClass(TypeDescription managedCtClass, FieldDescription persistentField) { + public static TypeDescription getTargetEntityClass(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { try { - AnnotationDescription.Loadable oto = EnhancerImpl.getAnnotation( persistentField, OneToOne.class ); - AnnotationDescription.Loadable otm = EnhancerImpl.getAnnotation( persistentField, OneToMany.class ); - AnnotationDescription.Loadable mto = EnhancerImpl.getAnnotation( persistentField, ManyToOne.class ); - AnnotationDescription.Loadable mtm = EnhancerImpl.getAnnotation( persistentField, ManyToMany.class ); + AnnotationDescription.Loadable oto = persistentField.getAnnotation( OneToOne.class ); + AnnotationDescription.Loadable otm = persistentField.getAnnotation( OneToMany.class ); + AnnotationDescription.Loadable mto = persistentField.getAnnotation( ManyToOne.class ); + AnnotationDescription.Loadable mtm = persistentField.getAnnotation( ManyToMany.class ); if ( oto == null && otm == null && mto == null && mtm == null ) { return null; @@ -157,23 +161,23 @@ else if ( !targetClass.resolve( TypeDescription.class ).represents( void.class ) return entityType( target( persistentField ) ); } - private static TypeDescription.Generic target(FieldDescription persistentField) { + private static TypeDescription.Generic target(AnnotatedFieldDescription persistentField) { AnnotationDescription.Loadable access = persistentField.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class ); if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { return persistentField.getType(); } else { - MethodDescription getter = EnhancerImpl.getterOf( persistentField ); - if ( getter == null ) { - return persistentField.getType(); + Optional getter = persistentField.getGetter(); + if ( getter.isPresent() ) { + return getter.get().getReturnType(); } else { - return getter.getReturnType(); + return persistentField.getType(); } } } - private static String getMappedBy(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { + private static String getMappedBy(AnnotatedFieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { String mappedBy = getMappedByNotManyToMany( target ); if ( mappedBy == null || mappedBy.isEmpty() ) { return getMappedByManyToMany( target, targetEntity, context ); @@ -183,19 +187,19 @@ private static String getMappedBy(FieldDescription target, TypeDescription targe } } - private static String getMappedByNotManyToMany(FieldDescription target) { + private static String getMappedByNotManyToMany(AnnotatedFieldDescription target) { try { - AnnotationDescription.Loadable oto = EnhancerImpl.getAnnotation( target, OneToOne.class ); + AnnotationDescription.Loadable oto = target.getAnnotation( OneToOne.class ); if ( oto != null ) { return oto.getValue( new MethodDescription.ForLoadedMethod( OneToOne.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } - AnnotationDescription.Loadable otm = EnhancerImpl.getAnnotation( target, OneToMany.class ); + AnnotationDescription.Loadable otm = target.getAnnotation( OneToMany.class ); if ( otm != null ) { return otm.getValue( new MethodDescription.ForLoadedMethod( OneToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } - AnnotationDescription.Loadable mtm = EnhancerImpl.getAnnotation( target, ManyToMany.class ); + AnnotationDescription.Loadable mtm = target.getAnnotation( ManyToMany.class ); if ( mtm != null ) { return mtm.getValue( new MethodDescription.ForLoadedMethod( ManyToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } @@ -206,11 +210,12 @@ private static String getMappedByNotManyToMany(FieldDescription target) { return null; } - private static String getMappedByManyToMany(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { + private static String getMappedByManyToMany(AnnotatedFieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { for ( FieldDescription f : targetEntity.getDeclaredFields() ) { - if ( context.isPersistentField( f ) - && target.getName().equals( getMappedByNotManyToMany( f ) ) - && target.getDeclaringType().asErasure().isAssignableTo( entityType( f.getType() ) ) ) { + AnnotatedFieldDescription annotatedF = new AnnotatedFieldDescription( context, f ); + if ( context.isPersistentField( annotatedF ) + && target.getName().equals( getMappedByNotManyToMany( annotatedF ) ) + && target.getDeclaringType().asErasure().isAssignableTo( entityType( annotatedF.getType() ) ) ) { log.debugf( "mappedBy association for field [%s#%s] is [%s#%s]", target.getDeclaringType().asErasure().getName(), @@ -327,4 +332,24 @@ else if ( name.equals( "setterNull" ) ) { }, implementationContext, instrumentedMethod ); } } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if ( o == null || BiDirectionalAssociationHandler.class != o.getClass() ) { + return false; + } + final BiDirectionalAssociationHandler that = (BiDirectionalAssociationHandler) o; + return Objects.equals( delegate, that.delegate ) && + Objects.equals( targetEntity, that.targetEntity ) && + Objects.equals( targetType, that.targetType ) && + Objects.equals( mappedBy, that.mappedBy ); + } + + @Override + public int hashCode() { + return Objects.hash( delegate, targetEntity, targetType, mappedBy ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java index 09a6e30620d6..082e73adc4a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java @@ -6,16 +6,33 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isGetter; + +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.bytecode.enhance.spi.UnloadedField; import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.scaffold.MethodGraph; +import net.bytebuddy.matcher.ElementMatcher; class ByteBuddyEnhancementContext { + private static final ElementMatcher.Junction IS_GETTER = isGetter(); + private final EnhancementContext enhancementContext; + private final ConcurrentHashMap> getterByTypeMap = new ConcurrentHashMap<>(); + ByteBuddyEnhancementContext(EnhancementContext enhancementContext) { this.enhancementContext = enhancementContext; } @@ -36,10 +53,6 @@ public boolean isMappedSuperclassClass(TypeDescription classDescriptor) { return enhancementContext.isMappedSuperclassClass( new UnloadedTypeDescription( classDescriptor ) ); } - public boolean doBiDirectionalAssociationManagement(FieldDescription field) { - return enhancementContext.doBiDirectionalAssociationManagement( new UnloadedFieldDescription( field ) ); - } - public boolean doDirtyCheckingInline(TypeDescription classDescriptor) { return enhancementContext.doDirtyCheckingInline( new UnloadedTypeDescription( classDescriptor ) ); } @@ -52,28 +65,54 @@ public boolean hasLazyLoadableAttributes(TypeDescription classDescriptor) { return enhancementContext.hasLazyLoadableAttributes( new UnloadedTypeDescription( classDescriptor ) ); } - public boolean isPersistentField(FieldDescription ctField) { - return enhancementContext.isPersistentField( new UnloadedFieldDescription( ctField ) ); + public boolean isPersistentField(AnnotatedFieldDescription field) { + return enhancementContext.isPersistentField( field ); } - public FieldDescription[] order(FieldDescription[] persistentFields) { - UnloadedField[] unloadedFields = new UnloadedField[persistentFields.length]; - for ( int i = 0; i < unloadedFields.length; i++ ) { - unloadedFields[i] = new UnloadedFieldDescription( persistentFields[i] ); - } - UnloadedField[] ordered = enhancementContext.order( unloadedFields ); - FieldDescription[] orderedFields = new FieldDescription[persistentFields.length]; - for ( int i = 0; i < orderedFields.length; i++ ) { - orderedFields[i] = ( (UnloadedFieldDescription) ordered[i] ).fieldDescription; - } - return orderedFields; + public AnnotatedFieldDescription[] order(AnnotatedFieldDescription[] persistentFields) { + return (AnnotatedFieldDescription[]) enhancementContext.order( persistentFields ); + } + + public boolean isLazyLoadable(AnnotatedFieldDescription field) { + return enhancementContext.isLazyLoadable( field ); } - public boolean isLazyLoadable(FieldDescription field) { - return enhancementContext.isLazyLoadable( new UnloadedFieldDescription( field ) ); + public boolean isMappedCollection(AnnotatedFieldDescription field) { + return enhancementContext.isMappedCollection( field ); } - public boolean isMappedCollection(FieldDescription field) { - return enhancementContext.isMappedCollection( new UnloadedFieldDescription( field ) ); + public boolean doBiDirectionalAssociationManagement(AnnotatedFieldDescription field) { + return enhancementContext.doBiDirectionalAssociationManagement( field ); + } + + Optional resolveGetter(FieldDescription fieldDescription) { + Map getters = getterByTypeMap + .computeIfAbsent( fieldDescription.getDeclaringType().asErasure(), declaringType -> { + return MethodGraph.Compiler.DEFAULT.compile( declaringType ) + .listNodes() + .asMethodList() + .filter( IS_GETTER ) + .stream() + .collect( Collectors.toMap( MethodDescription::getActualName, Function.identity() ) ); + } ); + + String capitalizedFieldName = Character.toUpperCase( fieldDescription.getName().charAt( 0 ) ) + + fieldDescription.getName().substring( 1 ); + + MethodDescription getCandidate = getters.get( "get" + capitalizedFieldName ); + MethodDescription isCandidate = getters.get( "is" + capitalizedFieldName ); + + if ( getCandidate != null ) { + if ( isCandidate != null ) { + // if there are two candidates, the existing code considered there was no getter. + // not sure it's such a good idea but throwing an exception apparently throws exception + // in cases where Hibernate does not usually throw a mapping error. + return Optional.empty(); + } + + return Optional.of( getCandidate ); + } + + return Optional.ofNullable( isCandidate ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java index 8bfc61d5da1b..996d7b3befb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java @@ -14,6 +14,7 @@ import org.hibernate.Hibernate; import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker; +import org.hibernate.bytecode.enhance.internal.tracker.NoopCollectionTracker; import org.hibernate.bytecode.enhance.internal.tracker.SimpleCollectionTracker; import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker; import org.hibernate.bytecode.enhance.spi.CollectionTracker; @@ -83,7 +84,23 @@ static class GetDirtyAttributes { } } - static class AreCollectionFieldsDirty { + static class GetDirtyAttributesWithoutCollections { + @Advice.OnMethodExit + static void $$_hibernate_getDirtyAttributes( + @Advice.Return(readOnly = false) String[] returned, + @Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME) DirtyTracker $$_hibernate_tracker) { + returned = $$_hibernate_tracker == null ? new String[0] : $$_hibernate_tracker.get(); + } + } + + static class GetCollectionTrackerWithoutCollections { + @Advice.OnMethodExit + static void $$_hibernate_getCollectionTracker( @Advice.Return(readOnly = false) CollectionTracker returned) { + returned = NoopCollectionTracker.INSTANCE; + } + } + + static class AreFieldsDirty { @Advice.OnMethodExit static void $$_hibernate_hasDirtyAttributes( @Advice.This ExtendedSelfDirtinessTracker self, @@ -93,6 +110,15 @@ static class AreCollectionFieldsDirty { } } + static class AreFieldsDirtyWithoutCollections { + @Advice.OnMethodExit + static void $$_hibernate_hasDirtyAttributes( + @Advice.Return(readOnly = false) boolean returned, + @Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME) DirtyTracker $$_hibernate_tracker) { + returned = $$_hibernate_tracker != null && !$$_hibernate_tracker.isEmpty(); + } + } + static class ClearDirtyAttributes { @Advice.OnMethodEnter static void $$_hibernate_clearDirtyAttributes( @@ -105,6 +131,16 @@ static class ClearDirtyAttributes { } } + static class ClearDirtyAttributesWithoutCollections { + @Advice.OnMethodEnter + static void $$_hibernate_clearDirtyAttributes( + @Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME) DirtyTracker $$_hibernate_tracker) { + if ( $$_hibernate_tracker != null ) { + $$_hibernate_tracker.clear(); + } + } + } + static class SuspendDirtyTracking { @Advice.OnMethodEnter static void $$_hibernate_suspendDirtyTracking( 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 43ef42992ced..2c7827f02d66 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -6,6 +6,9 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; + +import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -13,6 +16,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; + import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Transient; @@ -24,59 +29,85 @@ import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.bytecode.enhance.spi.UnloadedField; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; -import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; import org.hibernate.engine.spi.Managed; import org.hibernate.engine.spi.ManagedComposite; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.ManagedMappedSuperclass; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import net.bytebuddy.ByteBuddy; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldDescription.InDefinedShape; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.method.MethodList; -import net.bytebuddy.description.modifier.FieldManifestation; +import net.bytebuddy.description.modifier.FieldPersistence; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeDescription.Generic; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.DynamicType; -import net.bytebuddy.dynamic.scaffold.MethodGraph; -import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.isGetter; - public class EnhancerImpl implements Enhancer { private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class ); protected final ByteBuddyEnhancementContext enhancementContext; + private final ByteBuddyState byteBuddyState; - private final TypePool classPool; + private final EnhancerClassFileLocator classFileLocator; + private final TypePool typePool; + + /** + * Extract the following constants so that enhancement on large projects + * can be done efficiently: otherwise each instance use will trigger a + * resource load on the ClassLoader tree, triggering allocation of + * several streams to unzip each JAR file each time. + */ + private final ClassFileLocator adviceLocator = ClassFileLocator.ForClassLoader.of(CodeTemplates.class.getClassLoader()); + private final Implementation implementationTrackChange = Advice.to( CodeTemplates.TrackChange.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetDirtyAttributesWithoutCollections = Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationAreFieldsDirtyWithoutCollections = Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearDirtyAttributesWithoutCollections = Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationSuspendDirtyTracking = Advice.to( CodeTemplates.SuspendDirtyTracking.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetDirtyAttributes = Advice.to( CodeTemplates.GetDirtyAttributes.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationAreFieldsDirty = Advice.to( CodeTemplates.AreFieldsDirty.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetCollectionTrackerWithoutCollections = Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearDirtyAttributes = Advice.to( CodeTemplates.ClearDirtyAttributes.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + //In this case we just extract the Advice: + private final Advice adviceInitializeLazyAttributeLoadingInterceptor = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class, adviceLocator ); + private final Implementation implementationSetOwner = Advice.to( CodeTemplates.SetOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearOwner = Advice.to( CodeTemplates.ClearOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); /** * Constructs the Enhancer, using the given context. * * @param enhancementContext Describes the context in which enhancement will occur so as to give access * to contextual/environmental information. + * @param byteBuddyState refers to the ByteBuddy instance to use */ - public EnhancerImpl(EnhancementContext enhancementContext) { + public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState) { this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext ); - classPool = buildClassPool( this.enhancementContext ); + this.byteBuddyState = byteBuddyState; + this.classFileLocator = new EnhancerClassFileLocator( enhancementContext.getLoadingClassLoader() ); + this.typePool = buildTypePool( classFileLocator ); } /** @@ -92,28 +123,24 @@ public EnhancerImpl(EnhancementContext enhancementContext) { */ @Override public synchronized byte[] enhance(String className, byte[] originalBytes) throws EnhancementException { + //Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545 + final String safeClassName = className.replace( '/', '.' ); + classFileLocator.setClassNameAndBytes( safeClassName, originalBytes ); try { - final TypeDescription managedCtClass = classPool.describe( className ).resolve(); - DynamicType.Builder builder = doEnhance( - new ByteBuddy().with( TypeValidation.DISABLED ).redefine( managedCtClass, ClassFileLocator.Simple.of( className, originalBytes ) ), - managedCtClass - ); - if ( builder == null ) { - return null; - } - else { - return builder.make().getBytes(); - } + final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); + + return byteBuddyState.rewrite( typePool, safeClassName, byteBuddy -> doEnhance( + byteBuddy.ignore( isDefaultFinalizer() ).redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), + typeDescription + ) ); } catch (RuntimeException e) { - e.printStackTrace(); - log.unableToBuildEnhancementMetamodel( className ); - return null; + throw new EnhancementException( "Failed to enhance class " + className, e ); } } - private TypePool buildClassPool(final ByteBuddyEnhancementContext enhancementContext) { - return TypePool.Default.WithLazyResolution.of( enhancementContext.getLoadingClassLoader() ); + private TypePool buildTypePool(final ClassFileLocator classFileLocator) { + return TypePool.Default.WithLazyResolution.of( classFileLocator ); } private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDescription managedCtClass) { @@ -128,8 +155,6 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes return null; } - PersistentAttributeTransformer transformer = PersistentAttributeTransformer.collectPersistentFields( managedCtClass, enhancementContext, classPool ); - if ( enhancementContext.isEntityClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as Entity", managedCtClass.getName() ); builder = builder.implement( ManagedEntity.class ) @@ -161,83 +186,106 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes builder = addInterceptorHandling( builder, managedCtClass ); if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { - builder = builder.implement( ExtendedSelfDirtinessTracker.class ) - .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldManifestation.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) - .defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldManifestation.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) - .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) - .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) - .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.AreCollectionFieldsDirty.class ).wrap( StubMethod.INSTANCE ) ) - .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) - .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) - .withParameters( boolean.class ) - .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) - .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) - .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); - - Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE; - for ( FieldDescription collectionField : collectCollectionFields( managedCtClass ) ) { - if ( !enhancementContext.isMappedCollection( collectionField ) ) { + List collectionFields = collectCollectionFields( managedCtClass ); + + if ( collectionFields.isEmpty() ) { + builder = builder.implement( SelfDirtinessTracker.class ) + .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) + .withParameters( String.class ) + .intercept( implementationTrackChange ) + .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) + .intercept( implementationGetDirtyAttributesWithoutCollections ) + .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) + .intercept( implementationAreFieldsDirtyWithoutCollections ) + .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) + .intercept( implementationClearDirtyAttributesWithoutCollections ) + .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) + .withParameters( boolean.class ) + .intercept( implementationSuspendDirtyTracking ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) + .intercept( implementationGetCollectionTrackerWithoutCollections ); + } + else { + builder = builder.implement( ExtendedSelfDirtinessTracker.class ) + .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) + .withParameters( String.class ) + .intercept( implementationTrackChange ) + .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) + .intercept( implementationGetDirtyAttributes ) + .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) + .intercept( implementationAreFieldsDirty ) + .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) + .intercept( implementationClearDirtyAttributes ) + .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) + .withParameters( boolean.class ) + .intercept( implementationSuspendDirtyTracking ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) + .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + + Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE; + for ( AnnotatedFieldDescription collectionField : collectionFields ) { if ( collectionField.getType().asErasure().isAssignableTo( Map.class ) ) { isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapAreCollectionFieldsDirty.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.MapAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapGetCollectionFieldDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.MapGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapGetCollectionClearDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.MapGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } else { isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionAreCollectionFieldsDirty.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.CollectionAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } } - } - if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { - clearDirtyNames = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class ).wrap( clearDirtyNames ); - } + if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { + clearDirtyNames = adviceInitializeLazyAttributeLoadingInterceptor.wrap( clearDirtyNames ); + } - builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( isDirty ) - .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, void.class, Visibility.PUBLIC ) - .withParameters( DirtyTracker.class ) - .intercept( getDirtyNames ) - .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) ) - .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC ) - .withParameters( LazyAttributeLoadingInterceptor.class ) - .intercept( clearDirtyNames ); + builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) + .intercept( isDirty ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, void.class, Visibility.PUBLIC ) + .withParameters( DirtyTracker.class ) + .intercept( getDirtyNames ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC ) + .intercept( Advice.withCustomMapping() + .to( CodeTemplates.ClearDirtyCollectionNames.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ) ) + .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC ) + .withParameters( LazyAttributeLoadingInterceptor.class ) + .intercept( clearDirtyNames ); + } } - return transformer.applyTo( builder, false ); + return createTransformer( managedCtClass ).applyTo( builder, false ); } else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as Composite", managedCtClass.getName() ); @@ -250,37 +298,37 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { .defineField( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, CompositeOwnerTracker.class, - FieldManifestation.TRANSIENT, + FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, void.class, Visibility.PUBLIC ) - .withParameters( String.class, CompositeOwner.class ) - .intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( String.class, CompositeOwner.class ) + .intercept( implementationSetOwner ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) ); + .withParameters( String.class ) + .intercept( implementationClearOwner ); } - return transformer.applyTo( builder, false ); + return createTransformer( managedCtClass ).applyTo( builder, false ); } else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() ); builder = builder.implement( ManagedMappedSuperclass.class ); - return transformer.applyTo( builder, true ); + return createTransformer( managedCtClass ).applyTo( builder, true ); } else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { log.infof( "Extended enhancement of [%s]", managedCtClass.getName() ); - return transformer.applyExtended( builder ); + return createTransformer( managedCtClass ).applyExtended( builder ); } else { log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() ); @@ -288,6 +336,10 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { } } + private PersistentAttributeTransformer createTransformer(TypeDescription typeDescription) { + return PersistentAttributeTransformer.collectPersistentFields( typeDescription, enhancementContext, typePool ); + } + // See HHH-10977 HHH-11284 HHH-11404 --- check for declaration of Managed interface on the class, not inherited private boolean alreadyEnhanced(TypeDescription managedCtClass) { for ( TypeDescription.Generic declaredInterface : managedCtClass.getInterfaces() ) { @@ -323,26 +375,28 @@ private static DynamicType.Builder addFieldWithGetterAndSetter( String fieldName, String getterName, String setterName) { - return builder.defineField( fieldName, type, Visibility.PRIVATE, FieldManifestation.TRANSIENT ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + return builder + .defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( getterName, type, Visibility.PUBLIC ) - .intercept( FieldAccessor.ofField( fieldName ) ) + .intercept( FieldAccessor.ofField( fieldName ) ) .defineMethod( setterName, void.class, Visibility.PUBLIC ) - .withParameters( type ) - .intercept( FieldAccessor.ofField( fieldName ) ); + .withParameters( type ) + .intercept( FieldAccessor.ofField( fieldName ) ); } - private List collectCollectionFields(TypeDescription managedCtClass) { - List collectionList = new ArrayList<>(); + private List collectCollectionFields(TypeDescription managedCtClass) { + List collectionList = new ArrayList<>(); for ( FieldDescription ctField : managedCtClass.getDeclaredFields() ) { // skip static fields and skip fields added by enhancement if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { continue; } - if ( enhancementContext.isPersistentField( ctField ) ) { + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { - collectionList.add( ctField ); + collectionList.add( annotatedField ); } } } @@ -356,7 +410,7 @@ private List collectCollectionFields(TypeDescription managedCt return collectionList; } - private Collection collectInheritCollectionFields(TypeDefinition managedCtClass) { + private Collection collectInheritCollectionFields(TypeDefinition managedCtClass) { TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass(); if ( managedCtSuperclass == null || managedCtSuperclass.represents( Object.class ) ) { return Collections.emptyList(); @@ -365,12 +419,15 @@ private Collection collectInheritCollectionFields(TypeDefiniti if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritCollectionFields( managedCtSuperclass.asErasure() ); } - List collectionList = new ArrayList(); + List collectionList = new ArrayList<>(); for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) { - if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) { - if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { - collectionList.add( ctField ); + if ( !Modifier.isStatic( ctField.getModifiers() ) ) { + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { + if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { + collectionList.add( annotatedField ); + } } } } @@ -382,46 +439,140 @@ static String capitalize(String value) { return Character.toUpperCase( value.charAt( 0 ) ) + value.substring( 1 ); } - static boolean isAnnotationPresent(FieldDescription fieldDescription, Class type) { - return getAnnotation( fieldDescription, type ) != null; - } + static class AnnotatedFieldDescription implements UnloadedField { + + private final ByteBuddyEnhancementContext context; - static AnnotationDescription.Loadable getAnnotation(FieldDescription fieldDescription, Class type) { - AnnotationDescription.Loadable access = fieldDescription.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class ); - if ( access != null && access.loadSilent().value() == AccessType.PROPERTY ) { - MethodDescription getter = getterOf( fieldDescription ); + private final FieldDescription fieldDescription; + + private AnnotationList annotations; + + private Optional getter; + + AnnotatedFieldDescription(ByteBuddyEnhancementContext context, FieldDescription fieldDescription) { + this.context = context; + this.fieldDescription = fieldDescription; + } + + @Override + public boolean hasAnnotation(Class annotationType) { + return getAnnotations().isAnnotationPresent( annotationType ); + } + + @Override + public String toString() { + return fieldDescription.toString(); + } + + AnnotationDescription.Loadable getAnnotation(Class annotationType) { + return getAnnotations().ofType( annotationType ); + } + + String getName() { + return fieldDescription.getName(); + } + + TypeDefinition getDeclaringType() { + return fieldDescription.getDeclaringType(); + } + + Generic getType() { + return fieldDescription.getType(); + } + + InDefinedShape asDefined() { + return fieldDescription.asDefined(); + } + + String getDescriptor() { + return fieldDescription.getDescriptor(); + } + + boolean isVisibleTo(TypeDescription typeDescription) { + return fieldDescription.isVisibleTo( typeDescription ); + } + + FieldDescription getFieldDescription() { + return fieldDescription; + } + + Optional getGetter() { if ( getter == null ) { - return fieldDescription.getDeclaredAnnotations().ofType( type ); - } - else { - return getter.getDeclaredAnnotations().ofType( type ); + getter = context.resolveGetter( fieldDescription ); } + + return getter; } - else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { - return fieldDescription.getDeclaredAnnotations().ofType( type ); + + private AnnotationList getAnnotations() { + if ( annotations == null ) { + annotations = doGetAnnotations(); + } + return annotations; } - else { - MethodDescription getter = getterOf( fieldDescription ); - if ( getter != null ) { - AnnotationDescription.Loadable annotationDescription = getter.getDeclaredAnnotations().ofType( type ); - if ( annotationDescription != null ) { - return annotationDescription; + + private AnnotationList doGetAnnotations() { + AnnotationDescription.Loadable access = fieldDescription.getDeclaringType().asErasure() + .getDeclaredAnnotations().ofType( Access.class ); + if ( access != null && access.loadSilent().value() == AccessType.PROPERTY ) { + Optional getter = getGetter(); + if ( getter.isPresent() ) { + return getter.get().getDeclaredAnnotations(); + } + else { + return fieldDescription.getDeclaredAnnotations(); + } + } + else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { + return fieldDescription.getDeclaredAnnotations(); + } + else { + Optional getter = getGetter(); + + // Note that the order here is important + List annotationDescriptions = new ArrayList<>(); + if ( getter.isPresent() ) { + annotationDescriptions.addAll( getter.get().getDeclaredAnnotations() ); } + annotationDescriptions.addAll( fieldDescription.getDeclaredAnnotations() ); + + return fieldDescription.getDeclaredAnnotations(); } - return fieldDescription.getDeclaredAnnotations().ofType( type ); } } - static MethodDescription getterOf(FieldDescription persistentField) { - MethodList methodList = MethodGraph.Compiler.DEFAULT.compile( persistentField.getDeclaringType().asErasure() ) - .listNodes() - .asMethodList() - .filter( isGetter(persistentField.getName() ) ); - if ( methodList.size() == 1 ) { - return methodList.getOnly(); + private class EnhancerClassFileLocator extends ClassFileLocator.ForClassLoader { + + // The name of the class to (possibly be) transformed. + private String className; + // The explicitly resolved Resolution for the class to (possibly be) transformed. + private Resolution resolution; + + /** + * Creates a new class file locator for the given class loader. + * + * @param classLoader The class loader to query which must not be the bootstrap class loader, i.e. {@code null}. + */ + protected EnhancerClassFileLocator(ClassLoader classLoader) { + super( classLoader ); } - else { - return null; + + @Override + public Resolution locate(String className) throws IOException { + assert className != null; + if ( className.equals( this.className ) ) { + return resolution; + } + else { + return super.locate( className ); + } + } + + void setClassNameAndBytes(String className, byte[] bytes) { + assert className != null; + assert bytes != null; + this.className = className; + this.resolution = new Resolution.Explicit( bytes); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java index add33df02c30..3fff802b1257 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java @@ -6,16 +6,18 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor; +import static net.bytebuddy.matcher.ElementMatchers.named; + import javax.persistence.Id; -import net.bytebuddy.description.method.MethodList; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import net.bytebuddy.asm.AsmVisitorWrapper; -import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.field.FieldList; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -25,10 +27,9 @@ import net.bytebuddy.jar.asm.Type; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor; -import static net.bytebuddy.matcher.ElementMatchers.named; +import java.util.Objects; -class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { +final class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( FieldAccessEnhancer.class ); @@ -61,13 +62,13 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { return; } - FieldDescription field = findField( owner, name, desc ); + AnnotatedFieldDescription field = findField( owner, name, desc ); if ( ( enhancementContext.isEntityClass( field.getDeclaringType().asErasure() ) || enhancementContext.isCompositeClass( field.getDeclaringType().asErasure() ) ) && !field.getType().asErasure().equals( managedCtClass ) && enhancementContext.isPersistentField( field ) - && !EnhancerImpl.isAnnotationPresent( field, Id.class ) + && !field.hasAnnotation( Id.class ) && !field.getName().equals( "this$0" ) ) { log.debugf( @@ -108,12 +109,14 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { }; } - private FieldDescription findField(String owner, String name, String desc) { - TypePool.Resolution resolution = classPool.describe( owner.replace( '/', '.' ) ); + private AnnotatedFieldDescription findField(String owner, String name, String desc) { + //Classpool#describe does not accept '/' in the description name as it expects a class name + final String cleanedOwner = owner.replace( '/', '.' ); + final TypePool.Resolution resolution = classPool.describe( cleanedOwner ); if ( !resolution.isResolved() ) { final String msg = String.format( "Unable to perform extended enhancement - Unable to locate [%s]", - owner.replace( '/', '.' ) + cleanedOwner ); throw new EnhancementException( msg ); } @@ -122,10 +125,28 @@ private FieldDescription findField(String owner, String name, String desc) { final String msg = String.format( "Unable to perform extended enhancement - No unique field [%s] defined by [%s]", name, - owner.replace( '/', '.' ) + cleanedOwner ); throw new EnhancementException( msg ); } - return fields.getOnly(); + return new AnnotatedFieldDescription( enhancementContext, fields.getOnly() ); + } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || FieldAccessEnhancer.class != o.getClass() ) { + return false; + } + final FieldAccessEnhancer that = (FieldAccessEnhancer) o; + return Objects.equals( managedCtClass, that.managedCtClass ); } + + @Override + public int hashCode() { + return managedCtClass.hashCode(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java index a6729d425d68..56b12d8eac52 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java @@ -6,6 +6,9 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import java.util.Objects; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -24,19 +27,22 @@ abstract class FieldReaderAppender implements ByteCodeAppender { protected final TypeDescription managedCtClass; - protected final FieldDescription persistentField; + protected final AnnotatedFieldDescription persistentField; + + protected final FieldDescription.InDefinedShape persistentFieldAsDefined; - private FieldReaderAppender(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentField) { + private FieldReaderAppender(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; + this.persistentFieldAsDefined = persistentField.asDefined(); } - static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { - return new MethodDispatching( managedCtClass, persistentField.asDefined() ); + return new MethodDispatching( managedCtClass, persistentField ); } else { - return new FieldWriting( managedCtClass, persistentField.asDefined() ); + return new FieldWriting( managedCtClass, persistentField ); } } @@ -45,8 +51,8 @@ public Size apply( MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) { - TypeDescription dispatcherType = persistentField.getType().isPrimitive() - ? persistentField.getType().asErasure() + TypeDescription dispatcherType = persistentFieldAsDefined.getType().isPrimitive() + ? persistentFieldAsDefined.getType().asErasure() : TypeDescription.OBJECT; // if ( this.$$_hibernate_getInterceptor() != null ) methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); @@ -72,7 +78,7 @@ public Size apply( ); // .readXXX( self, fieldName, field ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitLdcInsn( persistentField.getName() ); + methodVisitor.visitLdcInsn( persistentFieldAsDefined.getName() ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); fieldRead( methodVisitor ); methodVisitor.visitMethodInsn( @@ -89,7 +95,7 @@ public Size apply( ); // field = (cast) XXX if ( !dispatcherType.isPrimitive() ) { - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentField.getType().asErasure().getInternalName() ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentFieldAsDefined.getType().asErasure().getInternalName() ); } fieldWrite( methodVisitor ); // end if @@ -100,8 +106,12 @@ public Size apply( // return field methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); fieldRead( methodVisitor ); - methodVisitor.visitInsn( Type.getType( persistentField.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.IRETURN ) ); - return new Size( 4 + persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() ); + if ( !persistentField.getType().isPrimitive() + && !persistentField.getType().asErasure().getInternalName().equals( persistentFieldAsDefined.getType().asErasure().getInternalName() ) ) { + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentField.getType().asErasure().getInternalName() ); + } + methodVisitor.visitInsn( Type.getType( persistentFieldAsDefined.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.IRETURN ) ); + return new Size( 4 + persistentFieldAsDefined.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } protected abstract void fieldRead(MethodVisitor methodVisitor); @@ -110,17 +120,17 @@ public Size apply( private static class FieldWriting extends FieldReaderAppender { - private FieldWriting(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) { - super( managedCtClass, fieldDescription ); + private FieldWriting(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { + super( managedCtClass, persistentField ); } @Override protected void fieldRead(MethodVisitor methodVisitor) { methodVisitor.visitFieldInsn( Opcodes.GETFIELD, - persistentField.getDeclaringType().asErasure().getInternalName(), - persistentField.getInternalName(), - persistentField.getDescriptor() + persistentFieldAsDefined.getDeclaringType().asErasure().getInternalName(), + persistentFieldAsDefined.getInternalName(), + persistentFieldAsDefined.getDescriptor() ); } @@ -128,17 +138,17 @@ protected void fieldRead(MethodVisitor methodVisitor) { protected void fieldWrite(MethodVisitor methodVisitor) { methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, - persistentField.getDeclaringType().asErasure().getInternalName(), - persistentField.getInternalName(), - persistentField.getDescriptor() + persistentFieldAsDefined.getDeclaringType().asErasure().getInternalName(), + persistentFieldAsDefined.getInternalName(), + persistentFieldAsDefined.getDescriptor() ); } } private static class MethodDispatching extends FieldReaderAppender { - private MethodDispatching(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) { - super( managedCtClass, fieldDescription ); + private MethodDispatching(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { + super( managedCtClass, persistentField ); } @Override @@ -146,8 +156,8 @@ protected void fieldRead(MethodVisitor methodVisitor) { methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, managedCtClass.getSuperClass().asErasure().getInternalName(), - EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(), - Type.getMethodDescriptor( Type.getType( persistentField.getType().asErasure().getDescriptor() ) ), + EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentFieldAsDefined.getName(), + Type.getMethodDescriptor( Type.getType( persistentFieldAsDefined.getType().asErasure().getDescriptor() ) ), false ); } @@ -157,10 +167,30 @@ protected void fieldWrite(MethodVisitor methodVisitor) { methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, managedCtClass.getSuperClass().asErasure().getInternalName(), - EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName(), - Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( persistentField.getType().asErasure().getDescriptor() ) ), + EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentFieldAsDefined.getName(), + Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( persistentFieldAsDefined.getType().asErasure().getDescriptor() ) ), false ); } } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + final FieldReaderAppender that = (FieldReaderAppender) o; + return Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( persistentField, that.persistentField ) && + Objects.equals( persistentFieldAsDefined, that.persistentFieldAsDefined ); + } + + @Override + public int hashCode() { + return Objects.hash( managedCtClass, persistentField, persistentFieldAsDefined ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java index 739581690321..d666ae43b10d 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java @@ -6,6 +6,7 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -24,14 +25,14 @@ abstract class FieldWriterAppender implements ByteCodeAppender { protected final TypeDescription managedCtClass; - protected final FieldDescription.InDefinedShape persistentField; + protected final FieldDescription.InDefinedShape persistentFieldAsDefined; - private FieldWriterAppender(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentField) { + private FieldWriterAppender(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined) { this.managedCtClass = managedCtClass; - this.persistentField = persistentField; + this.persistentFieldAsDefined = persistentFieldAsDefined; } - static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { return new MethodDispatching( managedCtClass, persistentField.asDefined() ); } @@ -45,8 +46,8 @@ public Size apply( MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) { - TypeDescription dispatcherType = persistentField.getType().isPrimitive() - ? persistentField.getType().asErasure() + TypeDescription dispatcherType = persistentFieldAsDefined.getType().isPrimitive() + ? persistentFieldAsDefined.getType().asErasure() : TypeDescription.OBJECT; // if ( this.$$_hibernate_getInterceptor() != null ) methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); @@ -72,7 +73,7 @@ public Size apply( ); // .writeXXX( self, fieldName, field, arg1 ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitLdcInsn( persistentField.getName() ); + methodVisitor.visitLdcInsn( persistentFieldAsDefined.getName() ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); fieldRead( methodVisitor ); methodVisitor.visitVarInsn( Type.getType( dispatcherType.getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 ); @@ -91,7 +92,7 @@ public Size apply( ); // arg1 = (cast) XXX if ( !dispatcherType.isPrimitive() ) { - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentField.getType().asErasure().getInternalName() ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentFieldAsDefined.getType().asErasure().getInternalName() ); } fieldWrite( methodVisitor ); // return @@ -106,12 +107,12 @@ public Size apply( // arg1 = (cast) XXX methodVisitor.visitVarInsn( Type.getType( dispatcherType.getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 ); if ( !dispatcherType.isPrimitive() ) { - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentField.getType().asErasure().getInternalName() ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentFieldAsDefined.getType().asErasure().getInternalName() ); } fieldWrite( methodVisitor ); // return methodVisitor.visitInsn( Opcodes.RETURN ); - return new Size( 4 + 2 * persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() ); + return new Size( 4 + 2 * persistentFieldAsDefined.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } protected abstract void fieldRead(MethodVisitor methodVisitor); @@ -120,17 +121,17 @@ public Size apply( private static class FieldWriting extends FieldWriterAppender { - private FieldWriting(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) { - super( managedCtClass, fieldDescription ); + private FieldWriting(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined) { + super( managedCtClass, persistentFieldAsDefined ); } @Override protected void fieldRead(MethodVisitor methodVisitor) { methodVisitor.visitFieldInsn( Opcodes.GETFIELD, - persistentField.getDeclaringType().asErasure().getInternalName(), - persistentField.getInternalName(), - persistentField.getDescriptor() + persistentFieldAsDefined.getDeclaringType().asErasure().getInternalName(), + persistentFieldAsDefined.getInternalName(), + persistentFieldAsDefined.getDescriptor() ); } @@ -138,17 +139,17 @@ protected void fieldRead(MethodVisitor methodVisitor) { protected void fieldWrite(MethodVisitor methodVisitor) { methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, - persistentField.getDeclaringType().asErasure().getInternalName(), - persistentField.getInternalName(), - persistentField.getDescriptor() + persistentFieldAsDefined.getDeclaringType().asErasure().getInternalName(), + persistentFieldAsDefined.getInternalName(), + persistentFieldAsDefined.getDescriptor() ); } } private static class MethodDispatching extends FieldWriterAppender { - private MethodDispatching(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) { - super( managedCtClass, fieldDescription ); + private MethodDispatching(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined) { + super( managedCtClass, persistentFieldAsDefined ); } @Override @@ -156,8 +157,8 @@ protected void fieldRead(MethodVisitor methodVisitor) { methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, managedCtClass.getSuperClass().asErasure().getInternalName(), - EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(), - Type.getMethodDescriptor( Type.getType( persistentField.getType().asErasure().getDescriptor() ) ), + EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentFieldAsDefined.getName(), + Type.getMethodDescriptor( Type.getType( persistentFieldAsDefined.getType().asErasure().getDescriptor() ) ), false ); } @@ -167,8 +168,8 @@ protected void fieldWrite(MethodVisitor methodVisitor) { methodVisitor.visitMethodInsn( Opcodes.INVOKESPECIAL, managedCtClass.getSuperClass().asErasure().getInternalName(), - EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName(), - Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( persistentField.getType().asErasure().getDescriptor() ) ), + EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentFieldAsDefined.getName(), + Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( persistentFieldAsDefined.getType().asErasure().getDescriptor() ) ), false ); } 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 d293d6f0f2cb..34e2bb30cf50 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 @@ -7,12 +7,14 @@ package org.hibernate.bytecode.enhance.internal.bytebuddy; import java.util.Collection; +import java.util.Objects; + import javax.persistence.Embedded; import javax.persistence.EmbeddedId; import javax.persistence.Id; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; -import org.hibernate.internal.util.compare.EqualsHelper; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.asm.Advice; @@ -27,7 +29,7 @@ import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; -class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender { +final class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender { private final Implementation delegate; @@ -44,25 +46,26 @@ private InlineDirtyCheckingHandler(Implementation delegate, TypeDescription mana static Implementation wrap( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, - FieldDescription persistentField, + AnnotatedFieldDescription persistentField, Implementation implementation) { if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { if ( enhancementContext.isCompositeClass( managedCtClass ) ) { implementation = Advice.to( CodeTemplates.CompositeDirtyCheckingHandler.class ).wrap( implementation ); } - else if ( !EnhancerImpl.isAnnotationPresent( persistentField, Id.class ) - && !EnhancerImpl.isAnnotationPresent( persistentField, EmbeddedId.class ) + else if ( !persistentField.hasAnnotation( Id.class ) + && !persistentField.hasAnnotation( EmbeddedId.class ) && !( persistentField.getType().asErasure().isAssignableTo( Collection.class ) && enhancementContext.isMappedCollection( persistentField ) ) ) { - implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass, persistentField.asDefined() ); + implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass, + persistentField.asDefined() ); } if ( enhancementContext.isCompositeClass( persistentField.getType().asErasure() ) - && EnhancerImpl.isAnnotationPresent( persistentField, Embedded.class ) ) { + && persistentField.hasAnnotation( Embedded.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.FieldName.class, persistentField.getName() ) .to( CodeTemplates.CompositeFieldDirtyCheckingHandler.class ) .wrap( implementation ); @@ -125,8 +128,8 @@ else if ( persistentField.getType().represents( double.class ) ) { else { methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, - Type.getInternalName( EqualsHelper.class ), - "areEqual", + Type.getInternalName( Objects.class ), + "deepEquals", Type.getMethodDescriptor( Type.getType( boolean.class ), Type.getType( Object.class ), Type.getType( Object.class ) ), false ); @@ -151,4 +154,23 @@ else if ( persistentField.getType().represents( double.class ) ) { } return new Size( 1 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || InlineDirtyCheckingHandler.class != o.getClass() ) { + return false; + } + final InlineDirtyCheckingHandler that = (InlineDirtyCheckingHandler) o; + return Objects.equals( delegate, that.delegate ) && + Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( persistentField, that.persistentField ); + } + + @Override + public int hashCode() { + return Objects.hash( delegate, managedCtClass, persistentField ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 69f5da9caac4..3b9eb4114b02 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -6,21 +6,24 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.not; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; + import javax.persistence.Embedded; -import net.bytebuddy.description.field.FieldList; -import net.bytebuddy.description.method.MethodList; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import net.bytebuddy.ClassFileVersion; import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.AsmVisitorWrapper; import net.bytebuddy.description.field.FieldDescription; @@ -36,28 +39,28 @@ import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; +import net.bytebuddy.matcher.ElementMatcher.Junction; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.not; - -class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { +final class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributeTransformer.class ); + private static final Junction NOT_HIBERNATE_GENERATED = not( nameStartsWith( "$$_hibernate_" ) ); + private final TypeDescription managedCtClass; private final ByteBuddyEnhancementContext enhancementContext; private final TypePool classPool; - private final FieldDescription[] enhancedFields; + private final AnnotatedFieldDescription[] enhancedFields; private PersistentAttributeTransformer( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool, - FieldDescription[] enhancedFields) { + AnnotatedFieldDescription[] enhancedFields) { this.managedCtClass = managedCtClass; this.enhancementContext = enhancementContext; this.classPool = classPool; @@ -68,14 +71,15 @@ public static PersistentAttributeTransformer collectPersistentFields( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool) { - List persistentFieldList = new ArrayList(); + List persistentFieldList = new ArrayList<>(); for ( FieldDescription ctField : managedCtClass.getDeclaredFields() ) { // skip static fields and skip fields added by enhancement and outer reference in inner classes if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - if ( !ctField.isStatic() && enhancementContext.isPersistentField( ctField ) ) { - persistentFieldList.add( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { + persistentFieldList.add( annotatedField ); } } // HHH-10646 Add fields inherited from @MappedSuperclass @@ -84,12 +88,12 @@ public static PersistentAttributeTransformer collectPersistentFields( persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass, enhancementContext ) ); } - FieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new FieldDescription[0] ) ); + AnnotatedFieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new AnnotatedFieldDescription[0] ) ); log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) ); return new PersistentAttributeTransformer( managedCtClass, enhancementContext, classPool, orderedFields ); } - private static Collection collectInheritPersistentFields( + private static Collection collectInheritPersistentFields( TypeDefinition managedCtClass, ByteBuddyEnhancementContext enhancementContext) { if ( managedCtClass == null || managedCtClass.represents( Object.class ) ) { @@ -97,18 +101,24 @@ private static Collection collectInheritPersistentFields( } TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass(); - if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { + if ( enhancementContext.isEntityClass( managedCtSuperclass.asErasure() ) ) { + return Collections.emptyList(); + } + else if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritPersistentFields( managedCtSuperclass, enhancementContext ); } + log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass ); - List persistentFieldList = new ArrayList(); + + List persistentFieldList = new ArrayList<>(); for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) { if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - if ( !ctField.isStatic() && enhancementContext.isPersistentField( ctField ) ) { - persistentFieldList.add( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { + persistentFieldList.add( annotatedField ); } } persistentFieldList.addAll( collectInheritPersistentFields( managedCtSuperclass, enhancementContext ) ); @@ -155,7 +165,7 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { } private boolean isEnhanced(String owner, String name, String desc) { - for ( FieldDescription enhancedField : enhancedFields ) { + for ( AnnotatedFieldDescription enhancedField : enhancedFields ) { if ( enhancedField.getName().equals( name ) && enhancedField.getDescriptor().equals( desc ) && enhancedField.getDeclaringType().asErasure().getInternalName().equals( owner ) ) { @@ -168,8 +178,8 @@ private boolean isEnhanced(String owner, String name, String desc) { DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) { boolean compositeOwner = false; - builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), this ) ); - for ( FieldDescription enhancedField : enhancedFields ) { + builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, this ) ); + for ( AnnotatedFieldDescription enhancedField : enhancedFields ) { builder = builder .defineMethod( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + enhancedField.getName(), @@ -193,7 +203,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) if ( !compositeOwner && !accessor - && EnhancerImpl.isAnnotationPresent( enhancedField, Embedded.class ) + && enhancedField.hasAnnotation( Embedded.class ) && enhancementContext.isCompositeClass( enhancedField.getType().asErasure() ) && enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { compositeOwner = true; @@ -217,7 +227,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) return builder; } - private Implementation fieldReader(FieldDescription enhancedField) { + private Implementation fieldReader(AnnotatedFieldDescription enhancedField) { if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) { if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) { return FieldAccessor.ofField( enhancedField.getName() ).in( enhancedField.getDeclaringType().asErasure() ); @@ -231,7 +241,7 @@ private Implementation fieldReader(FieldDescription enhancedField) { } } - private Implementation fieldWriter(FieldDescription enhancedField) { + private Implementation fieldWriter(AnnotatedFieldDescription enhancedField) { Implementation implementation; if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) { if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) { @@ -250,16 +260,16 @@ private Implementation fieldWriter(FieldDescription enhancedField) { DynamicType.Builder applyExtended(DynamicType.Builder builder) { AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper enhancer = new FieldAccessEnhancer( managedCtClass, enhancementContext, classPool ); - return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), enhancer ) ); + return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, enhancer ) ); } private static class FieldMethodReader implements ByteCodeAppender { private final TypeDescription managedCtClass; - private final FieldDescription persistentField; + private final AnnotatedFieldDescription persistentField; - private FieldMethodReader(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldMethodReader(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; } @@ -287,9 +297,9 @@ private static class FieldMethodWriter implements ByteCodeAppender { private final TypeDescription managedCtClass; - private final FieldDescription persistentField; + private final AnnotatedFieldDescription persistentField; - private FieldMethodWriter(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldMethodWriter(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; } @@ -313,4 +323,21 @@ public Size apply( return new Size( 1 + persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || PersistentAttributeTransformer.class != o.getClass() ) { + return false; + } + final PersistentAttributeTransformer that = (PersistentAttributeTransformer) o; + return Objects.equals( managedCtClass, that.managedCtClass ); + } + + @Override + public int hashCode() { + return managedCtClass.hashCode(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedFieldDescription.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedFieldDescription.java deleted file mode 100644 index c95915017d31..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedFieldDescription.java +++ /dev/null @@ -1,27 +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.bytecode.enhance.internal.bytebuddy; - -import java.lang.annotation.Annotation; - -import org.hibernate.bytecode.enhance.spi.UnloadedField; - -import net.bytebuddy.description.field.FieldDescription; - -class UnloadedFieldDescription implements UnloadedField { - - final FieldDescription fieldDescription; - - UnloadedFieldDescription(FieldDescription fieldDescription) { - this.fieldDescription = fieldDescription; - } - - @Override - public boolean hasAnnotation(Class annotationType) { - return fieldDescription.getDeclaredAnnotations().isAnnotationPresent( annotationType ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java index ef462326912d..e2ca1bde11e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java @@ -9,7 +9,6 @@ import java.lang.annotation.Annotation; import org.hibernate.bytecode.enhance.spi.UnloadedClass; -import org.hibernate.bytecode.enhance.spi.UnloadedField; import net.bytebuddy.description.type.TypeDescription; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java index 6193b68567b1..4c4d7462bc7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java @@ -8,6 +8,8 @@ import java.util.Collection; import java.util.Locale; +import java.util.Objects; + import javax.persistence.EmbeddedId; import javax.persistence.Id; @@ -15,14 +17,12 @@ import javassist.CtField; import javassist.NotFoundException; -import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; -import org.hibernate.internal.util.compare.EqualsHelper; /** * utility class to generate interceptor methods * @see org.hibernate.engine.spi.PersistentAttributeInterceptor - * + * * @author Luis Barreiro */ public abstract class AttributeTypeDescriptor { @@ -66,8 +66,8 @@ public String buildInLineDirtyCheckingBodyFragment(JavassistEnhancementContext c } builder.append( String.format( - " if ( !%s.areEqual( %s, $1 ) )", - EqualsHelper.class.getName(), + " if ( !%s.deepEquals( %s, $1 ) )", + Objects.class.getName(), readFragment ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/CompositeEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/CompositeEnhancer.java index f9dd94e0dc7e..c88e0745ccc3 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/CompositeEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/CompositeEnhancer.java @@ -10,7 +10,6 @@ import javassist.CtClass; import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; -import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; @@ -71,7 +70,7 @@ private void createCompositeTrackerMethod(CtClass managedCtClass) { EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME ); } catch (CannotCompileException cce) { - cce.printStackTrace(); + throw new RuntimeException( "createCompositeTrackerMethod failed", cce ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/EntityEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/EntityEnhancer.java index f5f468e60364..f55ac6d3a86e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/EntityEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/EntityEnhancer.java @@ -17,14 +17,13 @@ import javassist.CtClass; import javassist.CtField; import javassist.Modifier; - import javassist.NotFoundException; import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker; +import org.hibernate.bytecode.enhance.internal.tracker.NoopCollectionTracker; import org.hibernate.bytecode.enhance.internal.tracker.SimpleCollectionTracker; import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker; import org.hibernate.bytecode.enhance.spi.CollectionTracker; -import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; @@ -120,16 +119,84 @@ private void addInLineDirtyHandling(CtClass managedCtClass) { loadCtClassFromClass( DirtyTracker.class ), EnhancerConstants.TRACKER_FIELD_NAME ); - FieldWriter.addField( - managedCtClass, - loadCtClassFromClass( CollectionTracker.class ), - EnhancerConstants.TRACKER_COLLECTION_NAME - ); - createDirtyTrackerMethods( managedCtClass ); + if ( collectCollectionFields( managedCtClass ).isEmpty() ) { + createDirtyTrackerMethodsWithoutCollections( managedCtClass ); + } + else { + FieldWriter.addField( + managedCtClass, + loadCtClassFromClass( CollectionTracker.class ), + EnhancerConstants.TRACKER_COLLECTION_NAME + ); + createDirtyTrackerMethodsWithCollections( managedCtClass ); + } } - private void createDirtyTrackerMethods(CtClass managedCtClass) { + private void createDirtyTrackerMethodsWithoutCollections(CtClass managedCtClass) { + try { + MethodWriter.write( + managedCtClass, + "public void %1$s(String name) {%n" + + " if (%2$s == null) { %2$s = new %3$s(); }%n" + + " %2$s.add(name);%n" + + "}", + EnhancerConstants.TRACKER_CHANGER_NAME, + EnhancerConstants.TRACKER_FIELD_NAME, + DIRTY_TRACKER_IMPL + ); + + MethodWriter.write( + managedCtClass, + "public String[] %1$s() {%n" + + " return (%2$s == null) ? new String[0] : %2$s.get();%n" + + "}", + EnhancerConstants.TRACKER_GET_NAME, + EnhancerConstants.TRACKER_FIELD_NAME + ); + + MethodWriter.write( + managedCtClass, + "public boolean %1$s() {%n" + + " return (%2$s != null && !%2$s.isEmpty());%n" + + "}", + EnhancerConstants.TRACKER_HAS_CHANGED_NAME, + EnhancerConstants.TRACKER_FIELD_NAME + ); + + MethodWriter.write( + managedCtClass, + "public void %1$s() {%n" + + " if (%2$s != null) { %2$s.clear(); }%n" + + "}", + EnhancerConstants.TRACKER_CLEAR_NAME, + EnhancerConstants.TRACKER_FIELD_NAME + ); + + MethodWriter.write( + managedCtClass, + "public void %1$s(boolean f) {%n" + + " if (%2$s == null) %2$s = new %3$s();%n %2$s.suspend(f);%n" + + "}", + EnhancerConstants.TRACKER_SUSPEND_NAME, + EnhancerConstants.TRACKER_FIELD_NAME , + DIRTY_TRACKER_IMPL + ); + + MethodWriter.write( + managedCtClass, + "public %s %s() { return %s.INSTANCE; }", + CollectionTracker.class.getName(), + EnhancerConstants.TRACKER_COLLECTION_GET_NAME, + NoopCollectionTracker.class.getName() + ); + } + catch (CannotCompileException cce) { + throw new RuntimeException( "createDirtyTrackerMethodsWithoutCollections failed", cce ); + } + } + + private void createDirtyTrackerMethodsWithCollections(CtClass managedCtClass) { try { MethodWriter.write( managedCtClass, @@ -204,7 +271,7 @@ private void createDirtyTrackerMethods(CtClass managedCtClass) { ); } catch (CannotCompileException cce) { - cce.printStackTrace(); + throw new RuntimeException( "createDirtyTrackerMethodsWithCollections failed", cce ); } } @@ -216,7 +283,7 @@ private List collectCollectionFields(CtClass managedCtClass) { if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { continue; } - if ( enhancementContext.isPersistentField( ctField ) ) { + if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) { if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) || PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) { collectionList.add( ctField ); @@ -246,10 +313,12 @@ private Collection collectInheritCollectionFields(CtClass managedCtClas List collectionList = new ArrayList(); for ( CtField ctField : managedCtSuperclass.getDeclaredFields() ) { - if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) { - if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) || - PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) { - collectionList.add( ctField ); + if ( !Modifier.isStatic( ctField.getModifiers() ) ) { + if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) { + if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) || + PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) { + collectionList.add( ctField ); + } } } } @@ -275,24 +344,22 @@ private void createCollectionDirtyCheckMethod(CtClass managedCtClass) { ); for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { - if ( !enhancementContext.isMappedCollection( ctField ) ) { - body.append( - String.format( - " // collection field [%1$s]%n" + - " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n" + - " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n%n", - ctField.getName(), - EnhancerConstants.TRACKER_COLLECTION_NAME - ) - ); - } + body.append( + String.format( + " // collection field [%1$s]%n" + + " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n" + + " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME + ) + ); } body.append( " return false;%n}" ); MethodWriter.write( managedCtClass, body.toString() ); } catch (CannotCompileException cce) { - cce.printStackTrace(); + throw new RuntimeException( "createCollectionDirtyCheckMethod failed", cce ); } } @@ -311,24 +378,22 @@ private void createCollectionDirtyCheckGetFieldsMethod(CtClass managedCtClass) { ); for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { - if ( !enhancementContext.isMappedCollection( ctField ) ) { - body.append( - String.format( - " // Collection field [%1$s]%n" + - " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n" + - " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n%n", - ctField.getName(), - EnhancerConstants.TRACKER_COLLECTION_NAME - ) - ); - } + body.append( + String.format( + " // Collection field [%1$s]%n" + + " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n" + + " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME + ) + ); } body.append( "}" ); MethodWriter.write( managedCtClass, body.toString() ); } catch (CannotCompileException cce) { - cce.printStackTrace(); + throw new RuntimeException( "createCollectionDirtyCheckGetFieldsMethod failed", cce ); } } @@ -359,26 +424,24 @@ private void createClearDirtyCollectionMethod(CtClass managedCtClass) throws Can } for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { - if ( !enhancementContext.isMappedCollection( ctField ) ) { - body.append( - String.format( - " // collection field [%1$s]%n" + - " if (lazyInterceptor == null || lazyInterceptor.isAttributeLoaded(\"%1$s\")) {%n" + - " if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" + - " else { %2$s.add(\"%1$s\", %1$s.size()); }%n" + - " }%n%n", - ctField.getName(), - EnhancerConstants.TRACKER_COLLECTION_NAME - ) - ); - } + body.append( + String.format( + " // collection field [%1$s]%n" + + " if (lazyInterceptor == null || lazyInterceptor.isAttributeLoaded(\"%1$s\")) {%n" + + " if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" + + " else { %2$s.add(\"%1$s\", %1$s.size()); }%n" + + " }%n%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME + ) + ); } body.append( "}" ); MethodWriter.write( managedCtClass, body.toString() ); } catch (CannotCompileException cce) { - cce.printStackTrace(); + throw cce; } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java index 9bba897b917c..e33d8a47f25b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java @@ -105,7 +105,10 @@ private Collection collectInheritPersistentFields(CtClass managedCtClas try { CtClass managedCtSuperclass = managedCtClass.getSuperclass(); - if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) { + if ( enhancementContext.isEntityClass( managedCtSuperclass ) ) { + return Collections.emptyList(); + } + else if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) { return collectInheritPersistentFields( managedCtSuperclass ); } log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass.getName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java index 3b61833a4051..1c04d176de4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java @@ -24,4 +24,9 @@ public UnloadedCtField(CtField ctField) { public boolean hasAnnotation(Class annotationType) { return ctField.hasAnnotation( annotationType ); } + + @Override + public String toString() { + return this.ctField.toString(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/NoopCollectionTracker.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/NoopCollectionTracker.java new file mode 100644 index 000000000000..2d7394e49827 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/NoopCollectionTracker.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 . + */ +package org.hibernate.bytecode.enhance.internal.tracker; + +import org.hibernate.bytecode.enhance.spi.CollectionTracker; + +import java.util.Arrays; + +/** + * small low memory class to keep track of the number of elements in a collection + * + * @author Ståle W. Pedersen + */ +public final class NoopCollectionTracker implements CollectionTracker { + + public static final CollectionTracker INSTANCE = new NoopCollectionTracker(); + + @Override + public void add(String name, int size) { + } + + @Override + public int getSize(String name) { + return -1; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SortedFieldTracker.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SortedFieldTracker.java index 1025aaf61d75..e711c9e481c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SortedFieldTracker.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SortedFieldTracker.java @@ -38,7 +38,7 @@ public void add(String name) { insert = middle; } else if( compare < 0 ) { - // top half: lower bound in (middle + 1) and insert position afterQuery middle + // top half: lower bound in (middle + 1) and insert position after middle insert = low = middle + 1; } else { @@ -62,7 +62,7 @@ public boolean contains(String name) { high = middle - 1; } else if( compare < 0 ) { - // top half: lower bound in (middle + 1) and insert position afterQuery middle + // top half: lower bound in (middle + 1) and insert position after middle low = middle + 1; } else { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java index d90b92a9f336..1dc3bb46c389 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java @@ -49,11 +49,11 @@ public interface EnhancementContext { public boolean isCompositeClass(UnloadedClass classDescriptor); /** - * Does the given class name represent an MappedSuperclass class? + * Does the given class name represent a MappedSuperclass class? * * @param classDescriptor The descriptor of the class to check. * - * @return {@code true} if the class is an mapped super class; {@code false} otherwise. + * @return {@code true} if the class is a mapped super class; {@code false} otherwise. */ public boolean isMappedSuperclassClass(UnloadedClass classDescriptor); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java index 2f8ddce0b879..4e5da5c87d05 100755 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java @@ -7,8 +7,10 @@ package org.hibernate.bytecode.enhance.spi; import java.io.Serializable; +import java.util.Collections; import java.util.Set; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -32,9 +34,18 @@ public Object readResolve() { } }; + /** + * @deprecated Prefer the form of these methods defined on + * {@link BytecodeLazyAttributeInterceptor} instead + */ + @Deprecated interface InterceptorImplementor { - Set getInitializedLazyAttributeNames(); - void attributeInitialized(String name); + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + default void attributeInitialized(String name) { + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java new file mode 100644 index 000000000000..a201fdc31682 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java @@ -0,0 +1,160 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractInterceptor implements SessionAssociableInterceptor { + private final String entityName; + + private transient SharedSessionContractImplementor session; + private boolean allowLoadOutsideTransaction; + private String sessionFactoryUuid; + + @SuppressWarnings("WeakerAccess") + public AbstractInterceptor(String entityName) { + this.entityName = entityName; + } + + public String getEntityName() { + return entityName; + } + + @Override + public SharedSessionContractImplementor getLinkedSession() { + return session; + } + + @Override + public void setSession(SharedSessionContractImplementor session) { + this.session = session; + if ( session != null && !allowLoadOutsideTransaction ) { + this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); + if ( this.allowLoadOutsideTransaction ) { + this.sessionFactoryUuid = session.getFactory().getUuid(); + } + } + } + + @Override + public void unsetSession() { + this.session = null; + } + + @Override + public boolean allowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + @Override + public String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Handle the case of reading an attribute. The result is what is returned to the caller + */ + protected abstract Object handleRead(Object target, String attributeName, Object value); + + /** + * Handle the case of writing an attribute. The result is what is set as the entity state + */ + protected abstract Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue); + + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { + return (Boolean) handleRead( obj, name, oldValue ); + } + + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + return (Boolean) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public byte readByte(Object obj, String name, byte oldValue) { + return (Byte) handleRead( obj, name, oldValue ); + } + + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + return (Byte) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public char readChar(Object obj, String name, char oldValue) { + return (Character) handleRead( obj, name, oldValue ); + } + + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { + return (char) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public short readShort(Object obj, String name, short oldValue) { + return (Short) handleRead( obj, name, oldValue ); + } + + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { + return (Short) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public int readInt(Object obj, String name, int oldValue) { + return (Integer) handleRead( obj, name, oldValue ); + } + + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { + return (Integer) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public float readFloat(Object obj, String name, float oldValue) { + return (Float) handleRead( obj, name, oldValue ); + } + + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { + return (Float) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public double readDouble(Object obj, String name, double oldValue) { + return (Double) handleRead( obj, name, oldValue ); + } + + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { + return (Double) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public long readLong(Object obj, String name, long oldValue) { + return (Long) handleRead( obj, name, oldValue ); + } + + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { + return (Long) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public Object readObject(Object obj, String name, Object oldValue) { + return handleRead( obj, name, oldValue ); + } + + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + return handleWrite( obj, name, oldValue, newValue ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java new file mode 100644 index 000000000000..3eb53f0261a4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractLazyLoadInterceptor extends AbstractInterceptor implements BytecodeLazyAttributeInterceptor { + + @SuppressWarnings("unused") + public AbstractLazyLoadInterceptor(String entityName) { + super( entityName ); + } + + @SuppressWarnings("WeakerAccess") + public AbstractLazyLoadInterceptor(String entityName, SharedSessionContractImplementor session) { + super( entityName ); + setSession( session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java new file mode 100644 index 000000000000..ae92635ea736 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import java.util.Set; + +import org.hibernate.Incubating; + +/** + * @author Steve Ebersole + */ +@Incubating +public interface BytecodeLazyAttributeInterceptor extends SessionAssociableInterceptor { + /** + * The name of the entity this interceptor is meant to intercept + */ + String getEntityName(); + + /** + * The id of the entity instance this interceptor is associated with + */ + Object getIdentifier(); + + /** + * The names of all lazy attributes which have been initialized + */ + @Override + Set getInitializedLazyAttributeNames(); + + /** + * Callback from the enhanced class that an attribute has been read or written + */ + void attributeInitialized(String name); + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java new file mode 100644 index 000000000000..697f79a1d467 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java @@ -0,0 +1,291 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.EntityMode; +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.bytecode.BytecodeLogger; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.tuple.entity.EntityTuplizer; +import org.hibernate.type.CompositeType; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInterceptor { + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; + + private final EntityKey entityKey; + + private final boolean inLineDirtyChecking; + private Set writtenFieldNames; + + private boolean initialized; + + private boolean initializeBeforeWrite; + + public EnhancementAsProxyLazinessInterceptor( + String entityName, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + EntityKey entityKey, + SharedSessionContractImplementor session) { + super( entityName, session ); + + this.identifierAttributeNames = identifierAttributeNames; + assert identifierAttributeNames != null; + + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert nonAggregatedCidMapper != null || identifierAttributeNames.size() == 1; + + this.entityKey = entityKey; + + final EntityPersister entityPersister = session.getFactory().getMetamodel().entityPersister( entityName ); + this.inLineDirtyChecking = entityPersister.getEntityMode() == EntityMode.POJO + && SelfDirtinessTracker.class.isAssignableFrom( entityPersister.getMappedClass() ); + // if self-dirty tracking is enabled but DynamicUpdate is not enabled then we need to initialise the entity + // because the pre-computed update statement contains even not dirty properties and so we need all the values + initializeBeforeWrite = !inLineDirtyChecking || !entityPersister.getEntityMetamodel().isDynamicUpdate(); + } + + public EntityKey getEntityKey() { + return entityKey; + } + + @Override + protected Object handleRead(Object target, String attributeName, Object value) { + // it is illegal for this interceptor to still be attached to the entity after initialization + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + // the attribute being read is an entity-id attribute + // - we already know the id, return that + if ( identifierAttributeNames.contains( attributeName ) ) { + return extractIdValue( target, attributeName ); + } + + // Use `performWork` to group together multiple Session accesses + return EnhancementHelper.performWork( + this, + (session, isTempSession) -> { + final Object[] writtenValues; + + final EntityPersister entityPersister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + final EntityTuplizer entityTuplizer = entityPersister.getEntityTuplizer(); + + if ( writtenFieldNames != null && !writtenFieldNames.isEmpty() ) { + + // enhancement has dirty-tracking available and at least one attribute was explicitly set + + if ( writtenFieldNames.contains( attributeName ) ) { + // the requested attribute was one of the attributes explicitly set, we can just return the explicitly set value + return entityTuplizer.getPropertyValue( target, attributeName ); + } + + // otherwise we want to save all of the explicitly set values in anticipation of + // the force initialization below so that we can "replay" them after the + // initialization + + writtenValues = new Object[writtenFieldNames.size()]; + + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + writtenValues[index] = entityTuplizer.getPropertyValue( target, writtenFieldName ); + index++; + } + } + else { + writtenValues = null; + } + + final Object initializedValue = forceInitialize( + target, + attributeName, + session, + isTempSession + ); + + initialized = true; + + if ( writtenValues != null ) { + // here is the replaying of the explicitly set values we prepared above + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + entityTuplizer.setPropertyValue( target, writtenFieldName, writtenValues[index++] ); + } + writtenFieldNames.clear(); + } + + return initializedValue; + }, + getEntityName(), + attributeName + ); + } + + private Object extractIdValue(Object target, String attributeName) { + // access to the id or part of it for non-aggregated cid + if ( nonAggregatedCidMapper == null ) { + return getIdentifier(); + } + else { + return nonAggregatedCidMapper.getPropertyValue( + target, + nonAggregatedCidMapper.getPropertyIndex( attributeName ), + getLinkedSession() + ); + } + } + + public Object forceInitialize(Object target, String attributeName) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> forceInitialize( target, attributeName, session, isTemporarySession ), + getEntityName(), + attributeName + ); + } + + public Object forceInitialize(Object target, String attributeName, SharedSessionContractImplementor session, boolean isTemporarySession) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + final EntityPersister persister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + + if ( isTemporarySession ) { + // Add an entry for this entity in the PC of the temp Session + session.getPersistenceContext().addEntity( + target, + Status.READ_ONLY, + // loaded state + ArrayHelper.filledArray( + LazyPropertyInitializer.UNFETCHED_PROPERTY, + Object.class, + persister.getPropertyTypes().length + ), + entityKey, + persister.getVersion( target ), + LockMode.NONE, + // we assume an entry exists in the db + true, + persister, + true + ); + } + + return persister.initializeEnhancedEntityUsedAsProxy( + target, + attributeName, + session + ); + } + + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + if ( identifierAttributeNames.contains( attributeName ) ) { + // it is illegal for the identifier value to be changed. Normally Hibernate + // validates this during flush. However, here it is dangerous to just allow the + // new value to be set and continue on waiting for the flush for validation + // because this interceptor manages the entity's entry in the PC itself. So + // just do the check here up-front + final boolean changed; + if ( nonAggregatedCidMapper == null ) { + changed = ! entityKey.getPersister().getIdentifierType().isEqual( oldValue, newValue ); + } + else { + final int subAttrIndex = nonAggregatedCidMapper.getPropertyIndex( attributeName ); + final Type subAttrType = nonAggregatedCidMapper.getSubtypes()[subAttrIndex]; + changed = ! subAttrType.isEqual( oldValue, newValue ); + } + + if ( changed ) { + throw new HibernateException( + "identifier of an instance of " + entityKey.getEntityName() + " was altered from " + oldValue + " to " + newValue + ); + } + + // otherwise, setId has been called but passing in the same value - just pass it through + return newValue; + } + + if ( initializeBeforeWrite ) { + // we need to force-initialize the proxy - the fetch group to which the `attributeName` belongs + try { + forceInitialize( target, attributeName ); + } + finally { + initialized = true; + } + + if ( inLineDirtyChecking ) { + ( (SelfDirtinessTracker) target ).$$_hibernate_trackChange( attributeName ); + } + } + else { + // because of the entity being enhanced with `org.hibernate.engine.spi.SelfDirtinessTracker` + // we can skip forcing the initialization. However, in the case of a subsequent read we + // need to know which attributes had been explicitly set so that we can re-play the setters + // after the force-initialization there + if ( writtenFieldNames == null ) { + writtenFieldNames = new HashSet<>(); + } + writtenFieldNames.add( attributeName ); + } + + return newValue; + } + + @Override + public Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + @Override + public void attributeInitialized(String name) { + if ( initialized ) { + throw new UnsupportedOperationException( "Expected call to EnhancementAsProxyLazinessInterceptor#attributeInitialized" ); + } + } + + @Override + public Object getIdentifier() { + return entityKey.getIdentifier(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java new file mode 100644 index 000000000000..396321221102 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java @@ -0,0 +1,214 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.spi.interceptor; + +import java.util.Locale; +import java.util.function.BiFunction; + +import org.hibernate.FlushMode; +import org.hibernate.LazyInitializationException; +import org.hibernate.bytecode.BytecodeLogger; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.SessionFactoryRegistry; +import org.hibernate.mapping.OneToOne; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; + +/** + * @author Steve Ebersole + */ +public class EnhancementHelper { + /** + * Should the given property be included in the owner's base fetch group? + */ + public static boolean includeInBaseFetchGroup( + Property bootMapping, + boolean isEnhanced, + boolean allowEnhancementAsProxy) { + final Value value = bootMapping.getValue(); + + if ( ! isEnhanced ) { + if ( value instanceof ToOne ) { + if ( ( (ToOne) value ).isUnwrapProxy() ) { + BytecodeLogger.LOGGER.debugf( + "To-one property `%s#%s` was mapped with LAZY + NO_PROXY but the class was not enhanced", + bootMapping.getPersistentClass().getEntityName(), + bootMapping.getName() + ); + } + } + return true; + } + + if ( value instanceof ToOne ) { + final ToOne toOne = (ToOne) value; + if ( toOne.isLazy() ) { + if ( toOne.isUnwrapProxy() ) { + if ( toOne instanceof OneToOne ) { + return false; + } + // include it in the base fetch group so long as the config allows + // using the FK to create an "enhancement proxy" + return allowEnhancementAsProxy; + } + + } + + return true; + } + + return ! bootMapping.isLazy(); + } + + public static T performWork( + BytecodeLazyAttributeInterceptor interceptor, + BiFunction work, + String entityName, + String attributeName) { + SharedSessionContractImplementor session = interceptor.getLinkedSession(); + + boolean isTempSession = false; + boolean isJta = false; + + // first figure out which Session to use + if ( session == null ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.NO_SESSION, entityName, attributeName ); + } + } + else if ( !session.isOpen() ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.CLOSED_SESSION, entityName, attributeName ); + } + } + else if ( !session.isConnected() ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.DISCONNECTED_SESSION, entityName, attributeName); + } + } + + // If we are using a temporary Session, begin a transaction if necessary + if ( isTempSession ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork started temporary Session" ); + + isJta = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); + + if ( !isJta ) { + // Explicitly handle the transactions only if we're not in + // a JTA environment. A lazy loading temporary session can + // be created even if a current session and transaction are + // open (ex: session.clear() was used). We must prevent + // multiple transactions. + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork starting transaction on temporary Session" ); + session.beginTransaction(); + } + } + + try { + // do the actual work + return work.apply( session, isTempSession ); + } + finally { + if ( isTempSession ) { + try { + // Commit the JDBC transaction is we started one. + if ( !isJta ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork committing transaction on temporary Session" ); + session.getTransaction().commit(); + } + } + catch (Exception e) { + BytecodeLogger.LOGGER.warn( + "Unable to commit JDBC transaction on temporary session used to load lazy " + + "collection associated to no session" + ); + } + + // Close the just opened temp Session + try { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork closing temporary Session" ); + session.close(); + } + catch (Exception e) { + BytecodeLogger.LOGGER.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); + } + } + } + } + + enum Cause { + NO_SESSION, + CLOSED_SESSION, + DISCONNECTED_SESSION, + NO_SF_UUID + } + + private static void throwLazyInitializationException(Cause cause, String entityName, String attributeName) { + final String reason; + switch ( cause ) { + case NO_SESSION: { + reason = "no session and settings disallow loading outside the Session"; + break; + } + case CLOSED_SESSION: { + reason = "session is closed and settings disallow loading outside the Session"; + break; + } + case DISCONNECTED_SESSION: { + reason = "session is disconnected and settings disallow loading outside the Session"; + break; + } + case NO_SF_UUID: { + reason = "could not determine SessionFactory UUId to create temporary Session for loading"; + break; + } + default: { + reason = ""; + } + } + + final String message = String.format( + Locale.ROOT, + "Unable to perform requested lazy initialization [%s.%s] - %s", + entityName, + attributeName, + reason + ); + + throw new LazyInitializationException( message ); + } + + private static SharedSessionContractImplementor openTemporarySessionForLoading( + BytecodeLazyAttributeInterceptor interceptor, + String entityName, + String attributeName) { + if ( interceptor.getSessionFactoryUuid() == null ) { + throwLazyInitializationException( Cause.NO_SF_UUID, entityName, attributeName ); + } + + final SessionFactoryImplementor sf = (SessionFactoryImplementor) + SessionFactoryRegistry.INSTANCE.getSessionFactory( interceptor.getSessionFactoryUuid() ); + final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); + session.getPersistenceContext().setDefaultReadOnly( true ); + session.setHibernateFlushMode( FlushMode.MANUAL ); + return session; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java deleted file mode 100644 index 9459288a1739..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java +++ /dev/null @@ -1,179 +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.bytecode.enhance.spi.interceptor; - -import java.util.Locale; - -import org.hibernate.FlushMode; -import org.hibernate.LazyInitializationException; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.SessionFactoryRegistry; - -import org.jboss.logging.Logger; - -/** - * @author Steve Ebersole - */ -public class Helper { - private static final Logger log = Logger.getLogger( Helper.class ); - - interface Consumer { - SharedSessionContractImplementor getLinkedSession(); - boolean allowLoadOutsideTransaction(); - String getSessionFactoryUuid(); - } - - interface LazyInitializationWork { - T doWork(SharedSessionContractImplementor session, boolean isTemporarySession); - - // informational details - String getEntityName(); - String getAttributeName(); - } - - - private final Consumer consumer; - - public Helper(Consumer consumer) { - this.consumer = consumer; - } - - public T performWork(LazyInitializationWork lazyInitializationWork) { - SharedSessionContractImplementor session = consumer.getLinkedSession(); - - boolean isTempSession = false; - boolean isJta = false; - - // first figure out which Session to use - if ( session == null ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.NO_SESSION, lazyInitializationWork ); - } - } - else if ( !session.isOpen() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.CLOSED_SESSION, lazyInitializationWork ); - } - } - else if ( !session.isConnected() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.DISCONNECTED_SESSION, lazyInitializationWork ); - } - } - - // If we are using a temporary Session, begin a transaction if necessary - if ( isTempSession ) { - isJta = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); - - if ( !isJta ) { - // Explicitly handle the transactions only if we're not in - // a JTA environment. A lazy loading temporary session can - // be created even if a current session and transaction are - // open (ex: session.clear() was used). We must prevent - // multiple transactions. - session.beginTransaction(); - } - } - - try { - // do the actual work - return lazyInitializationWork.doWork( session, isTempSession ); - } - finally { - if ( isTempSession ) { - try { - // Commit the JDBC transaction is we started one. - if ( !isJta ) { - session.getTransaction().commit(); - } - } - catch (Exception e) { - log.warn( - "Unable to commit JDBC transaction on temporary session used to load lazy " + - "collection associated to no session" - ); - } - - // Close the just opened temp Session - try { - session.close(); - } - catch (Exception e) { - log.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); - } - } - } - } - - enum Cause { - NO_SESSION, - CLOSED_SESSION, - DISCONNECTED_SESSION, - NO_SF_UUID - } - - private void throwLazyInitializationException(Cause cause, LazyInitializationWork work) { - final String reason; - switch ( cause ) { - case NO_SESSION: { - reason = "no session and settings disallow loading outside the Session"; - break; - } - case CLOSED_SESSION: { - reason = "session is closed and settings disallow loading outside the Session"; - break; - } - case DISCONNECTED_SESSION: { - reason = "session is disconnected and settings disallow loading outside the Session"; - break; - } - case NO_SF_UUID: { - reason = "could not determine SessionFactory UUId to create temporary Session for loading"; - break; - } - default: { - reason = ""; - } - } - - final String message = String.format( - Locale.ROOT, - "Unable to perform requested lazy initialization [%s.%s] - %s", - work.getEntityName(), - work.getAttributeName(), - reason - ); - - throw new LazyInitializationException( message ); - } - - private SharedSessionContractImplementor openTemporarySessionForLoading(LazyInitializationWork lazyInitializationWork) { - if ( consumer.getSessionFactoryUuid() == null ) { - throwLazyInitializationException( Cause.NO_SF_UUID, lazyInitializationWork ); - } - - final SessionFactoryImplementor sf = (SessionFactoryImplementor) - SessionFactoryRegistry.INSTANCE.getSessionFactory( consumer.getSessionFactoryUuid() ); - final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); - session.getPersistenceContext().setDefaultReadOnly( true ); - session.setHibernateFlushMode( FlushMode.MANUAL ); - return session; - } -} 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 58672cc9bf35..86f9b4b1f209 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -16,47 +16,39 @@ import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.CollectionTracker; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.Consumer; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.LazyInitializationWork; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.persister.entity.EntityPersister; -import org.jboss.logging.Logger; - /** * Interceptor that loads attributes lazily * * @author Luis Barreiro * @author Steve Ebersole */ -public class LazyAttributeLoadingInterceptor - implements PersistentAttributeInterceptor, Consumer, InterceptorImplementor { - private static final Logger log = Logger.getLogger( LazyAttributeLoadingInterceptor.class ); - - private final String entityName; +public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor { + private final Object identifier; private final Set lazyFields; - private Set initializedLazyFields; - private transient SharedSessionContractImplementor session; - private boolean allowLoadOutsideTransaction; - private String sessionFactoryUuid; - public LazyAttributeLoadingInterceptor( String entityName, + Object identifier, Set lazyFields, SharedSessionContractImplementor session) { - this.entityName = entityName; + super( entityName, session ); + this.identifier = identifier; this.lazyFields = lazyFields; + } - setSession( session ); + @Override + public Object getIdentifier() { + return identifier; } - protected final Object intercept(Object target, String attributeName, Object value) { + @Override + protected Object handleRead(Object target, String attributeName, Object value) { if ( !isAttributeLoaded( attributeName ) ) { Object loadedValue = fetchAttribute( target, attributeName ); attributeInitialized( attributeName ); @@ -65,6 +57,14 @@ protected final Object intercept(Object target, String attributeName, Object val return value; } + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( !isAttributeLoaded( attributeName ) ) { + attributeInitialized( attributeName ); + } + return newValue; + } + /** * Fetches the lazy attribute. The attribute does not get associated with the entity. (To be used by hibernate methods) */ @@ -73,72 +73,48 @@ public Object fetchAttribute(final Object target, final String attributeName) { } protected Object loadAttribute(final Object target, final String attributeName) { - return new Helper( this ).performWork( - new LazyInitializationWork() { - @Override - public Object doWork(SharedSessionContractImplementor session, boolean isTemporarySession) { - final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); - - if ( isTemporarySession ) { - final Serializable id = persister.getIdentifier( target, null ); - - // Add an entry for this entity in the PC of the temp Session - // NOTE : a few arguments that would be nice to pass along here... - // 1) loadedState if we know any - final Object[] loadedState = null; - // 2) does a row exist in the db for this entity? - final boolean existsInDb = true; - session.getPersistenceContext().addEntity( - target, - Status.READ_ONLY, - loadedState, - session.generateEntityKey( id, persister ), - persister.getVersion( target ), - LockMode.NONE, - existsInDb, - persister, - true - ); - } - - final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; - final Object loadedValue = initializer.initializeLazyProperty( - attributeName, + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> { + final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); + + if ( isTemporarySession ) { + final Serializable id = persister.getIdentifier( target, null ); + + // Add an entry for this entity in the PC of the temp Session + // NOTE : a few arguments that would be nice to pass along here... + // 1) loadedState if we know any + final Object[] loadedState = null; + // 2) does a row exist in the db for this entity? + final boolean existsInDb = true; + session.getPersistenceContext().addEntity( target, - session + Status.READ_ONLY, + loadedState, + session.generateEntityKey( id, persister ), + persister.getVersion( target ), + LockMode.NONE, + existsInDb, + persister, + true ); - - takeCollectionSizeSnapshot( target, attributeName, loadedValue ); - return loadedValue; } - @Override - public String getEntityName() { - return entityName; - } - - @Override - public String getAttributeName() { - return attributeName; - } - } + final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; + final Object loadedValue = initializer.initializeLazyProperty( + attributeName, + target, + session + ); + + takeCollectionSizeSnapshot( target, attributeName, loadedValue ); + return loadedValue; + }, + getEntityName(), + attributeName ); } - public final void setSession(SharedSessionContractImplementor session) { - this.session = session; - if ( session != null && !allowLoadOutsideTransaction ) { - this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); - if ( this.allowLoadOutsideTransaction ) { - this.sessionFactoryUuid = session.getFactory().getUuid(); - } - } - } - - public final void unsetSession() { - this.session = null; - } - public boolean isAttributeLoaded(String fieldName) { return !isLazyAttribute( fieldName ) || isInitializedLazyField( fieldName ); } @@ -171,13 +147,11 @@ public boolean hasAnyUninitializedAttributes() { @Override public String toString() { - return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')'; + return "LazyAttributeLoader(entityName=" + getEntityName() + " ,lazyFields=" + lazyFields + ')'; } - // - private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) { - if ( value != null && value instanceof Collection && target instanceof SelfDirtinessTracker ) { + if ( value instanceof Collection && target instanceof SelfDirtinessTracker ) { CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker(); if ( tracker == null ) { ( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes(); @@ -187,152 +161,20 @@ private void takeCollectionSizeSnapshot(Object target, String fieldName, Object } } - @Override - public boolean readBoolean(Object obj, String name, boolean oldValue) { - return (Boolean) intercept( obj, name, oldValue ); - } - - @Override - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public byte readByte(Object obj, String name, byte oldValue) { - return (Byte) intercept( obj, name, oldValue ); - } - - @Override - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public char readChar(Object obj, String name, char oldValue) { - return (Character) intercept( obj, name, oldValue ); - } - - @Override - public char writeChar(Object obj, String name, char oldValue, char newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public short readShort(Object obj, String name, short oldValue) { - return (Short) intercept( obj, name, oldValue ); - } - - @Override - public short writeShort(Object obj, String name, short oldValue, short newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public int readInt(Object obj, String name, int oldValue) { - return (Integer) intercept( obj, name, oldValue ); - } - - @Override - public int writeInt(Object obj, String name, int oldValue, int newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public float readFloat(Object obj, String name, float oldValue) { - return (Float) intercept( obj, name, oldValue ); - } - - @Override - public float writeFloat(Object obj, String name, float oldValue, float newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public double readDouble(Object obj, String name, double oldValue) { - return (Double) intercept( obj, name, oldValue ); - } - - @Override - public double writeDouble(Object obj, String name, double oldValue, double newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public long readLong(Object obj, String name, long oldValue) { - return (Long) intercept( obj, name, oldValue ); - } - - @Override - public long writeLong(Object obj, String name, long oldValue, long newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public Object readObject(Object obj, String name, Object oldValue) { - return intercept( obj, name, oldValue ); - } - - @Override - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public SharedSessionContractImplementor getLinkedSession() { - return session; - } - - @Override - public boolean allowLoadOutsideTransaction() { - return allowLoadOutsideTransaction; - } - - @Override - public String getSessionFactoryUuid() { - return sessionFactoryUuid; - } - @Override public void attributeInitialized(String name) { if ( !isLazyAttribute( name ) ) { return; } if ( initializedLazyFields == null ) { - initializedLazyFields = new HashSet(); + initializedLazyFields = new HashSet<>(); } initializedLazyFields.add( name ); } @Override public Set getInitializedLazyAttributeNames() { - return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; + return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java index 312bea07fdad..8192d3d13293 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java @@ -29,12 +29,11 @@ public class LazyAttributesMetadata implements Serializable { /** * Build a LazyFetchGroupMetadata based on the attributes defined for the * PersistentClass - * - * @param mappedEntity The entity definition - * - * @return The built LazyFetchGroupMetadata */ - public static LazyAttributesMetadata from(PersistentClass mappedEntity) { + public static LazyAttributesMetadata from( + PersistentClass mappedEntity, + boolean isEnhanced, + boolean allowEnhancementAsProxy) { final Map lazyAttributeDescriptorMap = new LinkedHashMap<>(); final Map> fetchGroupToAttributesMap = new HashMap<>(); @@ -44,7 +43,12 @@ public static LazyAttributesMetadata from(PersistentClass mappedEntity) { while ( itr.hasNext() ) { i++; final Property property = (Property) itr.next(); - if ( property.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + isEnhanced, + allowEnhancementAsProxy + ); + if ( lazy ) { final LazyAttributeDescriptor lazyAttributeDescriptor = LazyAttributeDescriptor.from( property, i, x++ ); lazyAttributeDescriptorMap.put( lazyAttributeDescriptor.getName(), lazyAttributeDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java new file mode 100644 index 000000000000..7f60ffddbc19 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java @@ -0,0 +1,25 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public interface SessionAssociableInterceptor extends PersistentAttributeInterceptor { + SharedSessionContractImplementor getLinkedSession(); + + void setSession(SharedSessionContractImplementor session); + + void unsetSession(); + + boolean allowLoadOutsideTransaction(); + + String getSessionFactoryUuid(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index be998634216c..eae143eb673a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -9,81 +9,73 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.bytecode.spi.BasicProxyFactory; +import org.hibernate.cfg.Environment; import org.hibernate.proxy.ProxyConfiguration; -import net.bytebuddy.ByteBuddy; +import net.bytebuddy.NamingStrategy; +import net.bytebuddy.TypeCache; import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.dynamic.scaffold.TypeValidation; -import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.MethodDelegation; -import net.bytebuddy.implementation.auxiliary.AuxiliaryType; -import net.bytebuddy.implementation.bytecode.assign.Assigner; -import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; public class BasicProxyFactoryImpl implements BasicProxyFactory { - private static final ConcurrentMap, Class> CACHE = new ConcurrentHashMap, Class>(); - private static final Class[] NO_INTERFACES = new Class[0]; + private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateBasicProxy$" : "HibernateBasicProxy"; private final Class proxyClass; + private final ProxyConfiguration.Interceptor interceptor; - public BasicProxyFactoryImpl(Class superClass, Class[] interfaces) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState byteBuddyState) { if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) { throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" ); } - Set key = new HashSet(); - if ( superClass != null ) { - key.add( superClass ); - } - if ( interfaces != null && interfaces.length > 0 ) { - key.addAll( Arrays.asList( interfaces ) ); - } - - Class proxyClass = CACHE.get( key ); - - if ( proxyClass == null ) { - proxyClass = new ByteBuddy() - .with( TypeValidation.DISABLED ) - .with( new AuxiliaryType.NamingStrategy.SuffixingRandom( "HibernateBasicProxy" ) ) - .subclass( superClass == null ? Object.class : superClass ) - .implement( interfaces == null ? NO_INTERFACES : interfaces ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) - .intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) ) - .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) - .make() - .load( BasicProxyFactory.class.getClassLoader() ) - .getLoaded(); - Class previousProxy = CACHE.putIfAbsent( key, proxyClass ); - if ( previousProxy != null ) { - proxyClass = previousProxy; - } - } + final Class superClassOrMainInterface = superClass != null ? superClass : interfaces[0]; + final TypeCache.SimpleKey cacheKey = getCacheKey( superClass, interfaces ); - this.proxyClass = proxyClass; + this.proxyClass = byteBuddyState.loadBasicProxy( superClassOrMainInterface, cacheKey, byteBuddy -> byteBuddy + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) ) + .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) + .implement( interfaces == null ? NO_INTERFACES : interfaces ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) + .implement( ProxyConfiguration.class ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) + ); + this.interceptor = new PassThroughInterceptor( proxyClass.getName() ); } + @Override public Object getProxy() { try { final ProxyConfiguration proxy = (ProxyConfiguration) proxyClass.newInstance(); - proxy.$$_hibernate_set_interceptor( new PassThroughInterceptor( proxy, proxyClass.getName() ) ); + proxy.$$_hibernate_set_interceptor( this.interceptor ); return proxy; } catch (Throwable t) { - throw new HibernateException( "Unable to instantiate proxy instance" ); + throw new HibernateException( "Unable to instantiate proxy instance", t ); } } public boolean isInstance(Object object) { return proxyClass.isInstance( object ); } + + private TypeCache.SimpleKey getCacheKey(Class superClass, Class[] interfaces) { + Set> key = new HashSet>(); + if ( superClass != null ) { + key.add( superClass ); + } + if ( interfaces != null ) { + key.addAll( Arrays.>asList( interfaces ) ); + } + + return new TypeCache.SimpleKey( key ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java new file mode 100644 index 000000000000..9cc5ef9e1adf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -0,0 +1,377 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; +import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; +import static net.bytebuddy.matcher.ElementMatchers.isVirtual; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static org.hibernate.internal.CoreLogging.messageLogger; + +import java.io.File; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Function; + +import org.hibernate.HibernateException; +import org.hibernate.bytecode.spi.BasicProxyFactory; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.proxy.ProxyConfiguration; +import org.hibernate.proxy.ProxyFactory; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.TypeCache; +import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods; +import net.bytebuddy.asm.MemberSubstitution; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.DynamicType.Unloaded; +import net.bytebuddy.dynamic.loading.ClassInjector; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.dynamic.scaffold.TypeValidation; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.pool.TypePool; + +/** + * A utility to hold all ByteBuddy related state, as in the current version of + * Hibernate the Bytecode Provider state is held in a static field, yet ByteBuddy + * is able to benefit from some caching and general state reuse. + */ +public final class ByteBuddyState { + + private static final CoreMessageLogger LOG = messageLogger( ByteBuddyState.class ); + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final boolean DEBUG = false; + + private final ByteBuddy byteBuddy; + + private final ForDeclaredMethods getDeclaredMethodMemberSubstitution; + private final ForDeclaredMethods getMethodMemberSubstitution; + + private final ProxyDefinitionHelpers proxyDefinitionHelpers; + + /** + * It will be easier to maintain the cache and its state when it will no longer be static + * in Hibernate ORM 6+. + * Opted for WEAK keys to avoid leaking the classloader in case the SessionFactory isn't closed. + * Avoiding Soft keys as they are prone to cause issues with unstable performance. + */ + private final TypeCache proxyCache; + private final TypeCache basicProxyCache; + + ByteBuddyState() { + this.byteBuddy = new ByteBuddy().with( TypeValidation.DISABLED ); + + this.proxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + this.basicProxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + + if ( System.getSecurityManager() != null ) { + this.getDeclaredMethodMemberSubstitution = getDeclaredMethodMemberSubstitution(); + this.getMethodMemberSubstitution = getMethodMemberSubstitution(); + } + else { + this.getDeclaredMethodMemberSubstitution = null; + this.getMethodMemberSubstitution = null; + } + + this.proxyDefinitionHelpers = new ProxyDefinitionHelpers(); + } + + /** + * Load a proxy as generated by the {@link ProxyFactory}. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param cacheKey The cache key. + * @param makeProxyFunction A function building the proxy. + * @return The loaded proxy class. + */ + public Class loadProxy(Class referenceClass, TypeCache.SimpleKey cacheKey, + Function> makeProxyFunction) { + return load( referenceClass, proxyCache, cacheKey, makeProxyFunction ); + } + + /** + * Load a proxy as generated by the {@link BasicProxyFactory}. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param cacheKey The cache key. + * @param makeProxyFunction A function building the proxy. + * @return The loaded proxy class. + */ + Class loadBasicProxy(Class referenceClass, TypeCache.SimpleKey cacheKey, + Function> makeProxyFunction) { + return load( referenceClass, basicProxyCache, cacheKey, makeProxyFunction ); + } + + /** + * Load a class generated by ByteBuddy. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param makeClassFunction A function building the class. + * @return The loaded generated class. + */ + public Class load(Class referenceClass, Function> makeClassFunction) { + return make( makeClassFunction.apply( byteBuddy ) ) + .load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) ) + .getLoaded(); + } + + /** + * Rewrite a class, used by the enhancer. + *

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

    + * These elements are shared as they are immutable. + * + * @return The proxy definition helpers. + */ + public ProxyDefinitionHelpers getProxyDefinitionHelpers() { + return proxyDefinitionHelpers; + } + + /** + * Wipes out all known caches used by ByteBuddy. This implies it might trigger the need + * to re-create some helpers if used at runtime, especially as this state is shared by + * multiple SessionFactory instances, but at least ensures we cleanup anything which is no + * longer needed after a SessionFactory close. + * The assumption is that closing SessionFactories is a rare event; in this perspective the cost + * of re-creating the small helpers should be negligible. + */ + void clearState() { + proxyCache.clear(); + basicProxyCache.clear(); + } + + private Class load(Class referenceClass, TypeCache cache, + TypeCache.SimpleKey cacheKey, Function> makeProxyFunction) { + return cache.findOrInsert( + referenceClass.getClassLoader(), + cacheKey, + () -> make( makeProxyFunction.apply( byteBuddy ) ) + .load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) ) + .getLoaded(), + cache ); + } + + private Unloaded make(DynamicType.Builder builder) { + return make( null, builder ); + } + + private Unloaded make(TypePool typePool, DynamicType.Builder builder) { + if ( System.getSecurityManager() != null ) { + builder = builder.visit( getDeclaredMethodMemberSubstitution ); + builder = builder.visit( getMethodMemberSubstitution ); + } + + Unloaded unloadedClass; + if ( typePool != null ) { + unloadedClass = builder.make( typePool ); + } + else { + unloadedClass = builder.make(); + } + + if ( DEBUG ) { + try { + unloadedClass.saveIn( new File( System.getProperty( "java.io.tmpdir" ) + "/bytebuddy/" ) ); + } + catch (IOException e) { + LOG.warn( "Unable to save generated class %1$s", unloadedClass.getTypeDescription().getName(), e ); + } + } + + if ( System.getSecurityManager() != null ) { + // we authorize the proxy class to access the method lookup dispatcher + HibernateMethodLookupDispatcher.registerAuthorizedClass( unloadedClass.getTypeDescription().getName() ); + } + + return unloadedClass; + } + + // This method is kept public static as it is also required by a test. + public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { + if ( ClassInjector.UsingLookup.isAvailable() ) { + // This is only enabled for JDK 9+ + + Method privateLookupIn; + try { + privateLookupIn = MethodHandles.class.getMethod( "privateLookupIn", Class.class, MethodHandles.Lookup.class ); + } + catch (Exception e) { + throw new HibernateException( LOG.bytecodeEnhancementFailed( originalClass.getName() ), e ); + } + + try { + Object privateLookup; + + try { + privateLookup = privateLookupIn.invoke( null, originalClass, LOOKUP ); + } + catch (InvocationTargetException exception) { + if ( exception.getCause() instanceof IllegalAccessException ) { + return new ClassLoadingStrategy.ForUnsafeInjection( originalClass.getProtectionDomain() ); + } + else { + throw new HibernateException( LOG.bytecodeEnhancementFailed( originalClass.getName() ), exception.getCause() ); + } + } + + return ClassLoadingStrategy.UsingLookup.of( privateLookup ); + } + catch (Throwable e) { + throw new HibernateException( LOG.bytecodeEnhancementFailedUnableToGetPrivateLookupFor( originalClass.getName() ), e ); + } + } + else { + return new ClassLoadingStrategy.ForUnsafeInjection( originalClass.getProtectionDomain() ); + } + } + + private static ForDeclaredMethods getDeclaredMethodMemberSubstitution() { + // this should only be called if the security manager is enabled, thus the privileged calls + return MemberSubstitution.relaxed() + .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, + "getDeclaredMethod", String.class, Class[].class ) ) ) ) + .replaceWith( + AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class, + "getDeclaredMethod", Class.class, String.class, Class[].class ) ) ) + .on( ElementMatchers.isTypeInitializer() ); + } + + private static ForDeclaredMethods getMethodMemberSubstitution() { + // this should only be called if the security manager is enabled, thus the privileged calls + return MemberSubstitution.relaxed() + .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, + "getMethod", String.class, Class[].class ) ) ) ) + .replaceWith( + AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class, + "getMethod", Class.class, String.class, Class[].class ) ) ) + .on( ElementMatchers.isTypeInitializer() ); + } + + private static class GetDeclaredMethodAction implements PrivilegedAction { + private final Class clazz; + private final String methodName; + private final Class[] parameterTypes; + + private GetDeclaredMethodAction(Class clazz, String methodName, Class... parameterTypes) { + this.clazz = clazz; + this.methodName = methodName; + this.parameterTypes = parameterTypes; + } + + @Override + public Method run() { + try { + Method method = clazz.getDeclaredMethod( methodName, parameterTypes ); + + return method; + } + catch (NoSuchMethodException e) { + throw new HibernateException( "Unable to prepare getDeclaredMethod()/getMethod() substitution", e ); + } + } + } + + /** + * Shared proxy definition helpers. They are immutable so we can safely share them. + */ + public static class ProxyDefinitionHelpers { + + private final ElementMatcher groovyGetMetaClassFilter; + private final ElementMatcher virtualNotFinalizerFilter; + private final ElementMatcher hibernateGeneratedMethodFilter; + private final MethodDelegation delegateToInterceptorDispatcherMethodDelegation; + private final FieldAccessor.PropertyConfigurable interceptorFieldAccessor; + + private ProxyDefinitionHelpers() { + this.groovyGetMetaClassFilter = isSynthetic().and( named( "getMetaClass" ) + .and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ); + this.virtualNotFinalizerFilter = isVirtual().and( not( isFinalizer() ) ); + this.hibernateGeneratedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() ); + + PrivilegedAction delegateToInterceptorDispatcherMethodDelegationPrivilegedAction = + new PrivilegedAction() { + + @Override + public MethodDelegation run() { + return MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ); + } + }; + + this.delegateToInterceptorDispatcherMethodDelegation = System.getSecurityManager() != null + ? AccessController.doPrivileged( delegateToInterceptorDispatcherMethodDelegationPrivilegedAction ) + : delegateToInterceptorDispatcherMethodDelegationPrivilegedAction.run(); + + PrivilegedAction interceptorFieldAccessorPrivilegedAction = + new PrivilegedAction() { + + @Override + public FieldAccessor.PropertyConfigurable run() { + return FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) + .withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ); + } + }; + + this.interceptorFieldAccessor = System.getSecurityManager() != null + ? AccessController.doPrivileged( interceptorFieldAccessorPrivilegedAction ) + : interceptorFieldAccessorPrivilegedAction.run(); + } + + public ElementMatcher getGroovyGetMetaClassFilter() { + return groovyGetMetaClassFilter; + } + + public ElementMatcher getVirtualNotFinalizerFilter() { + return virtualNotFinalizerFilter; + } + + public ElementMatcher getHibernateGeneratedMethodFilter() { + return hibernateGeneratedMethodFilter; + } + + public MethodDelegation getDelegateToInterceptorDispatcherMethodDelegation() { + return delegateToInterceptorDispatcherMethodDelegation; + } + + public FieldAccessor.PropertyConfigurable getInterceptorFieldAccessor() { + return interceptorFieldAccessor; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index 5b27e82fa5d3..15f4c1ecc88e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -11,7 +11,6 @@ import java.lang.reflect.Modifier; import java.util.concurrent.Callable; -import net.bytebuddy.TypeCache; import org.hibernate.HibernateException; import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; import org.hibernate.bytecode.enhance.spi.EnhancementContext; @@ -19,12 +18,11 @@ import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.ProxyFactoryFactory; import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; -import net.bytebuddy.ByteBuddy; import net.bytebuddy.NamingStrategy; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.MethodCall; import net.bytebuddy.implementation.bytecode.ByteCodeAppender; @@ -35,16 +33,30 @@ import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; +import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; public class BytecodeProviderImpl implements BytecodeProvider { - private final TypeCache FAST_CLASSES = new TypeCache.WithInlineExpunction(TypeCache.Sort.SOFT); - private final TypeCache BULK_ACCESSORS = new TypeCache.WithInlineExpunction(TypeCache.Sort.SOFT); + private static final String INSTANTIATOR_PROXY_NAMING_SUFFIX = "HibernateInstantiator"; + private static final String OPTIMIZER_PROXY_NAMING_SUFFIX = "HibernateAccessOptimizer"; + private static final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( "newInstance" ); + private static final ElementMatcher.Junction getPropertyValuesMethodName = ElementMatchers.named( "getPropertyValues" ); + private static final ElementMatcher.Junction setPropertyValuesMethodName = ElementMatchers.named( "setPropertyValues" ); + private static final ElementMatcher.Junction getPropertyNamesMethodName = ElementMatchers.named( "getPropertyNames" ); + + private final ByteBuddyState byteBuddyState; + + private final ByteBuddyProxyHelper byteBuddyProxyHelper; + + public BytecodeProviderImpl() { + this.byteBuddyState = new ByteBuddyState(); + this.byteBuddyProxyHelper = new ByteBuddyProxyHelper( byteBuddyState ); + } @Override public ProxyFactoryFactory getProxyFactoryFactory() { - return new ProxyFactoryFactoryImpl(); + return new ProxyFactoryFactoryImpl( byteBuddyState, byteBuddyProxyHelper ); } @Override @@ -53,50 +65,42 @@ public ReflectionOptimizer getReflectionOptimizer( final String[] getterNames, final String[] setterNames, final Class[] types) { + final Class fastClass; + if ( !clazz.isInterface() && !Modifier.isAbstract( clazz.getModifiers() ) ) { + // we only provide a fast class instantiator if the class can be instantiated + final Constructor constructor = findConstructor( clazz ); + + fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy + .with( new NamingStrategy.SuffixingRandom( INSTANTIATOR_PROXY_NAMING_SUFFIX, + new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) + .subclass( ReflectionOptimizer.InstantiationOptimizer.class ) + .method( newInstanceMethodName ) + .intercept( MethodCall.construct( constructor ) ) + ); + } + else { + fastClass = null; + } + final Method[] getters = new Method[getterNames.length]; final Method[] setters = new Method[setterNames.length]; findAccessors( clazz, getterNames, setterNames, types, getters, setters ); - final Constructor constructor = findConstructor( clazz ); - - Class fastClass = FAST_CLASSES.findOrInsert( clazz.getClassLoader(), clazz.getName(), new Callable>() { - @Override - public Class call() throws Exception { - return new ByteBuddy() - .with(TypeValidation.DISABLED) - .with(new NamingStrategy.SuffixingRandom("HibernateInstantiator")) - .subclass(ReflectionOptimizer.InstantiationOptimizer.class) - .method(ElementMatchers.named("newInstance")) - .intercept(MethodCall.construct(constructor)) - .make() - .load(clazz.getClassLoader()) - .getLoaded(); - } - }, FAST_CLASSES); - - fastClass = FAST_CLASSES.insert( clazz.getClassLoader(), clazz.getName(), fastClass ); - - Class bulkAccessor = BULK_ACCESSORS.findOrInsert( clazz.getClassLoader(), clazz.getName(), new Callable>() { - @Override - public Class call() throws Exception { - return new ByteBuddy() - .with(TypeValidation.DISABLED) - .with(new NamingStrategy.SuffixingRandom("HibernateAccessOptimizer")) - .subclass(ReflectionOptimizer.AccessOptimizer.class) - .method(ElementMatchers.named("getPropertyValues")) - .intercept(new Implementation.Simple(new GetPropertyValues(clazz, getters))) - .method(ElementMatchers.named("setPropertyValues")) - .intercept(new Implementation.Simple(new SetPropertyValues(clazz, setters))) - .method(ElementMatchers.named("getPropertyNames")) - .intercept(MethodCall.call(new CloningPropertyCall(getterNames))) - .make() - .load(clazz.getClassLoader()) - .getLoaded(); - } - }, BULK_ACCESSORS); + + final Class bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy + .with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX, + new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) + .subclass( ReflectionOptimizer.AccessOptimizer.class ) + .method( getPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) ) + .method( setPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) ) + .method( getPropertyNamesMethodName ) + .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) + ); try { return new ReflectionOptimizerImpl( - (ReflectionOptimizer.InstantiationOptimizer) fastClass.newInstance(), + fastClass != null ? (ReflectionOptimizer.InstantiationOptimizer) fastClass.newInstance() : null, (ReflectionOptimizer.AccessOptimizer) bulkAccessor.newInstance() ); } @@ -105,6 +109,10 @@ public Class call() throws Exception { } } + public ByteBuddyProxyHelper getByteBuddyProxyHelper() { + return byteBuddyProxyHelper; + } + private static class GetPropertyValues implements ByteCodeAppender { private final Class clazz; @@ -272,6 +280,12 @@ public String[] call() { @Override public Enhancer getEnhancer(EnhancementContext enhancementContext) { - return new EnhancerImpl( enhancementContext ); + return new EnhancerImpl( enhancementContext, byteBuddyState ); } + + @Override + public void resetCaches() { + byteBuddyState.clearState(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java new file mode 100644 index 000000000000..61cc1c962d2b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java @@ -0,0 +1,225 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.hibernate.HibernateException; + +/** + * This dispatcher analyzes the stack frames to detect if a particular call should be authorized. + *

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

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

    + * Beyond that, we give up and throw an exception. + */ + private static final int MAX_STACK_FRAMES = 16; + private static final PrivilegedAction[]> GET_CALLER_STACK_ACTION; + + // Currently, the bytecode provider is created statically and shared between all the session factories. Thus we + // can't clear this set when we close a session factory as we might remove elements coming from another one. + // Considering we can't clear these elements, we use the class names instead of the classes themselves to avoid + // issues. + private static Set authorizedClasses = ConcurrentHashMap.newKeySet(); + + public static Method getDeclaredMethod(Class type, String name, Class[] parameters) { + PrivilegedAction getDeclaredMethodAction = new PrivilegedAction() { + + @Override + public Method run() { + try { + return type.getDeclaredMethod( name, parameters ); + } + catch (NoSuchMethodException | SecurityException e) { + return null; + } + } + }; + + return doPrivilegedAction( getDeclaredMethodAction ); + } + + public static Method getMethod(Class type, String name, Class[] parameters) { + PrivilegedAction getMethodAction = new PrivilegedAction() { + + @Override + public Method run() { + try { + return type.getMethod( name, parameters ); + } + catch (NoSuchMethodException | SecurityException e) { + return null; + } + } + }; + + return doPrivilegedAction( getMethodAction ); + } + + private static Method doPrivilegedAction(PrivilegedAction privilegedAction) { + Class callerClass = getCallerClass(); + + if ( !authorizedClasses.contains( callerClass.getName() ) ) { + throw new SecurityException( "Unauthorized call by class " + callerClass ); + } + + return System.getSecurityManager() != null ? AccessController.doPrivileged( privilegedAction ) : + privilegedAction.run(); + } + + static void registerAuthorizedClass(String className) { + authorizedClasses.add( className ); + } + + static { + // The action below will return the action used at runtime to retrieve the caller stack + PrivilegedAction[]>> initializeGetCallerStackAction = new PrivilegedAction[]>>() { + @Override + public PrivilegedAction[]> run() { + Class stackWalkerClass = null; + try { + // JDK 9 introduced the StackWalker + stackWalkerClass = Class.forName( "java.lang.StackWalker" ); + } + catch (ClassNotFoundException e) { + // ignore, we will deal with that later. + } + + if ( stackWalkerClass != null ) { + // We can use a stack walker + try { + Class optionClass = Class.forName( "java.lang.StackWalker$Option" ); + Object stackWalker = stackWalkerClass.getMethod( "getInstance", optionClass ) + // The first one is RETAIN_CLASS_REFERENCE + .invoke( null, optionClass.getEnumConstants()[0] ); + + Method stackWalkerWalkMethod = stackWalkerClass.getMethod( "walk", Function.class ); + Method stackFrameGetDeclaringClass = Class.forName( "java.lang.StackWalker$StackFrame" ) + .getMethod( "getDeclaringClass" ); + return new StackWalkerGetCallerStackAction( + stackWalker, stackWalkerWalkMethod,stackFrameGetDeclaringClass + ); + } + catch (Throwable e) { + throw new HibernateException( "Unable to initialize the stack walker", e ); + } + } + else { + // We cannot use a stack walker, default to fetching the security manager class context + return new SecurityManagerClassContextGetCallerStackAction(); + } + } + }; + + GET_CALLER_STACK_ACTION = System.getSecurityManager() != null + ? AccessController.doPrivileged( initializeGetCallerStackAction ) + : initializeGetCallerStackAction.run(); + } + + private static Class getCallerClass() { + Class[] stackTrace = System.getSecurityManager() != null + ? AccessController.doPrivileged( GET_CALLER_STACK_ACTION ) + : GET_CALLER_STACK_ACTION.run(); + + // this shouldn't happen but let's be safe + if ( stackTrace.length <= MIN_STACK_FRAMES ) { + throw new SecurityException( "Unable to determine the caller class" ); + } + + boolean hibernateMethodLookupDispatcherDetected = false; + // start at the 4th frame and limit that to the MAX_STACK_FRAMES first frames + int maxFrames = Math.min( MAX_STACK_FRAMES, stackTrace.length ); + for ( int i = MIN_STACK_FRAMES; i < maxFrames; i++ ) { + if ( stackTrace[i].getName().equals( HibernateMethodLookupDispatcher.class.getName() ) ) { + hibernateMethodLookupDispatcherDetected = true; + continue; + } + if ( hibernateMethodLookupDispatcherDetected ) { + return stackTrace[i]; + } + } + + throw new SecurityException( "Unable to determine the caller class" ); + } + + /** + * A privileged action that retrieves the caller stack from the security manager class context. + */ + private static class SecurityManagerClassContextGetCallerStackAction extends SecurityManager + implements PrivilegedAction[]> { + @Override + public Class[] run() { + return getClassContext(); + } + } + + /** + * A privileged action that retrieves the caller stack using a stack walker. + */ + private static class StackWalkerGetCallerStackAction implements PrivilegedAction[]> { + private final Object stackWalker; + private final Method stackWalkerWalkMethod; + private final Method stackFrameGetDeclaringClass; + + StackWalkerGetCallerStackAction(Object stackWalker, Method stackWalkerWalkMethod, + Method stackFrameGetDeclaringClass) { + this.stackWalker = stackWalker; + this.stackWalkerWalkMethod = stackWalkerWalkMethod; + this.stackFrameGetDeclaringClass = stackFrameGetDeclaringClass; + } + + @Override + public Class[] run() { + try { + return (Class[]) stackWalkerWalkMethod.invoke( stackWalker, stackFrameExtractFunction ); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new SecurityException( "Unable to determine the caller class", e ); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private final Function stackFrameExtractFunction = new Function() { + @Override + public Object apply(Stream stream) { + return stream.map( stackFrameGetDeclaringClassFunction ) + .limit( MAX_STACK_FRAMES ) + .toArray( Class[]::new ); + } + }; + + private final Function> stackFrameGetDeclaringClassFunction = new Function>() { + @Override + public Class apply(Object t) { + try { + return (Class) stackFrameGetDeclaringClass.invoke( t ); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new HibernateException( "Unable to get stack frame declaring class", e ); + } + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java index 7d45e2c6cd38..09b108a148e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java @@ -11,57 +11,58 @@ import org.hibernate.proxy.ProxyConfiguration; -import net.bytebuddy.implementation.bind.annotation.AllArguments; -import net.bytebuddy.implementation.bind.annotation.Origin; -import net.bytebuddy.implementation.bind.annotation.RuntimeType; -import net.bytebuddy.implementation.bind.annotation.This; - public class PassThroughInterceptor implements ProxyConfiguration.Interceptor { - private HashMap data = new HashMap(); - private final Object proxiedObject; + private HashMap data = new HashMap<>(); private final String proxiedClassName; - public PassThroughInterceptor(Object proxiedObject, String proxiedClassName) { - this.proxiedObject = proxiedObject; + public PassThroughInterceptor(String proxiedClassName) { this.proxiedClassName = proxiedClassName; } - @SuppressWarnings("unchecked") @Override public Object intercept(Object instance, Method method, Object[] arguments) throws Exception { final String name = method.getName(); - if ( "toString".equals( name ) ) { + + if ( "toString".equals( name ) && arguments.length == 0 ) { return proxiedClassName + "@" + System.identityHashCode( instance ); } - else if ( "equals".equals( name ) ) { - return proxiedObject == instance; + + if ( "equals".equals( name ) && arguments.length == 1 ) { + return instance == arguments[0]; } - else if ( "hashCode".equals( name ) ) { + + if ( "hashCode".equals( name ) && arguments.length == 0 ) { return System.identityHashCode( instance ); } - final boolean hasGetterSignature = method.getParameterCount() == 0 - && method.getReturnType() != null; - final boolean hasSetterSignature = method.getParameterCount() == 1 - && ( method.getReturnType() == null || method.getReturnType() == void.class ); - - if ( name.startsWith( "get" ) && hasGetterSignature ) { + if ( name.startsWith( "get" ) && hasGetterSignature( method ) ) { final String propName = name.substring( 3 ); return data.get( propName ); } - else if ( name.startsWith( "is" ) && hasGetterSignature ) { + + if ( name.startsWith( "is" ) && hasGetterSignature( method ) ) { final String propName = name.substring( 2 ); return data.get( propName ); } - else if ( name.startsWith( "set" ) && hasSetterSignature ) { + + if ( name.startsWith( "set" ) && hasSetterSignature( method ) ) { final String propName = name.substring( 3 ); data.put( propName, arguments[0] ); return null; } - else { - // todo : what else to do here? - return null; - } + + // todo : what else to do here? + return null; + } + + private boolean hasGetterSignature(Method method) { + return method.getParameterCount() == 0 + && method.getReturnType() != null; + } + + private boolean hasSetterSignature(Method method) { + return method.getParameterCount() == 1 + && ( method.getReturnType() == null || method.getReturnType() == void.class ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java index 00b08f540aed..0f68aead89c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java @@ -11,16 +11,26 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.proxy.ProxyFactory; import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory { + private final ByteBuddyState byteBuddyState; + + private final ByteBuddyProxyHelper byteBuddyProxyHelper; + + public ProxyFactoryFactoryImpl(ByteBuddyState byteBuddyState, ByteBuddyProxyHelper byteBuddyProxyHelper) { + this.byteBuddyState = byteBuddyState; + this.byteBuddyProxyHelper = byteBuddyProxyHelper; + } + @Override public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) { - return new ByteBuddyProxyFactory(); + return new ByteBuddyProxyFactory( byteBuddyProxyHelper ); } @Override public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) { - return new BasicProxyFactoryImpl( superClass, interfaces ); + return new BasicProxyFactoryImpl( superClass, interfaces, byteBuddyState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/package.html b/hibernate-core/src/main/java/org/hibernate/bytecode/package.html index 5a06dbf44c43..14baef7101ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/package.html +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/package.html @@ -14,7 +14,7 @@ in three scenarios:

    1. Reflection optimization - to speed up the performance of - POJO entity and component conctruction and field/property access + POJO entity and component construction and field/property access
    2. Proxy generation - runtime building of proxies used for diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ByteCodeHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ByteCodeHelper.java index 05004d7e3317..9ae56751e098 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ByteCodeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ByteCodeHelper.java @@ -51,7 +51,7 @@ public static byte[] readByteCode(InputStream inputStream) throws IOException { final byte[] temp = new byte[ classBytes.length + buffer.length ]; // copy any previously read bytes into the temp array System.arraycopy( classBytes, 0, temp, 0, classBytes.length ); - // copy the just read bytes into the temp array (afterQuery the previously read) + // copy the just read bytes into the temp array (after the previously read) System.arraycopy( buffer, 0, temp, classBytes.length, buffer.length ); classBytes = temp; // read the next set of bytes into buffer @@ -61,7 +61,7 @@ public static byte[] readByteCode(InputStream inputStream) throws IOException { final byte[] temp = new byte[ classBytes.length + r ]; // copy any previously read bytes into the temp array System.arraycopy( classBytes, 0, temp, 0, classBytes.length ); - // copy the just read bytes into the temp array (afterQuery the previously read) + // copy the just read bytes into the temp array (after the previously read) System.arraycopy( buffer, 0, temp, classBytes.length, r ); classBytes = temp; } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java index b71ba5aad200..deecb83f9ff1 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java @@ -6,8 +6,12 @@ */ package org.hibernate.bytecode.spi; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -33,10 +37,19 @@ public interface BytecodeEnhancementMetadata { LazyAttributesMetadata getLazyAttributesMetadata(); + /** + * Create an "enhancement as proxy" instance for the given entity + * + * @apiNote The `addEmptyEntry` parameter is used to avoid creation of `EntityEntry` instances when we + * do not need them. - mainly from StatelessSession + */ + PersistentAttributeInterceptable createEnhancedProxy(EntityKey keyToLoad, boolean addEmptyEntry, SharedSessionContractImplementor session); + /** * Build and inject an interceptor instance into the enhanced entity. * * @param entity The entity into which built interceptor should be injected + * @param identifier * @param session The session to which the entity instance belongs. * * @return The built and injected interceptor @@ -45,8 +58,19 @@ public interface BytecodeEnhancementMetadata { */ LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException; + void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session); + + void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session); + /** * Extract the field interceptor instance from the enhanced entity. * @@ -58,6 +82,8 @@ LazyAttributeLoadingInterceptor injectInterceptor( */ LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException; + BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException; + boolean hasUnFetchedAttributes(Object entity); boolean isAttributeLoaded(Object entity, String attributeName); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java index 5420162ef781..f0c617247b95 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java @@ -48,4 +48,16 @@ public interface BytecodeProvider { * @return An enhancer to perform byte code manipulations. */ Enhancer getEnhancer(EnhancementContext enhancementContext); + + /** + * Some BytecodeProvider implementations will have classloader specific caching. + * These caches are useful at runtime but need to be reset at least on SessionFactory shutdown + * to prevent leaking the deployment classloader. + * Since the BytecodeProvider is static these caches are potentially shared across multiple + * deployments; in this case we'll clear all caches which might show as a small, temporary + * performance degradation on the SessionFactory instances which haven't been closed. + * This limitation will be removed in the future, when these providers will no longer be static. + */ + default void resetCaches() {} + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ClassTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ClassTransformer.java index 195c25dde125..ecbb8a6c89bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ClassTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ClassTransformer.java @@ -14,7 +14,7 @@ * to the PersistenceUnitInfo.addTransformer method. * The supplied transformer instance will get called to transform * entity class files when they are loaded and redefined. The transformation - * occurs beforeQuery the class is defined by the JVM + * occurs before the class is defined by the JVM * * @author Bill Burke * @author Emmanuel Bernard diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ProxyFactoryFactory.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ProxyFactoryFactory.java index 7c94d55aa5e1..2565bf35a4aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ProxyFactoryFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/ProxyFactoryFactory.java @@ -15,7 +15,7 @@ /** * An interface for factories of {@link ProxyFactory proxy factory} instances. *

      - * Currently used to abstract from the tupizer whether we are using CGLIB or + * Currently used to abstract from the tupizer whether we are using Byte Buddy or * Javassist for lazy proxy generation. * * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/AbstractDomainDataCachingConfig.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/AbstractDomainDataCachingConfig.java new file mode 100644 index 000000000000..49ef73b326df --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/AbstractDomainDataCachingConfig.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.cfg.internal; + +import org.hibernate.cache.cfg.spi.DomainDataCachingConfig; +import org.hibernate.cache.spi.access.AccessType; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractDomainDataCachingConfig implements DomainDataCachingConfig { + private final AccessType accessType; + + public AbstractDomainDataCachingConfig(AccessType accessType) { + this.accessType = accessType; + } + + @Override + public AccessType getAccessType() { + return accessType; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/CollectionDataCachingConfigImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/CollectionDataCachingConfigImpl.java new file mode 100644 index 000000000000..30a15aae9adf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/CollectionDataCachingConfigImpl.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.cache.cfg.internal; + +import java.util.Comparator; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.mapping.Collection; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.type.VersionType; + +/** + * @author Steve Ebersole + */ +public class CollectionDataCachingConfigImpl + extends AbstractDomainDataCachingConfig + implements CollectionDataCachingConfig { + private final Collection collectionDescriptor; + private final NavigableRole navigableRole; + + public CollectionDataCachingConfigImpl( + Collection collectionDescriptor, + AccessType accessType) { + super( accessType ); + this.collectionDescriptor = collectionDescriptor; + this.navigableRole = new NavigableRole( collectionDescriptor.getRole() ); + } + + @Override + public boolean isMutable() { + return collectionDescriptor.isMutable(); + } + + @Override + public boolean isVersioned() { + return collectionDescriptor.getOwner().isVersioned(); + } + + @Override + public Comparator getOwnerVersionComparator() { + if ( !isVersioned() ) { + return null; + } + return ( (VersionType) collectionDescriptor.getOwner().getVersion().getType() ).getComparator(); + } + + @Override + public NavigableRole getNavigableRole() { + return navigableRole; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/DomainDataRegionConfigImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/DomainDataRegionConfigImpl.java new file mode 100644 index 000000000000..36cef8d90a5b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/DomainDataRegionConfigImpl.java @@ -0,0 +1,168 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.cfg.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.cfg.spi.DomainDataCachingConfig; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.RootClass; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.type.VersionType; + +/** + * DomainDataRegionConfig implementation + * + * @author Steve Ebersole + */ +public class DomainDataRegionConfigImpl implements DomainDataRegionConfig { + private final String regionName; + + private final List entityConfigs; + private final List naturalIdConfigs; + private final List collectionConfigs; + + private DomainDataRegionConfigImpl( + String regionName, + List entityConfigs, + List naturalIdConfigs, + List collectionConfigs) { + this.regionName = regionName; + this.entityConfigs = entityConfigs; + this.naturalIdConfigs = naturalIdConfigs; + this.collectionConfigs = collectionConfigs; + } + + @Override + public String getRegionName() { + return regionName; + } + + @Override + public List getEntityCaching() { + return entityConfigs; + } + + @Override + public List getNaturalIdCaching() { + return naturalIdConfigs; + } + + @Override + public List getCollectionCaching() { + return collectionConfigs; + } + + public static class Builder { + private final String regionName; + + private Map entityConfigsByRootName; + private List naturalIdConfigs; + private List collectionConfigs; + + /** + * Constructs a {@link Builder} + * + * @param regionName - the unqualified region name + */ + public Builder(String regionName) { + this.regionName = regionName; + } + + @SuppressWarnings("UnusedReturnValue") + public Builder addEntityConfig(PersistentClass bootEntityDescriptor, AccessType accessType) { + if ( entityConfigsByRootName == null ) { + entityConfigsByRootName = new HashMap<>(); + } + + // todo (5.3) : this is another place where having `BootstrapContext` / `TypeConfiguration` helps + // would allow us to delay the attempt to resolve the comparator (usual timing issues wrt Type resolution) + final NavigableRole rootEntityName = new NavigableRole( bootEntityDescriptor.getRootClass().getEntityName() ); + final EntityDataCachingConfigImpl entityDataCachingConfig = entityConfigsByRootName.computeIfAbsent( + rootEntityName, + x -> new EntityDataCachingConfigImpl( + rootEntityName, + bootEntityDescriptor.isVersioned() + ? (Supplier) () -> ( (VersionType) bootEntityDescriptor.getVersion().getType() ).getComparator() + : null, + bootEntityDescriptor.isMutable(), + accessType + ) + ); + + if ( bootEntityDescriptor == bootEntityDescriptor.getRootClass() ) { + entityDataCachingConfig.addCachedType( rootEntityName ); + } + else { + entityDataCachingConfig.addCachedType( new NavigableRole( bootEntityDescriptor.getEntityName() ) ); + } + + return this; + } + + + // todo (6.0) : `EntityPersister` and `CollectionPersister` references here should be replaces with `EntityHierarchy` and `PersistentCollectionDescriptor` + // + // todo : although ^^, couldn't this just be the boot-time model? Is there a specific need for it to be the run-time model? + // that would alleviate the difference between 5.3 and 6.0 from the SPI POV + + @SuppressWarnings("UnusedReturnValue") + public Builder addNaturalIdConfig(RootClass rootEntityDescriptor, AccessType accessType) { + if ( naturalIdConfigs == null ) { + naturalIdConfigs = new ArrayList<>(); + } + + naturalIdConfigs.add( new NaturalIdDataCachingConfigImpl( rootEntityDescriptor, accessType ) ); + return this; + } + + @SuppressWarnings("UnusedReturnValue") + public Builder addCollectionConfig(Collection collectionDescriptor, AccessType accessType) { + if ( collectionConfigs == null ) { + collectionConfigs = new ArrayList<>(); + } + + collectionConfigs.add( new CollectionDataCachingConfigImpl( collectionDescriptor, accessType ) ); + return this; + } + + public DomainDataRegionConfigImpl build() { + return new DomainDataRegionConfigImpl( + regionName, + finalize( entityConfigsByRootName ), + finalize( naturalIdConfigs ), + finalize( collectionConfigs ) + ); + } + + @SuppressWarnings("unchecked") + private List finalize(Map configs) { + return configs == null + ? Collections.emptyList() + : Collections.unmodifiableList( new ArrayList( configs.values() ) ); + } + + private List finalize(List configs) { + return configs == null + ? Collections.emptyList() + : Collections.unmodifiableList( configs ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/EntityDataCachingConfigImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/EntityDataCachingConfigImpl.java new file mode 100644 index 000000000000..a4f9c02f46ef --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/EntityDataCachingConfigImpl.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.cache.cfg.internal; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.metamodel.model.domain.NavigableRole; + +/** + * @author Steve Ebersole + */ +public class EntityDataCachingConfigImpl + extends AbstractDomainDataCachingConfig + implements EntityDataCachingConfig { + private final NavigableRole navigableRole; + private final Supplier versionComparatorAccess; + private final boolean isEntityMutable; + + private final Set cachedTypes = new HashSet<>(); + + public EntityDataCachingConfigImpl( + NavigableRole rootEntityName, + Supplier versionComparatorAccess, + boolean isEntityMutable, + AccessType accessType) { + super( accessType ); + this.navigableRole = rootEntityName; + this.versionComparatorAccess = versionComparatorAccess; + this.isEntityMutable = isEntityMutable; + } + + @Override + public Supplier getVersionComparatorAccess() { + return versionComparatorAccess; + } + + @Override + public boolean isMutable() { + return isEntityMutable; + } + + @Override + public boolean isVersioned() { + return getVersionComparatorAccess() != null; + } + + @Override + public NavigableRole getNavigableRole() { + return navigableRole; + } + + @Override + public Set getCachedTypes() { + return cachedTypes; + } + + public void addCachedType(NavigableRole typeRole) { + cachedTypes.add( typeRole ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/NaturalIdDataCachingConfigImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/NaturalIdDataCachingConfigImpl.java new file mode 100644 index 000000000000..7ed683e3abf9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/internal/NaturalIdDataCachingConfigImpl.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.cfg.internal; + +import java.util.Iterator; + +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; +import org.hibernate.metamodel.model.domain.NavigableRole; + +/** + * @author Steve Ebersole + */ +public class NaturalIdDataCachingConfigImpl + extends AbstractDomainDataCachingConfig + implements NaturalIdDataCachingConfig { + private final RootClass rootEntityDescriptor; + private final NavigableRole navigableRole; + private final boolean mutable; + + public NaturalIdDataCachingConfigImpl( + RootClass rootEntityDescriptor, + AccessType accessType) { + super( accessType ); + this.rootEntityDescriptor = rootEntityDescriptor; + this.navigableRole = new NavigableRole( rootEntityDescriptor.getEntityName() ); + + // sucks that we need to do this here. persister does the same "calculation" + this.mutable = hasAnyMutableNaturalIdProps(); + } + + private boolean hasAnyMutableNaturalIdProps() { + final Iterator itr = rootEntityDescriptor.getDeclaredPropertyIterator(); + while ( itr.hasNext() ) { + final Property prop = (Property) itr.next(); + if ( prop.isNaturalIdentifier() && prop.isUpdateable() ) { + return true; + } + } + + return false; + } + + @Override + public NavigableRole getNavigableRole() { + return navigableRole; + } + + @Override + public boolean isMutable() { + return mutable; + } + + @Override + public boolean isVersioned() { + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/package-info.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/package-info.java new file mode 100644 index 000000000000..e8df17b908c9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/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 + */ + +/** + * Package used to model various aspects of caching configuration + */ +package org.hibernate.cache.cfg; diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/CollectionDataCachingConfig.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/CollectionDataCachingConfig.java new file mode 100644 index 000000000000..f690fa8722af --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/CollectionDataCachingConfig.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.cache.cfg.spi; + +import java.util.Comparator; + +/** + * Specialized DomainDataCachingConfig describing the requested + * caching config for a particular persistent collection's data + * + * @author Steve Ebersole + */ +public interface CollectionDataCachingConfig extends DomainDataCachingConfig { + /** + * The comparator to be used with the owning entity's version (if it has one). + */ + Comparator getOwnerVersionComparator(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataCachingConfig.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataCachingConfig.java new file mode 100644 index 000000000000..5056a7b54d64 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataCachingConfig.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.cache.cfg.spi; + +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.metamodel.model.domain.NavigableRole; + +/** + * Configuration for a specific type of data to be stored in the + * region + * + * @author Steve Ebersole + */ +public interface DomainDataCachingConfig { + /** + * The requested AccessType + */ + AccessType getAccessType(); + + /** + * Is the data marked as being mutable? + */ + boolean isMutable(); + + /** + * Is the data to be cached considered versioned? + */ + boolean isVersioned(); + + /** + * The {@link NavigableRole} of the thing to be cached + */ + NavigableRole getNavigableRole(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataRegionBuildingContext.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataRegionBuildingContext.java new file mode 100644 index 000000000000..cd6e078ce67f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataRegionBuildingContext.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.cache.cfg.spi; + +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * A "parameter object" for {@link RegionFactory#buildDomainDataRegion} + * calls, giving it access to information it needs. + * + * @author Steve Ebersole + */ +public interface DomainDataRegionBuildingContext { + /** + * The CacheKeyFactory explicitly specified as part of the + * bootstrap by the user, by some "container", etc. + * + * If this method returns a non-null value, it is expected + * that RegionFactory implementors will use to be its + * CacheKeyFactory and return it when asked later. + */ + CacheKeysFactory getEnforcedCacheKeysFactory(); + + /** + * Access to the SessionFactory for which a Region is + * being built. + */ + SessionFactoryImplementor getSessionFactory(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataRegionConfig.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataRegionConfig.java new file mode 100644 index 000000000000..e2789124420f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/DomainDataRegionConfig.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.cache.cfg.spi; + +import java.util.List; + +import org.hibernate.boot.spi.SessionFactoryOptions; + +/** + * Configuration for a named region for caching domain data. + * A region's name is "unqualified"; i.e. it is not prefixed by + * {@link SessionFactoryOptions#getCacheRegionPrefix()}. + * + * @author Steve Ebersole + */ +public interface DomainDataRegionConfig { + + /** + * Retrieve the unqualified name of this region. + */ + String getRegionName(); + + /** + * Retrieve the list of all entity to be stored in this region + */ + List getEntityCaching(); + + /** + * Retrieve the list of all natural-id data to be stored in this region + */ + List getNaturalIdCaching(); + + /** + * Retrieve the list of all collection data to be stored in this region + */ + List getCollectionCaching(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/EntityDataCachingConfig.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/EntityDataCachingConfig.java new file mode 100644 index 000000000000..5d7347f1336b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/EntityDataCachingConfig.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.cache.cfg.spi; + +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +import org.hibernate.metamodel.model.domain.NavigableRole; + +/** + * Specialized DomainDataCachingConfig describing the requested + * caching config for a particular entity hierarchy's state data + * + * @author Steve Ebersole + */ +public interface EntityDataCachingConfig extends DomainDataCachingConfig { + /** + * Mainly here to allow optimization of not having to know the + * actual comparator instance to use here yet. If this method + * returns {@code true}, then users can safely assume that + * accessing {@link #getVersionComparatorAccess()} will + * not produce a null Comparator later + * + */ + boolean isVersioned(); + + /** + * Access to the comparator to be used with the entity's + * version. If the entity is not versioned, then this method + * returns {@code null}. + */ + Supplier getVersionComparatorAccess(); + + /** + * The list of specific subclasses of the root that are actually + * written to cache. + */ + Set getCachedTypes(); + + // todo (5.3) : what else is needed here? +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/NaturalIdDataCachingConfig.java b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/NaturalIdDataCachingConfig.java new file mode 100644 index 000000000000..d25c63b47c25 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/cfg/spi/NaturalIdDataCachingConfig.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.cfg.spi; + +/** + * Specialized DomainDataCachingConfig describing the requested + * caching config for the natural-id data of a particular entity (hierarchy) + * + * @author Steve Ebersole + */ +public interface NaturalIdDataCachingConfig extends DomainDataCachingConfig { +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheDataDescriptionImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheDataDescriptionImpl.java deleted file mode 100644 index 3e3f1231553a..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheDataDescriptionImpl.java +++ /dev/null @@ -1,106 +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.cache.internal; - -import java.util.Comparator; - -import org.hibernate.cache.spi.CacheDataDescription; -import org.hibernate.mapping.Collection; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.type.Type; -import org.hibernate.type.VersionType; -import org.hibernate.type.descriptor.java.IncomparableComparator; - -/** - * Standard CacheDataDescription implementation. - * - * @author Steve Ebersole - */ -public class CacheDataDescriptionImpl implements CacheDataDescription { - private final boolean mutable; - private final boolean versioned; - private final Comparator versionComparator; - private final Type keyType; - - /** - * Constructs a CacheDataDescriptionImpl instance. Generally speaking, code should use one of the - * overloaded {@link #decode} methods rather than direct instantiation. - * @param mutable Is the described data mutable? - * @param versioned Is the described data versioned? - * @param versionComparator The described data's version value comparator (if versioned). - * @param keyType - */ - public CacheDataDescriptionImpl(boolean mutable, boolean versioned, Comparator versionComparator, Type keyType) { - this.mutable = mutable; - this.versioned = versioned; - this.versionComparator = versionComparator; - if ( versioned && - ( versionComparator == null || IncomparableComparator.class.isInstance( versionComparator ) ) ) { - throw new IllegalArgumentException( - "versionComparator must not be null or an instance of " + IncomparableComparator.class.getName() - ); - } - this.keyType = keyType; - } - - @Override - public boolean isMutable() { - return mutable; - } - - @Override - public boolean isVersioned() { - return versioned; - } - - @Override - public Comparator getVersionComparator() { - return versionComparator; - } - - @Override - public Type getKeyType() { - return keyType; - } - - /** - * Builds a CacheDataDescriptionImpl from the mapping model of an entity class. - * - * @param model The mapping model. - * - * @return The constructed CacheDataDescriptionImpl - */ - public static CacheDataDescriptionImpl decode(PersistentClass model) { - return new CacheDataDescriptionImpl( - model.isMutable(), - model.isVersioned(), - model.isVersioned() - ? ( (VersionType) model.getVersion().getType() ).getComparator() - : null, - model.getIdentifier().getType() - ); - } - - /** - * Builds a CacheDataDescriptionImpl from the mapping model of a collection - * - * @param model The mapping model. - * - * @return The constructed CacheDataDescriptionImpl - */ - public static CacheDataDescriptionImpl decode(Collection model) { - return new CacheDataDescriptionImpl( - model.isMutable(), - model.getOwner().isVersioned(), - model.getOwner().isVersioned() - ? ( (VersionType) model.getOwner().getVersion().getType() ).getComparator() - : null, - model.getKey().getType() - ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java index 272e26aa0466..ba70417a5584 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java @@ -7,9 +7,9 @@ package org.hibernate.cache.internal; import java.io.Serializable; +import java.util.Objects; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.compare.EqualsHelper; import org.hibernate.type.Type; /** @@ -76,9 +76,9 @@ public boolean equals(Object other) { return false; } final CacheKeyImplementation that = (CacheKeyImplementation) other; - return EqualsHelper.equals( entityOrRoleName, that.entityOrRoleName ) - && type.isEqual( id, that.id) - && EqualsHelper.equals( tenantId, that.tenantId ); + return Objects.equals( entityOrRoleName, that.entityOrRoleName ) + && type.isEqual( id, that.id ) + && Objects.equals( tenantId, that.tenantId ); } @Override 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 f24ed358ca35..b22ab86d844f 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 @@ -194,5 +194,7 @@ public AfterTransactionCompletionProcess lockCache() { beforeExecutions(); return getAfterTransactionCompletionProcess(); } + + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/DisabledCaching.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/DisabledCaching.java new file mode 100644 index 000000000000..ddfbb12b7cfc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/DisabledCaching.java @@ -0,0 +1,230 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.internal; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Set; + +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.cache.spi.Region; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.TimestampsCache; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.model.domain.NavigableRole; + +/** + * CacheImplementor implementation for disabled caching + * + * @author Steve Ebersole + */ +public class DisabledCaching implements CacheImplementor { + private final SessionFactoryImplementor sessionFactory; + private final RegionFactory regionFactory; + + public DisabledCaching(SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; + this.regionFactory = sessionFactory.getServiceRegistry().getService( RegionFactory.class ); + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public RegionFactory getRegionFactory() { + return regionFactory; + } + + @Override + public void prime(Set cacheRegionConfigs) { + // nothing to do + } + + @Override + public boolean containsEntity(Class entityClass, Serializable identifier) { + return false; + } + + @Override + public boolean containsEntity(String entityName, Serializable identifier) { + return false; + } + + @Override + public void evictEntityData(Class entityClass, Serializable identifier) { + // nothing to do + + } + + @Override + public void evictEntityData(String entityName, Serializable identifier) { + // nothing to do + + } + + @Override + public void evictEntityData(Class entityClass) { + // nothing to do + } + + @Override + public void evictEntityData(String entityName) { + // nothing to do + } + + @Override + public void evictEntityData() { + // nothing to do + } + + @Override + public void evictNaturalIdData(Class entityClass) { + // nothing to do + } + + @Override + public void evictNaturalIdData(String entityName) { + // nothing to do + } + + @Override + public void evictNaturalIdData() { + // nothing to do + } + + @Override + public boolean containsCollection(String role, Serializable ownerIdentifier) { + return false; + } + + @Override + public void evictCollectionData(String role, Serializable ownerIdentifier) { + // nothing to do + } + + @Override + public void evictCollectionData(String role) { + // nothing to do + } + + @Override + public void evictCollectionData() { + // nothing to do + } + + @Override + public boolean containsQuery(String regionName) { + return false; + } + + @Override + public void evictDefaultQueryRegion() { + // nothing to do + } + + @Override + public void evictQueryRegion(String regionName) { + // nothing to do + } + + @Override + public void evictQueryRegions() { + // nothing to do + } + + @Override + public void evictRegion(String regionName) { + // nothing to do + } + + @Override + public Region getRegion(String fullRegionName) { + return null; + } + + @Override + public TimestampsCache getTimestampsCache() { + return null; + } + + @Override + public QueryResultsCache getDefaultQueryResultsCache() { + return null; + } + + @Override + public QueryResultsCache getQueryResultsCache(String regionName) { + return null; + } + + @Override + public QueryResultsCache getQueryResultsCacheStrictly(String regionName) { + return null; + } + + @Override + public void close() { + } + + @Override + public String[] getSecondLevelCacheRegionNames() { + return new String[0]; + } + + @Override + public Set getCacheRegionNames() { + return null; + } + + @Override + public EntityDataAccess getEntityRegionAccess(NavigableRole rootEntityName) { + return null; + } + + @Override + public NaturalIdDataAccess getNaturalIdCacheRegionAccessStrategy(NavigableRole rootEntityName) { + return null; + } + + @Override + public CollectionDataAccess getCollectionRegionAccess(NavigableRole collectionRole) { + return null; + } + + @Override + public boolean contains(Class cls, Object primaryKey) { + return false; + } + + @Override + public void evict(Class cls, Object primaryKey) { + + } + + @Override + public void evict(Class cls) { + + } + + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class cls) { + return (T) this; + } + + @Override + public Set getNaturalIdAccessesInRegion(String regionName) { + return Collections.emptySet(); + } +} 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 new file mode 100644 index 000000000000..9ce34807a33b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java @@ -0,0 +1,618 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.internal; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.persistence.PersistenceException; + +import org.hibernate.HibernateException; +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.cache.spi.QueryResultsRegion; +import org.hibernate.cache.spi.Region; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.TimestampsRegion; +import org.hibernate.cache.spi.TimestampsCache; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.pretty.MessageHelper; + +/** + * @author Steve Ebersole + * @author Strong Liu + * @author Gail Badner + */ +public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildingContext { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( EnabledCaching.class ); + + private final SessionFactoryImplementor sessionFactory; + private final RegionFactory regionFactory; + + private final Map regionsByName = new ConcurrentHashMap<>(); + + // A map by name for QueryResultsRegion instances that have the same name as a Region + // in #regionsByName. + private final Map queryResultsRegionsByDuplicateName = new ConcurrentHashMap<>(); + + private final Map entityAccessMap = new ConcurrentHashMap<>(); + private final Map naturalIdAccessMap = new ConcurrentHashMap<>(); + private final Map collectionAccessMap = new ConcurrentHashMap<>(); + + private final TimestampsCache timestampsCache; + + private final QueryResultsCache defaultQueryResultsCache; + private final Map namedQueryResultsCacheMap = new ConcurrentHashMap<>(); + + + private final Set legacySecondLevelCacheNames = new LinkedHashSet<>(); + private final Map> legacyNaturalIdAccessesForRegion = new ConcurrentHashMap<>(); + + public EnabledCaching(SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; + + this.regionFactory = getSessionFactory().getSessionFactoryOptions().getServiceRegistry().getService( RegionFactory.class ); + this.regionFactory.start( sessionFactory.getSessionFactoryOptions(), sessionFactory.getProperties() ); + + if ( getSessionFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { + final TimestampsRegion timestampsRegion = regionFactory.buildTimestampsRegion( + RegionFactory.DEFAULT_UPDATE_TIMESTAMPS_REGION_UNQUALIFIED_NAME, + sessionFactory + ); + timestampsCache = sessionFactory.getSessionFactoryOptions() + .getTimestampsCacheFactory() + .buildTimestampsCache( this, timestampsRegion ); + legacySecondLevelCacheNames.add( timestampsRegion.getName() ); + + final QueryResultsRegion queryResultsRegion = regionFactory.buildQueryResultsRegion( + RegionFactory.DEFAULT_QUERY_RESULTS_REGION_UNQUALIFIED_NAME, + sessionFactory + ); + regionsByName.put( queryResultsRegion.getName(), queryResultsRegion ); + defaultQueryResultsCache = new QueryResultsCacheImpl( + queryResultsRegion, + timestampsCache + ); + } + else { + timestampsCache = new TimestampsCacheDisabledImpl(); + defaultQueryResultsCache = null; + } + } + + @Override + public void prime(Set cacheRegionConfigs) { + for ( DomainDataRegionConfig regionConfig : cacheRegionConfigs ) { + final DomainDataRegion region = getRegionFactory().buildDomainDataRegion( regionConfig, this ); + regionsByName.put( region.getName(), region ); + + if ( ! Objects.equals( region.getName(), regionConfig.getRegionName() ) ) { + throw new HibernateException( + String.format( + Locale.ROOT, + "Region [%s] returned from RegionFactory [%s] was named differently than requested name. Expecting `%s`, but found `%s`", + region, + getRegionFactory().getClass().getName(), + regionConfig.getRegionName(), + region.getName() + ) + ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity caching + + for ( EntityDataCachingConfig entityAccessConfig : regionConfig.getEntityCaching() ) { + final EntityDataAccess entityDataAccess = entityAccessMap.put( + entityAccessConfig.getNavigableRole(), + region.getEntityDataAccess( entityAccessConfig.getNavigableRole() ) + ); + + legacySecondLevelCacheNames.add( + StringHelper.qualifyConditionally( + getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix(), + region.getName() + ) + ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Natural-id caching + + if ( regionConfig.getNaturalIdCaching().isEmpty() ) { + legacyNaturalIdAccessesForRegion.put( region.getName(), Collections.emptySet() ); + } + else { + final HashSet accesses = new HashSet<>(); + + for ( NaturalIdDataCachingConfig naturalIdAccessConfig : regionConfig.getNaturalIdCaching() ) { + final NaturalIdDataAccess naturalIdDataAccess = naturalIdAccessMap.put( + naturalIdAccessConfig.getNavigableRole(), + region.getNaturalIdDataAccess( naturalIdAccessConfig.getNavigableRole() ) + ); + accesses.add( naturalIdDataAccess ); + } + + legacyNaturalIdAccessesForRegion.put( region.getName(), accesses ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection caching + + for ( CollectionDataCachingConfig collectionAccessConfig : regionConfig.getCollectionCaching() ) { + final CollectionDataAccess collectionDataAccess = collectionAccessMap.put( + collectionAccessConfig.getNavigableRole(), + region.getCollectionDataAccess( collectionAccessConfig.getNavigableRole() ) + ); + + legacySecondLevelCacheNames.add( + StringHelper.qualifyConditionally( + getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix(), + region.getName() + ) + ); + } + } + + } + + @Override + public CacheKeysFactory getEnforcedCacheKeysFactory() { + // todo (6.0) : allow configuration of this + return null; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public RegionFactory getRegionFactory() { + return regionFactory; + } + + @Override + public TimestampsCache getTimestampsCache() { + return timestampsCache; + } + + + @Override + public Region getRegion(String regionName) { + // The Region in regionsByName has precedence over the + // QueryResultsRegion in #queryResultsRegionsByDuplicateName + return regionsByName.get( regionName ); + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity data + + @Override + public boolean containsEntity(Class entityClass, Serializable identifier) { + return containsEntity( entityClass.getName(), identifier ); + } + + @Override + public boolean containsEntity(String entityName, Serializable identifier) { + final EntityPersister entityDescriptor = sessionFactory.getMetamodel().entityPersister( entityName ); + final EntityDataAccess cacheAccess = entityDescriptor.getCacheAccessStrategy(); + if ( cacheAccess == null ) { + return false; + } + + final Object key = cacheAccess.generateCacheKey( identifier, entityDescriptor, sessionFactory, null ); + return cacheAccess.contains( key ); + } + + @Override + public void evictEntityData(Class entityClass, Serializable identifier) { + evictEntityData( entityClass.getName(), identifier ); + } + + @Override + public void evictEntityData(String entityName, Serializable identifier) { + final EntityPersister entityDescriptor = sessionFactory.getMetamodel().entityPersister( entityName ); + final EntityDataAccess cacheAccess = entityDescriptor.getCacheAccessStrategy(); + if ( cacheAccess == null ) { + return; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Evicting second-level cache: %s", + MessageHelper.infoString( entityDescriptor, identifier, sessionFactory ) + ); + } + + final Object key = cacheAccess.generateCacheKey( identifier, entityDescriptor, sessionFactory, null ); + cacheAccess.evict( key ); + } + + @Override + public void evictEntityData(Class entityClass) { + evictEntityData( entityClass.getName() ); + } + + @Override + public void evictEntityData(String entityName) { + evictEntityData( getSessionFactory().getMetamodel().entityPersister( entityName ) ); + } + + protected void evictEntityData(EntityPersister entityDescriptor) { + EntityPersister rootEntityDescriptor = entityDescriptor; + if ( entityDescriptor.isInherited() + && ! entityDescriptor.getEntityName().equals( entityDescriptor.getRootEntityName() ) ) { + rootEntityDescriptor = getSessionFactory().getMetamodel().entityPersister( entityDescriptor.getRootEntityName() ); + } + + evictEntityData( + rootEntityDescriptor.getNavigableRole(), + rootEntityDescriptor.getCacheAccessStrategy() + ); + } + + private void evictEntityData(NavigableRole navigableRole, EntityDataAccess cacheAccess) { + if ( cacheAccess == null ) { + return; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Evicting entity cache: %s", navigableRole.getFullPath() ); + } + + cacheAccess.evictAll(); + } + + @Override + public void evictEntityData() { + sessionFactory.getMetamodel().entityPersisters().values().forEach( this::evictEntityData ); + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Natural-id data + + @Override + public void evictNaturalIdData(Class entityClass) { + evictNaturalIdData( entityClass.getName() ); + } + + @Override + public void evictNaturalIdData(String entityName) { + evictNaturalIdData( + sessionFactory.getMetamodel().entityPersister( entityName ) + ); + } + + private void evictNaturalIdData(EntityPersister rootEntityDescriptor) { + evictNaturalIdData( rootEntityDescriptor.getNavigableRole(), rootEntityDescriptor.getNaturalIdCacheAccessStrategy() ); + } + + @Override + public void evictNaturalIdData() { + naturalIdAccessMap.forEach( this::evictNaturalIdData ); + } + + private void evictNaturalIdData(NavigableRole rootEntityRole, NaturalIdDataAccess cacheAccess) { + if ( cacheAccess == null ) { + return; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Evicting natural-id cache: %s", rootEntityRole.getFullPath() ); + } + + cacheAccess.evictAll(); + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection data + + @Override + public boolean containsCollection(String role, Serializable ownerIdentifier) { + final CollectionPersister collectionDescriptor = sessionFactory.getMetamodel() + .collectionPersister( role ); + + final CollectionDataAccess cacheAccess = collectionDescriptor.getCacheAccessStrategy(); + if ( cacheAccess == null ) { + return false; + } + + final Object key = cacheAccess.generateCacheKey( ownerIdentifier, collectionDescriptor, sessionFactory, null ); + return cacheAccess.contains( key ); + } + + @Override + public void evictCollectionData(String role, Serializable ownerIdentifier) { + final CollectionPersister collectionDescriptor = sessionFactory.getMetamodel() + .collectionPersister( role ); + + final CollectionDataAccess cacheAccess = collectionDescriptor.getCacheAccessStrategy(); + if ( cacheAccess == null ) { + return; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Evicting second-level cache: %s", + MessageHelper.collectionInfoString( collectionDescriptor, ownerIdentifier, sessionFactory ) + ); + } + + final Object key = cacheAccess.generateCacheKey( ownerIdentifier, collectionDescriptor, sessionFactory, null ); + cacheAccess.evict( key ); + } + + @Override + public void evictCollectionData(String role) { + final CollectionPersister collectionDescriptor = sessionFactory.getMetamodel() + .collectionPersister( role ); + + evictCollectionData( collectionDescriptor ); + } + + private void evictCollectionData(CollectionPersister collectionDescriptor) { + evictCollectionData( + collectionDescriptor.getNavigableRole(), + collectionDescriptor.getCacheAccessStrategy() + ); + } + + private void evictCollectionData(NavigableRole navigableRole, CollectionDataAccess cacheAccess) { + if ( cacheAccess == null ) { + return; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Evicting second-level cache: %s", navigableRole.getFullPath() ); + } + cacheAccess.evictAll(); + + } + + @Override + public void evictCollectionData() { + collectionAccessMap.forEach( this::evictCollectionData ); + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Query-results data + + @Override + public boolean containsQuery(String regionName) { + final QueryResultsCache cache = getQueryResultsCacheStrictly( regionName ); + return cache != null; + } + + @Override + public void evictDefaultQueryRegion() { + evictQueryResultRegion( defaultQueryResultsCache ); + } + + @Override + public void evictQueryRegion(String regionName) { + final QueryResultsCache cache = getQueryResultsCache( regionName ); + if ( cache == null ) { + return; + } + + evictQueryResultRegion( cache ); + } + + private void evictQueryResultRegion(QueryResultsCache cache) { + if ( cache == null ) { + return; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Evicting query cache, region: %s", cache.getRegion().getName() ); + } + + cache.clear(); + } + + @Override + public void evictQueryRegions() { + if ( LOG.isDebugEnabled() ) { + LOG.debug( "Evicting cache of all query regions." ); + } + + evictQueryResultRegion( defaultQueryResultsCache ); + + for ( QueryResultsCache cache : namedQueryResultsCacheMap.values() ) { + evictQueryResultRegion( cache ); + } + } + + @Override + public QueryResultsCache getDefaultQueryResultsCache() { + return defaultQueryResultsCache; + } + + @Override + public QueryResultsCache getQueryResultsCache(String regionName) throws HibernateException { + if ( !getSessionFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { + return null; + } + + + if ( regionName == null || regionName.equals( getDefaultQueryResultsCache().getRegion().getName() ) ) { + return getDefaultQueryResultsCache(); + } + + final QueryResultsCache existing = namedQueryResultsCacheMap.get( regionName ); + if ( existing != null ) { + return existing; + } + + return makeQueryResultsRegionAccess( regionName ); + } + + @Override + public QueryResultsCache getQueryResultsCacheStrictly(String regionName) { + if ( !getSessionFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { + return null; + } + + return namedQueryResultsCacheMap.get( regionName ); + } + + protected QueryResultsCache makeQueryResultsRegionAccess(String regionName) { + final Region region = regionsByName.computeIfAbsent( + regionName, + this::makeQueryResultsRegion + ); + final QueryResultsRegion queryResultsRegion; + if ( QueryResultsRegion.class.isInstance( region ) ) { + queryResultsRegion = (QueryResultsRegion) region; + } + else { + // There was already a different type of Region with the same name. + queryResultsRegion = queryResultsRegionsByDuplicateName.computeIfAbsent( + regionName, + this::makeQueryResultsRegion + ); + } + final QueryResultsCacheImpl regionAccess = new QueryResultsCacheImpl( + queryResultsRegion, + timestampsCache + ); + namedQueryResultsCacheMap.put( regionName, regionAccess ); + legacySecondLevelCacheNames.add( regionName ); + return regionAccess; + } + + protected QueryResultsRegion makeQueryResultsRegion(String regionName) { + return regionFactory.buildQueryResultsRegion( regionName, getSessionFactory() ); + } + + @Override + public Set getCacheRegionNames() { + return regionsByName.keySet(); + } + + @Override + public void evictRegion(String regionName) { + getRegion( regionName ).clear(); + final QueryResultsRegion queryResultsRegionWithDuplicateName = queryResultsRegionsByDuplicateName.get( regionName ); + if ( queryResultsRegionWithDuplicateName != null ) { + queryResultsRegionWithDuplicateName.clear(); + } + } + + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class cls) { + if ( org.hibernate.Cache.class.isAssignableFrom( cls ) ) { + return (T) this; + } + + if ( RegionFactory.class.isAssignableFrom( cls ) ) { + return (T) regionFactory; + } + + throw new PersistenceException( "Hibernate cannot unwrap Cache as " + cls.getName() ); + } + + @Override + public void close() { + for ( Region region : regionsByName.values() ) { + region.destroy(); + } + for ( Region region : queryResultsRegionsByDuplicateName.values() ) { + region.destroy(); + } + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // JPA-defined methods + + @Override + public boolean contains(Class cls, Object primaryKey) { + // JPA + return containsEntity( cls, (Serializable) primaryKey ); + } + + @Override + public void evict(Class cls, Object primaryKey) { + // JPA call + evictEntityData( cls, (Serializable) primaryKey ); + } + + @Override + public void evict(Class cls) { + // JPA + evictEntityData( cls ); + } + + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Deprecations + + @Override + public EntityDataAccess getEntityRegionAccess(NavigableRole rootEntityName) { + return entityAccessMap.get( rootEntityName ); + } + + @Override + public NaturalIdDataAccess getNaturalIdCacheRegionAccessStrategy(NavigableRole rootEntityName) { + return naturalIdAccessMap.get( rootEntityName ); + } + + @Override + public CollectionDataAccess getCollectionRegionAccess(NavigableRole collectionRole) { + return collectionAccessMap.get( collectionRole ); + } + + @Override + public String[] getSecondLevelCacheRegionNames() { + return ArrayHelper.toStringArray( legacySecondLevelCacheNames ); + } + + + @Override + public Set getNaturalIdAccessesInRegion(String regionName) { + return legacyNaturalIdAccessesForRegion.get( regionName ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java index 11204d882f90..e6b89f82ea15 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java @@ -10,11 +10,11 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Arrays; +import java.util.Objects; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.ValueHolder; -import org.hibernate.internal.util.compare.EqualsHelper; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -143,8 +143,8 @@ public boolean equals(Object o) { } final NaturalIdCacheKey other = (NaturalIdCacheKey) o; - return EqualsHelper.equals( entityName, other.entityName ) - && EqualsHelper.equals( tenantId, other.tenantId ) + return Objects.equals( entityName, other.entityName ) + && Objects.equals( tenantId, other.tenantId ) && Arrays.deepEquals( this.naturalIdValues, other.naturalIdValues ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingRegionFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingRegionFactory.java index 0fadf4b98ea2..067db2dec4f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingRegionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingRegionFactory.java @@ -6,19 +6,21 @@ */ package org.hibernate.cache.internal; -import java.util.Properties; +import java.util.Map; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.CacheException; import org.hibernate.cache.NoCacheRegionFactoryAvailableException; -import org.hibernate.cache.spi.CacheDataDescription; -import org.hibernate.cache.spi.CollectionRegion; -import org.hibernate.cache.spi.EntityRegion; -import org.hibernate.cache.spi.NaturalIdRegion; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.spi.CacheTransactionSynchronization; +import org.hibernate.cache.spi.DomainDataRegion; import org.hibernate.cache.spi.QueryResultsRegion; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.spi.TimestampsRegion; import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; /** * Factory used if no caching enabled in config... @@ -38,13 +40,18 @@ public NoCachingRegionFactory() { } @Override - public void start(SessionFactoryOptions settings, Properties properties) throws CacheException { + public void start(SessionFactoryOptions settings, Map configValues) throws CacheException { } @Override public void stop() { } + @Override + public String qualify(String regionName) { + return regionName; + } + @Override public boolean isMinimalPutsEnabledByDefault() { return false; @@ -57,36 +64,29 @@ public AccessType getDefaultAccessType() { @Override public long nextTimestamp() { - return System.currentTimeMillis() / 100; - } - - @Override - public EntityRegion buildEntityRegion(String regionName, Properties properties, CacheDataDescription metadata) - throws CacheException { - throw new NoCacheRegionFactoryAvailableException(); + return System.currentTimeMillis(); } @Override - public NaturalIdRegion buildNaturalIdRegion(String regionName, Properties properties, CacheDataDescription metadata) - throws CacheException { - throw new NoCacheRegionFactoryAvailableException(); + public CacheTransactionSynchronization createTransactionContext(SharedSessionContractImplementor session) { + return new NoCachingTransactionSynchronizationImpl( this ); } @Override - public CollectionRegion buildCollectionRegion( - String regionName, - Properties properties, - CacheDataDescription metadata) throws CacheException { + public DomainDataRegion buildDomainDataRegion( + DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) { throw new NoCacheRegionFactoryAvailableException(); } @Override - public QueryResultsRegion buildQueryResultsRegion(String regionName, Properties properties) throws CacheException { + public QueryResultsRegion buildQueryResultsRegion( + String regionName, SessionFactoryImplementor sessionFactory) { throw new NoCacheRegionFactoryAvailableException(); } @Override - public TimestampsRegion buildTimestampsRegion(String regionName, Properties properties) throws CacheException { + public TimestampsRegion buildTimestampsRegion( + String regionName, SessionFactoryImplementor sessionFactory) { throw new NoCacheRegionFactoryAvailableException(); } } 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 new file mode 100644 index 000000000000..b6914f3a484a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.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.cache.internal; + +import org.hibernate.cache.spi.AbstractCacheTransactionSynchronization; +import org.hibernate.cache.spi.RegionFactory; + +/** + * @author Steve Ebersole + */ +public class NoCachingTransactionSynchronizationImpl extends AbstractCacheTransactionSynchronization { + public NoCachingTransactionSynchronizationImpl(RegionFactory regionFactory) { + super( regionFactory ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/QueryResultsCacheImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/QueryResultsCacheImpl.java new file mode 100644 index 000000000000..dbdf38940473 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/QueryResultsCacheImpl.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.internal; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.cache.spi.QueryKey; +import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.cache.spi.QueryResultsRegion; +import org.hibernate.cache.spi.QuerySpacesHelper; +import org.hibernate.cache.spi.TimestampsCache; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.type.Type; +import org.hibernate.type.TypeHelper; + +/** + * The standard implementation of the Hibernate QueryCache interface. Works + * hind-in-hand with {@link TimestampsCache} to help in recognizing + * stale query results. + * + * @author Gavin King + * @author Steve Ebersole + */ +public class QueryResultsCacheImpl implements QueryResultsCache { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( QueryResultsCacheImpl.class ); + + private static final boolean DEBUGGING = LOG.isDebugEnabled(); + private static final boolean TRACING = LOG.isTraceEnabled(); + + private final QueryResultsRegion cacheRegion; + private final TimestampsCache timestampsCache; + + QueryResultsCacheImpl( + QueryResultsRegion cacheRegion, + TimestampsCache timestampsCache) { + this.cacheRegion = cacheRegion; + this.timestampsCache = timestampsCache; + } + + @Override + public QueryResultsRegion getRegion() { + return cacheRegion; + } + + @Override + @SuppressWarnings({ "unchecked" }) + public boolean put( + final QueryKey key, + final List results, + final Type[] returnTypes, + final SharedSessionContractImplementor session) throws HibernateException { + if ( DEBUGGING ) { + LOG.debugf( "Caching query results in region: %s; timestamp=%s", cacheRegion.getName(), session.getTransactionStartTimestamp() ); + } + + final List resultsCopy = CollectionHelper.arrayList( results.size() ); + + final boolean isSingleResult = returnTypes.length == 1; + for ( Object aResult : results ) { + final Serializable resultRowForCache; + if ( isSingleResult ) { + resultRowForCache = returnTypes[0].disassemble( aResult, session, null ); + } + else { + resultRowForCache = TypeHelper.disassemble( (Object[]) aResult, returnTypes, null, session, null ); + } + resultsCopy.add( resultRowForCache ); + if ( TRACING ) { + logCachedResultRowDetails( returnTypes, aResult ); + } + } + + if ( TRACING ) { + logCachedResultDetails( key, null, returnTypes, resultsCopy ); + } + + final CacheItem cacheItem = new CacheItem( + session.getTransactionStartTimestamp(), + resultsCopy + ); + + try { + session.getEventListenerManager().cachePutStart(); + cacheRegion.putIntoCache( key, cacheItem, session ); + } + finally { + session.getEventListenerManager().cachePutEnd(); + } + + return true; + } + + private static void logCachedResultDetails(QueryKey key, Set querySpaces, Type[] returnTypes, List result) { + if ( !TRACING ) { + return; + } + LOG.trace( "key.hashCode=" + key.hashCode() ); + LOG.trace( "querySpaces=" + querySpaces ); + if ( returnTypes == null || returnTypes.length == 0 ) { + LOG.trace( + "Unexpected returnTypes is " + + ( returnTypes == null ? "null" : "empty" ) + "! result" + + ( result == null ? " is null" : ".size()=" + result.size() ) + ); + } + else { + final StringBuilder returnTypeInfo = new StringBuilder(); + for ( Type returnType : returnTypes ) { + returnTypeInfo.append( "typename=" ) + .append( returnType.getName() ) + .append( " class=" ) + .append( returnType.getReturnedClass().getName() ) + .append( ' ' ); + } + LOG.trace( "unexpected returnTypes is " + returnTypeInfo.toString() + "! result" ); + } + } + + @Override + public List get( + QueryKey key, + Set spaces, + final Type[] returnTypes, + SharedSessionContractImplementor session) { + return get( + key, + QuerySpacesHelper.INSTANCE.toStringArray( spaces ), + returnTypes, + session + ); + } + + @Override + @SuppressWarnings({ "unchecked" }) + public List get( + final QueryKey key, + final String[] spaces, + final Type[] returnTypes, + final SharedSessionContractImplementor session) { + if ( DEBUGGING ) { + LOG.debugf( "Checking cached query results in region: %s", cacheRegion.getName() ); + } + + final CacheItem cacheItem = getCachedData( key, session ); + if ( cacheItem == null ) { + if ( DEBUGGING ) { + LOG.debug( "Query results were not found in cache" ); + } + return null; + } + + if ( !timestampsCache.isUpToDate( spaces, cacheItem.timestamp, session ) ) { + if ( DEBUGGING ) { + LOG.debug( "Cached query results were not up-to-date" ); + } + return null; + } + + if ( DEBUGGING ) { + LOG.debug( "Returning cached query results" ); + } + + final boolean singleResult = returnTypes.length == 1; + for ( int i = 0; i < cacheItem.results.size(); i++ ) { + if ( singleResult ) { + returnTypes[0].beforeAssemble( (Serializable) cacheItem.results.get( i ), session ); + } + else { + TypeHelper.beforeAssemble( (Serializable[]) cacheItem.results.get( i ), returnTypes, session ); + } + } + + return assembleCachedResult( key, cacheItem.results, singleResult, returnTypes, session ); + } + + private CacheItem getCachedData(QueryKey key, SharedSessionContractImplementor session) { + CacheItem cachedItem = null; + try { + session.getEventListenerManager().cacheGetStart(); + cachedItem = (CacheItem) cacheRegion.getFromCache( key, session ); + } + finally { + session.getEventListenerManager().cacheGetEnd( cachedItem != null ); + } + return cachedItem; + } + + @SuppressWarnings("unchecked") + private List assembleCachedResult( + final QueryKey key, + final List cached, + boolean singleResult, + final Type[] returnTypes, + final SharedSessionContractImplementor session) throws HibernateException { + + final List result = new ArrayList( cached.size() ); + if ( singleResult ) { + for ( Object aCached : cached ) { + result.add( returnTypes[0].assemble( (Serializable) aCached, session, null ) ); + } + } + else { + for ( int i = 0; i < cached.size(); i++ ) { + result.add( + TypeHelper.assemble( (Serializable[]) cached.get( i ), returnTypes, session, null ) + ); + if ( TRACING ) { + logCachedResultRowDetails( returnTypes, result.get( i ) ); + } + } + } + return result; + } + + private static void logCachedResultRowDetails(Type[] returnTypes, Object result) { + logCachedResultRowDetails( + returnTypes, + ( result instanceof Object[] ? (Object[]) result : new Object[] { result } ) + ); + } + + private static void logCachedResultRowDetails(Type[] returnTypes, Object[] tuple) { + if ( !TRACING ) { + return; + } + if ( tuple == null ) { + LOG.tracef( + "tuple is null; returnTypes is %s", + returnTypes == null ? "null" : "Type[" + returnTypes.length + "]" + ); + if ( returnTypes != null && returnTypes.length > 1 ) { + LOG.trace( + "Unexpected result tuple! tuple is null; should be Object[" + + returnTypes.length + "]!" + ); + } + } + else { + if ( returnTypes == null || returnTypes.length == 0 ) { + LOG.trace( + "Unexpected result tuple! tuple is null; returnTypes is " + + ( returnTypes == null ? "null" : "empty" ) + ); + } + LOG.tracef( + "tuple is Object[%s]; returnTypes is %s", + tuple.length, + returnTypes == null ? "null" : "Type[" + returnTypes.length + "]" + ); + if ( returnTypes != null && tuple.length != returnTypes.length ) { + LOG.trace( + "Unexpected tuple length! transformer= expected=" + + returnTypes.length + " got=" + tuple.length + ); + } + else { + for ( int j = 0; j < tuple.length; j++ ) { + if ( tuple[j] != null && returnTypes != null + && ! returnTypes[j].getReturnedClass().isInstance( tuple[j] ) ) { + LOG.trace( + "Unexpected tuple value type! transformer= expected=" + + returnTypes[j].getReturnedClass().getName() + + " got=" + + tuple[j].getClass().getName() + ); + } + } + } + } + } + + @Override + public String toString() { + return "QueryResultsCache(" + cacheRegion.getName() + ')'; + } + + public static class CacheItem implements Serializable { + private final long timestamp; + private final List results; + + CacheItem(long timestamp, List results) { + this.timestamp = timestamp; + this.results = results; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/RegionFactoryInitiator.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/RegionFactoryInitiator.java index a01948ddd440..d2261b7330b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/RegionFactoryInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/RegionFactoryInitiator.java @@ -6,12 +6,13 @@ */ package org.hibernate.cache.internal; +import java.util.Collection; import java.util.Map; import java.util.Properties; import org.hibernate.boot.registry.StandardServiceInitiator; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cfg.AvailableSettings; import org.hibernate.internal.CoreLogging; @@ -19,6 +20,9 @@ import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.spi.ServiceRegistryImplementor; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + /** * Initiator for the {@link RegionFactory} service. * @@ -39,62 +43,89 @@ public Class getServiceInitiated() { } @Override - @SuppressWarnings({ "unchecked" }) public RegionFactory initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + final RegionFactory regionFactory = resolveRegionFactory( configurationValues, registry ); + + LOG.debugf( "Cache region factory : %s", regionFactory.getClass().getName() ); + + return regionFactory; + } + + + @SuppressWarnings({"unchecked", "WeakerAccess"}) + protected RegionFactory resolveRegionFactory(Map configurationValues, ServiceRegistryImplementor registry) { final Properties p = new Properties(); - if (configurationValues != null) { - p.putAll( configurationValues ); - } - - final boolean useSecondLevelCache = ConfigurationHelper.getBoolean( + p.putAll( configurationValues ); + + final Boolean useSecondLevelCache = ConfigurationHelper.getBooleanWrapper( AvailableSettings.USE_SECOND_LEVEL_CACHE, configurationValues, - true + null ); - final boolean useQueryCache = ConfigurationHelper.getBoolean( + final Boolean useQueryCache = ConfigurationHelper.getBooleanWrapper( AvailableSettings.USE_QUERY_CACHE, - configurationValues + configurationValues, + null ); - RegionFactory regionFactory = NoCachingRegionFactory.INSTANCE; - - // The cache provider is needed when we either have second-level cache enabled - // or query cache enabled. Note that useSecondLevelCache is enabled by default - if ( useSecondLevelCache || useQueryCache ) { - final Object setting = configurationValues != null - ? configurationValues.get( AvailableSettings.CACHE_REGION_FACTORY ) - : null; - regionFactory = registry.getService( StrategySelector.class ).resolveStrategy( - RegionFactory.class, - setting, - NoCachingRegionFactory.INSTANCE, - new StrategyCreatorRegionFactoryImpl( p ) - ); + // We should immediately return NoCachingRegionFactory if either: + // 1) both are explicitly FALSE + // 2) USE_SECOND_LEVEL_CACHE is FALSE and USE_QUERY_CACHE is null + if ( useSecondLevelCache != null && useSecondLevelCache == FALSE ) { + if ( useQueryCache == null || useQueryCache == FALSE ) { + return NoCachingRegionFactory.INSTANCE; + } } - LOG.debugf( "Cache region factory : %s", regionFactory.getClass().getName() ); + final Object setting = configurationValues.get( AvailableSettings.CACHE_REGION_FACTORY ); - return regionFactory; - } + final StrategySelector selector = registry.getService( StrategySelector.class ); + final Collection> implementors = selector.getRegisteredStrategyImplementors( RegionFactory.class ); - /** - * Map legacy names unto the new corollary. - * - * TODO: temporary hack for org.hibernate.cfg.SettingsFactory.createRegionFactory() - * - * @param name The (possibly legacy) factory name - * - * @return The factory name to use. - */ - public static String mapLegacyNames(final String name) { - if ( "org.hibernate.cache.EhCacheRegionFactory".equals( name ) ) { - return "org.hibernate.cache.ehcache.EhCacheRegionFactory"; + if ( setting == null && implementors.size() != 1 ) { + // if either are explicitly defined as TRUE we need a RegionFactory + if ( ( useSecondLevelCache != null && useSecondLevelCache == TRUE ) + || ( useQueryCache != null && useQueryCache == TRUE ) ) { + throw new CacheException( "Caching was explicitly requested, but no RegionFactory was defined and there is not a single registered RegionFactory" ); + } + } + + final RegionFactory regionFactory = registry.getService( StrategySelector.class ).resolveStrategy( + RegionFactory.class, + setting, + (RegionFactory) null, + new StrategyCreatorRegionFactoryImpl( p ) + ); + + if ( regionFactory != null ) { + return regionFactory; + } + + + final RegionFactory fallback = getFallback( configurationValues, registry ); + if ( fallback != null ) { + return fallback; } - if ( "org.hibernate.cache.SingletonEhCacheRegionFactory".equals( name ) ) { - return "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"; + if ( implementors.size() == 1 ) { + final RegionFactory registeredFactory = selector.resolveStrategy( RegionFactory.class, implementors.iterator().next() ); + configurationValues.put( AvailableSettings.CACHE_REGION_FACTORY, registeredFactory ); + configurationValues.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" ); + + return registeredFactory; } + else { + LOG.debugf( + "Cannot default RegionFactory based on registered strategies as `%s` RegionFactory strategies were registered", + implementors + ); + } + + return NoCachingRegionFactory.INSTANCE; + } - return name; + @SuppressWarnings({"WeakerAccess", "unused"}) + protected RegionFactory getFallback(Map configurationValues, ServiceRegistryImplementor registry) { + return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCache.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCache.java deleted file mode 100644 index f0b7010f5f7f..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCache.java +++ /dev/null @@ -1,348 +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.cache.internal; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.Set; -import javax.persistence.EntityNotFoundException; - -import org.hibernate.HibernateException; -import org.hibernate.UnresolvableObjectException; -import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.QueryCache; -import org.hibernate.cache.spi.QueryKey; -import org.hibernate.cache.spi.QueryResultsRegion; -import org.hibernate.cache.spi.RegionFactory; -import org.hibernate.cache.spi.UpdateTimestampsCache; -import org.hibernate.engine.spi.CacheImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.type.Type; -import org.hibernate.type.TypeHelper; - -/** - * The standard implementation of the Hibernate QueryCache interface. This - * implementation is very good at recognizing stale query results and - * and re-running queries when it detects this condition, re-caching the new - * results. - * - * @author Gavin King - * @author Steve Ebersole - */ -public class StandardQueryCache implements QueryCache { - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( StandardQueryCache.class ); - - private static final boolean DEBUGGING = LOG.isDebugEnabled(); - private static final boolean TRACING = LOG.isTraceEnabled(); - - private final QueryResultsRegion cacheRegion; - private final UpdateTimestampsCache updateTimestampsCache; - - /** - * Constructs a StandardQueryCache instance - * - * @param settings The SessionFactory settings. - * @param props Any properties - * @param updateTimestampsCache The update-timestamps cache to use. - * @param regionName The base query cache region name - */ - public StandardQueryCache( - final SessionFactoryOptions settings, - final Properties props, - final UpdateTimestampsCache updateTimestampsCache, - final String regionName) { - String regionNameToUse = regionName; - if ( regionNameToUse == null ) { - regionNameToUse = StandardQueryCache.class.getName(); - } - final String prefix = settings.getCacheRegionPrefix(); - if ( prefix != null ) { - regionNameToUse = prefix + '.' + regionNameToUse; - } - LOG.startingQueryCache( regionNameToUse ); - - this.cacheRegion = settings.getServiceRegistry().getService( RegionFactory.class ).buildQueryResultsRegion( - regionNameToUse, - props - ); - this.updateTimestampsCache = updateTimestampsCache; - } - - public StandardQueryCache(QueryResultsRegion cacheRegion, CacheImplementor cacheManager) { - LOG.startingQueryCache( cacheRegion.getName() ); - this.cacheRegion = cacheRegion; - this.updateTimestampsCache = cacheManager.getUpdateTimestampsCache(); - } - - @Override - public QueryResultsRegion getRegion() { - return cacheRegion; - } - - @Override - public void destroy() { - try { - cacheRegion.destroy(); - } - catch ( Exception e ) { - LOG.unableToDestroyQueryCache( cacheRegion.getName(), e.getMessage() ); - } - } - - @Override - public void clear() throws CacheException { - cacheRegion.evictAll(); - } - - @Override - @SuppressWarnings({ "unchecked" }) - public boolean put( - final QueryKey key, - final Type[] returnTypes, - final List result, - final boolean isNaturalKeyLookup, - final SharedSessionContractImplementor session) throws HibernateException { - if ( isNaturalKeyLookup && result.isEmpty() ) { - return false; - } - if ( DEBUGGING ) { - LOG.debugf( "Caching query results in region: %s; timestamp=%s", cacheRegion.getName(), session.getTimestamp() ); - } - - final List cacheable = new ArrayList( result.size() + 1 ); - if ( TRACING ) { - logCachedResultDetails( key, null, returnTypes, cacheable ); - } - cacheable.add( session.getTimestamp() ); - - final boolean isSingleResult = returnTypes.length == 1; - for ( Object aResult : result ) { - final Serializable cacheItem = isSingleResult - ? returnTypes[0].disassemble( aResult, session, null ) - : TypeHelper.disassemble( (Object[]) aResult, returnTypes, null, session, null ); - cacheable.add( cacheItem ); - if ( TRACING ) { - logCachedResultRowDetails( returnTypes, aResult ); - } - } - - try { - session.getEventListenerManager().cachePutStart(); - cacheRegion.put( session, key, cacheable ); - } - finally { - session.getEventListenerManager().cachePutEnd(); - } - - return true; - } - - @Override - @SuppressWarnings({ "unchecked" }) - public List get( - final QueryKey key, - final Type[] returnTypes, - final boolean isNaturalKeyLookup, - final Set spaces, - final SharedSessionContractImplementor session) throws HibernateException { - if ( DEBUGGING ) { - LOG.debugf( "Checking cached query results in region: %s", cacheRegion.getName() ); - } - - final List cacheable = getCachedResults( key, session ); - if ( TRACING ) { - logCachedResultDetails( key, spaces, returnTypes, cacheable ); - } - if ( cacheable == null ) { - if ( DEBUGGING ) { - LOG.debug( "Query results were not found in cache" ); - } - return null; - } - - final Long timestamp = (Long) cacheable.get( 0 ); - if ( !isNaturalKeyLookup && !isUpToDate( spaces, timestamp, session ) ) { - if ( DEBUGGING ) { - LOG.debug( "Cached query results were not up-to-date" ); - } - return null; - } - - if ( DEBUGGING ) { - LOG.debug( "Returning cached query results" ); - } - final boolean singleResult = returnTypes.length == 1; - for ( int i = 1; i < cacheable.size(); i++ ) { - if ( singleResult ) { - returnTypes[0].beforeAssemble( (Serializable) cacheable.get( i ), session ); - } - else { - TypeHelper.beforeAssemble( (Serializable[]) cacheable.get( i ), returnTypes, session ); - } - } - - return assembleCachedResult(key, cacheable, isNaturalKeyLookup, singleResult, returnTypes, session); - } - - private List assembleCachedResult( - final QueryKey key, - final List cacheable, - final boolean isNaturalKeyLookup, - boolean singleResult, - final Type[] returnTypes, - final SharedSessionContractImplementor session) throws HibernateException { - - try { - final List result = new ArrayList( cacheable.size() - 1 ); - if ( singleResult ) { - for ( int i = 1; i < cacheable.size(); i++ ) { - result.add( returnTypes[0].assemble( (Serializable) cacheable.get( i ), session, null ) ); - } - } - else { - for ( int i = 1; i < cacheable.size(); i++ ) { - result.add( - TypeHelper.assemble( (Serializable[]) cacheable.get( i ), returnTypes, session, null ) - ); - if ( TRACING ) { - logCachedResultRowDetails( returnTypes, result.get( i - 1 ) ); - } - } - } - return result; - } - catch ( RuntimeException ex ) { - if ( isNaturalKeyLookup ) { - // potentially perform special handling for natural-id look ups. - if ( UnresolvableObjectException.class.isInstance( ex ) - || EntityNotFoundException.class.isInstance( ex ) ) { - if ( DEBUGGING ) { - LOG.debug( "Unable to reassemble cached natural-id query result" ); - } - cacheRegion.evict( key ); - - // EARLY EXIT ! - return null; - } - } - throw ex; - } - } - - private List getCachedResults(QueryKey key, SharedSessionContractImplementor session) { - List cacheable = null; - try { - session.getEventListenerManager().cacheGetStart(); - cacheable = (List) cacheRegion.get( session, key ); - } - finally { - session.getEventListenerManager().cacheGetEnd( cacheable != null ); - } - return cacheable; - } - - - protected boolean isUpToDate(Set spaces, Long timestamp, SharedSessionContractImplementor session) { - if ( DEBUGGING ) { - LOG.debugf( "Checking query spaces are up-to-date: %s", spaces ); - } - return updateTimestampsCache.isUpToDate( spaces, timestamp, session ); - } - - @Override - public String toString() { - return "StandardQueryCache(" + cacheRegion.getName() + ')'; - } - - private static void logCachedResultDetails(QueryKey key, Set querySpaces, Type[] returnTypes, List result) { - if ( !TRACING ) { - return; - } - LOG.trace( "key.hashCode=" + key.hashCode() ); - LOG.trace( "querySpaces=" + querySpaces ); - if ( returnTypes == null || returnTypes.length == 0 ) { - LOG.trace( - "Unexpected returnTypes is " - + ( returnTypes == null ? "null" : "empty" ) + "! result" - + ( result == null ? " is null" : ".size()=" + result.size() ) - ); - } - else { - final StringBuilder returnTypeInfo = new StringBuilder(); - for ( Type returnType : returnTypes ) { - returnTypeInfo.append( "typename=" ) - .append( returnType.getName() ) - .append( " class=" ) - .append( returnType.getReturnedClass().getName() ) - .append( ' ' ); - } - LOG.trace( "unexpected returnTypes is " + returnTypeInfo.toString() + "! result" ); - } - } - - private static void logCachedResultRowDetails(Type[] returnTypes, Object result) { - logCachedResultRowDetails( - returnTypes, - ( result instanceof Object[] ? (Object[]) result : new Object[] { result } ) - ); - } - - private static void logCachedResultRowDetails(Type[] returnTypes, Object[] tuple) { - if ( !TRACING ) { - return; - } - if ( tuple == null ) { - LOG.tracef( - "tuple is null; returnTypes is %s", - returnTypes == null ? "null" : "Type[" + returnTypes.length + "]" - ); - if ( returnTypes != null && returnTypes.length > 1 ) { - LOG.trace( - "Unexpected result tuple! tuple is null; should be Object[" - + returnTypes.length + "]!" - ); - } - } - else { - if ( returnTypes == null || returnTypes.length == 0 ) { - LOG.trace( - "Unexpected result tuple! tuple is null; returnTypes is " - + ( returnTypes == null ? "null" : "empty" ) - ); - } - LOG.tracef( - "tuple is Object[%s]; returnTypes is %s", - tuple.length, - returnTypes == null ? "null" : "Type[" + returnTypes.length + "]" - ); - if ( returnTypes != null && tuple.length != returnTypes.length ) { - LOG.trace( - "Unexpected tuple length! transformer= expected=" - + returnTypes.length + " got=" + tuple.length - ); - } - else { - for ( int j = 0; j < tuple.length; j++ ) { - if ( tuple[j] != null && returnTypes != null - && ! returnTypes[j].getReturnedClass().isInstance( tuple[j] ) ) { - LOG.trace( - "Unexpected tuple value type! transformer= expected=" - + returnTypes[j].getReturnedClass().getName() - + " got=" - + tuple[j].getClass().getName() - ); - } - } - } - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCacheFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCacheFactory.java deleted file mode 100644 index f0d0b4d0f2fe..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCacheFactory.java +++ /dev/null @@ -1,28 +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.cache.internal; - -import org.hibernate.cache.spi.QueryCache; -import org.hibernate.cache.spi.QueryCacheFactory; -import org.hibernate.cache.spi.QueryResultsRegion; -import org.hibernate.engine.spi.CacheImplementor; - -/** - * Standard Hibernate implementation of the QueryCacheFactory interface. Returns instances of - * {@link StandardQueryCache}. - */ -public class StandardQueryCacheFactory implements QueryCacheFactory { - /** - * Singleton access - */ - public static final StandardQueryCacheFactory INSTANCE = new StandardQueryCacheFactory(); - - @Override - public QueryCache buildQueryCache(QueryResultsRegion region, CacheImplementor cacheManager) { - return new StandardQueryCache( region, cacheManager ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardTimestampsCacheFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardTimestampsCacheFactory.java new file mode 100644 index 000000000000..2d44b050faa5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardTimestampsCacheFactory.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.cache.internal; + +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cache.spi.TimestampsCache; +import org.hibernate.cache.spi.TimestampsCacheFactory; +import org.hibernate.cache.spi.TimestampsRegion; + +/** + * Standard Hibernate implementation of the QueryCacheFactory interface. Returns instances of + * {@link QueryResultsCacheImpl}. + */ +public class StandardTimestampsCacheFactory implements TimestampsCacheFactory { + /** + * Singleton access + */ + public static final StandardTimestampsCacheFactory INSTANCE = new StandardTimestampsCacheFactory(); + + @Override + public TimestampsCache buildTimestampsCache( + CacheImplementor cacheManager, + TimestampsRegion timestampsRegion) { + return new TimestampsCacheEnabledImpl( timestampsRegion ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/TimestampsCacheDisabledImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/TimestampsCacheDisabledImpl.java new file mode 100644 index 000000000000..4651d040494a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/TimestampsCacheDisabledImpl.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.cache.internal; + +import org.hibernate.cache.spi.TimestampsCache; +import org.hibernate.cache.spi.TimestampsRegion; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +import org.jboss.logging.Logger; + +/** + * TimestampsRegionAccess implementation for cases where query results caching + * (or second level caching overall) is disabled. + * + * @author Steve Ebersole + */ +public class TimestampsCacheDisabledImpl implements TimestampsCache { + private static final Logger log = Logger.getLogger( TimestampsCacheDisabledImpl.class ); + + @Override + public TimestampsRegion getRegion() { + return null; + } + + @Override + public void preInvalidate(String[] spaces, SharedSessionContractImplementor session) { + log.trace( "TimestampsRegionAccess#preInvalidate - disabled" ); + } + + @Override + public void invalidate(String[] spaces, SharedSessionContractImplementor session) { + log.trace( "TimestampsRegionAccess#invalidate - disabled" ); + } + + @Override + public boolean isUpToDate( + String[] spaces, + Long timestamp, + SharedSessionContractImplementor session) { + log.trace( "TimestampsRegionAccess#isUpToDate - disabled" ); + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/TimestampsCacheEnabledImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/TimestampsCacheEnabledImpl.java new file mode 100644 index 000000000000..f9f2b85863a4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/TimestampsCacheEnabledImpl.java @@ -0,0 +1,146 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.internal; + +import java.io.Serializable; + +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.TimestampsRegion; +import org.hibernate.cache.spi.TimestampsCache; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +import org.jboss.logging.Logger; + +/** + * Standard implementation of TimestampsCache + * + * @author Steve Ebersole + */ +public class TimestampsCacheEnabledImpl implements TimestampsCache { + private static final Logger log = Logger.getLogger( TimestampsCacheEnabledImpl.class ); + private static final boolean DEBUG_ENABLED = log.isDebugEnabled(); + + private final TimestampsRegion timestampsRegion; + + public TimestampsCacheEnabledImpl(TimestampsRegion timestampsRegion) { + this.timestampsRegion = timestampsRegion; + } + + @Override + public TimestampsRegion getRegion() { + return timestampsRegion; + } + + @Override + public void preInvalidate( + String[] spaces, + SharedSessionContractImplementor session) { + final SessionFactoryImplementor factory = session.getFactory(); + final RegionFactory regionFactory = factory.getCache().getRegionFactory(); + + final boolean stats = factory.getStatistics().isStatisticsEnabled(); + + final Long ts = regionFactory.nextTimestamp() + regionFactory.getTimeout(); + + for ( Serializable space : spaces ) { + if ( DEBUG_ENABLED ) { + log.debugf( "Pre-invalidating space [%s], timestamp: %s", space, ts ); + } + + try { + session.getEventListenerManager().cachePutStart(); + + //put() has nowait semantics, is this really appropriate? + //note that it needs to be async replication, never local or sync + timestampsRegion.putIntoCache( space, ts, session ); + } + finally { + session.getEventListenerManager().cachePutEnd(); + } + + if ( stats ) { + factory.getStatistics().updateTimestampsCachePut(); + } + } + } + + @Override + public void invalidate( + String[] spaces, + SharedSessionContractImplementor session) { + final boolean stats = session.getFactory().getStatistics().isStatisticsEnabled(); + + final Long ts = session.getFactory().getCache().getRegionFactory().nextTimestamp(); + + for (Serializable space : spaces) { + if ( DEBUG_ENABLED ) { + log.debugf( "Invalidating space [%s], timestamp: %s", space, ts ); + } + + try { + session.getEventListenerManager().cachePutStart(); + timestampsRegion.putIntoCache( space, ts, session ); + } + finally { + session.getEventListenerManager().cachePutEnd(); + + if ( stats ) { + session.getFactory().getStatistics().updateTimestampsCachePut(); + } + } + } + } + + @Override + public boolean isUpToDate( + String[] spaces, + Long timestamp, + SharedSessionContractImplementor session) { + final boolean stats = session.getFactory().getStatistics().isStatisticsEnabled(); + + for ( Serializable space : spaces ) { + final Long lastUpdate = getLastUpdateTimestampForSpace( space, session ); + if ( lastUpdate == null ) { + // the last update timestamp for the given space was evicted from the + // cache or there have been no writes to it since startup + if ( stats ) { + session.getFactory().getStatistics().updateTimestampsCacheMiss(); + } + } + else { + if ( DEBUG_ENABLED ) { + log.debugf( + "[%s] last update timestamp: %s", + space, + lastUpdate + ", result set timestamp: " + timestamp + ); + } + if ( stats ) { + session.getFactory().getStatistics().updateTimestampsCacheHit(); + } + if ( lastUpdate >= timestamp ) { + return false; + } + } + } + return true; + } + + private Long getLastUpdateTimestampForSpace(Serializable space, SharedSessionContractImplementor session) { + Long ts = null; + try { + session.getEventListenerManager().cacheGetStart(); + ts = (Long) timestampsRegion.getFromCache( space, session ); + } + finally { + session.getEventListenerManager().cacheGetEnd( ts != null ); + } + return ts; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/AbstractCacheTransactionSynchronization.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/AbstractCacheTransactionSynchronization.java new file mode 100644 index 000000000000..b686893bcd0b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/AbstractCacheTransactionSynchronization.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.cache.spi; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractCacheTransactionSynchronization implements CacheTransactionSynchronization { + private long lastTransactionCompletionTimestamp; + private final RegionFactory regionFactory; + + public AbstractCacheTransactionSynchronization(RegionFactory regionFactory) { + // prime the timestamp for any non-transactional access - until (if) we + // later join a new txn + this.lastTransactionCompletionTimestamp = regionFactory.nextTimestamp(); + this.regionFactory = regionFactory; + } + + @Override + public long getCurrentTransactionStartTimestamp() { + return lastTransactionCompletionTimestamp; + } + + @Override + public final void transactionJoined() { + // reset the timestamp + this.lastTransactionCompletionTimestamp = regionFactory.nextTimestamp(); + processTransactionJoin(); + } + + private void processTransactionJoin() { + // by default, nothing to do. + } + + @Override + public final void transactionCompleting() { + processTransactionCompleting(); + } + + private void processTransactionCompleting() { + // by default, nothing to do. + } + + @Override + public void transactionCompleted(boolean successful) { + // reset the timestamp for any non-transactional access after this + // point - until (if) we later join a new txn +// this.lastTransactionCompletionTimestamp = regionFactory.nextTimestamp(); + + processTransactionCompleted( successful ); + } + + private void processTransactionCompleted(boolean successful) { + // by default, nothing to do. + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/AbstractRegionFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/AbstractRegionFactory.java new file mode 100644 index 000000000000..73260ca11a87 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/AbstractRegionFactory.java @@ -0,0 +1,157 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.CacheException; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.support.RegionNameQualifier; +import org.hibernate.cache.spi.support.SimpleTimestamper; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractRegionFactory implements RegionFactory { + + private final AtomicBoolean started = new AtomicBoolean( false ); + + /** + * Legacy names that used to be the default for the query results region. + */ + public static final List LEGACY_QUERY_RESULTS_REGION_UNQUALIFIED_NAMES = + Collections.unmodifiableList( Arrays.asList( + "org.hibernate.cache.spi.QueryResultsRegion", + "org.hibernate.cache.internal.StandardQueryCache" + ) ); + + /** + * Legacy names that used to be the default for the update timestamps region. + */ + public static final List LEGACY_UPDATE_TIMESTAMPS_REGION_UNQUALIFIED_NAMES = + Collections.unmodifiableList( Arrays.asList( + "org.hibernate.cache.spi.TimestampsRegion", + "org.hibernate.cache.spi.UpdateTimestampsCache" + ) ); + + private Exception startingException; + + private SessionFactoryOptions options; + + + protected boolean isStarted() { + if ( started.get() ) { + assert options != null; + return true; + } + else { + assert options == null; + throw new IllegalStateException( "Cache provider not started", startingException ); + } + } + + protected void verifyStarted() { + if ( ! verifiedStartStatus() ) { + throw new IllegalStateException( "Cache provider not started", startingException ); + } + } + + protected boolean verifiedStartStatus() { + if ( started.get() ) { + assert options != null; + return true; + } + else { + assert options == null; + return false; + } + } + + protected SessionFactoryOptions getOptions() { + verifyStarted(); + return options; + } + + @Override + public final void start(SessionFactoryOptions settings, Map configValues) throws CacheException { + if ( started.compareAndSet( false, true ) ) { + synchronized (this) { + this.options = settings; + try { + prepareForUse( settings, configValues ); + startingException = null; + } + catch ( Exception e ) { + options = null; + started.set( false ); + startingException = e; + } + } + } + else { + SecondLevelCacheLogger.INSTANCE.attemptToStartAlreadyStartedCacheProvider(); + } + } + + protected abstract void prepareForUse(SessionFactoryOptions settings, Map configValues); + + @Override + public final void stop() { + if ( started.compareAndSet( true, false ) ) { + synchronized ( this ) { + try { + releaseFromUse(); + } + finally { + options = null; + startingException = null; + } + } + } + else { + SecondLevelCacheLogger.INSTANCE.attemptToStopAlreadyStoppedCacheProvider(); + } + } + + protected abstract void releaseFromUse(); + + @Override + public boolean isMinimalPutsEnabledByDefault() { + return false; + } + + @Override + public AccessType getDefaultAccessType() { + return AccessType.READ_WRITE; + } + + @Override + public String qualify(String regionName) { + return RegionNameQualifier.INSTANCE.qualify( regionName, options ); + } + + @Override + public CacheTransactionSynchronization createTransactionContext(SharedSessionContractImplementor session) { + return new StandardCacheTransactionSynchronization( this ); + } + + @Override + public long nextTimestamp() { + return SimpleTimestamper.next(); + } + + @Override + public long getTimeout() { + return SimpleTimestamper.timeOut(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheDataDescription.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheDataDescription.java deleted file mode 100644 index 850075284cb2..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheDataDescription.java +++ /dev/null @@ -1,49 +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.cache.spi; - -import java.util.Comparator; - -import org.hibernate.type.Type; - -/** - * Describes attributes regarding the type of data to be cached. - * - * @author Steve Ebersole - */ -public interface CacheDataDescription { - /** - * Is the data marked as being mutable? - * - * @return {@code true} if the data is mutable; {@code false} otherwise. - */ - public boolean isMutable(); - - /** - * Is the data to be cached considered versioned? - * - * If {@code true}, it is illegal for {@link #getVersionComparator} to return {@code null} - * or an instance of {@link org.hibernate.type.descriptor.java.IncomparableComparator}. - * - * @return {@code true} if the data is versioned; {@code false} otherwise. - */ - public boolean isVersioned(); - - /** - * Get the comparator used to compare two different version values. May return {@code null} if - * {@link #isVersioned()} returns false. - * - * @return The comparator for versions, or {@code null} - */ - public Comparator getVersionComparator(); - - /** - * @return Type of the key that will be used as the key in the cache, or {@code null} if the natural comparison - * ({@link Object#hashCode()} and {@link Object#equals(Object)} methods should be used. - */ - Type getKeyType(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java new file mode 100644 index 000000000000..c77b13db7b59 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java @@ -0,0 +1,260 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +import java.io.Serializable; +import java.util.Locale; +import java.util.Set; + +import org.hibernate.Cache; +import org.hibernate.HibernateException; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.service.Service; + +/** + * SPI contract for Hibernate's second-level cache engine + * + * @since 4.1 + * + * @author Strong Liu + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public interface CacheImplementor extends Service, Cache, org.hibernate.engine.spi.CacheImplementor, Serializable { + @Override + SessionFactoryImplementor getSessionFactory(); + + /** + * The underlying RegionFactory in use. + * + * @apiNote CacheImplementor acts partially as a wrapper for details + * of interacting with the configured RegionFactory. Care should + * be taken when accessing the RegionFactory directly. + */ + RegionFactory getRegionFactory(); + + /** + * An initialization phase allowing the caching provider to prime itself + * from the passed configs + * + * @since 5.3 + */ + void prime(Set cacheRegionConfigs); + + /** + * Get a cache Region by name. If there is both a {@link DomainDataRegion} + * and a {@link QueryResultsRegion} with the specified name, then the + * {@link DomainDataRegion} will be returned. + * + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed + * + * @since 5.3 + */ + Region getRegion(String regionName); + + /** + * The unqualified name of all regions. Intended for use with {@link #getRegion} + * + * @since 5.3 + */ + Set getCacheRegionNames(); + + /** + * Find the cache data access strategy for Hibernate's timestamps cache. + * Will return {@code null} if Hibernate is not configured for query result caching + * + * @since 5.3 + */ + TimestampsCache getTimestampsCache(); + + /** + * Access to the "default" region used to store query results when caching + * was requested but no region was explicitly named. Will return {@code null} + * if Hibernate is not configured for query result caching + */ + QueryResultsCache getDefaultQueryResultsCache(); + + /** + * Get query cache by region name or create a new one if none exist. + * + * If the region name is null, then default query cache region will be returned. + * + * Will return {@code null} if Hibernate is not configured for query result caching + */ + QueryResultsCache getQueryResultsCache(String regionName); + + /** + * Get the named QueryResultRegionAccess but not creating one if it + * does not already exist. This is intended for use by statistics. + * + * Will return {@code null} if Hibernate is not configured for query result + * caching or if no such region (yet) exists + * + * @since 5.3 + */ + QueryResultsCache getQueryResultsCacheStrictly(String regionName); + + /** + * Clean up the default query cache + */ + default void evictQueries() throws HibernateException { + QueryResultsCache cache = getDefaultQueryResultsCache(); + if ( cache != null ) { + cache.clear(); + } + } + + /** + * Close this "cache", releasing all underlying resources. + */ + void close(); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Deprecations (5.3) + + /** + * Get the *qualified* names of all regions caching entity and collection data. + * + * @return All cache region names + * + * @deprecated (since 5.3) Use {@link CacheImplementor#getCacheRegionNames()} instead + */ + @Deprecated + String[] getSecondLevelCacheRegionNames(); + + /** + * Find the cache data access strategy for an entity. Will + * return {@code null} when the entity is not configured for caching. + * + * @param rootEntityName The NavigableRole representation of the root entity + * + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed + * + * @deprecated Use {@link EntityPersister#getCacheAccessStrategy()} instead + */ + @Deprecated + EntityDataAccess getEntityRegionAccess(NavigableRole rootEntityName); + + /** + * Find the cache data access strategy for the given entity's natural-id cache. + * Will return {@code null} when the entity does not define a natural-id, or its + * natural-id is not configured for caching. + * + * @param rootEntityName The NavigableRole representation of the root entity + * + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed + * + * @deprecated Use {@link EntityPersister#getNaturalIdCacheAccessStrategy()} ()} instead + */ + @Deprecated + NaturalIdDataAccess getNaturalIdCacheRegionAccessStrategy(NavigableRole rootEntityName); + + /** + * Find the cache data access strategy for the given collection. Will + * return {@code null} when the collection is not configured for caching. + * + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed + * + * @deprecated Use {@link EntityPersister#getNaturalIdCacheAccessStrategy()} ()} instead + */ + @Deprecated + CollectionDataAccess getCollectionRegionAccess(NavigableRole collectionRole); + + + /** + * Get {@code UpdateTimestampsCache} instance managed by the {@code SessionFactory}. + * + * @deprecated Use {@link #getTimestampsCache} instead + */ + @Deprecated + default UpdateTimestampsCache getUpdateTimestampsCache() { + return getTimestampsCache(); + } + + /** + * Get the default {@code QueryCache}. + * + * @deprecated Use {@link #getDefaultQueryResultsCache} instead. + */ + @Deprecated + default QueryCache getQueryCache() { + return getDefaultQueryResultsCache(); + } + + /** + * Get the default {@code QueryCache}. + * + * @deprecated Use {@link #getDefaultQueryResultsCache} instead. + */ + @Deprecated + default QueryCache getDefaultQueryCache() { + return getDefaultQueryResultsCache(); + } + + /** + * @deprecated Use {@link #getQueryResultsCache(String)} instead, but using unqualified name + */ + @Deprecated + default QueryCache getQueryCache(String regionName) throws HibernateException { + return getQueryResultsCache( unqualifyRegionName( regionName ) ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Some new (default) support methods for the above deprecations + // - themselves deprecated + + /** + * @deprecated (since 5.3) No replacement - added just to continue some backwards compatibility + * in supporting the newly deprecated methods expecting a qualified (prefix +) region name + */ + @Deprecated + default String unqualifyRegionName(String name) { + if ( getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix() == null ) { + return name; + } + + if ( !name.startsWith( getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix() ) ) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Legacy methods for accessing cache information expect a qualified (prefix) region name - " + + "but passed name [%s] was not qualified by the configured prefix [%s]", + name, + getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix() + ) + ); + } + + return name.substring( getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix().length() + 1 ); + } + + /** + * @deprecated No replacement - added just for support of the newly deprecated methods expecting a qualified region name + */ + @Deprecated + default Region getRegionByLegacyName(String legacyName) { + return getRegion( unqualifyRegionName( legacyName ) ); + } + + /** + * @deprecated No replacement - added just for support of the newly deprecated methods expecting a qualified region name + */ + @Deprecated + Set getNaturalIdAccessesInRegion(String legacyQualifiedRegionName); +} 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 new file mode 100644 index 000000000000..415abdc70c48 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +/** + * Defines a context object that a {@link RegionFactory} is asked to create + * ({@link RegionFactory#createTransactionContext}}) when a Hibernate Session + * is created. It's lifecycle is that of the Session. It receives + * "transactional event callbacks" around joining and completing resource + * transactions. + * + * This allows the cache impl to book-keep data related to current transaction, + * such as and process it in unique ways. E.g. this allows an impl to perform + * batch updates if Hibernate is configured to use JDBC-only transactions, + * and therefore information cannot be retrieved from the JTA transaction + * assigned to current thread. + * + * While transactional semantics might be fully implemented by the cache + * provider, Hibernate may require different transactional semantics: In order + * to prevent inconsistent reads, 2LC should not expose entities that are + * modified in any concurrently executing transactions, and force DB load + * instead. Native transactional implementation may provide looser semantics + * and 2LC implementation has to adapt to these. + * + * @implNote Even though a JTA transaction may involve more than one Session + * the CacheTransactionContext is specific to each Session since the distinction + * is generally unimportant. However, a provider is free to attempt to scope + * these CacheTransactionContext instances in such a way that they may be + * associated with more than one Session at a time. This SPI is designed + * to not require this of the caching impl, but it certainly allows the + * provider to do it + * + * @author Steve Ebersole + * @author Radim Vansa + */ +public interface CacheTransactionSynchronization { + /** + * 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. + */ + long getCurrentTransactionStartTimestamp(); + + /** + * Callback that owning Session has become joined to a resource transaction. + * + * @apiNote Implementors can consider this the effective start of a + * transaction. + */ + void transactionJoined(); + + /** + * Callback that the underling resource transaction to which the owning + * Session was joined is in the beginning stages of completing. Note that + * this is only called for successful "begin completion" of the underlying + * resource transaction (not rolling-back, marked-for-rollback, etc) + */ + void transactionCompleting(); + + /** + * Callback that the underling resource transaction to which the owning + * Session was joined is in the "completed" stage. This method is called + * regardless of success or failure of the transaction - the outcome is + * passed as a boolean. + * + * @param successful Was the resource transaction successful? + */ + void transactionCompleted(boolean successful); + + /** + * Currently not used. Here for future expansion + * + * @implNote Currently not used. JTA defines no standard means to + * be notified when a transaction is suspended nor resumed. Such + * a feature is proposed. + */ + @SuppressWarnings("unused") + default void transactionSuspended() { + // nothing to do since it is currently not used/supported + } + + /** + * Currently not used. Here for future expansion + * + * @implNote Currently not used. JTA defines no standard means to + * be notified when a transaction is suspended nor resumed + */ + @SuppressWarnings("unused") + default void transactionResumed() { + // nothing to do since it is currently not used/supported + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CollectionRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CollectionRegion.java deleted file mode 100644 index 7f77c636d28f..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/CollectionRegion.java +++ /dev/null @@ -1,35 +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.cache.spi; - -import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; - -/** - * Defines the contract for a cache region which will specifically be used to - * store collection data. - *

      - * Impl note: Hibernate always deals with changes to collections which - * (potentially) has its data in the L2 cache by removing that collection - * data; in other words it never tries to update the cached state, thereby - * allowing it to avoid a bunch of concurrency problems. - * - * @author Steve Ebersole - */ -public interface CollectionRegion extends TransactionalDataRegion { - - /** - * Build an access strategy for the requested access type. - * - * @param accessType The type of access strategy to build; never null. - * @return The appropriate strategy contract for accessing this region - * for the requested type of access. - * @throws org.hibernate.cache.CacheException Usually indicates mis-configuration. - */ - public CollectionRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/DirectAccessRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/DirectAccessRegion.java new file mode 100644 index 000000000000..53bd3725d786 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/DirectAccessRegion.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.cache.spi; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Specialized Region whose data is accessed directly (not requiring + * key/item wrapping, e.g. + * + * Does not define a "remove" operation because Hibernate's query and timestamps + * caches only ever "get" and "put" + * + * @author Steve Ebersole + */ +public interface DirectAccessRegion extends Region { + /** + * Get value by key + */ + Object getFromCache(Object key, SharedSessionContractImplementor session); + + /** + * Put a value by key + */ + void putIntoCache(Object key, Object value, SharedSessionContractImplementor session); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/DomainDataRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/DomainDataRegion.java new file mode 100644 index 000000000000..726a45cbe01c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/DomainDataRegion.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.cache.spi; + +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.metamodel.model.domain.NavigableRole; + +/** + * A Region for cacheable domain data - entity, collection, natural-id. + * + * Generally speaking, this type of data has: + * + * * specific key and value wrapping that needs to be applied + * * specific access patterns ({@link EntityDataAccess}, etc), + * including some form of locking + * + * @author Steve Ebersole + */ +public interface DomainDataRegion extends Region { + /** + * Build a EntityRegionAccess instance representing access to entity data + * stored in this cache region using the given AccessType. + * + * @apiNote Calling this method is illegal if the given entity is + * not cached + * + * @param rootEntityRole The root entity name for the hierarchy whose data + * we want to access + * + * @throws org.hibernate.cache.CacheException If the provider cannot provide the requested access + */ + EntityDataAccess getEntityDataAccess(NavigableRole rootEntityRole); + + /** + * Build a NaturalIdRegionAccess instance representing access to natural-id + * data stored in this cache region using the given AccessType. + * + * @apiNote Calling this method is illegal if the given entity is + * not cached + * + * @param rootEntityRole The NavigableRole of the root entity whose + * natural-id data we want to access + * + * @throws org.hibernate.cache.CacheException If the provider cannot provide the requested access + */ + NaturalIdDataAccess getNaturalIdDataAccess(NavigableRole rootEntityRole); + + /** + * Build a CollectionRegionAccess instance representing access to collection + * data stored in this cache region using the given AccessType. + * + * @apiNote Calling this method is illegal if the given entity is + * not cached + * + * @param collectionRole The NavigableRole of the collection whose data + * we want to access + * + * @throws org.hibernate.cache.CacheException If the provider cannot provide the requested access + */ + CollectionDataAccess getCollectionDataAccess(NavigableRole collectionRole); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/EntityRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/EntityRegion.java deleted file mode 100644 index ecd3139bebf7..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/EntityRegion.java +++ /dev/null @@ -1,30 +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.cache.spi; - -import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; - -/** - * Defines the contract for a cache region which will specifically be used to - * store entity data. - * - * @author Steve Ebersole - */ -public interface EntityRegion extends TransactionalDataRegion { - - /** - * Build an access strategy for the requested access type. - * - * @param accessType The type of access strategy to build; never null. - * @return The appropriate strategy contract for accessing this region - * for the requested type of access. - * @throws org.hibernate.cache.CacheException Usually indicates mis-configuration. - */ - EntityRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/ExtendedStatisticsSupport.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/ExtendedStatisticsSupport.java new file mode 100644 index 000000000000..3577e18387bf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/ExtendedStatisticsSupport.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.cache.spi; + +/** + * Optional Region contract defining support for extra statistic information + * + * @author Steve Ebersole + */ +public interface ExtendedStatisticsSupport { + long getElementCountInMemory(); + + long getElementCountOnDisk(); + + long getSizeInMemory(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/GeneralDataRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/GeneralDataRegion.java deleted file mode 100644 index 6abcadf4a30e..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/GeneralDataRegion.java +++ /dev/null @@ -1,58 +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.cache.spi; - -import org.hibernate.cache.CacheException; -import org.hibernate.engine.spi.SharedSessionContractImplementor; - -/** - * Contract for general-purpose cache regions. - * - * @author Steve Ebersole - */ -public interface GeneralDataRegion extends Region { - - /** - * Get an item from the cache. - * - * @param session - * @param key The key of the item to be retrieved. - * - * @return the cached object or null - * - * @throws org.hibernate.cache.CacheException Indicates a problem accessing the item or region. - */ - Object get(SharedSessionContractImplementor session, Object key) throws CacheException; - - /** - * Put an item into the cache. - * - * @param session - * @param key The key under which to cache the item. - * @param value The item to cache. - * - * @throws CacheException Indicates a problem accessing the region. - */ - void put(SharedSessionContractImplementor session, Object key, Object value) throws CacheException; - - /** - * Evict an item from the cache immediately (without regard for transaction - * isolation). - * - * @param key The key of the item to remove - * @throws CacheException Indicates a problem accessing the item or region. - */ - void evict(Object key) throws CacheException; - - /** - * Evict all contents of this particular cache region (without regard for transaction - * isolation). - * - * @throws CacheException Indicates problem accessing the region. - */ - void evictAll() throws CacheException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/NaturalIdRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/NaturalIdRegion.java deleted file mode 100644 index b6c571dceedf..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/NaturalIdRegion.java +++ /dev/null @@ -1,31 +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.cache.spi; - -import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; - -/** - * Defines the contract for a cache region which will specifically be used to - * store naturalId data. - * - * @author Eric Dalquist - * @author Steve Ebersole - */ -public interface NaturalIdRegion extends TransactionalDataRegion { - - /** - * Build an access strategy for the requested access type. - * - * @param accessType The type of access strategy to build; never null. - * @return The appropriate strategy contract for accessing this region - * for the requested type of access. - * @throws org.hibernate.cache.CacheException Usually indicates mis-configuration. - */ - public NaturalIdRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/OptimisticCacheSource.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/OptimisticCacheSource.java deleted file mode 100644 index 20f67f4f1dce..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/OptimisticCacheSource.java +++ /dev/null @@ -1,38 +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.cache.spi; - -import java.util.Comparator; - -/** - * Contract for sources of optimistically lockable data sent to the second level - * cache. - *

      - * Note currently {@link org.hibernate.persister.entity.EntityPersister}s are - * the only viable source. - * - * @author Steve Ebersole - */ -public interface OptimisticCacheSource { - /** - * Is the data to be cached considered versioned? - *

      - * If true, it is illegal for {@link #getVersionComparator} to return - * null. - * - * @return True if the data is versioned; false otherwise. - */ - public boolean isVersioned(); - - /** - * Get the comparator used to compare two different version values. - *

      - * May return null if {@link #isVersioned()} returns false. - * @return Comparator used to compare two different version values. - */ - public Comparator getVersionComparator(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryCache.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryCache.java index 0ba1b8c0b182..cafe02c95dd6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryCache.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryCache.java @@ -1,8 +1,8 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi; @@ -10,26 +10,25 @@ import java.util.List; import java.util.Set; -import org.hibernate.HibernateException; import org.hibernate.cache.CacheException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.Type; /** - * Defines the contract for caches capable of storing query results. These - * caches should only concern themselves with storing the matching result ids. - * The transactional semantics are necessarily less strict than the semantics - * of an item cache. - * - * @author Gavin King + * @author Steve Ebersole + * + * @deprecated Use {@link QueryResultsCache} instead - + * {@link CacheImplementor#getQueryResultsCache} rather than + * {@link CacheImplementor#getQueryCache} */ +@Deprecated public interface QueryCache { /** * Clear items from the query cache. * * @throws CacheException Indicates a problem delegating to the underlying cache. */ - void clear() throws CacheException; + void clear(); /** * Put a result into the query cache. @@ -41,15 +40,13 @@ public interface QueryCache { * @param session The originating session * * @return Whether the put actually happened. - * - * @throws HibernateException Indicates a problem delegating to the underlying cache. */ boolean put( QueryKey key, Type[] returnTypes, List result, boolean isNaturalKeyLookup, - SharedSessionContractImplementor session) throws HibernateException; + SharedSessionContractImplementor session); /** * Get results from the cache. @@ -61,15 +58,13 @@ boolean put( * @param session The originating session * * @return The cached results; may be null. - * - * @throws HibernateException Indicates a problem delegating to the underlying cache. */ List get( QueryKey key, Type[] returnTypes, boolean isNaturalKeyLookup, Set spaces, - SharedSessionContractImplementor session) throws HibernateException; + SharedSessionContractImplementor session); /** * Destroy the cache. diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryCacheFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryCacheFactory.java deleted file mode 100644 index b6c55ebf4827..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryCacheFactory.java +++ /dev/null @@ -1,27 +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.cache.spi; - -import org.hibernate.engine.spi.CacheImplementor; - -/** - * Defines a factory for query cache instances. These factories are responsible for - * creating individual QueryCache instances. - * - * @author Steve Ebersole - */ -public interface QueryCacheFactory { - /** - * Builds a named query cache. - * - * @param region The cache region - * @param cacheManager The CacheImplementor reference. - * - * @return The cache. - */ - QueryCache buildQueryCache(QueryResultsRegion region, CacheImplementor cacheManager); -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryKey.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryKey.java index a75ed848ec06..4e3b86a50ad1 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryKey.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryKey.java @@ -10,6 +10,7 @@ import java.io.Serializable; import java.util.Collections; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.hibernate.engine.spi.QueryParameters; @@ -17,7 +18,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.TypedValue; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.internal.util.compare.EqualsHelper; import org.hibernate.transform.CacheableResultTransformer; import org.hibernate.type.Type; @@ -55,7 +55,7 @@ public class QueryKey implements Serializable { * @param queryParameters The query parameters * @param filterKeys The keys of any enabled filters. * @param session The current session. - * @param customTransformer The result transformer; should be null if data is not transformed beforeQuery being cached. + * @param customTransformer The result transformer; should be null if data is not transformed before being cached. * * @return The generate query cache key. */ @@ -214,10 +214,10 @@ public boolean equals(Object other) { if ( !sqlQueryString.equals( that.sqlQueryString ) ) { return false; } - if ( !EqualsHelper.equals( firstRow, that.firstRow ) || !EqualsHelper.equals( maxRows, that.maxRows ) ) { + if ( !Objects.equals( firstRow, that.firstRow ) || !Objects.equals( maxRows, that.maxRows ) ) { return false; } - if ( !EqualsHelper.equals( customTransformer, that.customTransformer ) ) { + if ( !Objects.equals( customTransformer, that.customTransformer ) ) { return false; } if ( positionalParameterTypes == null ) { @@ -242,9 +242,9 @@ public boolean equals(Object other) { } } - return EqualsHelper.equals( filterKeys, that.filterKeys ) - && EqualsHelper.equals( namedParameters, that.namedParameters ) - && EqualsHelper.equals( tenantIdentifier, that.tenantIdentifier ); + return Objects.equals( filterKeys, that.filterKeys ) + && Objects.equals( namedParameters, that.namedParameters ) + && Objects.equals( tenantIdentifier, that.tenantIdentifier ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryResultsCache.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryResultsCache.java new file mode 100644 index 000000000000..85886566f8f0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryResultsCache.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +import java.io.Serializable; +import java.util.List; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.cache.CacheException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.Type; + +/** + * Defines the responsibility for managing query result data caching + * in regards to a specific region. + * + * @author Gavin King + * @author Steve Ebersole + */ +public interface QueryResultsCache extends QueryCache { + /** + * The underlying cache region being used. + */ + @Override + QueryResultsRegion getRegion(); + + /** + * Clear items from the query cache. + * + * @throws CacheException Indicates a problem delegating to the underlying cache. + */ + @Override + default void clear() throws CacheException { + getRegion().clear(); + } + + /** + * Put a result into the query cache. + * + * @param key The cache key + * @param result The results to cache + * @param session The originating session + * + * @return Whether the put actually happened. + * + * @throws HibernateException Indicates a problem delegating to the underlying cache. + */ + boolean put( + QueryKey key, + List result, + Type[] returnTypes, + SharedSessionContractImplementor session) throws HibernateException; + + /** + * Get results from the cache. + * + * @param key The cache key + * @param spaces The query spaces (used in invalidation plus validation checks) + * @param session The originating session + * + * @return The cached results; may be null. + * + * @throws HibernateException Indicates a problem delegating to the underlying cache. + */ + List get( + QueryKey key, + Set spaces, + Type[] returnTypes, + SharedSessionContractImplementor session) throws HibernateException; + + /** + * Get results from the cache. + * + * @param key The cache key + * @param spaces The query spaces (used in invalidation plus validation checks) + * @param session The originating session + * + * @return The cached results; may be null. + * + * @throws HibernateException Indicates a problem delegating to the underlying cache. + */ + List get( + QueryKey key, + String[] spaces, + Type[] returnTypes, + SharedSessionContractImplementor session) throws HibernateException; + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Deprecations + + @Override + default boolean put( + QueryKey key, + Type[] returnTypes, + List result, + boolean isNaturalKeyLookup, + SharedSessionContractImplementor session) { + return put( key, result, returnTypes, session ); + } + + @Override + default List get( + QueryKey key, + Type[] returnTypes, + boolean isNaturalKeyLookup, + Set spaces, + SharedSessionContractImplementor session) { + return get( key, spaces, returnTypes, session ); + } + + @Override + default void destroy() { + // nothing to do.. the region itself gets destroyed + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryResultsRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryResultsRegion.java index def8aaf739ea..6179a544c45d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryResultsRegion.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/QueryResultsRegion.java @@ -1,8 +1,8 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi; @@ -12,5 +12,5 @@ * * @author Steve Ebersole */ -public interface QueryResultsRegion extends GeneralDataRegion { +public interface QueryResultsRegion extends DirectAccessRegion { } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/QuerySpacesHelper.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/QuerySpacesHelper.java new file mode 100644 index 000000000000..0eeb66cd4d6c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/QuerySpacesHelper.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.cache.spi; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Steve Ebersole + */ +public class QuerySpacesHelper { + /** + * Singleton access + */ + public static final QuerySpacesHelper INSTANCE = new QuerySpacesHelper(); + + private QuerySpacesHelper() { + } + + public String[] toStringArray(Set spacesSet) { + return (String[]) spacesSet.toArray( new String[0] ); + } + + public Set toStringSet(String[] spacesArray) { + final HashSet set = new HashSet<>(); + Collections.addAll( set, spacesArray ); + return set; + } + + public Set toSerializableSet(String[] spacesArray) { + final HashSet set = new HashSet<>(); + Collections.addAll( set, spacesArray ); + return set; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/Region.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/Region.java index ec7493d991cd..7ed60687f59d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/Region.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/Region.java @@ -1,99 +1,53 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi; -import java.util.Map; - +import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.CacheException; /** - * Defines a contract for accessing a particular named region within the - * underlying cache implementation. + * Contract for a named "region". The concept of a Region might not + * necessarily correlate to a specific concept in the underlying caching + * provider - it is just a thing that can be referenced by name later. + *

      + * A region's name is "unqualified"; i.e. it is not prefixed by + * {@link SessionFactoryOptions#getCacheRegionPrefix()}. + *

      + * Region is the base contract defining some common characteristics + * regardless of the type of data intended to be stored within this + * Region. The more specific sub-types are {@link DomainDataRegion} + * (storing entity, collection and natural-id data) and + * {@link DirectAccessRegion} (storing query result and timestamp + * data). * * @author Steve Ebersole */ public interface Region { /** - * Retrieve the name of this region. - * - * @return The region name + * Retrieve the unqualified name of this region. */ String getName(); /** - * The "end state" contract of the region's lifecycle. Called - * during {@link org.hibernate.SessionFactory#close()} to give - * the region a chance to cleanup. - * - * @throws org.hibernate.cache.CacheException Indicates problem shutting down + * The RegionFactory that generated this Region */ - void destroy() throws CacheException; + RegionFactory getRegionFactory(); /** - * Determine whether this region contains data for the given key. - *

      - * The semantic here is whether the cache contains data visible for the - * current call context. This should be viewed as a "best effort", meaning - * blocking should be avoid if possible. - * - * @param key The cache key - * - * @return True if the underlying cache contains corresponding data; false - * otherwise. + * Clear all data cached in the region */ - boolean contains(Object key); + void clear(); /** - * The number of bytes is this cache region currently consuming in memory. - * - * @return The number of bytes consumed by this region; -1 if unknown or - * unsupported. - */ - long getSizeInMemory(); - - /** - * The count of entries currently contained in the regions in-memory store. - * - * @return The count of entries in memory; -1 if unknown or unsupported. - */ - long getElementCountInMemory(); - - /** - * The count of entries currently contained in the regions disk store. - * - * @return The count of entries on disk; -1 if unknown or unsupported. - */ - long getElementCountOnDisk(); - - /** - * Get the contents of this region as a map. - *

      - * Implementors which do not support this notion - * should simply return an empty map. - * - * @return The content map. - */ - Map toMap(); - - /** - * Get the next timestamp according to the underlying cache implementor. - * - * @todo Document the usages of this method so providers know exactly what is expected. - * - * @return The next timestamp - */ - long nextTimestamp(); - - /** - * Get a timeout value. - * - * @todo Again, document the usages of this method so providers know exactly what is expected. + * The "end state" contract of the region's lifecycle. Called + * during {@link org.hibernate.SessionFactory#close()} to give + * the region a chance to cleanup. * - * @return The time out value + * @throws org.hibernate.cache.CacheException Indicates problem shutting down */ - int getTimeout(); + void destroy() throws CacheException; } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/RegionFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/RegionFactory.java index f9a68f40151d..0fd21f1fc0b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/RegionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/RegionFactory.java @@ -1,18 +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 . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi; import java.util.Map; -import java.util.Properties; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.CacheException; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.service.Service; +import org.hibernate.service.spi.Stoppable; /** * Contract for building second level cache regions. @@ -22,32 +26,19 @@ *

    3. MyRegionFactoryImpl()
    4. * * Use the first when we need to read config properties prior to - * {@link #start(SessionFactoryOptions, Properties)} being called. + * {@link #start} being called. * * @author Steve Ebersole */ -public interface RegionFactory extends Service { +public interface RegionFactory extends Service, Stoppable { - /** - * Lifecycle callback to perform any necessary initialization of the - * underlying cache implementation(s). Called exactly once during the - * construction of a {@link org.hibernate.internal.SessionFactoryImpl}. - * - * @param settings The settings in effect. - * @param properties The defined cfg properties - * - * @throws org.hibernate.cache.CacheException Indicates problems starting the L2 cache impl; - * considered as a sign to stop {@link org.hibernate.SessionFactory} - * building. - * - * @deprecated (since 5.2) use the form accepting map instead. - */ - @Deprecated - void start(SessionFactoryOptions settings, Properties properties) throws CacheException; + // These are names that users have to include in their caching configuration, do not change them + String DEFAULT_QUERY_RESULTS_REGION_UNQUALIFIED_NAME = "default-query-results-region"; + String DEFAULT_UPDATE_TIMESTAMPS_REGION_UNQUALIFIED_NAME = "default-update-timestamps-region"; /** * Lifecycle callback to perform any necessary initialization of the - * underlying cache implementation(s). Called exactly once during the + * underlying cache provider. Called exactly once during the * construction of a {@link org.hibernate.internal.SessionFactoryImpl}. * * @param settings The settings in effect. @@ -57,18 +48,7 @@ public interface RegionFactory extends Service { * considered as a sign to stop {@link org.hibernate.SessionFactory} * building. */ - default void start(SessionFactoryOptions settings, Map configValues) throws CacheException { - final Properties properties = new Properties(); - properties.putAll( configValues ); - start( settings, properties ); - } - - /** - * Lifecycle callback to perform any necessary cleanup of the underlying - * cache implementation(s). Called exactly once during - * {@link org.hibernate.SessionFactory#close}. - */ - void stop(); + void start(SessionFactoryOptions settings, Map configValues) throws CacheException; /** * By default should we perform "minimal puts" when using this second @@ -80,185 +60,47 @@ default void start(SessionFactoryOptions settings, Map configVal boolean isMinimalPutsEnabledByDefault(); /** - * Get the default access type for {@link EntityRegion entity} and - * {@link CollectionRegion collection} regions. - * - * @return This factory's default access type. + * Get the default access type for any "user model" data */ AccessType getDefaultAccessType(); - /** - * Generate a timestamp. - *

      - * This is generally used for cache content locking/unlocking purposes - * depending upon the access-strategy being used. - * - * @return The generated timestamp. - */ - long nextTimestamp(); - - /** - * Build a cache region specialized for storing entity data. - * - * @param regionName The name of the region. - * @param properties Configuration properties. - * @param metadata Information regarding the type of data to be cached - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - * - * @deprecated (since 5.2) use the form taking Map instead - */ - @Deprecated - EntityRegion buildEntityRegion(String regionName, Properties properties, CacheDataDescription metadata) - throws CacheException; + String qualify(String regionName); - /** - * Build a cache region specialized for storing entity data. - * - * @param regionName The name of the region. - * @param configValues Available config values. - * @param metadata Information regarding the type of data to be cached - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - */ - default EntityRegion buildEntityRegion(String regionName, Map configValues, CacheDataDescription metadata) - throws CacheException { - final Properties properties = new Properties(); - properties.putAll( configValues ); - return buildEntityRegion( regionName, properties, metadata ); + default CacheTransactionSynchronization createTransactionContext(SharedSessionContractImplementor session) { + return new StandardCacheTransactionSynchronization( this ); } /** - * Build a cache region specialized for storing NaturalId to Primary Key mappings. - * - * @param regionName The name of the region. - * @param properties Configuration properties. - * @param metadata Information regarding the type of data to be cached - * - * @return The built region + * Generate a timestamp. This value is generally used for purpose of + * locking/unlocking cache content depending upon the access-strategy being + * used. The intended consumer of this method is the Session to manage + * its {@link SharedSessionContractImplementor#getTransactionStartTimestamp} value. * - * @throws CacheException Indicates problems building the region. + * It is also expected that this be the value used for this's RegionFactory's + * CacheTransactionContext * - * @deprecated (since 5.2) use the form accepting a Map instead + * @apiNote This "timestamp" need not be related to timestamp in the Java Date/millisecond + * sense. It just needs to be an incrementing value */ - @Deprecated - NaturalIdRegion buildNaturalIdRegion(String regionName, Properties properties, CacheDataDescription metadata) - throws CacheException; - - /** - * Build a cache region specialized for storing NaturalId to Primary Key mappings. - * - * @param regionName The name of the region. - * @param configValues Available config values. - * @param metadata Information regarding the type of data to be cached - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - */ - default NaturalIdRegion buildNaturalIdRegion(String regionName, Map configValues, CacheDataDescription metadata) - throws CacheException { - final Properties properties = new Properties(); - properties.putAll( configValues ); - return buildNaturalIdRegion( regionName, properties, metadata ); - } - - /** - * Build a cache region specialized for storing collection data. - * - * @param regionName The name of the region. - * @param properties Configuration properties. - * @param metadata Information regarding the type of data to be cached - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - */ - CollectionRegion buildCollectionRegion(String regionName, Properties properties, CacheDataDescription metadata) - throws CacheException; + long nextTimestamp(); - /** - * Build a cache region specialized for storing collection data. - * - * @param regionName The name of the region. - * @param configValues Available config values. - * @param metadata Information regarding the type of data to be cached - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - */ - default CollectionRegion buildCollectionRegion(String regionName, Map configValues, CacheDataDescription metadata) - throws CacheException { - final Properties properties = new Properties(); - properties.putAll( configValues ); - return buildCollectionRegion( regionName, properties, metadata ); + default long getTimeout() { + // most existing providers defined this as 60 seconds. + return 60000; } /** - * Build a cache region specialized for storing query results. + * Create a named Region for holding domain model data * - * @param regionName The name of the region. - * @param properties Configuration properties. - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - * - * @deprecated (since 5.2) use the form taking Map instead + * @param regionConfig The user requested caching configuration for this Region + * @param buildingContext Access to delegates useful in building the Region */ - @Deprecated - QueryResultsRegion buildQueryResultsRegion(String regionName, Properties properties) throws CacheException; + DomainDataRegion buildDomainDataRegion( + DomainDataRegionConfig regionConfig, + DomainDataRegionBuildingContext buildingContext); - /** - * Build a cache region specialized for storing query results. - * - * @param qualifyRegionName The qualified name of the region. - * @param configValues Available config values. - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - */ - default QueryResultsRegion buildQueryResultsRegion(String qualifyRegionName, Map configValues) { - final Properties properties = new Properties(); - properties.putAll( configValues ); - return buildQueryResultsRegion( qualifyRegionName, properties ); - } - - /** - * Build a cache region specialized for storing update-timestamps data. - * - * @param regionName The name of the region. - * @param properties Configuration properties. - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - * - * @deprecated (since 5.2) use the form taking Map - */ - @Deprecated - TimestampsRegion buildTimestampsRegion(String regionName, Properties properties) throws CacheException; - /** - * Build a cache region specialized for storing update-timestamps data. - * - * @param regionName The name of the region. - * @param configValues The available config values. - * - * @return The built region - * - * @throws CacheException Indicates problems building the region. - */ - default TimestampsRegion buildTimestampsRegion(String regionName, Map configValues) throws CacheException { - final Properties properties = new Properties(); - properties.putAll( configValues ); - return buildTimestampsRegion( regionName, properties ); - } + QueryResultsRegion buildQueryResultsRegion(String regionName, SessionFactoryImplementor sessionFactory); + TimestampsRegion buildTimestampsRegion(String regionName, SessionFactoryImplementor sessionFactory); } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java new file mode 100644 index 000000000000..413e741664a0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.metamodel.model.domain.NavigableRole; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.LogMessage; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; +import org.jboss.logging.annotations.ValidIdRange; + +import static org.jboss.logging.Logger.Level.INFO; +import static org.jboss.logging.Logger.Level.WARN; + +/** + * @author Steve Ebersole + */ +@MessageLogger( projectCode = "HHH" ) +@ValidIdRange( min = 90001001, max = 90002000 ) +public interface SecondLevelCacheLogger extends BasicLogger { + SecondLevelCacheLogger INSTANCE = Logger.getMessageLogger( + SecondLevelCacheLogger.class, + "org.hibernate.orm.cache" + ); + + int NAMESPACE = 90001000; + + @LogMessage(level = WARN) + @Message( + value = "Attempt to restart an already started RegionFactory. Use sessionFactory.close() between " + + "repeated calls to buildSessionFactory. Using previously created RegionFactory.", + id = NAMESPACE + 1 + ) + void attemptToStartAlreadyStartedCacheProvider(); + + @LogMessage(level = WARN) + @Message( + value = "Attempt to restop an already stopped JCacheRegionFactory.", + id = NAMESPACE + 2 + ) + void attemptToStopAlreadyStoppedCacheProvider(); + + @LogMessage( level = WARN ) + @Message( + value = "Read-only caching was requested for mutable entity [%s]", + id = NAMESPACE + 3 + ) + void readOnlyCachingMutableEntity(NavigableRole navigableRole); + + @LogMessage( level = WARN ) + @Message( + value = "Read-only caching was requested for mutable natural-id for entity [%s]", + id = NAMESPACE + 4 + ) + void readOnlyCachingMutableNaturalId(NavigableRole navigableRole); + + /** + * Log a message (WARN) about expiry of soft-locked region. + */ + @LogMessage(level = INFO) + @Message( + value = "Cache[%s] Key[%s]\n" + + "A soft-locked cache entry was expired by the underlying cache. If this happens regularly you " + + "should consider increasing the cache timeouts and/or capacity limits", + id = NAMESPACE + 5 + ) + void softLockedCacheExpired(String regionName, Object key); + + @LogMessage(level = WARN) + @Message( + value = "Missing cache[%1$s] was created on-the-fly." + + " The created cache will use a provider-specific default configuration:" + + " make sure you defined one." + + " You can disable this warning by setting '%2$s' to '%3$s'.", + id = NAMESPACE + 6 + ) + void missingCacheCreated(String regionName, String configurationPropertyToDisableKey, String configurationPropertyToDisableValue); + + @LogMessage(level = WARN) + @Message( + value = "Using legacy cache name [%2$s] because configuration could not be found for cache [%1$s]." + + " Update your configuration to rename cache [%2$s] to [%1$s].", + id = NAMESPACE + 7 + ) + void usingLegacyCacheName(String currentName, String legacyName); + + @LogMessage(level = WARN) + @Message( + value = "Cache [%1$s] uses the [%2$s] access type, but [%3$s] does not support it natively." + + " Make sure your cache implementation supports JTA transactions.", + id = NAMESPACE + 8 + ) + void nonStandardSupportForAccessType(String key, String accessType, String regionName); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/StandardCacheTransactionSynchronization.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/StandardCacheTransactionSynchronization.java new file mode 100644 index 000000000000..eeab0da19d91 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/StandardCacheTransactionSynchronization.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +/** + * @author Steve Ebersole + */ +public class StandardCacheTransactionSynchronization extends AbstractCacheTransactionSynchronization { + public StandardCacheTransactionSynchronization(RegionFactory regionFactory) { + super( regionFactory ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsCache.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsCache.java new file mode 100644 index 000000000000..ca3b3144fe19 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsCache.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +import java.io.Serializable; +import java.util.Set; +import java.util.function.Consumer; + +import org.hibernate.cache.CacheException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Wrapper for a {@link TimestampsRegion} adding handling of stale results + * + * @author Steve Ebersole + */ +public interface TimestampsCache extends UpdateTimestampsCache { + /** + * The region used to store all timestamps data + */ + TimestampsRegion getRegion(); + + /** + * Perform pre-invalidation of the passed spaces (table names) + * against the timestamps region data + */ + void preInvalidate( + String[] spaces, + SharedSessionContractImplementor session); + + /** + * Perform invalidation of the passed spaces (table names) + * against the timestamps region data + */ + void invalidate( + String[] spaces, + SharedSessionContractImplementor session); + + /** + * Perform an up-to-date check for the given set of query spaces as + * part of verifying the validity of cached query results. + * + * @param spaces The spaces to check + * @param timestamp The timestamp from the transaction when the query results were cached. + * @param session The session whether this check originated. + * + * @return Whether all those spaces are up-to-date + */ + boolean isUpToDate( + String[] spaces, + Long timestamp, + SharedSessionContractImplementor session); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Deprecations + + + @Override + default void preInvalidate(Serializable[] spaces, SharedSessionContractImplementor session) { + final String[] spaceStrings = new String[ spaces.length ]; + // todo - does this copy work? + System.arraycopy( spaces, 0, spaceStrings, 0, spaces.length ); + preInvalidate( spaceStrings, session ); + } + + @Override + default void invalidate(Serializable[] spaces, SharedSessionContractImplementor session) { + final String[] spaceStrings = new String[ spaces.length ]; + // todo - does this copy work? + System.arraycopy( spaces, 0, spaceStrings, 0, spaces.length ); + invalidate( spaceStrings, session ); + } + + @Override + default boolean isUpToDate( + Set spaces, + Long timestamp, + SharedSessionContractImplementor session) { + final String[] spaceArray = new String[ spaces.size() ]; + + spaces.forEach( + new Consumer() { + int position = 0; + @Override + public void accept(Serializable serializable) { + spaceArray[position++] = (String) serializable; + } + } + ); + + return isUpToDate( spaceArray, timestamp, session ); + } + + @Override + default void clear() throws CacheException { + getRegion().clear(); + } + + @Override + default void destroy() { + // nothing to do - the region itself is destroyed + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsCacheFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsCacheFactory.java new file mode 100644 index 000000000000..5b9a65570b26 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsCacheFactory.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi; + +/** + * Responsible for building the TimestampsRegionAccessFactory to use for + * managing query results in regards to staleness of the underlying + * tables (sometimes called "query spaces" or "table spaces") + * + * @author Steve Ebersole + */ +public interface TimestampsCacheFactory { + /** + * Build the TimestampsCache + */ + TimestampsCache buildTimestampsCache(CacheImplementor cacheManager, TimestampsRegion timestampsRegion); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsRegion.java index 88fa3a745443..63807f2052a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsRegion.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/TimestampsRegion.java @@ -1,16 +1,13 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi; /** - * Defines the contract for a cache region which will specifically be used to - * store entity "update timestamps". - * * @author Steve Ebersole */ -public interface TimestampsRegion extends GeneralDataRegion { +public interface TimestampsRegion extends DirectAccessRegion { } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/TransactionAwareCache.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/TransactionAwareCache.java deleted file mode 100644 index 08fab8e0ff92..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/TransactionAwareCache.java +++ /dev/null @@ -1,15 +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.cache.spi; - -/** - * Marker interface for identifying {@link org.hibernate.Cache} implementations which are aware of JTA transactions - * - * @author Steve Ebersole - */ -public interface TransactionAwareCache { -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/TransactionalDataRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/TransactionalDataRegion.java deleted file mode 100644 index 8151759c9712..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/TransactionalDataRegion.java +++ /dev/null @@ -1,43 +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.cache.spi; - -/** - * Defines contract for regions which hold transactionally-managed data. - *

      - * The data is not transactionally managed within the region; merely it is - * transactionally-managed in relation to its association with a particular - * {@link org.hibernate.Session}. - * - * @author Steve Ebersole - */ -public interface TransactionalDataRegion extends Region { - /** - * Is the underlying cache implementation aware of (and "participating in") - * ongoing JTA transactions? - *

      - * Regions which report that they are transaction-aware are considered - * "synchronous", in that we assume we can immediately (i.e. synchronously) - * write the changes to the cache and that the cache will properly manage - * application of the written changes within the bounds of ongoing JTA - * transactions. Conversely, regions reporting false are considered - * "asynchronous", where it is assumed that changes must be manually - * delayed by Hibernate until we are certain that the current transaction - * is successful (i.e. maintaining READ_COMMITTED isolation). - * - * @return True if transaction aware; false otherwise. - */ - public boolean isTransactionAware(); - - /** - * Get the description of the type of data to be stored here, which would have been given to the RegionFactory - * when creating this region - * - * @return The data descriptor. - */ - public CacheDataDescription getCacheDataDescription(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/UpdateTimestampsCache.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/UpdateTimestampsCache.java index 8e9d293b9c60..21a911f171d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/UpdateTimestampsCache.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/UpdateTimestampsCache.java @@ -1,8 +1,8 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi; @@ -10,11 +10,7 @@ import java.util.Set; import org.hibernate.cache.CacheException; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.CoreMessageLogger; - -import org.jboss.logging.Logger; /** * Tracks the timestamps of the most recent updates to particular tables. It is @@ -25,102 +21,38 @@ * * @author Gavin King * @author Mikheil Kapanadze + * + * @deprecated Use {@link TimestampsCache} instead */ -public class UpdateTimestampsCache { - private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, UpdateTimestampsCache.class.getName() ); - private static final boolean DEBUG_ENABLED = LOG.isDebugEnabled(); - +@SuppressWarnings("unused") +@Deprecated +public interface UpdateTimestampsCache { /** - * The region name of the update-timestamps cache. - */ - public static final String REGION_NAME = UpdateTimestampsCache.class.getName(); - - private final SessionFactoryImplementor factory; - private final TimestampsRegion region; - - /** - * Constructs an UpdateTimestampsCache. + * Get the underlying cache region where data is stored.. * - * @param sessionFactory The SessionFactory - * @param region The underlying second level cache region to use. + * @return The underlying region. */ - public UpdateTimestampsCache(SessionFactoryImplementor sessionFactory, TimestampsRegion region) { - LOG.startingUpdateTimestampsCache( region.getName() ); - this.factory = sessionFactory; - this.region = region; - } + TimestampsRegion getRegion(); /** * Perform pre-invalidation. * - * * @param spaces The spaces to pre-invalidate * - * @param session * @throws CacheException Indicated problem delegating to underlying region. */ - public void preInvalidate(Serializable[] spaces, SharedSessionContractImplementor session) throws CacheException { - final boolean stats = factory != null && factory.getStatistics().isStatisticsEnabled(); - - final Long ts = region.nextTimestamp() + region.getTimeout(); - - for ( Serializable space : spaces ) { - if ( DEBUG_ENABLED ) { - LOG.debugf( "Pre-invalidating space [%s], timestamp: %s", space, ts ); - } - - try { - session.getEventListenerManager().cachePutStart(); - - //put() has nowait semantics, is this really appropriate? - //note that it needs to be async replication, never local or sync - region.put( session, space, ts ); - } - finally { - session.getEventListenerManager().cachePutEnd(); - } - - if ( stats ) { - factory.getStatistics().updateTimestampsCachePut(); - } - } - } + void preInvalidate(Serializable[] spaces, SharedSessionContractImplementor session) throws CacheException; /** * Perform invalidation. * * - * @param spaces The spaces to pre-invalidate - * + * @param spaces The spaces to invalidate. * @param session + * * @throws CacheException Indicated problem delegating to underlying region. */ - public void invalidate(Serializable[] spaces, SharedSessionContractImplementor session) throws CacheException { - final boolean stats = factory != null && factory.getStatistics().isStatisticsEnabled(); - - final Long ts = region.nextTimestamp(); - - for (Serializable space : spaces) { - if ( DEBUG_ENABLED ) { - LOG.debugf( "Invalidating space [%s], timestamp: %s", space, ts ); - } - - try { - session.getEventListenerManager().cachePutStart(); - - //put() has nowait semantics, is this really appropriate? - //note that it needs to be async replication, never local or sync - region.put( session, space, ts ); - } - finally { - session.getEventListenerManager().cachePutEnd(); - } - - if ( stats ) { - factory.getStatistics().updateTimestampsCachePut(); - } - } - } + void invalidate(Serializable[] spaces, SharedSessionContractImplementor session) throws CacheException; /** * Perform an up-to-date check for the given set of query spaces. @@ -129,91 +61,21 @@ public void invalidate(Serializable[] spaces, SharedSessionContractImplementor s * @param spaces The spaces to check * @param timestamp The timestamp against which to check. * - * @param session - * @return Whether all those spaces are up-to-date - * * @throws CacheException Indicated problem delegating to underlying region. */ - public boolean isUpToDate(Set spaces, Long timestamp, SharedSessionContractImplementor session) throws CacheException { - final boolean stats = factory != null && factory.getStatistics().isStatisticsEnabled(); - - for ( Serializable space : spaces ) { - final Long lastUpdate = getLastUpdateTimestampForSpace( space, session ); - if ( lastUpdate == null ) { - if ( stats ) { - factory.getStatistics().updateTimestampsCacheMiss(); - } - //the last update timestamp was lost from the cache - //(or there were no updates since startup!) - //updateTimestamps.put( space, new Long( updateTimestamps.nextTimestamp() ) ); - //result = false; // safer - } - else { - if ( DEBUG_ENABLED ) { - LOG.debugf( - "[%s] last update timestamp: %s", - space, - lastUpdate + ", result set timestamp: " + timestamp - ); - } - if ( stats ) { - factory.getStatistics().updateTimestampsCacheHit(); - } - if ( lastUpdate >= timestamp ) { - return false; - } - } - } - return true; - } - - private Long getLastUpdateTimestampForSpace(Serializable space, SharedSessionContractImplementor session) { - Long ts = null; - try { - session.getEventListenerManager().cacheGetStart(); - ts = (Long) region.get( session, space ); - } - finally { - session.getEventListenerManager().cacheGetEnd( ts != null ); - } - return ts; - } + boolean isUpToDate(Set spaces, Long timestamp, SharedSessionContractImplementor session) throws CacheException; /** * Clear the update-timestamps data. * * @throws CacheException Indicates problem delegating call to underlying region. */ - public void clear() throws CacheException { - region.evictAll(); - } + void clear() throws CacheException; /** * Destroys the cache. * * @throws CacheException Indicates problem delegating call to underlying region. */ - public void destroy() { - try { - region.destroy(); - } - catch (Exception e) { - LOG.unableToDestroyUpdateTimestampsCache( region.getName(), e.getMessage() ); - } - } - - /** - * Get the underlying cache region where data is stored.. - * - * @return The underlying region. - */ - public TimestampsRegion getRegion() { - return region; - } - - @Override - public String toString() { - return "UpdateTimestampsCache"; - } - + void destroy(); } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CachedDomainDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CachedDomainDataAccess.java new file mode 100644 index 000000000000..01c1e0c61549 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CachedDomainDataAccess.java @@ -0,0 +1,216 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.access; + +import java.io.Serializable; +import javax.persistence.Cache; + +import org.hibernate.cache.CacheException; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Base contract for accessing the underlying cached data for a particular + * Navigable of the user's domain model in a transactionally ACID manner. + * + * @apiNote Note that the following methods are not considered "transactional" + * in this sense : {@link #contains}, {@link #lockRegion}, {@link #unlockRegion}, + * {@link #evict}, {@link #evictAll}. The semantics of these methods come + * from JPA's {@link Cache} contract. + * + * @implSpec The "non transactional" methods noted in the `@apiNote` should + * be implemented to ignore any locking. In other words, if {@link #evict} + * is called that item should be forcibly removed from the cache regardless of + * whether anything has locked it. + * + * @author Steve Ebersole + * @author Gail Badner + */ +public interface CachedDomainDataAccess { + /** + * The region containing the data being accessed + */ + DomainDataRegion getRegion(); + + /** + * The type of access implemented + */ + AccessType getAccessType(); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Transactional + + /** + * Attempt to retrieve an object from the cache. Mainly used in attempting + * to resolve entities/collections from the second level cache. + * + * @param session Current session. + * @param key The key of the item to be retrieved. + * + * @return the cached data or {@code null} + * + * @throws CacheException Propagated from underlying cache provider + */ + Object get(SharedSessionContractImplementor session, Object key); + + /** + * Attempt to cache an object, afterQuery loading from the database. + * + * @param session Current session. + * @param key The item key + * @param value The item + * @param version the item version number + * + * @return {@code true} if the object was successfully cached + * + * @throws CacheException Propagated from underlying cache provider + */ + boolean putFromLoad( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version); + + /** + * Attempt to cache an object, afterQuery loading from the database, explicitly + * specifying the minimalPut behavior. + * + * @param session Current session. + * @param key The item key + * @param value The item + * @param version the item version number + * @param minimalPutOverride Explicit minimalPut flag + * + * @return {@code true} if the object was successfully cached + * + * @throws CacheException Propagated from underlying cache provider + */ + boolean putFromLoad( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version, + boolean minimalPutOverride); + + /** + * We are going to attempt to update/delete the keyed object. This + * method is used by "asynchronous" concurrency strategies. + *

      + * The returned object must be passed back to {@link #unlockItem}, to release the + * lock. Concurrency strategies which do not support client-visible + * locks may silently return null. + * + * @param session Current session. + * @param key The key of the item to lock + * @param version The item's current version value + * + * @return A representation of our lock on the item; or {@code null}. + * + * @throws CacheException Propagated from underlying cache provider + */ + SoftLock lockItem(SharedSessionContractImplementor session, Object key, Object version); + + /** + * Called when we have finished the attempted update/delete (which may or + * may not have been successful), after transaction completion. This method + * is used by "asynchronous" concurrency strategies. + * + * @param session Current session. + * @param key The item key + * @param lock The lock previously obtained from {@link #lockItem} + * + * @throws CacheException Propagated from underlying cache provider + */ + void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock); + + /** + * Called afterQuery an item has become stale (beforeQuery the transaction completes). + * This method is used by "synchronous" concurrency strategies. + * + * @param session Current session. + * @param key The key of the item to remove + * + * @throws CacheException Propagated from underlying cache provider + */ + void remove(SharedSessionContractImplementor session, Object key); + + /** + * Remove all data for this accessed type + * + * @throws CacheException Propagated from underlying cache provider + * @param session + */ + void removeAll(SharedSessionContractImplementor session); + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Non-transactional + + /** + * Determine whether this region contains data for the given key. + *

      + * The semantic here is whether the cache contains data visible for the + * current call context. This should be viewed as a "best effort", meaning + * blocking should be avoid if possible. + * + * @param key The cache key + * + * @return True if the underlying cache contains corresponding data; false + * otherwise. + */ + boolean contains(Object key); + + /** + * Lock the entire region + * + * @return A representation of our lock on the item; or {@code null}. + * + * @throws CacheException Propagated from underlying cache provider + */ + SoftLock lockRegion(); + + /** + * Called after we have finished the attempted invalidation of the entire + * region + * + * @param lock The lock previously obtained from {@link #lockRegion} + * + * @throws CacheException Propagated from underlying cache provider + */ + void unlockRegion(SoftLock lock); + + /** + * Forcibly evict an item from the cache immediately without regard for transaction + * isolation and/or locking. This behavior is exactly Hibernate legacy behavior, but + * it is also required by JPA - so we cannot remove it. + *

      + * Used from JPA's {@link javax.persistence.Cache#evict(Class, Object)}, as well as the + * Hibernate extension {@link org.hibernate.Cache#evictEntityData(Class, Serializable)} + * and {@link org.hibernate.Cache#evictEntityData(String, Serializable)} + * + * @param key The key of the item to remove + * + * @throws CacheException Propagated from underlying cache provider + */ + void evict(Object key); + + /** + * Forcibly evict all items from the cache immediately without regard for transaction + * isolation. This behavior is exactly Hibernate legacy behavior, but it is also required + * by JPA - so we cannot remove it. + *

      + * Used from our JPA impl of {@link Cache#evictAll()} as well as the Hibernate + * extensions {@link org.hibernate.Cache#evictEntityData(Class)}, + * {@link org.hibernate.Cache#evictEntityData(String)} and + * {@link org.hibernate.Cache#evictEntityData()} + * + * @throws CacheException Propagated from underlying cache provider + */ + void evictAll(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CollectionDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CollectionDataAccess.java new file mode 100644 index 000000000000..6b1383959e58 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CollectionDataAccess.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.cache.spi.access; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.collection.CollectionPersister; + +/** + * Contract for managing transactional and concurrent access to cached collection + * data. For cached collection data, all modification actions actually just + * invalidate the entry(s). The call sequence here is: + * {@link #lockItem} -> {@link #remove} -> {@link #unlockItem} + *

      + * There is another usage pattern that is used to invalidate entries + * afterQuery performing "bulk" HQL/SQL operations: + * {@link #lockRegion} -> {@link #removeAll} -> {@link #unlockRegion} + * + * @author Gavin King + * @author Steve Ebersole + */ +public interface CollectionDataAccess extends CachedDomainDataAccess { + /** + * To create instances of CollectionCacheKey for this region, Hibernate will invoke this method + * exclusively so that generated implementations can generate optimised keys. + * @param id the primary identifier of the Collection + * @param collectionDescriptor the descriptor of the collection for which a key is being generated + * @param factory a reference to the current SessionFactory + * @param tenantIdentifier the tenant id, or null if multi-tenancy is not being used. + * + * @return a key which can be used to identify this collection on this same region + */ + Object generateCacheKey( + Object id, + CollectionPersister collectionDescriptor, + SessionFactoryImplementor factory, + String tenantIdentifier); + + /** + * Performs reverse operation to {@link #generateCacheKey} + * + * @param cacheKey key previously returned from {@link #generateCacheKey} + * + * @return original key passed to {@link #generateCacheKey} + */ + Object getCacheKeyId(Object cacheKey); + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CollectionRegionAccessStrategy.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CollectionRegionAccessStrategy.java deleted file mode 100644 index 115323ea63a5..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/CollectionRegionAccessStrategy.java +++ /dev/null @@ -1,54 +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.cache.spi.access; - -import org.hibernate.cache.spi.CollectionRegion; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.persister.collection.CollectionPersister; - -/** - * Contract for managing transactional and concurrent access to cached collection - * data. For cached collection data, all modification actions actually just - * invalidate the entry(s). The call sequence here is: - * {@link #lockItem} -> {@link #remove} -> {@link #unlockItem} - *

      - * There is another usage pattern that is used to invalidate entries - * afterQuery performing "bulk" HQL/SQL operations: - * {@link #lockRegion} -> {@link #removeAll} -> {@link #unlockRegion} - * - * @author Gavin King - * @author Steve Ebersole - */ -public interface CollectionRegionAccessStrategy extends RegionAccessStrategy { - - /** - * To create instances of CollectionCacheKey for this region, Hibernate will invoke this method - * exclusively so that generated implementations can generate optimised keys. - * @param id the primary identifier of the Collection - * @param persister the persister for the type for which a key is being generated - * @param factory a reference to the current SessionFactory - * @param tenantIdentifier the tenant id, or null if multi-tenancy is not being used. - * @return a key which can be used to identify this collection on this same region - */ - public Object generateCacheKey(Object id, CollectionPersister persister, SessionFactoryImplementor factory, String tenantIdentifier); - - /** - * Performs reverse operation to {@link #generateCacheKey(Object, CollectionPersister, SessionFactoryImplementor, String)} - * - * @param cacheKey key previously returned from {@link #generateCacheKey(Object, CollectionPersister, SessionFactoryImplementor, String)} - * @return original key passed to {@link #generateCacheKey(Object, CollectionPersister, SessionFactoryImplementor, String)} - */ - public Object getCacheKeyId(Object cacheKey); - - /** - * Get the wrapped collection cache region - * - * @return The underlying region - */ - public CollectionRegion getRegion(); - -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/EntityDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/EntityDataAccess.java new file mode 100644 index 000000000000..fac7eff55326 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/EntityDataAccess.java @@ -0,0 +1,126 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.access; + +import org.hibernate.cache.CacheException; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Contract for managing transactional and concurrent access to cached entity + * data. The expected call sequences related to various operations are:

        + *
      • INSERTS : {@link #insert} -> {@link #afterInsert}
      • + *
      • UPDATES : {@link #lockItem} -> {@link #update} -> {@link #afterUpdate}
      • + *
      • DELETES : {@link #lockItem} -> {@link #remove} -> {@link #unlockItem}
      • + *
      • LOADS : {@link #putFromLoad}
      • + *
      + *

      + * There is another usage pattern that is used to invalidate entries + * after performing "bulk" HQL/SQL operations: + * {@link #lockRegion} -> {@link #removeAll} -> {@link #unlockRegion} + * + * @author Gavin King + * @author Steve Ebersole + */ +public interface EntityDataAccess extends CachedDomainDataAccess { + /** + * To create instances of keys for this region, Hibernate will invoke this method + * exclusively so that generated implementations can generate optimised keys. + * @param id the primary identifier of the entity + * @param rootEntityDescriptor Hierarchy for which a key is being generated + * @param factory a reference to the current SessionFactory + * @param tenantIdentifier the tenant id, or null if multi-tenancy is not being used. + * @return a key which can be used to identify this entity on this same region + * + * todo (6.0) : the access for an entity knows the entity hierarchy and the factory. why pass them in? + */ + Object generateCacheKey( + Object id, + EntityPersister rootEntityDescriptor, + SessionFactoryImplementor factory, + String tenantIdentifier); + + /** + * Performs reverse operation to {@link #generateCacheKey} + * + * @param cacheKey key previously returned from {@link #generateCacheKey} + * @return original id passed to {@link #generateCacheKey} + */ + Object getCacheKeyId(Object cacheKey); + + /** + * Called afterQuery an item has been inserted (beforeQuery the transaction completes), + * instead of calling evict(). + * This method is used by "synchronous" concurrency strategies. + * + * @param session Current session + * @param key The item key + * @param value The item + * @param version The item's version value + * @return Were the contents of the cache actual changed by this operation? + * @throws CacheException Propagated from underlying cache provider + */ + boolean insert(SharedSessionContractImplementor session, Object key, Object value, Object version); + + /** + * Called afterQuery an item has been inserted (afterQuery the transaction completes), + * instead of calling release(). + * This method is used by "asynchronous" concurrency strategies. + * + * @param session Current session + * @param key The item key + * @param value The item + * @param version The item's version value + * @return Were the contents of the cache actual changed by this operation? + * @throws CacheException Propagated from underlying cache provider + */ + boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value, Object version); + + /** + * Called afterQuery an item has been updated (beforeQuery the transaction completes), + * instead of calling evict(). This method is used by "synchronous" concurrency + * strategies. + * + * + * @param session Current session + * @param key The item key + * @param value The item + * @param currentVersion The item's current version value + * @param previousVersion The item's previous version value + * @return Were the contents of the cache actual changed by this operation? + * @throws CacheException Propagated from underlying cache provider + */ + boolean update( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion); + + /** + * Called afterQuery an item has been updated (afterQuery the transaction completes), + * instead of calling release(). This method is used by "asynchronous" + * concurrency strategies. + * + * @param session Current session + * @param key The item key + * @param value The item + * @param currentVersion The item's current version value + * @param previousVersion The item's previous version value + * @param lock The lock previously obtained from {@link #lockItem} + * @return Were the contents of the cache actual changed by this operation? + * @throws CacheException Propagated from underlying cache provider + */ + boolean afterUpdate( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion, + SoftLock lock); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/EntityRegionAccessStrategy.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/EntityRegionAccessStrategy.java deleted file mode 100644 index d6d2b5a891de..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/EntityRegionAccessStrategy.java +++ /dev/null @@ -1,128 +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.cache.spi.access; - -import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.EntityRegion; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.persister.entity.EntityPersister; - -/** - * Contract for managing transactional and concurrent access to cached entity - * data. The expected call sequences related to various operations are:

        - *
      • INSERTS : {@link #insert} -> {@link #afterInsert}
      • - *
      • UPDATES : {@link #lockItem} -> {@link #update} -> {@link #afterUpdate}
      • - *
      • DELETES : {@link #lockItem} -> {@link #remove} -> {@link #unlockItem}
      • - *
      • LOADS : {@link @putFromLoad}
      • - *
      - *

      - * There is another usage pattern that is used to invalidate entries - * afterQuery performing "bulk" HQL/SQL operations: - * {@link #lockRegion} -> {@link #removeAll} -> {@link #unlockRegion} - * - * @author Gavin King - * @author Steve Ebersole - */ -public interface EntityRegionAccessStrategy extends RegionAccessStrategy { - - /** - * To create instances of keys for this region, Hibernate will invoke this method - * exclusively so that generated implementations can generate optimised keys. - * @param id the primary identifier of the entity - * @param persister the persister for the type for which a key is being generated - * @param factory a reference to the current SessionFactory - * @param tenantIdentifier the tenant id, or null if multi-tenancy is not being used. - * @return a key which can be used to identify this entity on this same region - */ - Object generateCacheKey( - Object id, - EntityPersister persister, - SessionFactoryImplementor factory, - String tenantIdentifier); - - /** - * Performs reverse operation to {@link #generateCacheKey(Object, EntityPersister, SessionFactoryImplementor, String)} - * - * @param cacheKey key previously returned from {@link #generateCacheKey(Object, EntityPersister, SessionFactoryImplementor, String)} - * @return original id passed to {@link #generateCacheKey(Object, EntityPersister, SessionFactoryImplementor, String)} - */ - Object getCacheKeyId(Object cacheKey); - - /** - * Get the wrapped entity cache region - * - * @return The underlying region - */ - EntityRegion getRegion(); - - /** - * Called afterQuery an item has been inserted (beforeQuery the transaction completes), - * instead of calling evict(). - * This method is used by "synchronous" concurrency strategies. - * - * @param session Current session - * @param key The item key - * @param value The item - * @param version The item's version value - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propagated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean insert(SharedSessionContractImplementor session, Object key, Object value, Object version) throws CacheException; - - /** - * Called afterQuery an item has been inserted (afterQuery the transaction completes), - * instead of calling release(). - * This method is used by "asynchronous" concurrency strategies. - * - * @param session Current session - * @param key The item key - * @param value The item - * @param version The item's version value - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propagated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value, Object version) throws CacheException; - - /** - * Called afterQuery an item has been updated (beforeQuery the transaction completes), - * instead of calling evict(). This method is used by "synchronous" concurrency - * strategies. - * - * - * @param session Current session - * @param key The item key - * @param value The item - * @param currentVersion The item's current version value - * @param previousVersion The item's previous version value - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propagated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean update(SharedSessionContractImplementor session, Object key, Object value, Object currentVersion, Object previousVersion) throws CacheException; - - /** - * Called afterQuery an item has been updated (afterQuery the transaction completes), - * instead of calling release(). This method is used by "asynchronous" - * concurrency strategies. - * - * @param session Current session - * @param key The item key - * @param value The item - * @param currentVersion The item's current version value - * @param previousVersion The item's previous version value - * @param lock The lock previously obtained from {@link #lockItem} - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propagated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean afterUpdate( - SharedSessionContractImplementor session, - Object key, - Object value, - Object currentVersion, - Object previousVersion, - SoftLock lock) throws CacheException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdDataAccess.java new file mode 100644 index 000000000000..bec175ad1c8c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdDataAccess.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.access; + +import org.hibernate.cache.CacheException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Contract for managing transactional and concurrent access to cached naturalId + * data. The expected call sequences related to various operations are:

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

      + * There is another usage pattern that is used to invalidate entries + * afterQuery performing "bulk" HQL/SQL operations: + * {@link #lockRegion} -> {@link #removeAll} -> {@link #unlockRegion} + *

      + * IMPORTANT : NaturalIds are not versioned so {@code null} will always be passed to the version parameter to:

        + *
      • {@link CachedDomainDataAccess#putFromLoad(SharedSessionContractImplementor, Object, Object, Object)}
      • + *
      • {@link CachedDomainDataAccess#putFromLoad(SharedSessionContractImplementor, Object, Object, Object, boolean)}
      • + *
      • {@link CachedDomainDataAccess#lockItem(SharedSessionContractImplementor, Object, Object)}
      • + *
      + * + * @author Gavin King + * @author Steve Ebersole + * @author Eric Dalquist + */ +public interface NaturalIdDataAccess extends CachedDomainDataAccess { + + + /** + * To create instances of NaturalIdCacheKey for this region, Hibernate will invoke this method + * exclusively so that generated implementations can generate optimised keys. + * @param naturalIdValues the sequence of values which unequivocally identifies a cached element on this region + * @param rootEntityDescriptor the persister of the element being cached + * + * @return a key which can be used to identify this an element unequivocally on this same region + */ + Object generateCacheKey( + Object[] naturalIdValues, + EntityPersister rootEntityDescriptor, + SharedSessionContractImplementor session); + + /** + * Performs reverse operation to {@link #generateCacheKey}, returning + * the original naturalIdValues. + * @param cacheKey key returned from {@link #generateCacheKey} + * + * @return the sequence of values which unequivocally identifies a cached element on this region + */ + Object[] getNaturalIdValues(Object cacheKey); + + /** + * Called afterQuery an item has been inserted (beforeQuery the transaction completes), + * instead of calling evict(). + * This method is used by "synchronous" concurrency strategies. + * + * @param session Current session + * @param key The item key + * @param value The item + * + * @return Were the contents of the cache actual changed by this operation? + * + * @throws CacheException Propagated from underlying cache provider + */ + boolean insert(SharedSessionContractImplementor session, Object key, Object value); + + /** + * Called afterQuery an item has been inserted (afterQuery the transaction completes), + * instead of calling release(). + * This method is used by "asynchronous" concurrency strategies. + * + * @param session Current session + * @param key The item key + * @param value The item + * + * @return Were the contents of the cache actual changed by this operation? + * + * @throws CacheException Propagated from underlying cache provider + */ + boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value); + + /** + * Called afterQuery an item has been updated (beforeQuery the transaction completes), + * instead of calling evict(). This method is used by "synchronous" concurrency + * strategies. + * + * @param session Current session + * @param key The item key + * @param value The item + * + * @return Were the contents of the cache actual changed by this operation? + * + * @throws CacheException Propagated from underlying cache provider + */ + boolean update(SharedSessionContractImplementor session, Object key, Object value); + + /** + * Called afterQuery an item has been updated (afterQuery the transaction completes), + * instead of calling release(). This method is used by "asynchronous" + * concurrency strategies. + * + * @param session Current session + * @param key The item key + * @param value The item + * @param lock The lock previously obtained from {@link #lockItem} + * + * @return Were the contents of the cache actual changed by this operation? + * + * @throws CacheException Propagated from underlying cache provider + */ + boolean afterUpdate(SharedSessionContractImplementor session, Object key, Object value, SoftLock lock); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdRegionAccessStrategy.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdRegionAccessStrategy.java deleted file mode 100644 index 497a008d92c4..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdRegionAccessStrategy.java +++ /dev/null @@ -1,121 +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.cache.spi.access; - -import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.NaturalIdRegion; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.persister.entity.EntityPersister; - -/** - * Contract for managing transactional and concurrent access to cached naturalId - * data. The expected call sequences related to various operations are:
        - *
      • INSERTS : {@link #insert} -> {@link #afterInsert}
      • - *
      • UPDATES : {@link #lockItem} -> {@link #remove} -> {@link #update} -> {@link #afterUpdate}
      • - *
      • DELETES : {@link #lockItem} -> {@link #remove} -> {@link #unlockItem}
      • - *
      • LOADS : {@link @putFromLoad}
      • - *
      - * Note the special case of UPDATES above. Because the cache key itself has changed here we need to remove the - * old entry as well as - *

      - * There is another usage pattern that is used to invalidate entries - * afterQuery performing "bulk" HQL/SQL operations: - * {@link #lockRegion} -> {@link #removeAll} -> {@link #unlockRegion} - *

      - * IMPORTANT : NaturalIds are not versioned so {@code null} will always be passed to the version parameter to:

        - *
      • {@link RegionAccessStrategy#putFromLoad(SharedSessionContractImplementor, Object, Object, long, Object)}
      • - *
      • {@link RegionAccessStrategy#putFromLoad(SharedSessionContractImplementor, Object, Object, long, Object, boolean)}
      • - *
      • {@link RegionAccessStrategy#lockItem(SharedSessionContractImplementor, Object, Object)}
      • - *
      - * - * @author Gavin King - * @author Steve Ebersole - * @author Eric Dalquist - */ -public interface NaturalIdRegionAccessStrategy extends RegionAccessStrategy { - - /** - * To create instances of NaturalIdCacheKey for this region, Hibernate will invoke this method - * exclusively so that generated implementations can generate optimised keys. - * @param naturalIdValues the sequence of values which unequivocally identifies a cached element on this region - * @param persister the persister of the element being cached - * @param session - * @return a key which can be used to identify this an element unequivocally on this same region - */ - Object generateCacheKey( - Object[] naturalIdValues, - EntityPersister persister, - SharedSessionContractImplementor session); - - /** - * Performs reverse operation to {@link #generateCacheKey(Object[], EntityPersister, SharedSessionContractImplementor)}, returning - * the original naturalIdValues. - * @param cacheKey key returned from {@link #generateCacheKey(Object[], EntityPersister, SharedSessionContractImplementor)} - * @return the sequence of values which unequivocally identifies a cached element on this region - */ - Object[] getNaturalIdValues(Object cacheKey); - - /** - * Get the wrapped naturalId cache region - * - * @return The underlying region - */ - NaturalIdRegion getRegion(); - - /** - * Called afterQuery an item has been inserted (beforeQuery the transaction completes), - * instead of calling evict(). - * This method is used by "synchronous" concurrency strategies. - * - * @param session Current session - * @param key The item key - * @param value The item - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propagated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean insert(SharedSessionContractImplementor session, Object key, Object value) throws CacheException; - - /** - * Called afterQuery an item has been inserted (afterQuery the transaction completes), - * instead of calling release(). - * This method is used by "asynchronous" concurrency strategies. - * - * @param session Current session - * @param key The item key - * @param value The item - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propagated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value) throws CacheException; - - /** - * Called afterQuery an item has been updated (beforeQuery the transaction completes), - * instead of calling evict(). This method is used by "synchronous" concurrency - * strategies. - * - * @param session Current session - * @param key The item key - * @param value The item - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propagated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean update(SharedSessionContractImplementor session, Object key, Object value) throws CacheException; - - /** - * Called afterQuery an item has been updated (afterQuery the transaction completes), - * instead of calling release(). This method is used by "asynchronous" - * concurrency strategies. - * - * @param session Current session - * @param key The item key - * @param value The item - * @param lock The lock previously obtained from {@link #lockItem} - * @return Were the contents of the cache actual changed by this operation? - * @throws CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean afterUpdate(SharedSessionContractImplementor session, Object key, Object value, SoftLock lock) throws CacheException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/RegionAccessStrategy.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/RegionAccessStrategy.java deleted file mode 100644 index 55d0d1cf1c4f..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/RegionAccessStrategy.java +++ /dev/null @@ -1,149 +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.cache.spi.access; - - -import org.hibernate.cache.CacheException; -import org.hibernate.engine.spi.SharedSessionContractImplementor; - -/** - * Base access strategy for all regions. - * - * @author Gail Badner - */ -public interface RegionAccessStrategy { - - /** - * Attempt to retrieve an object from the cache. Mainly used in attempting - * to resolve entities/collections from the second level cache. - * - * @param session Current session. - * @param key The key of the item to be retrieved. - * @param txTimestamp a timestamp prior to the transaction start time - * @return the cached object or null - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - Object get(SharedSessionContractImplementor session, Object key, long txTimestamp) throws CacheException; - - /** - * Attempt to cache an object, afterQuery loading from the database. - * - * @param session Current session. - * @param key The item key - * @param value The item - * @param txTimestamp a timestamp prior to the transaction start time - * @param version the item version number - * @return true if the object was successfully cached - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean putFromLoad( - SharedSessionContractImplementor session, - Object key, - Object value, - long txTimestamp, - Object version) throws CacheException; - - /** - * Attempt to cache an object, afterQuery loading from the database, explicitly - * specifying the minimalPut behavior. - * - * @param session Current session. - * @param key The item key - * @param value The item - * @param txTimestamp a timestamp prior to the transaction start time - * @param version the item version number - * @param minimalPutOverride Explicit minimalPut flag - * @return true if the object was successfully cached - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - boolean putFromLoad( - SharedSessionContractImplementor session, - Object key, - Object value, - long txTimestamp, - Object version, - boolean minimalPutOverride) throws CacheException; - - /** - * We are going to attempt to update/delete the keyed object. This - * method is used by "asynchronous" concurrency strategies. - *

      - * The returned object must be passed back to {@link #unlockItem}, to release the - * lock. Concurrency strategies which do not support client-visible - * locks may silently return null. - * - * @param session Current session. - * @param key The key of the item to lock - * @param version The item's current version value - * @return A representation of our lock on the item; or null. - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - SoftLock lockItem(SharedSessionContractImplementor session, Object key, Object version) throws CacheException; - - /** - * Lock the entire region - * - * @return A representation of our lock on the item; or null. - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - SoftLock lockRegion() throws CacheException; - - /** - * Called when we have finished the attempted update/delete (which may or - * may not have been successful), afterQuery transaction completion. This method - * is used by "asynchronous" concurrency strategies. - * - * @param session Current session. - * @param key The item key - * @param lock The lock previously obtained from {@link #lockItem} - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) throws CacheException; - - /** - * Called afterQuery we have finished the attempted invalidation of the entire - * region - * - * @param lock The lock previously obtained from {@link #lockRegion} - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - void unlockRegion(SoftLock lock) throws CacheException; - - /** - * Called afterQuery an item has become stale (beforeQuery the transaction completes). - * This method is used by "synchronous" concurrency strategies. - * - * @param session - * @param key The key of the item to remove - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - void remove(SharedSessionContractImplementor session, Object key) throws CacheException; - - /** - * Called to evict data from the entire region - * - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - void removeAll() throws CacheException; - - /** - * Forcibly evict an item from the cache immediately without regard for transaction - * isolation. - * - * @param key The key of the item to remove - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - void evict(Object key) throws CacheException; - - /** - * Forcibly evict all items from the cache immediately without regard for transaction - * isolation. - * - * @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region} - */ - void evictAll() throws CacheException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/SoftLock.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/SoftLock.java index 6d39b159815c..b37b7d32d094 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/SoftLock.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/SoftLock.java @@ -1,13 +1,13 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi.access; /** - * Marker object for use by synchronous concurrency strategies + * Memento object for use by synchronous concurrency strategies * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/UnknownAccessTypeException.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/UnknownAccessTypeException.java index e9f5df679507..1989d231e1e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/UnknownAccessTypeException.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/UnknownAccessTypeException.java @@ -1,8 +1,8 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.cache.spi.access; @@ -10,6 +10,8 @@ /** * Indicates that an unknown AccessType external name was encountered + * or that an AccessType was requested that the underlying cache provider + * does not support. * * @author Steve Ebersole * diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/package.html b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/package.html index f42d33a317da..a84889bdb5a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/package.html +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/package.html @@ -8,13 +8,13 @@

      - Defines contracts for transactional and concurrent access to cached - {@link org.hibernate.cache.spi.access.EntityRegionAccessStrategy entity} and - {@link org.hibernate.cache.spi.access.CollectionRegionAccessStrategy collection} data. Transactions pass in a + Defines contracts for transactional and concurrent access to cached + {@link org.hibernate.cache.spi.access.EntityDataAccess entity} and + {@link org.hibernate.cache.spi.access.CollectionDataAccess collection} data. Transactions pass in a timestamp indicating transaction start time which is then used to protect against concurrent access (exactly how that occurs is based on the actual access-strategy impl used). Two different implementation patterns are provided for: -

        +
        • A transaction-aware cache implementation might be wrapped by a synchronous access strategy, where updates to the cache are written to the cache inside the transaction. diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java index ab51d8fd87e3..2d88392db568 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java @@ -27,8 +27,8 @@ * @author Steve Ebersole */ public class StandardCacheEntryImpl implements CacheEntry { + private final Serializable[] disassembledState; - private final String disassembledStateText; private final Object version; private final String subclass; @@ -57,18 +57,12 @@ public StandardCacheEntryImpl( session, owner ); - this.disassembledStateText = TypeHelper.toLoggableString( - state, - persister.getPropertyTypes(), - session.getFactory() - ); this.subclass = persister.getEntityName(); this.version = version; } - StandardCacheEntryImpl(Serializable[] state, String disassembledStateText, String subclass, Object version) { - this.disassembledState = state; - this.disassembledStateText = disassembledStateText; + StandardCacheEntryImpl(Serializable[] disassembledState, String subclass, Object version) { + this.disassembledState = disassembledState; this.subclass = subclass; this.version = version; } @@ -138,18 +132,18 @@ public Object[] assemble( } //assembled state gets put in a new array (we read from cache by value!) - final Object[] assembledProps = TypeHelper.assemble( + final Object[] state = TypeHelper.assemble( disassembledState, persister.getPropertyTypes(), session, instance ); - //persister.setIdentifier(instance, id); //beforeQuery calling interceptor, for consistency with normal load + //persister.setIdentifier(instance, id); //before calling interceptor, for consistency with normal load //TODO: reuse the PreLoadEvent final PreLoadEvent preLoadEvent = new PreLoadEvent( session ) .setEntity( instance ) - .setState( assembledProps ) + .setState( state ) .setId( id ) .setPersister( persister ); @@ -162,13 +156,14 @@ public Object[] assemble( listener.onPreLoad( preLoadEvent ); } - persister.setPropertyValues( instance, assembledProps ); + persister.setPropertyValues( instance, state ); - return assembledProps; + return state; } @Override public String toString() { - return "CacheEntry(" + subclass + " {" + disassembledStateText + "})"; + return "CacheEntry(" + subclass + ')'; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java index 3fe74701327d..46e0738a6001 100755 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java @@ -9,11 +9,9 @@ import java.io.Serializable; import java.util.HashMap; import java.util.Map; -import java.util.Set; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.TypeHelper; /** * Structured CacheEntry format for entities. Used to store the entry into the second-level cache @@ -45,15 +43,14 @@ public Object destructure(Object structured, SessionFactoryImplementor factory) final Object version = map.get( VERSION_KEY ); final EntityPersister subclassPersister = factory.getEntityPersister( subclass ); final String[] names = subclassPersister.getPropertyNames(); - final Serializable[] state = new Serializable[names.length]; + final Serializable[] disassembledState = new Serializable[names.length]; for ( int i = 0; i < names.length; i++ ) { - state[i] = (Serializable) map.get( names[i] ); + disassembledState[i] = (Serializable) map.get( names[i] ); } return new StandardCacheEntryImpl( - state, - TypeHelper.toLoggableString( state, subclassPersister.getPropertyTypes(), factory ), - subclass, - version + disassembledState, + subclass, + version ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/package-info.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/package-info.java new file mode 100644 index 000000000000..655dc3f2a109 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/package-info.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 + */ + +/** + * Defines the integration aspect of Hibernate's second-level + * caching allowing "caching back ends" to be plugged in as + * a caching provider. + * + * {@link org.hibernate.cache.spi.RegionFactory} is the main + * integration contract that defines how Hibernate accesses + * the provider. It's main contract is the generation of + * {@link org.hibernate.cache.spi.Region} references with the + * requested intent (what will be stored there). + * + * Generally a provider will integrate with Hibernate by: + * + * 1. implementing the contracts in {@link org.hibernate.cache.spi} + * 2. implementing the contracts in {@link org.hibernate.cache.spi.support} + * 3. a mix of (1) and (2) + * + * The first approach allows for more control of the set up, but also requires more + * to implement. The second approach tries to minimize the amount of work needed + * to integrate with caching providers to basically the + * {@link org.hibernate.cache.spi.support.StorageAccess} and + * {@link org.hibernate.cache.spi.support.DomainDataStorageAccess} contracts which + * are basic read/write type abstractions of the underlying "cache" object - it + * is a nearly complete implementation aside from providing the proper "storage + * access" objects. + * + * Note: providers may also integrate with Hibernate via + * Hibernate's JCache support as defined by the `hibernate-jcache` + * module - no code involved aside from being a JCache implementation + * properly registered via the JCache spec. + */ +package org.hibernate.cache.spi; diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/package.html b/hibernate-core/src/main/java/org/hibernate/cache/spi/package.html deleted file mode 100644 index eaabc3a957a3..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/package.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - -

          - Defines the Hibernate second level caching SPI. -

          -

          - The initial contract here is {@link org.hibernate.cache.spi.RegionFactory} whose implementations are - responsible for configuring and managing lifecycle operations in regards to the particular underlying - caching library. Its other main purpose is to build specializations {@link org.hibernate.cache.spi.Region} - instances based on the type of data we will be storing in that given region. -

          - - diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java new file mode 100644 index 000000000000..e4475a3a5a83 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.CachedDomainDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractCachedDomainDataAccess implements CachedDomainDataAccess, AbstractDomainDataRegion.Destructible { + private static final Logger log = Logger.getLogger( AbstractCachedDomainDataAccess.class ); + + private final DomainDataRegion region; + private final DomainDataStorageAccess storageAccess; + + protected AbstractCachedDomainDataAccess( + DomainDataRegion region, + DomainDataStorageAccess storageAccess) { + this.region = region; + this.storageAccess = storageAccess; + } + + @Override + public DomainDataRegion getRegion() { + return region; + } + + protected DomainDataStorageAccess getStorageAccess() { + return storageAccess; + } + + protected void clearCache() { + log.debugf( "Clearing cache data map [region=`%s`]", region.getName() ); + getStorageAccess().evictData(); + } + + @Override + public boolean contains(Object key) { + return getStorageAccess().contains( key ); + } + + @Override + public Object get(SharedSessionContractImplementor session, Object key) { + return getStorageAccess().getFromCache( key, session ); + } + + @Override + public boolean putFromLoad( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version) { + getStorageAccess().putFromLoad( key, value, session ); + return true; + } + + @Override + public boolean putFromLoad( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version, + boolean minimalPutOverride) { + return putFromLoad( session, key, value, version ); + } + + private static final SoftLock REGION_LOCK = new SoftLock() { + }; + + @Override + public SoftLock lockRegion() { + return REGION_LOCK; + } + + @Override + public void unlockRegion(SoftLock lock) { + evictAll(); + } + + @Override + public void remove(SharedSessionContractImplementor session, Object key) { + getStorageAccess().removeFromCache( key, session ); + } + + @Override + public void removeAll(SharedSessionContractImplementor session) { + getStorageAccess().clearCache( session ); + } + + @Override + public void evict(Object key) { + getStorageAccess().evictData( key ); + } + + @Override + public void evictAll() { + getStorageAccess().evictData(); + } + + @Override + public void destroy() { + getStorageAccess().release(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCollectionDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCollectionDataAccess.java new file mode 100644 index 000000000000..1556561a73af --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCollectionDataAccess.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.cache.spi.support; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.collection.CollectionPersister; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractCollectionDataAccess + extends AbstractCachedDomainDataAccess + implements CollectionDataAccess { + + private final CacheKeysFactory keysFactory; + + public AbstractCollectionDataAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + CollectionDataCachingConfig config) { + super( region, storageAccess ); + this.keysFactory = keysFactory; + } + + @Override + public Object generateCacheKey(Object id, CollectionPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) { + return keysFactory.createCollectionKey( id, persister, factory, tenantIdentifier ); + } + + @Override + public Object getCacheKeyId(Object cacheKey) { + return keysFactory.getCollectionId( cacheKey ); + } + + @Override + public SoftLock lockItem(SharedSessionContractImplementor session, Object key, Object version) { + return null; + } + + @Override + public void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) { + + } + + @Override + public SoftLock lockRegion() { + return null; + } + + @Override + public void unlockRegion(SoftLock lock) { + clearCache(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractDomainDataRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractDomainDataRegion.java new file mode 100644 index 000000000000..a76eacfc1c6a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractDomainDataRegion.java @@ -0,0 +1,233 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.hibernate.cache.CacheException; +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.internal.DefaultCacheKeysFactory; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.model.domain.NavigableRole; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractDomainDataRegion extends AbstractRegion implements DomainDataRegion { + private static final Logger log = Logger.getLogger( AbstractDomainDataRegion.class ); + + private final SessionFactoryImplementor sessionFactory; + private final CacheKeysFactory effectiveKeysFactory; + + private Map entityDataAccessMap; + private Map naturalIdDataAccessMap; + private Map collectionDataAccessMap; + + public AbstractDomainDataRegion( + DomainDataRegionConfig regionConfig, + RegionFactory regionFactory, + CacheKeysFactory defaultKeysFactory, + DomainDataRegionBuildingContext buildingContext) { +// super( regionFactory.qualify( regionConfig.getRegionName() ), regionFactory ); + super( regionConfig.getRegionName(), regionFactory ); + + this.sessionFactory = buildingContext.getSessionFactory(); + + if ( defaultKeysFactory == null ) { + defaultKeysFactory = DefaultCacheKeysFactory.INSTANCE; + } + this.effectiveKeysFactory = buildingContext.getEnforcedCacheKeysFactory() != null + ? buildingContext.getEnforcedCacheKeysFactory() + : defaultKeysFactory; + } + + /** + * Should be called at the end of the subtype's constructor, or at least after the + * `#super(...)` (aka, this type's constructor) call. It's a timing issue - we need access + * to the DomainDataStorageAccess in DomainDataRegionTemplate but in methods initiated + * (atm) from AbstractDomainDataRegion's constructor + */ + protected void completeInstantiation( + DomainDataRegionConfig regionConfig, + DomainDataRegionBuildingContext buildingContext) { + log.tracef( "DomainDataRegion created [%s]; key-factory = %s", regionConfig.getRegionName(), effectiveKeysFactory ); + + this.entityDataAccessMap = generateEntityDataAccessMap( regionConfig ); + this.naturalIdDataAccessMap = generateNaturalIdDataAccessMap( regionConfig ); + this.collectionDataAccessMap = generateCollectionDataAccessMap( regionConfig ); + + } + + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + public CacheKeysFactory getEffectiveKeysFactory() { + return effectiveKeysFactory; + } + + @Override + public EntityDataAccess getEntityDataAccess(NavigableRole rootEntityRole) { + final EntityDataAccess access = entityDataAccessMap.get( rootEntityRole ); + if ( access == null ) { + throw new IllegalArgumentException( "Caching was not configured for entity : " + rootEntityRole.getFullPath() ); + } + return access; + } + + + @Override + public NaturalIdDataAccess getNaturalIdDataAccess(NavigableRole rootEntityRole) { + final NaturalIdDataAccess access = naturalIdDataAccessMap.get( rootEntityRole ); + if ( access == null ) { + throw new IllegalArgumentException( "Caching was not configured for entity natural-id : " + rootEntityRole.getFullPath() ); + } + return access; + } + + @Override + public CollectionDataAccess getCollectionDataAccess(NavigableRole collectionRole) { + final CollectionDataAccess access = collectionDataAccessMap.get( collectionRole ); + if ( access == null ) { + throw new IllegalArgumentException( "Caching was not configured for collection : " + collectionRole.getFullPath() ); + } + return access; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // creation + + protected abstract EntityDataAccess generateEntityAccess(EntityDataCachingConfig entityAccessConfig); + protected abstract CollectionDataAccess generateCollectionAccess(CollectionDataCachingConfig cachingConfig); + protected abstract NaturalIdDataAccess generateNaturalIdAccess(NaturalIdDataCachingConfig naturalIdAccessConfig); + + private Map generateEntityDataAccessMap( + DomainDataRegionConfig regionConfig) { + if ( regionConfig.getEntityCaching().isEmpty() ) { + return Collections.emptyMap(); + } + + final Map accessMap = new ConcurrentHashMap<>(); + for ( EntityDataCachingConfig entityAccessConfig : regionConfig.getEntityCaching() ) { + accessMap.computeIfAbsent( + entityAccessConfig.getNavigableRole(), + hierarchy -> generateEntityAccess( entityAccessConfig ) + ); + } + + return Collections.unmodifiableMap( accessMap ); + } + + private Map generateNaturalIdDataAccessMap(DomainDataRegionConfig regionConfig) { + if ( regionConfig.getNaturalIdCaching().isEmpty() ) { + return Collections.emptyMap(); + } + + final Map accessMap = new ConcurrentHashMap<>(); + for ( NaturalIdDataCachingConfig naturalIdAccessConfig : regionConfig.getNaturalIdCaching() ) { + accessMap.computeIfAbsent( + naturalIdAccessConfig.getNavigableRole(), + hierarchy -> generateNaturalIdAccess( naturalIdAccessConfig ) + ); + } + + return Collections.unmodifiableMap( accessMap ); + } + + private Map generateCollectionDataAccessMap( + DomainDataRegionConfig regionConfig) { + if ( regionConfig.getCollectionCaching().isEmpty() ) { + return Collections.emptyMap(); + } + + final Map accessMap = new ConcurrentHashMap<>(); + for ( CollectionDataCachingConfig cachingConfig : regionConfig.getCollectionCaching() ) { + accessMap.computeIfAbsent( + cachingConfig.getNavigableRole(), + hierarchy -> generateCollectionAccess( cachingConfig ) + ); + } + + return Collections.unmodifiableMap( accessMap ); + } + + @Override + public void clear() { + for ( EntityDataAccess cacheAccess : entityDataAccessMap.values() ) { + cacheAccess.evictAll(); + } + + for ( NaturalIdDataAccess cacheAccess : naturalIdDataAccessMap.values() ) { + cacheAccess.evictAll(); + } + + for ( CollectionDataAccess cacheAccess : collectionDataAccessMap.values() ) { + cacheAccess.evictAll(); + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // destruction + + /** + * Optional interface caching implementors can implement in their + * CachedDomainDataAccess impls to automatically have them destroyed + * when this region is destroyed + */ + public interface Destructible { + void destroy(); + } + + protected void releaseDataAccess(EntityDataAccess cacheAccess) { + if ( Destructible.class.isInstance( cacheAccess ) ) { + ( (Destructible) cacheAccess ).destroy(); + } + } + + protected void releaseDataAccess(NaturalIdDataAccess cacheAccess) { + if ( Destructible.class.isInstance( cacheAccess ) ) { + ( (Destructible) cacheAccess ).destroy(); + } + } + + protected void releaseDataAccess(CollectionDataAccess cacheAccess) { + if ( Destructible.class.isInstance( cacheAccess ) ) { + ( (Destructible) cacheAccess ).destroy(); + } + } + + @Override + public void destroy() throws CacheException { + for ( EntityDataAccess cacheAccess : entityDataAccessMap.values() ) { + releaseDataAccess( cacheAccess ); + } + + for ( NaturalIdDataAccess cacheAccess : naturalIdDataAccessMap.values() ) { + releaseDataAccess( cacheAccess ); + } + + for ( CollectionDataAccess cacheAccess : collectionDataAccessMap.values() ) { + releaseDataAccess( cacheAccess ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractEntityDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractEntityDataAccess.java new file mode 100644 index 000000000000..7370895f7b76 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractEntityDataAccess.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractEntityDataAccess + extends AbstractCachedDomainDataAccess + implements EntityDataAccess { + + private final CacheKeysFactory cacheKeysFactory; + + public AbstractEntityDataAccess( + DomainDataRegion region, + CacheKeysFactory cacheKeysFactory, + DomainDataStorageAccess storageAccess) { + super( region, storageAccess ); + this.cacheKeysFactory = cacheKeysFactory; + } + + @Override + public Object generateCacheKey( + Object id, + EntityPersister rootEntityDescriptor, + SessionFactoryImplementor factory, + String tenantIdentifier) { + return cacheKeysFactory.createEntityKey( + id, + rootEntityDescriptor, + factory, + tenantIdentifier + ); + } + + @Override + public Object getCacheKeyId(Object cacheKey) { + return cacheKeysFactory.getEntityId( cacheKey ); + } + + @Override + public SoftLock lockRegion() { + return null; + } + + @Override + public void unlockRegion(SoftLock lock) { + clearCache(); + } + + public SoftLock lockItem( + SharedSessionContractImplementor session, + Object key, + Object version) { + return null; + } + + @Override + public void unlockItem( + SharedSessionContractImplementor session, + Object key, + SoftLock lock) { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractNaturalIdDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractNaturalIdDataAccess.java new file mode 100644 index 000000000000..292e7c85a423 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractNaturalIdDataAccess.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractNaturalIdDataAccess extends AbstractCachedDomainDataAccess implements NaturalIdDataAccess { + private final CacheKeysFactory keysFactory; + + public AbstractNaturalIdDataAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + NaturalIdDataCachingConfig config) { + super( region, storageAccess ); + this.keysFactory = keysFactory; + } + + @Override + public Object generateCacheKey( + Object[] naturalIdValues, + EntityPersister persister, + SharedSessionContractImplementor session) { + return keysFactory.createNaturalIdKey( naturalIdValues, persister, session ); + } + + @Override + public Object[] getNaturalIdValues(Object cacheKey) { + return keysFactory.getNaturalIdValues( cacheKey ); + } + + + @Override + public boolean insert(SharedSessionContractImplementor session, Object key, Object value) { + getStorageAccess().putIntoCache( key, value, session ); + return true; + } + + @Override + public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value) { + return false; + } + + @Override + public boolean update(SharedSessionContractImplementor session, Object key, Object value) { + getStorageAccess().putIntoCache( key, value, session ); + return true; + } + + @Override + public boolean afterUpdate(SharedSessionContractImplementor session, Object key, Object value, SoftLock lock) { + return false; + } + + @Override + public SoftLock lockRegion() { + return null; + } + + @Override + public void unlockRegion(SoftLock lock) { + clearCache(); + } + + public SoftLock lockItem( + SharedSessionContractImplementor session, + Object key, + Object version) { + return null; + } + + @Override + public void unlockItem( + SharedSessionContractImplementor session, + Object key, + SoftLock lock) { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractReadWriteAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractReadWriteAccess.java new file mode 100644 index 000000000000..29154d62c58e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractReadWriteAccess.java @@ -0,0 +1,440 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Locale; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.SecondLevelCacheLogger; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractReadWriteAccess extends AbstractCachedDomainDataAccess { + private static final Logger log = Logger.getLogger( AbstractReadWriteAccess.class ); + private static final boolean DEBUG_ENABLED = log.isDebugEnabled(); + + private final UUID uuid = UUID.randomUUID(); + private final AtomicLong nextLockId = new AtomicLong(); + private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); + private final Lock readLock = reentrantReadWriteLock.readLock(); + private final Lock writeLock = reentrantReadWriteLock.writeLock(); + + protected AbstractReadWriteAccess( + DomainDataRegion domainDataRegion, + DomainDataStorageAccess storageAccess) { + super( domainDataRegion, storageAccess ); + } + + protected abstract Comparator getVersionComparator(); + + protected UUID uuid() { + return uuid; + } + + protected long nextLockId() { + return nextLockId.getAndIncrement(); + } + + protected Lock readLock() { + return readLock; + } + + protected Lock writeLock() { + return writeLock; + } + + /** + * Returns null if the item is not readable. Locked items are not readable, nor are items created + * afterQuery the start of this transaction. + */ + @Override + public Object get(SharedSessionContractImplementor session, Object key) { + log.debugf( "Getting cached data from region [`%s` (%s)] by key [%s]", getRegion().getName(), getAccessType(), key ); + try { + readLock.lock(); + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + + if ( item == null ) { + log.debugf( "Cache miss : region = `%s`, key = `%s`", getRegion().getName(), key ); + return null; + } + + boolean readable = item.isReadable( session.getTransactionStartTimestamp() ); + if ( readable ) { + log.debugf( "Cache hit : region = `%s`, key = `%s`", getRegion().getName(), key ); + return item.getValue(); + } + else { + log.debugf( "Cache hit, but item is unreadable/invalid : region = `%s`, key = `%s`", getRegion().getName(), key ); + return null; + } + } + finally { + readLock.unlock(); + } + } + + @Override + public boolean putFromLoad( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version) { + try { + log.debugf( "Caching data from load [region=`%s` (%s)] : key[%s] -> value[%s]", getRegion().getName(), getAccessType(), key, value ); + writeLock.lock(); + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + + boolean writable = item == null || item.isWriteable( session.getTransactionStartTimestamp(), version, getVersionComparator() ); + if ( writable ) { + getStorageAccess().putIntoCache( + key, + new Item( value, version, session.getTransactionStartTimestamp() ), + session + ); + return true; + } + else { + log.debugf( + "Cache put-from-load [region=`%s` (%s), key=`%s`, value=`%s`] failed due to being non-writable", + getAccessType(), + getRegion().getName(), + key, + value + ); + return false; + } + } + finally { + writeLock.unlock(); + } + } + + protected abstract AccessedDataClassification getAccessedDataClassification(); + + @Override + public final boolean putFromLoad( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version, + boolean minimalPutOverride) { + return putFromLoad( session, key, value, version ); + } + + @Override + public SoftLock lockItem(SharedSessionContractImplementor session, Object key, Object version) { + try { + writeLock.lock(); + + long timeout = getRegion().getRegionFactory().nextTimestamp() + getRegion().getRegionFactory().getTimeout(); + log.debugf( "Locking cache item [region=`%s` (%s)] : `%s` (timeout=%s, version=%s)", getRegion().getName(), getAccessType(), key, timeout, version ); + + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + final SoftLockImpl lock = ( item == null ) + ? new SoftLockImpl( timeout, uuid, nextLockId(), version ) + : item.lock( timeout, uuid, nextLockId() ); + getStorageAccess().putIntoCache( key, lock, session ); + return lock; + } + finally { + writeLock.unlock(); + } + } + + @Override + public void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) { + try { + log.debugf( "Unlocking cache item [region=`%s` (%s)] : %s", getRegion().getName(), getAccessType(), key ); + writeLock.lock(); + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + + if ( ( item != null ) && item.isUnlockable( lock ) ) { + decrementLock( session, key, (SoftLockImpl) item ); + } + else { + handleLockExpiry( session, key, item ); + } + } + finally { + writeLock.unlock(); + } + } + + @SuppressWarnings("WeakerAccess") + protected void decrementLock(SharedSessionContractImplementor session, Object key, SoftLockImpl lock) { + lock.unlock( getRegion().getRegionFactory().nextTimestamp() ); + getStorageAccess().putIntoCache( key, lock, session ); + } + + @SuppressWarnings("WeakerAccess") + protected void handleLockExpiry(SharedSessionContractImplementor session, Object key, Lockable lock) { + SecondLevelCacheLogger.INSTANCE.softLockedCacheExpired( getRegion().getName(), key ); + log.info( "Cached entry expired : " + key ); + + // create new lock that times out immediately + long ts = getRegion().getRegionFactory().nextTimestamp() + getRegion().getRegionFactory().getTimeout(); + SoftLockImpl newLock = new SoftLockImpl( ts, uuid, nextLockId.getAndIncrement(), null ); + //newLock.unlock( ts ); + newLock.unlock( ts - getRegion().getRegionFactory().getTimeout() ); + getStorageAccess().putIntoCache( key, newLock, session ); + } + + @Override + public void remove(SharedSessionContractImplementor session, Object key) { + if ( getStorageAccess().getFromCache( key, session ) instanceof SoftLock ) { + log.debugf( "Skipping #remove call in read-write access to maintain SoftLock : %s", key ); + // don'tm do anything... we want the SoftLock to remain in place + } + else { + super.remove( session, key ); + } + } + + /** + * Interface type implemented by all wrapper objects in the cache. + */ + public interface Lockable { + + /** + * Returns true if the enclosed value can be read by a transaction started at the given time. + */ + boolean isReadable(long txTimestamp); + + /** + * Returns true if the enclosed value can be replaced with one of the given version by a + * transaction started at the given time. + */ + boolean isWriteable(long txTimestamp, Object version, Comparator versionComparator); + + /** + * Returns the enclosed value. + */ + Object getValue(); + + /** + * Returns true if the given lock can be unlocked using the given SoftLock instance as a handle. + */ + boolean isUnlockable(SoftLock lock); + + /** + * Locks this entry, stamping it with the UUID and lockId given, with the lock timeout occuring at the specified + * time. The returned Lock object can be used to unlock the entry in the future. + */ + SoftLockImpl lock(long timeout, UUID uuid, long lockId); + } + + /** + * Wrapper type representing unlocked items. + */ + public final static class Item implements Serializable, Lockable { + private static final long serialVersionUID = 1L; + private final Object value; + private final Object version; + private final long timestamp; + + /** + * Creates an unlocked item wrapping the given value with a version and creation timestamp. + */ + Item(Object value, Object version, long timestamp) { + this.value = value; + this.version = version; + this.timestamp = timestamp; + } + + @Override + public boolean isReadable(long txTimestamp) { + if ( DEBUG_ENABLED ) { + log.debugf( + "Checking readability of read-write cache item [timestamp=`%s`, version=`%s`] : txTimestamp=`%s`", + (Object) timestamp, + version, + txTimestamp + ); + } + + return txTimestamp > timestamp; + } + + @Override + public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) { + if ( DEBUG_ENABLED ) { + log.debugf( + "Checking writeability of read-write cache item [timestamp=`%s`, version=`%s`] : txTimestamp=`%s`, newVersion=`%s`", + timestamp, + version, + txTimestamp, + newVersion + ); + } + + //noinspection unchecked + return version != null && versionComparator.compare( version, newVersion ) < 0; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean isUnlockable(SoftLock lock) { + return false; + } + + @Override + public SoftLockImpl lock(long timeout, UUID uuid, long lockId) { + return new SoftLockImpl( timeout, uuid, lockId, version ); + } + + @Override + public String toString() { + return String.format( + Locale.ROOT, + "read-write Item(%s)", + getValue() + ); + } + } + + /** + * Wrapper type representing locked items. + */ + public static class SoftLockImpl implements Serializable, Lockable, SoftLock { + + private static final long serialVersionUID = 2L; + + private final UUID sourceUuid; + private final long lockId; + private final Object version; + + private long timeout; + private boolean concurrent; + private int multiplicity = 1; + private long unlockTimestamp; + + /** + * Creates a locked item with the given identifiers and object version. + */ + SoftLockImpl(long timeout, UUID sourceUuid, long lockId, Object version) { + this.timeout = timeout; + this.lockId = lockId; + this.version = version; + this.sourceUuid = sourceUuid; + } + + + @Override + public boolean isReadable(long txTimestamp) { + return false; + } + + @Override + public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) { + if ( DEBUG_ENABLED ) { + log.debugf( + "Checking writeability of read-write cache lock [timeout=`%s`, lockId=`%s`, version=`%s`, sourceUuid=%s, multiplicity=`%s`, unlockTimestamp=`%s`] : txTimestamp=`%s`, newVersion=`%s`", + timeout, + lockId, + version, + sourceUuid, + multiplicity, + unlockTimestamp, + txTimestamp, + newVersion + ); + } + + if ( txTimestamp > timeout ) { + // if timed-out - allow write + return true; + } + if ( multiplicity > 0 ) { + // if still locked - disallow write + return false; + } + + //noinspection unchecked + return version == null + ? txTimestamp > unlockTimestamp + : versionComparator.compare( version, newVersion ) < 0; + } + + @Override + public Object getValue() { + return null; + } + + @Override + public boolean isUnlockable(SoftLock lock) { + return equals( lock ); + } + + @Override + public boolean equals(Object o) { + if ( o == this ) { + return true; + } + else if ( o instanceof SoftLockImpl ) { + return ( lockId == ( (SoftLockImpl) o ).lockId ) && sourceUuid.equals( ( (SoftLockImpl) o ).sourceUuid ); + } + else { + return false; + } + } + + @Override + public int hashCode() { + int hash = ( sourceUuid != null ? sourceUuid.hashCode() : 0 ); + int temp = (int) lockId; + for ( int i = 1; i < Long.SIZE / Integer.SIZE; i++ ) { + temp ^= ( lockId >>> ( i * Integer.SIZE ) ); + } + return hash + temp; + } + + /** + * Returns true if this Lock has been concurrently locked by more than one transaction. + */ + public boolean wasLockedConcurrently() { + return concurrent; + } + + @Override + public SoftLockImpl lock(long timeout, UUID uuid, long lockId) { + concurrent = true; + multiplicity++; + this.timeout = timeout; + return this; + } + + /** + * Unlocks this Lock, and timestamps the unlock event. + */ + public void unlock(long timestamp) { + if ( --multiplicity == 0 ) { + unlockTimestamp = timestamp; + } + } + + @Override + public String toString() { + return "Lock Source-UUID:" + sourceUuid + " Lock-ID:" + lockId; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractRegion.java new file mode 100644 index 000000000000..d407da4061ef --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractRegion.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.spi.Region; +import org.hibernate.cache.spi.RegionFactory; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractRegion implements Region { + private final String name; + private final RegionFactory regionFactory; + + /** + * Constructs an {@link AbstractRegion}. + * + * @param name - the unqualified region name + * @param regionFactory - the region factory + */ + public AbstractRegion(String name, RegionFactory regionFactory) { + this.name = name; + this.regionFactory = regionFactory; + } + + @Override + public String getName() { + return name; + } + + @Override + public RegionFactory getRegionFactory() { + return regionFactory; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AccessedDataClassification.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AccessedDataClassification.java new file mode 100644 index 000000000000..458165af6eec --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AccessedDataClassification.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.cache.spi.support; + +/** + * @author Steve Ebersole + */ +public enum AccessedDataClassification { + ENTITY, + NATURAL_ID, + COLLECTION, + QUERY_RESULTS, + TIMESTAMPS +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CacheUtils.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CacheUtils.java new file mode 100644 index 000000000000..b9e92fe2bb22 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CacheUtils.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.cache.spi.support; + +import org.hibernate.boot.spi.SessionFactoryOptions; + +/** + * @author Steve Ebersole + */ +public class CacheUtils { + public static boolean isUnqualified(String regionName, SessionFactoryOptions options) { + final String prefix = options.getCacheRegionPrefix(); + if ( prefix == null ) { + return true; + } + else { + return !regionName.startsWith( prefix ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionNonStrictReadWriteAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionNonStrictReadWriteAccess.java new file mode 100644 index 000000000000..c5e39b2fff42 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionNonStrictReadWriteAccess.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.CollectionDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#NONSTRICT_READ_WRITE} access type. + * + * @author Steve Ebersole + */ +public class CollectionNonStrictReadWriteAccess extends AbstractCollectionDataAccess { + public CollectionNonStrictReadWriteAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + CollectionDataCachingConfig config) { + super( region, keysFactory, storageAccess, config ); + } + + @Override + public AccessType getAccessType() { + return AccessType.READ_WRITE; + } + + @Override + public void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) { + getStorageAccess().removeFromCache( key, session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionReadOnlyAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionReadOnlyAccess.java new file mode 100644 index 000000000000..bcbc4fe45a2c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionReadOnlyAccess.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.cache.spi.support; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.CollectionDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#READ_ONLY} access type. + * + * @author Steve Ebersole + */ +public class CollectionReadOnlyAccess extends AbstractCollectionDataAccess { + public CollectionReadOnlyAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + CollectionDataCachingConfig config) { + super( region, keysFactory, storageAccess, config ); + } + + @Override + public AccessType getAccessType() { + return AccessType.READ_ONLY; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionReadWriteAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionReadWriteAccess.java new file mode 100644 index 000000000000..d5ec0c7036bd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionReadWriteAccess.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.cache.spi.support; + +import java.util.Comparator; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.collection.CollectionPersister; +/** + * Standard support for {@link org.hibernate.cache.spi.access.CollectionDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#READ_WRITE} access type. + * + * @author Chris Cranford + * @author Steve Ebersole + */ +public class CollectionReadWriteAccess extends AbstractReadWriteAccess implements CollectionDataAccess { + private final NavigableRole collectionRole; + private final Comparator versionComparator; + private final CacheKeysFactory keysFactory; + + public CollectionReadWriteAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + CollectionDataCachingConfig config) { + super( region, storageAccess ); + this.keysFactory = keysFactory; + this.collectionRole = config.getNavigableRole(); + this.versionComparator = config.getOwnerVersionComparator(); + } + + @Override + protected AccessedDataClassification getAccessedDataClassification() { + return AccessedDataClassification.COLLECTION; + } + + @Override + public AccessType getAccessType() { + return AccessType.READ_WRITE; + } + + @Override + public Object generateCacheKey( + Object id, + CollectionPersister collectionDescriptor, + SessionFactoryImplementor factory, + String tenantIdentifier) { + return keysFactory.createCollectionKey( id, collectionDescriptor, factory, tenantIdentifier ); + } + + @Override + public Object getCacheKeyId(Object cacheKey) { + return keysFactory.getCollectionId( cacheKey ); + } + + @Override + protected Comparator getVersionComparator() { + return versionComparator; + } + + @Override + public Object get(SharedSessionContractImplementor session, Object key) { + return super.get( session, key ); + } + + @Override + public boolean putFromLoad(SharedSessionContractImplementor session, Object key, Object value, Object version) { + return super.putFromLoad( session, key, value, version ); + } + + @Override + public SoftLock lockItem( + SharedSessionContractImplementor session, Object key, Object version) { + return super.lockItem( session, key, version ); + } + + @Override + public void unlockItem( + SharedSessionContractImplementor session, Object key, SoftLock lock) { + super.unlockItem( session, key, lock ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java new file mode 100644 index 000000000000..10fd566be7b4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.support.AbstractCollectionDataAccess; +import org.hibernate.cache.spi.support.DomainDataStorageAccess; + +/** + * @author Steve Ebersole + */ +public class CollectionTransactionAccess extends AbstractCollectionDataAccess { + public CollectionTransactionAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + CollectionDataCachingConfig config) { + super( region, keysFactory, storageAccess, config ); + } + + @Override + public AccessType getAccessType() { + return AccessType.TRANSACTIONAL; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DirectAccessRegionTemplate.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DirectAccessRegionTemplate.java new file mode 100644 index 000000000000..cc2599316787 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DirectAccessRegionTemplate.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.spi.DirectAccessRegion; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Bridge between DirectAccessRegion and StorageAccess + * + * @author Steve Ebersole + */ +public abstract class DirectAccessRegionTemplate extends AbstractRegion implements DirectAccessRegion { + private final StorageAccess storageAccess; + + /** + * Constructs a {@link DirectAccessRegionTemplate}. + * + * @param name - the unqualified region name + * @param regionFactory - the region factory + * @param storageAccess - the cache storage access strategy + */ + public DirectAccessRegionTemplate(String name, RegionFactory regionFactory, StorageAccess storageAccess) { + super( name, regionFactory ); + this.storageAccess = storageAccess; + } + + public StorageAccess getStorageAccess() { + return storageAccess; + } + + @Override + public Object getFromCache(Object key, SharedSessionContractImplementor session) { + return getStorageAccess().getFromCache( key, session ); + } + + @Override + public void putIntoCache(Object key, Object value, SharedSessionContractImplementor session) { + getStorageAccess().putIntoCache( key, value, session ); + } + + @Override + public void clear() { + getStorageAccess().evictData(); + } + + @Override + public void destroy() { + getStorageAccess().release(); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java new file mode 100644 index 000000000000..a33a6e82cc16 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.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.cache.spi.support; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; + +/** + * @author Steve Ebersole + */ +public class DomainDataRegionImpl extends DomainDataRegionTemplate { + @SuppressWarnings("WeakerAccess") + public DomainDataRegionImpl( + DomainDataRegionConfig regionConfig, + RegionFactoryTemplate regionFactory, + DomainDataStorageAccess domainDataStorageAccess, + CacheKeysFactory defaultKeysFactory, + DomainDataRegionBuildingContext buildingContext) { + super( + regionConfig, + regionFactory, + domainDataStorageAccess, + defaultKeysFactory, + buildingContext + ); + } + + @Override + protected EntityDataAccess generateTransactionalEntityDataAccess(EntityDataCachingConfig entityAccessConfig) { + return new EntityTransactionalAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + entityAccessConfig + ); + } + + @Override + protected NaturalIdDataAccess generateTransactionalNaturalIdDataAccess(NaturalIdDataCachingConfig accessConfig) { + return new NaturalIdTransactionalAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @Override + protected CollectionDataAccess generateTransactionalCollectionDataAccess(CollectionDataCachingConfig accessConfig) { + return new CollectionTransactionAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionTemplate.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionTemplate.java new file mode 100644 index 000000000000..845426bfdc51 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionTemplate.java @@ -0,0 +1,249 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.metamodel.model.domain.NavigableRole; + +import org.jboss.logging.Logger; + +/** + * Abstract implementation of {@link org.hibernate.cache.spi.DomainDataRegion} based + * on implementations just needing to provide a {@link DomainDataStorageAccess} reference + * for basic caching support - DomainDataStorageAccess acts as a simple wrapper around + * some generalized cache actions such as put or get. Most implementations (our own + * JCache-based one included) can likely be as simple as: + * + * * Custom DomainDataStorageAccess implementation, bridging calls back + * to the specific cache provider's APIs + * * Custom DomainDataRegionTemplate implementation that creates its custom + * DomainDataStorageAccess reference + * * Custom RegionFactory implementation that creates its custom DomainDataRegionTemplate + * + * todo (5.3) : move this javadoc into DomainDataRegion and/or package javadoc + * + * @author Steve Ebersole + */ +public class DomainDataRegionTemplate extends AbstractDomainDataRegion { + private static final Logger log = Logger.getLogger( DomainDataRegionTemplate.class ); + + private final DomainDataStorageAccess storageAccess; + + public DomainDataRegionTemplate( + DomainDataRegionConfig regionConfig, + RegionFactory regionFactory, + DomainDataStorageAccess storageAccess, + CacheKeysFactory defaultKeysFactory, + DomainDataRegionBuildingContext buildingContext) { + super( regionConfig, regionFactory, defaultKeysFactory, buildingContext ); + this.storageAccess = storageAccess; + + // now the super-type calls will have access to the `DomainDataStorageAccess` reference + completeInstantiation( regionConfig, buildingContext ); + } + + /** + * Public for testing purposes + */ + public DomainDataStorageAccess getCacheStorageAccess() { + return storageAccess; + } + + + @Override + public EntityDataAccess generateEntityAccess(EntityDataCachingConfig entityAccessConfig) { + final NavigableRole namedEntityRole = entityAccessConfig.getNavigableRole(); + final AccessType accessType = entityAccessConfig.getAccessType(); + + log.debugf( "Generating entity cache access [%s] : %s", accessType.getExternalName(), namedEntityRole ); + + switch ( accessType ) { + case READ_ONLY: { + return generateReadOnlyEntityAccess( entityAccessConfig ); + } + case READ_WRITE: { + return generateReadWriteEntityAccess( entityAccessConfig ); + } + case NONSTRICT_READ_WRITE: { + return generateNonStrictReadWriteEntityAccess( entityAccessConfig ); + } + case TRANSACTIONAL: { + return generateTransactionalEntityDataAccess( entityAccessConfig ); + } + default: { + throw new IllegalArgumentException( "Unrecognized cache AccessType - " + accessType ); + } + } + } + + @SuppressWarnings("WeakerAccess") + protected EntityDataAccess generateReadOnlyEntityAccess(EntityDataCachingConfig accessConfig) { + return new EntityReadOnlyAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @SuppressWarnings("WeakerAccess") + protected EntityDataAccess generateReadWriteEntityAccess(EntityDataCachingConfig accessConfig) { + return new EntityReadWriteAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @SuppressWarnings("WeakerAccess") + protected EntityDataAccess generateNonStrictReadWriteEntityAccess(EntityDataCachingConfig accessConfig) { + return new EntityNonStrictReadWriteAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @SuppressWarnings({"WeakerAccess"}) + protected EntityDataAccess generateTransactionalEntityDataAccess(EntityDataCachingConfig entityAccessConfig) { + throw generateTransactionalNotSupportedException(); + } + + private UnsupportedOperationException generateTransactionalNotSupportedException() { + return new UnsupportedOperationException( "Cache provider [" + getRegionFactory() + "] does not support `" + AccessType.TRANSACTIONAL.getExternalName() + "` access" ); + } + + @Override + public NaturalIdDataAccess generateNaturalIdAccess(NaturalIdDataCachingConfig accessConfig) { + final NavigableRole namedEntityRole = accessConfig.getNavigableRole(); + final AccessType accessType = accessConfig.getAccessType(); + + log.debugf( "Generating entity natural-id access [%s] : %s", accessType.getExternalName(), namedEntityRole ); + + switch ( accessType ) { + case READ_ONLY: { + return generateReadOnlyNaturalIdAccess( accessConfig ); + } + case READ_WRITE: { + return generateReadWriteNaturalIdAccess( accessConfig ); + } + case NONSTRICT_READ_WRITE: { + return generateNonStrictReadWriteNaturalIdAccess( accessConfig ); + } + case TRANSACTIONAL: { + return generateTransactionalNaturalIdDataAccess( accessConfig ); + } + default: { + throw new IllegalArgumentException( "Unrecognized cache AccessType - " + accessType ); + } + } + } + + @SuppressWarnings("WeakerAccess") + protected NaturalIdDataAccess generateReadOnlyNaturalIdAccess(NaturalIdDataCachingConfig accessConfig) { + return new NaturalIdReadOnlyAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @SuppressWarnings("WeakerAccess") + protected NaturalIdDataAccess generateReadWriteNaturalIdAccess(NaturalIdDataCachingConfig accessConfig) { + return new NaturalIdReadWriteAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @SuppressWarnings("WeakerAccess") + protected NaturalIdDataAccess generateNonStrictReadWriteNaturalIdAccess(NaturalIdDataCachingConfig accessConfig) { + return new NaturalIdNonStrictReadWriteAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @SuppressWarnings({"WeakerAccess"}) + protected NaturalIdDataAccess generateTransactionalNaturalIdDataAccess(NaturalIdDataCachingConfig accessConfig) { + throw generateTransactionalNotSupportedException(); + } + + @Override + public CollectionDataAccess generateCollectionAccess(CollectionDataCachingConfig accessConfig) { + final NavigableRole namedCollectionRole = accessConfig.getNavigableRole(); + + log.debugf( "Generating collection cache access : %s", namedCollectionRole ); + + switch ( accessConfig.getAccessType() ) { + case READ_ONLY: { + return generateReadOnlyCollectionAccess( accessConfig ); + } + case READ_WRITE: { + return generateReadWriteCollectionAccess( accessConfig ); + } + case NONSTRICT_READ_WRITE: { + return generateNonStrictReadWriteCollectionAccess( accessConfig ); + } + case TRANSACTIONAL: { + return generateTransactionalCollectionDataAccess( accessConfig ); + } + default: { + throw new IllegalArgumentException( "Unrecognized cache AccessType - " + accessConfig.getAccessType() ); + } + } + } + + private CollectionDataAccess generateReadOnlyCollectionAccess(CollectionDataCachingConfig accessConfig) { + return new CollectionReadOnlyAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + private CollectionDataAccess generateReadWriteCollectionAccess(CollectionDataCachingConfig accessConfig) { + return new CollectionReadWriteAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + private CollectionDataAccess generateNonStrictReadWriteCollectionAccess(CollectionDataCachingConfig accessConfig) { + return new CollectionNonStrictReadWriteAccess( + this, + getEffectiveKeysFactory(), + getCacheStorageAccess(), + accessConfig + ); + } + + @SuppressWarnings({"WeakerAccess", "unused"}) + protected CollectionDataAccess generateTransactionalCollectionDataAccess(CollectionDataCachingConfig accessConfig) { + throw generateTransactionalNotSupportedException(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataStorageAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataStorageAccess.java new file mode 100644 index 000000000000..e078c973c486 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataStorageAccess.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.cache.spi.support; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Specialization of StorageAccess for domain data regions + * + * @author Steve Ebersole + */ +public interface DomainDataStorageAccess extends StorageAccess { + /** + * Specialized form of putting something into the cache + * in cases where the put is coming from a load (read) from + * the database + * + * @implNote the method default is to call {@link #putIntoCache} + */ + default void putFromLoad(Object key, Object value, SharedSessionContractImplementor session) { + putIntoCache( key, value, session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityNonStrictReadWriteAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityNonStrictReadWriteAccess.java new file mode 100644 index 000000000000..1b2f71ba1da0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityNonStrictReadWriteAccess.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.CacheException; +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.EntityDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#NONSTRICT_READ_WRITE} access type. + * + * @author Steve Ebersole + */ +public class EntityNonStrictReadWriteAccess extends AbstractEntityDataAccess { + public EntityNonStrictReadWriteAccess( + DomainDataRegion domainDataRegion, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + EntityDataCachingConfig entityAccessConfig) { + super( domainDataRegion, keysFactory, storageAccess ); + } + + @Override + public AccessType getAccessType() { + return AccessType.NONSTRICT_READ_WRITE; + } + + @Override + public boolean insert( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version) { + return false; + } + + @Override + public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value, Object version) { + return false; + } + + @Override + public boolean update( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion) { + getStorageAccess().removeFromCache( key, session ); + return false; + } + + @Override + public boolean afterUpdate( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion, + SoftLock lock) { + unlockItem( session, key, lock ); + return false; + } + + /** + * Since this is a non-strict read/write strategy item locking is not used. + */ + @Override + public void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) throws CacheException { + getStorageAccess().removeFromCache( key, session ); + } + + @Override + public void remove(SharedSessionContractImplementor session, Object key) { + getStorageAccess().removeFromCache( key, session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityReadOnlyAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityReadOnlyAccess.java new file mode 100644 index 000000000000..a39260fdeef4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityReadOnlyAccess.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.cache.spi.support; + +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.SecondLevelCacheLogger; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +import org.jboss.logging.Logger; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.EntityDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#READ_ONLY} access type. + * + * @author Steve Ebersole + */ +public class EntityReadOnlyAccess extends AbstractEntityDataAccess { + private static final Logger log = Logger.getLogger( EntityReadOnlyAccess.class ); + + public EntityReadOnlyAccess( + DomainDataRegion region, + CacheKeysFactory cacheKeysFactory, + DomainDataStorageAccess storageAccess, + EntityDataCachingConfig config) { + super( region, cacheKeysFactory, storageAccess ); + if ( config.isMutable() ) { + SecondLevelCacheLogger.INSTANCE.readOnlyCachingMutableEntity( config.getNavigableRole() ); + } + } + + @Override + public AccessType getAccessType() { + return AccessType.READ_ONLY; + } + + @Override + public boolean insert(SharedSessionContractImplementor session, Object key, Object value, Object version) { + // wait until tx complete - see `#afterInsert` + return false; + } + + @Override + public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value, Object version) { + getStorageAccess().putIntoCache( key, value, session ); + return true; + } + + @Override + public void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) { + evict( key ); + } + + @Override + public boolean update( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion) { + log.debugf( "Illegal attempt to update item cached as read-only [%s]", key ); + throw new UnsupportedOperationException( "Can't update readonly object" ); + } + + @Override + public boolean afterUpdate( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion, + SoftLock lock) { + log.debugf( "Illegal attempt to update item cached as read-only [%s]", key ); + throw new UnsupportedOperationException( "Can't write to a readonly object" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityReadWriteAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityReadWriteAccess.java new file mode 100644 index 000000000000..0b6764d66535 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityReadWriteAccess.java @@ -0,0 +1,159 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import java.util.Comparator; + +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.EntityDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#READ_WRITE} access type. + * + * @author Steve Ebersole + */ +public class EntityReadWriteAccess extends AbstractReadWriteAccess implements EntityDataAccess { + private final CacheKeysFactory keysFactory; + private final Comparator versionComparator; + + public EntityReadWriteAccess( + DomainDataRegion domainDataRegion, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + EntityDataCachingConfig entityAccessConfig) { + super( domainDataRegion, storageAccess ); + this.keysFactory = keysFactory; + this.versionComparator = entityAccessConfig.getVersionComparatorAccess() == null + ? null + : entityAccessConfig.getVersionComparatorAccess().get(); + } + + @Override + public AccessType getAccessType() { + return AccessType.READ_WRITE; + } + + @Override + protected AccessedDataClassification getAccessedDataClassification() { + return AccessedDataClassification.ENTITY; + } + + @Override + protected Comparator getVersionComparator() { + return versionComparator; + } + + @Override + public Object generateCacheKey( + Object id, + EntityPersister rootEntityDescriptor, + SessionFactoryImplementor factory, + String tenantIdentifier) { + return keysFactory.createEntityKey( id, rootEntityDescriptor, factory, tenantIdentifier ); + } + + @Override + public Object getCacheKeyId(Object cacheKey) { + return keysFactory.getEntityId( cacheKey ); + } + + @Override + public boolean insert( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version) { + return false; + } + + @Override + public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value, Object version) { + try { + writeLock().lock(); + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + if ( item == null ) { + getStorageAccess().putIntoCache( + key, + new Item( value, version, getRegion().getRegionFactory().nextTimestamp() ), + session + ); + return true; + } + else { + return false; + } + } + finally { + writeLock().unlock(); + } + } + + @Override + public boolean update( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion) { + return false; + } + + @Override + public boolean afterUpdate( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion, + SoftLock lock) { + try { + writeLock().lock(); + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + + if ( item != null && item.isUnlockable( lock ) ) { + SoftLockImpl lockItem = (SoftLockImpl) item; + if ( lockItem.wasLockedConcurrently() ) { + decrementLock( session, key, lockItem ); + return false; + } + else { + getStorageAccess().putIntoCache( + key, + new Item( value, currentVersion, getRegion().getRegionFactory().nextTimestamp() ), + session + ); + return true; + } + } + else { + handleLockExpiry(session, key, item ); + return false; + } + } + finally { + writeLock().unlock(); + } + } + + @Override + public SoftLock lockRegion() { + return null; + } + + @Override + public void unlockRegion(SoftLock lock) { + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java new file mode 100644 index 000000000000..33cd0d858360 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.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.cache.spi.support; + +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.cache.spi.support.AbstractEntityDataAccess; +import org.hibernate.cache.spi.support.DomainDataStorageAccess; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public class EntityTransactionalAccess extends AbstractEntityDataAccess { + public EntityTransactionalAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + EntityDataCachingConfig accessConfig) { + super( region, keysFactory, storageAccess ); + } + + @Override + public boolean insert( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version) { + getStorageAccess().putIntoCache( key, value, session ); + return true; + } + + @Override + public boolean afterInsert( + SharedSessionContractImplementor session, + Object key, + Object value, + Object version) { + return false; + } + + @Override + public boolean update( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion) { + getStorageAccess().putIntoCache( key, value, session ); + return true; + } + + @Override + public boolean afterUpdate( + SharedSessionContractImplementor session, + Object key, + Object value, + Object currentVersion, + Object previousVersion, + SoftLock lock) { + return false; + } + + @Override + public AccessType getAccessType() { + return AccessType.TRANSACTIONAL; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdNonStrictReadWriteAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdNonStrictReadWriteAccess.java new file mode 100644 index 000000000000..34bcfd8c5fdd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdNonStrictReadWriteAccess.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.cache.spi.support; + +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.NaturalIdDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#NONSTRICT_READ_WRITE} access type. + * + * @author Steve Ebersole + */ +public class NaturalIdNonStrictReadWriteAccess extends AbstractNaturalIdDataAccess { + public NaturalIdNonStrictReadWriteAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + NaturalIdDataCachingConfig config) { + super( region, keysFactory, storageAccess, config ); + } + + @Override + public AccessType getAccessType() { + return AccessType.NONSTRICT_READ_WRITE; + } + + @Override + public void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) { + getStorageAccess().removeFromCache( key, session ); + } + + @Override + public void remove(SharedSessionContractImplementor session, Object key) { + getStorageAccess().removeFromCache( key, session ); + } + + @Override + public boolean insert(SharedSessionContractImplementor session, Object key, Object value) { + return false; + } + + @Override + public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value) { + return false; + } + + @Override + public boolean update(SharedSessionContractImplementor session, Object key, Object value) { + getStorageAccess().removeFromCache( key, session ); + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdReadOnlyAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdReadOnlyAccess.java new file mode 100644 index 000000000000..e0f6980e0db1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdReadOnlyAccess.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.cache.spi.support; + +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.SecondLevelCacheLogger; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.NaturalIdDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#READ_ONLY} access type. + * + * @author Steve Ebersole + */ +public class NaturalIdReadOnlyAccess extends AbstractNaturalIdDataAccess { + public NaturalIdReadOnlyAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + NaturalIdDataCachingConfig config) { + super( region, keysFactory, storageAccess, config ); + if ( config.isMutable() ) { + SecondLevelCacheLogger.INSTANCE.readOnlyCachingMutableNaturalId( config.getNavigableRole() ); + } + } + + @Override + public AccessType getAccessType() { + return AccessType.READ_ONLY; + } + + @Override + public void unlockItem( + SharedSessionContractImplementor session, + Object key, + SoftLock lock) { + evict( key ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdReadWriteAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdReadWriteAccess.java new file mode 100644 index 000000000000..f6e553458c83 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdReadWriteAccess.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.cache.spi.support; + +import java.util.Comparator; + +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Standard support for {@link org.hibernate.cache.spi.access.NaturalIdDataAccess} + * using the {@link org.hibernate.cache.spi.access.AccessType#READ_WRITE} access type. + * + * @author Steve Ebersole + */ +public class NaturalIdReadWriteAccess extends AbstractReadWriteAccess implements NaturalIdDataAccess { + private final CacheKeysFactory keysFactory; + + public NaturalIdReadWriteAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + NaturalIdDataCachingConfig naturalIdDataCachingConfig) { + super( region, storageAccess ); + this.keysFactory = keysFactory; + } + + @Override + protected AccessedDataClassification getAccessedDataClassification() { + return AccessedDataClassification.NATURAL_ID; + } + + @Override + public AccessType getAccessType() { + return AccessType.READ_WRITE; + } + + @Override + protected Comparator getVersionComparator() { + // natural-id has no comparator + return null; + } + + @Override + public Object generateCacheKey( + Object[] naturalIdValues, + EntityPersister rootEntityDescriptor, + SharedSessionContractImplementor session) { + return keysFactory.createNaturalIdKey( naturalIdValues, rootEntityDescriptor, session ); + } + + @Override + public Object[] getNaturalIdValues(Object cacheKey) { + return keysFactory.getNaturalIdValues( cacheKey ); + } + + @Override + public boolean insert(SharedSessionContractImplementor session, Object key, Object value) { + return false; + } + + @Override + public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value) { + try { + writeLock().lock(); + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + if ( item == null ) { + getStorageAccess().putIntoCache( + key, + new Item( value, null, getRegion().getRegionFactory().nextTimestamp() ), + session + ); + return true; + } + else { + return false; + } + } + finally { + writeLock().unlock(); + } + } + + @Override + public boolean update(SharedSessionContractImplementor session, Object key, Object value) { + return false; + } + + @Override + public boolean afterUpdate(SharedSessionContractImplementor session, Object key, Object value, SoftLock lock) { + try { + writeLock().lock(); + Lockable item = (Lockable) getStorageAccess().getFromCache( key, session ); + + if ( item != null && item.isUnlockable( lock ) ) { + SoftLockImpl lockItem = (SoftLockImpl) item; + if ( lockItem.wasLockedConcurrently() ) { + decrementLock( session, key, lockItem ); + return false; + } + else { + getStorageAccess().putIntoCache( + key, + new Item( value, null, getRegion().getRegionFactory().nextTimestamp() ), + session + ); + return true; + } + } + else { + handleLockExpiry( session, key, item ); + return false; + } + } + finally { + writeLock().unlock(); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java new file mode 100644 index 000000000000..66cf1a9faebb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.support.AbstractNaturalIdDataAccess; +import org.hibernate.cache.spi.support.DomainDataStorageAccess; + +/** + * @author Steve Ebersole + */ +public class NaturalIdTransactionalAccess extends AbstractNaturalIdDataAccess { + public NaturalIdTransactionalAccess( + DomainDataRegion region, + CacheKeysFactory keysFactory, + DomainDataStorageAccess storageAccess, + NaturalIdDataCachingConfig config) { + super( region, keysFactory, storageAccess, config ); + } + + @Override + public AccessType getAccessType() { + return AccessType.TRANSACTIONAL; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/QueryResultsRegionTemplate.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/QueryResultsRegionTemplate.java new file mode 100644 index 000000000000..aaa6e2deba43 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/QueryResultsRegionTemplate.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.cache.spi.support; + +import org.hibernate.cache.spi.QueryResultsRegion; +import org.hibernate.cache.spi.RegionFactory; + +/** + * @author Steve Ebersole + */ +public class QueryResultsRegionTemplate extends DirectAccessRegionTemplate implements QueryResultsRegion { + /** + * Constructs a {@link QueryResultsRegionTemplate}. + * + * @param name - the unqualified region name + * @param regionFactory - the region factory + * @param storageAccess - the cache storage access strategy + */ + public QueryResultsRegionTemplate( + String name, + RegionFactory regionFactory, + StorageAccess storageAccess) { + super( name, regionFactory, storageAccess ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/RegionFactoryTemplate.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/RegionFactoryTemplate.java new file mode 100644 index 000000000000..1f5554a4b80a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/RegionFactoryTemplate.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.internal.DefaultCacheKeysFactory; +import org.hibernate.cache.spi.AbstractRegionFactory; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.QueryResultsRegion; +import org.hibernate.cache.spi.TimestampsRegion; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class RegionFactoryTemplate extends AbstractRegionFactory { + @Override + public DomainDataRegion buildDomainDataRegion( + DomainDataRegionConfig regionConfig, + DomainDataRegionBuildingContext buildingContext) { + verifyStarted(); + return new DomainDataRegionTemplate( + regionConfig, + this, + createDomainDataStorageAccess( regionConfig, buildingContext ), + getImplicitCacheKeysFactory(), + buildingContext + ); + } + + protected CacheKeysFactory getImplicitCacheKeysFactory() { + return DefaultCacheKeysFactory.INSTANCE; + } + + protected DomainDataStorageAccess createDomainDataStorageAccess( + DomainDataRegionConfig regionConfig, + DomainDataRegionBuildingContext buildingContext) { + throw new UnsupportedOperationException( "Not implemented by caching provider" ); + } + + @Override + public QueryResultsRegion buildQueryResultsRegion( + String regionName, + SessionFactoryImplementor sessionFactory) { + verifyStarted(); + return new QueryResultsRegionTemplate( + regionName, + this, + createQueryResultsRegionStorageAccess( regionName, sessionFactory ) + ); + } + + protected abstract StorageAccess createQueryResultsRegionStorageAccess( + String regionName, + SessionFactoryImplementor sessionFactory); + + @Override + public TimestampsRegion buildTimestampsRegion( + String regionName, SessionFactoryImplementor sessionFactory) { + verifyStarted(); + return new TimestampsRegionTemplate( + regionName, + this, + createTimestampsRegionStorageAccess( regionName, sessionFactory ) + ); + } + + protected abstract StorageAccess createTimestampsRegionStorageAccess( + String regionName, + SessionFactoryImplementor sessionFactory); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/RegionNameQualifier.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/RegionNameQualifier.java new file mode 100644 index 000000000000..a9ea95ab325e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/RegionNameQualifier.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.cache.spi.support; + +import org.hibernate.boot.spi.SessionFactoryOptions; + +/** + * @author Steve Ebersole + */ +public class RegionNameQualifier { + /** + * Singleton access + */ + public static final RegionNameQualifier INSTANCE = new RegionNameQualifier(); + + public String qualify(String regionName, SessionFactoryOptions options) { + final String prefix = options.getCacheRegionPrefix(); + if ( prefix == null ) { + return regionName; + } + + return qualify( prefix, regionName ); + } + + public String qualify(String prefix, String regionName) { + if ( regionName.startsWith( prefix + '.' ) ) { + return regionName; + } + + return prefix + '.' + regionName; + } + + + public boolean isQualified(String regionName, SessionFactoryOptions options) { + return isQualified( options.getCacheRegionPrefix(), regionName ); + } + + public boolean isQualified(String prefix, String regionName) { + return prefix != null && regionName.startsWith( prefix ); + } + + private RegionNameQualifier() { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/SimpleTimestamper.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/SimpleTimestamper.java new file mode 100644 index 000000000000..4c347e1e6ba4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/SimpleTimestamper.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.cache.spi.support; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Generates increasing identifiers (in a single VM only). Not valid across multiple VMs. Identifiers are not + * necessarily strictly increasing, but usually are. + *

          + * Core while loop implemented by Alex Snaps - EHCache project - under ASL 2.0 + * + * @author Hibernate team + * @author Alex Snaps + */ +public final class SimpleTimestamper { + private static final int BIN_DIGITS = 12; + private static final AtomicLong VALUE = new AtomicLong(); + + public static final short ONE_MS = 1 << BIN_DIGITS; + + public static long next() { + while ( true ) { + long base = System.currentTimeMillis() << BIN_DIGITS; + long maxValue = base + ONE_MS - 1; + + for ( long current = VALUE.get(), update = Math.max( base, current + 1 ); update < maxValue; + current = VALUE.get(), update = Math.max( base, current + 1 ) ) { + if ( VALUE.compareAndSet( current, update ) ) { + return update; + } + } + } + } + + public static int timeOut() { + return (int) TimeUnit.SECONDS.toMillis( 60 ) * ONE_MS; + } + + private SimpleTimestamper() { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/StorageAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/StorageAccess.java new file mode 100644 index 000000000000..f5c5ba1dfd12 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/StorageAccess.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.spi.support; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * A general read/write abstraction over the specific "cache" + * object from the caching provider. + * + * @apiNote Similar to {@link org.hibernate.cache.spi.access.CachedDomainDataAccess}, + * some methods represent "transactional" (access to Session) and some are non-"transactional" + * + * @author Steve Ebersole + */ +public interface StorageAccess { + /** + * Get an item from the cache. + */ + Object getFromCache(Object key, SharedSessionContractImplementor session); + + /** + * Put an item into the cache + */ + void putIntoCache(Object key, Object value, SharedSessionContractImplementor session); + + /** + * Remove an item from the cache by key + */ + default void removeFromCache(Object key, SharedSessionContractImplementor session) { + evictData( key ); + } + + /** + * Clear data from the cache + */ + default void clearCache(SharedSessionContractImplementor session) { + evictData(); + } + + /** + * Does the cache contain this key? + */ + boolean contains(Object key); + + /** + * Clear all data regardless of transaction/locking + */ + void evictData(); + + /** + * Remove the entry regardless of transaction/locking + */ + void evictData(Object key); + + /** + * Release any resources. Called during cache shutdown + */ + void release(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/TimestampsRegionTemplate.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/TimestampsRegionTemplate.java new file mode 100644 index 000000000000..57482e579867 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/TimestampsRegionTemplate.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.cache.spi.support; + +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.TimestampsRegion; + +/** + * @author Steve Ebersole + */ +public class TimestampsRegionTemplate extends DirectAccessRegionTemplate implements TimestampsRegion { + /** + * Constructs a {@link TimestampsRegionTemplate}. + * + * @param name - the unqualified region name + * @param regionFactory - the region factory + * @param storageAccess - the cache storage access strategy + */ + public TimestampsRegionTemplate( + String name, + RegionFactory regionFactory, + StorageAccess storageAccess) { + super( name, regionFactory, storageAccess ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/package-info.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/package-info.java new file mode 100644 index 000000000000..ba7d1fd2a1f4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/package-info.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 intended for simplifying the worked needed to implement + * a caching provider. Centers around the concept of + * {@link org.hibernate.cache.spi.support.StorageAccess} and + * {@link org.hibernate.cache.spi.support.DomainDataStorageAccess} + * too implement most of the "grunt work" associated with the + * implementation. + * + * Most integrations would just: + * + * 1. implement a custom StorageAccess/DomainDataStorageAccess + * 2. implement a custom RegionFactoryTemplate, implementing specifically: + * a. `RegionFactoryTemplate#createDomainDataStorageAccess` + * b. `RegionFactoryTemplate#createQueryResultsRegionStorageAccess` + * c. `RegionFactoryTemplate#createTimestampsRegionStorageAccess` + * + * Voila.. functioning cache provider + * + * The preferred approach to "provide a integration" is through a custom + * {@link org.hibernate.boot.registry.selector.StrategyRegistrationProvider} + * + * Both `hibernate-testing` (`org.hibernate.testing.cache.CachingRegionFactory`) + * and `hibernate-jcache` (`org.hibernate.cache.jcache.internal.JCacheRegionFactory`) + * provide examples of using this support package to implement a caching + * provider. + */ +package org.hibernate.cache.spi.support; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java index 5825f4d5c11a..f5459296a845 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java @@ -6,7 +6,10 @@ */ package org.hibernate.cfg; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.persistence.AssociationOverride; import javax.persistence.AssociationOverrides; @@ -25,12 +28,11 @@ import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.boot.internal.AttributeConverterDescriptorImpl; -import org.hibernate.boot.spi.AttributeConverterDescriptor; +import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.type.PrimitiveWrapperHelper; import org.jboss.logging.Logger; @@ -73,7 +75,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder { protected abstract AttributeConversionInfo locateAttributeConversionInfo(String path); @Override - public AttributeConverterDescriptor resolveAttributeConverterDescriptor(XProperty property) { + public ConverterDescriptor resolveAttributeConverterDescriptor(XProperty property) { AttributeConversionInfo info = locateAttributeConversionInfo( property ); if ( info != null ) { if ( info.isConversionDisabled() ) { @@ -117,28 +119,19 @@ protected IllegalStateException buildExceptionFromInstantiationError(AttributeCo } } - protected AttributeConverterDescriptor makeAttributeConverterDescriptor(AttributeConversionInfo conversion) { + protected ConverterDescriptor makeAttributeConverterDescriptor(AttributeConversionInfo conversion) { try { - AttributeConverterDefinition definition = new AttributeConverterDefinition( conversion.getConverterClass().newInstance(), false ); - return AttributeConverterDescriptorImpl.create( definition, context.getMetadataCollector().getClassmateContext() ); + return new ClassBasedConverterDescriptor( + conversion.getConverterClass(), + false, + context.getBootstrapContext().getClassmateContext() + ); } catch (Exception e) { throw new AnnotationException( "Unable to create AttributeConverter instance", e ); } } - protected boolean areTypeMatch(Class converterDefinedType, Class propertyType) { - if ( converterDefinedType == null ) { - throw new AnnotationException( "AttributeConverter defined java type cannot be null" ); - } - if ( propertyType == null ) { - throw new AnnotationException( "Property defined java type cannot be null" ); - } - - return converterDefinedType.equals( propertyType ) - || PrimitiveWrapperHelper.arePrimitiveWrapperEquivalents( converterDefinedType, propertyType ); - } - @Override public boolean isInIdClass() { return isInIdClass != null ? isInIdClass : parent != null ? parent.isInIdClass() : false; @@ -379,7 +372,7 @@ private void buildHierarchyColumnOverride(XClass element) { Map joinColumnOverride = new HashMap(); Map joinTableOverride = new HashMap(); Map foreignKeyOverride = new HashMap(); - while ( current != null && !context.getBuildingOptions().getReflectionManager().toXClass( Object.class ).equals( current ) ) { + while ( current != null && !context.getBootstrapContext().getReflectionManager().toXClass( Object.class ).equals( current ) ) { if ( current.isAnnotationPresent( Entity.class ) || current.isAnnotationPresent( MappedSuperclass.class ) || current.isAnnotationPresent( Embeddable.class ) ) { //FIXME is embeddable override? @@ -422,10 +415,26 @@ else if ( multipleOverrides != null ) { } if ( overrides != null ) { + Map> columnOverrideList = new HashMap<>(); + for ( AttributeOverride depAttr : overrides ) { + String qualifiedName = StringHelper.qualify( path, depAttr.name() ); + + if ( columnOverrideList.containsKey( qualifiedName ) ) { + columnOverrideList.get( qualifiedName ).add( depAttr.column() ); + } + else { + columnOverrideList.put( + qualifiedName, + new ArrayList<>( Arrays.asList( depAttr.column() ) ) + ); + } + } + + for (Map.Entry> entry : columnOverrideList.entrySet()) { columnOverride.put( - StringHelper.qualify( path, depAttr.name() ), - new Column[]{ depAttr.column() } + entry.getKey(), + entry.getValue().toArray( new Column[entry.getValue().size()] ) ); } } 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 c05533dd9df5..0128bf3143b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -6,7 +6,6 @@ */ package org.hibernate.cfg; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -20,8 +19,9 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; import javax.persistence.Basic; -import javax.persistence.Cacheable; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ConstraintMode; @@ -35,7 +35,6 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.InheritanceType; @@ -62,11 +61,13 @@ import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.PrimaryKeyJoinColumns; import javax.persistence.SequenceGenerator; +import javax.persistence.SequenceGenerators; import javax.persistence.SharedCacheMode; import javax.persistence.SqlResultSetMapping; import javax.persistence.SqlResultSetMappings; import javax.persistence.Table; import javax.persistence.TableGenerator; +import javax.persistence.TableGenerators; import javax.persistence.UniqueConstraint; import javax.persistence.Version; @@ -77,7 +78,6 @@ import org.hibernate.MappingException; import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.Check; @@ -104,7 +104,6 @@ import org.hibernate.annotations.ManyToAny; import org.hibernate.annotations.MapKeyType; import org.hibernate.annotations.NaturalId; -import org.hibernate.annotations.NaturalIdCache; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OnDelete; @@ -124,7 +123,6 @@ import org.hibernate.annotations.TypeDefs; import org.hibernate.annotations.Where; import org.hibernate.annotations.common.reflection.ClassLoadingException; -import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XMethod; @@ -133,6 +131,7 @@ import org.hibernate.boot.model.IdGeneratorStrategyInterpreter; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; +import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector.EntityTableXref; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.CollectionBinder; @@ -155,7 +154,6 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.Constraint; import org.hibernate.mapping.DependantValue; -import org.hibernate.mapping.IdGenerator; import org.hibernate.mapping.Join; import org.hibernate.mapping.JoinedSubclass; import org.hibernate.mapping.KeyValue; @@ -198,7 +196,7 @@ private AnnotationBinder() { } public static void bindDefaults(MetadataBuildingContext context) { - Map defaults = context.getBuildingOptions().getReflectionManager().getDefaults(); + Map defaults = context.getBootstrapContext().getReflectionManager().getDefaults(); // id generators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -225,6 +223,34 @@ public static void bindDefaults(MetadataBuildingContext context) { } } + { + List anns = (List) defaults.get( TableGenerators.class ); + if ( anns != null ) { + anns.forEach( tableGenerators -> { + for ( TableGenerator tableGenerator : tableGenerators.value() ) { + IdentifierGeneratorDefinition idGen = buildIdGenerator( tableGenerator, context ); + if ( idGen != null ) { + context.getMetadataCollector().addDefaultIdentifierGenerator( idGen ); + } + } + } ); + } + } + + { + List anns = (List) defaults.get( SequenceGenerators.class ); + if ( anns != null ) { + anns.forEach( sequenceGenerators -> { + for ( SequenceGenerator ann : sequenceGenerators.value() ) { + IdentifierGeneratorDefinition idGen = buildIdGenerator( ann, context ); + if ( idGen != null ) { + context.getMetadataCollector().addDefaultIdentifierGenerator( idGen ); + } + } + } ); + } + } + // queries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { @@ -280,7 +306,7 @@ public static void bindDefaults(MetadataBuildingContext context) { public static void bindPackage(String packageName, MetadataBuildingContext context) { XPackage pckg; try { - pckg = context.getBuildingOptions().getReflectionManager().packageForName( packageName ); + pckg = context.getBootstrapContext().getReflectionManager().packageForName( packageName ); } catch (ClassLoadingException e) { LOG.packageNotFound( packageName ); @@ -299,12 +325,24 @@ public static void bindPackage(String packageName, MetadataBuildingContext conte LOG.tracev( "Add sequence generator with name: {0}", idGen.getName() ); } } + if ( pckg.isAnnotationPresent( SequenceGenerators.class ) ) { + SequenceGenerators ann = pckg.getAnnotation( SequenceGenerators.class ); + for ( SequenceGenerator tableGenerator : ann.value() ) { + context.getMetadataCollector().addIdentifierGenerator( buildIdGenerator( tableGenerator, context ) ); + } + } if ( pckg.isAnnotationPresent( TableGenerator.class ) ) { TableGenerator ann = pckg.getAnnotation( TableGenerator.class ); IdentifierGeneratorDefinition idGen = buildIdGenerator( ann, context ); context.getMetadataCollector().addIdentifierGenerator( idGen ); } + if ( pckg.isAnnotationPresent( TableGenerators.class ) ) { + TableGenerators ann = pckg.getAnnotation( TableGenerators.class ); + for ( TableGenerator tableGenerator : ann.value() ) { + context.getMetadataCollector().addIdentifierGenerator( buildIdGenerator( tableGenerator, context ) ); + } + } bindGenericGenerators( pckg, context ); bindQueries( pckg, context ); @@ -332,59 +370,67 @@ private static void bindGenericGenerator(GenericGenerator def, MetadataBuildingC context.getMetadataCollector().addIdentifierGenerator( buildIdGenerator( def, context ) ); } - private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) { - { - SqlResultSetMapping ann = annotatedElement.getAnnotation( SqlResultSetMapping.class ); - QueryBinder.bindSqlResultSetMapping( ann, context, false ); - } - { - SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class ); - if ( ann != null ) { - for ( SqlResultSetMapping current : ann.value() ) { - QueryBinder.bindSqlResultSetMapping( current, context, false ); - } + private static void bindNamedJpaQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) { + QueryBinder.bindSqlResultSetMapping( + annotatedElement.getAnnotation( SqlResultSetMapping.class ), + context, + false + ); + + final SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class ); + if ( ann != null ) { + for ( SqlResultSetMapping current : ann.value() ) { + QueryBinder.bindSqlResultSetMapping( current, context, false ); } } - { - NamedQuery ann = annotatedElement.getAnnotation( NamedQuery.class ); - QueryBinder.bindQuery( ann, context, false ); - } - { - org.hibernate.annotations.NamedQuery ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedQuery.class - ); - QueryBinder.bindQuery( ann, context ); - } - { - NamedQueries ann = annotatedElement.getAnnotation( NamedQueries.class ); - QueryBinder.bindQueries( ann, context, false ); - } - { - org.hibernate.annotations.NamedQueries ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedQueries.class - ); - QueryBinder.bindQueries( ann, context ); - } - { - NamedNativeQuery ann = annotatedElement.getAnnotation( NamedNativeQuery.class ); - QueryBinder.bindNativeQuery( ann, context, false ); - } - { - org.hibernate.annotations.NamedNativeQuery ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedNativeQuery.class - ); - QueryBinder.bindNativeQuery( ann, context ); - } - { - NamedNativeQueries ann = annotatedElement.getAnnotation( NamedNativeQueries.class ); - QueryBinder.bindNativeQueries( ann, context, false ); - } - { - org.hibernate.annotations.NamedNativeQueries ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedNativeQueries.class - ); - QueryBinder.bindNativeQueries( ann, context ); - } + + QueryBinder.bindQuery( + annotatedElement.getAnnotation( NamedQuery.class ), + context, + false + ); + + QueryBinder.bindQueries( + annotatedElement.getAnnotation( NamedQueries.class ), + context, + false + ); + + QueryBinder.bindNativeQuery( + annotatedElement.getAnnotation( NamedNativeQuery.class ), + context, + false + ); + + QueryBinder.bindNativeQueries( + annotatedElement.getAnnotation( NamedNativeQueries.class ), + context, + false + ); + } + + private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) { + bindNamedJpaQueries( annotatedElement, context ); + + QueryBinder.bindQuery( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedQuery.class ), + context + ); + + QueryBinder.bindQueries( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedQueries.class ), + context + ); + + QueryBinder.bindNativeQuery( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQuery.class ), + context + ); + + QueryBinder.bindNativeQueries( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQueries.class ), + context + ); // NamedStoredProcedureQuery handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bindNamedStoredProcedureQuery( @@ -415,8 +461,10 @@ private static void bindNamedStoredProcedureQuery(NamedStoredProcedureQuery anno } } - private static IdentifierGeneratorDefinition buildIdGenerator(java.lang.annotation.Annotation ann, MetadataBuildingContext context) { - if ( ann == null ) { + private static IdentifierGeneratorDefinition buildIdGenerator( + java.lang.annotation.Annotation generatorAnn, + MetadataBuildingContext context) { + if ( generatorAnn == null ) { return null; } @@ -436,26 +484,26 @@ private static IdentifierGeneratorDefinition buildIdGenerator(java.lang.annotati ); } - if ( ann instanceof TableGenerator ) { + if ( generatorAnn instanceof TableGenerator ) { context.getBuildingOptions().getIdGenerationTypeInterpreter().interpretTableGenerator( - (TableGenerator) ann, + (TableGenerator) generatorAnn, definitionBuilder ); if ( LOG.isTraceEnabled() ) { LOG.tracev( "Add table generator with name: {0}", definitionBuilder.getName() ); } } - else if ( ann instanceof SequenceGenerator ) { + else if ( generatorAnn instanceof SequenceGenerator ) { context.getBuildingOptions().getIdGenerationTypeInterpreter().interpretSequenceGenerator( - (SequenceGenerator) ann, + (SequenceGenerator) generatorAnn, definitionBuilder ); if ( LOG.isTraceEnabled() ) { LOG.tracev( "Add sequence generator with name: {0}", definitionBuilder.getName() ); } } - else if ( ann instanceof GenericGenerator ) { - GenericGenerator genGen = ( GenericGenerator ) ann; + else if ( generatorAnn instanceof GenericGenerator ) { + GenericGenerator genGen = ( GenericGenerator ) generatorAnn; definitionBuilder.setName( genGen.name() ); definitionBuilder.setStrategy( genGen.strategy() ); Parameter[] params = genGen.parameters(); @@ -467,19 +515,19 @@ else if ( ann instanceof GenericGenerator ) { } } else { - throw new AssertionFailure( "Unknown Generator annotation: " + ann ); + throw new AssertionFailure( "Unknown Generator annotation: " + generatorAnn ); } return definitionBuilder.build(); } /** - * Bind a class having JSR175 annotations. Subclasses have to be bound afterQuery its parent class. + * Bind a class having JSR175 annotations. Subclasses have to be bound after its parent class. * * @param clazzToProcess entity to bind as {@code XClass} instance * @param inheritanceStatePerClass Meta data about the inheritance relationships for all mapped classes * - * @throws MappingException in case there is an configuration error + * @throws MappingException in case there is a configuration error */ public static void bindClass( XClass clazzToProcess, @@ -518,6 +566,12 @@ public static void bindClass( inheritanceState ); + if(superEntity != null && ( + clazzToProcess.getAnnotation( AttributeOverride.class ) != null || + clazzToProcess.getAnnotation( AttributeOverrides.class ) != null ) ) { + LOG.unsupportedAttributeOverrideWithEntityInheritance( clazzToProcess.getName() ); + } + PersistentClass persistentClass = makePersistentClass( inheritanceState, superEntity, context ); Entity entityAnn = clazzToProcess.getAnnotation( Entity.class ); org.hibernate.annotations.Entity hibEntityAnn = clazzToProcess.getAnnotation( @@ -541,7 +595,7 @@ public static void bindClass( String schema = ""; String table = ""; //might be no @Table annotation on the annotated class String catalog = ""; - List uniqueConstraints = new ArrayList(); + List uniqueConstraints = new ArrayList<>(); javax.persistence.Table tabAnn = null; if ( clazzToProcess.isAnnotationPresent( javax.persistence.Table.class ) ) { tabAnn = clazzToProcess.getAnnotation( javax.persistence.Table.class ); @@ -582,8 +636,7 @@ else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { entityBinder.setProxy( clazzToProcess.getAnnotation( Proxy.class ) ); entityBinder.setBatchSize( clazzToProcess.getAnnotation( BatchSize.class ) ); entityBinder.setWhere( clazzToProcess.getAnnotation( Where.class ) ); - entityBinder.setCache( determineCacheSettings( clazzToProcess, context ) ); - entityBinder.setNaturalIdCache( clazzToProcess, clazzToProcess.getAnnotation( NaturalIdCache.class ) ); + applyCacheSettings( entityBinder, clazzToProcess, context ); bindFilters( clazzToProcess, entityBinder, context ); @@ -649,7 +702,7 @@ else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { if ( inheritanceState.hasParents() ) { onDeleteAppropriate = true; final JoinedSubclass jsc = ( JoinedSubclass ) persistentClass; - SimpleValue key = new DependantValue( context.getMetadataCollector(), jsc.getTable(), jsc.getIdentifier() ); + 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() ) ) { @@ -725,15 +778,14 @@ else if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.getType() ) ) { } // try to find class level generators - HashMap classGenerators = buildLocalGenerators( clazzToProcess, context ); - + HashMap classGenerators = buildGenerators( clazzToProcess, context ); // check properties final InheritanceState.ElementsToProcess elementsToProcess = inheritanceState.getElementsToProcess(); inheritanceState.postProcess( persistentClass, entityBinder ); final boolean subclassAndSingleTableStrategy = inheritanceState.getType() == InheritanceType.SINGLE_TABLE && inheritanceState.hasParents(); - Set idPropertiesIfIdClass = new HashSet(); + Set idPropertiesIfIdClass = new HashSet<>(); boolean isIdClass = mapAsIdClass( inheritanceStatePerClass, inheritanceState, @@ -918,7 +970,7 @@ private static void processIdPropertiesIfNotAlready( InheritanceState.ElementsToProcess elementsToProcess, boolean subclassAndSingleTableStrategy, Set idPropertiesIfIdClass) { - Set missingIdProperties = new HashSet( idPropertiesIfIdClass ); + Set missingIdProperties = new HashSet<>( idPropertiesIfIdClass ); for ( PropertyData propertyAnnotatedElement : elementsToProcess.getElements() ) { String propertyName = propertyAnnotatedElement.getPropertyName(); if ( !idPropertiesIfIdClass.contains( propertyName ) ) { @@ -975,7 +1027,7 @@ private static boolean mapAsIdClass( XClass classWithIdClass = inheritanceState.getClassWithIdClass( false ); if ( classWithIdClass != null ) { IdClass idClass = classWithIdClass.getAnnotation( IdClass.class ); - XClass compositeClass = context.getBuildingOptions().getReflectionManager().toXClass( idClass.value() ); + XClass compositeClass = context.getBootstrapContext().getReflectionManager().toXClass( idClass.value() ); PropertyData inferredData = new PropertyPreloadedData( entityBinder.getPropertyAccessType(), "id", compositeClass ); @@ -1104,7 +1156,7 @@ private static boolean isIdClassPkOfTheAssociatedEntity( } else { - final XClass idClass = context.getBuildingOptions().getReflectionManager().toXClass( + final XClass idClass = context.getBootstrapContext().getReflectionManager().toXClass( associatedClassWithIdClass.getAnnotation( IdClass.class ).value() ); return idClass.equals( compositeClass ); @@ -1115,85 +1167,19 @@ private static boolean isIdClassPkOfTheAssociatedEntity( } } - private static Cache determineCacheSettings(XClass clazzToProcess, MetadataBuildingContext context) { - Cache cacheAnn = clazzToProcess.getAnnotation( Cache.class ); - if ( cacheAnn != null ) { - return cacheAnn; - } + private static void applyCacheSettings(EntityBinder binder, XClass clazzToProcess, MetadataBuildingContext context) { + binder.applyCaching( + clazzToProcess, + determineSharedCacheMode( context ), + context - Cacheable cacheableAnn = clazzToProcess.getAnnotation( Cacheable.class ); - SharedCacheMode mode = determineSharedCacheMode( context ); - switch ( mode ) { - case ALL: { - cacheAnn = buildCacheMock( clazzToProcess.getName(), context ); - break; - } - case ENABLE_SELECTIVE: { - if ( cacheableAnn != null && cacheableAnn.value() ) { - cacheAnn = buildCacheMock( clazzToProcess.getName(), context ); - } - break; - } - case DISABLE_SELECTIVE: { - if ( cacheableAnn == null || cacheableAnn.value() ) { - cacheAnn = buildCacheMock( clazzToProcess.getName(), context ); - } - break; - } - default: { - // treat both NONE and UNSPECIFIED the same - break; - } - } - return cacheAnn; + ); } private static SharedCacheMode determineSharedCacheMode(MetadataBuildingContext context) { return context.getBuildingOptions().getSharedCacheMode(); } - private static Cache buildCacheMock(String region, MetadataBuildingContext context) { - return new LocalCacheAnnotationImpl( region, determineCacheConcurrencyStrategy( context ) ); - } - - private static CacheConcurrencyStrategy DEFAULT_CACHE_CONCURRENCY_STRATEGY; - - private static CacheConcurrencyStrategy determineCacheConcurrencyStrategy(MetadataBuildingContext context) { - if ( DEFAULT_CACHE_CONCURRENCY_STRATEGY == null ) { - DEFAULT_CACHE_CONCURRENCY_STRATEGY = CacheConcurrencyStrategy.fromAccessType( - context.getBuildingOptions().getImplicitCacheAccessType() - ); - } - return DEFAULT_CACHE_CONCURRENCY_STRATEGY; - } - - @SuppressWarnings({ "ClassExplicitlyAnnotation" }) - private static class LocalCacheAnnotationImpl implements Cache { - private final String region; - private final CacheConcurrencyStrategy usage; - - private LocalCacheAnnotationImpl(String region, CacheConcurrencyStrategy usage) { - this.region = region; - this.usage = usage; - } - - public CacheConcurrencyStrategy usage() { - return usage; - } - - public String region() { - return region; - } - - public String include() { - return "all"; - } - - public Class annotationType() { - return Cache.class; - } - } - private static PersistentClass makePersistentClass( InheritanceState inheritanceState, PersistentClass superEntity, @@ -1282,7 +1268,7 @@ private static PersistentClass getSuperEntity( //check if superclass is not a potential persistent class if ( inheritanceState.hasParents() ) { throw new AssertionFailure( - "Subclass has to be binded afterQuery it's mother class: " + "Subclass has to be binded after it's mother class: " + superEntityState.getClazz().getName() ); } @@ -1367,7 +1353,7 @@ private static void bindFilterDefs(XAnnotatedElement annotatedElement, MetadataB } private static void bindFilterDef(FilterDef defAnn, MetadataBuildingContext context) { - Map params = new HashMap(); + Map params = new HashMap<>(); for ( ParamDef param : defAnn.parameters() ) { params.put( param.name(), context.getMetadataCollector().getTypeResolver().heuristicType( param.type() ) ); } @@ -1476,7 +1462,7 @@ private static void bindDiscriminatorColumnToRootPersistentClass( } discriminatorColumn.setJoins( secondaryTables ); discriminatorColumn.setPropertyHolder( propertyHolder ); - SimpleValue discriminatorColumnBinding = new SimpleValue( context.getMetadataCollector(), rootClass.getTable() ); + SimpleValue discriminatorColumnBinding = new SimpleValue( context, rootClass.getTable() ); rootClass.setDiscriminator( discriminatorColumnBinding ); discriminatorColumn.linkWithValue( discriminatorColumnBinding ); discriminatorColumnBinding.setTypeName( discriminatorColumn.getDiscriminatorTypeName() ); @@ -1534,12 +1520,12 @@ private static int addProperty( declaringClass, property, propertyContainer.getClassLevelAccessType().getType(), - context.getBuildingOptions().getReflectionManager() + context.getBootstrapContext().getReflectionManager() ); /* * put element annotated by @Id in front - * since it has to be parsed beforeQuery any association by Hibernate + * since it has to be parsed before any association by Hibernate */ final XAnnotatedElement element = propertyAnnotatedElement.getProperty(); if ( element.isAnnotationPresent( Id.class ) || element.isAnnotationPresent( EmbeddedId.class ) ) { @@ -1582,7 +1568,7 @@ else if ( prop.isAnnotationPresent( JoinColumns.class ) ) { // the actual @XToOne property propertyContainer.getClassLevelAccessType().getType(), //TODO we should get the right accessor but the same as id would do - context.getBuildingOptions().getReflectionManager() + context.getBootstrapContext().getReflectionManager() ); context.getMetadataCollector().addPropertyAnnotatedWithMapsIdSpecj( entity, @@ -2097,7 +2083,7 @@ else if ( property.isAnnotationPresent( MapKeyJoinColumn.class ) ) { collectionBinder.setFkJoinColumns( joinColumns ); mappedBy = oneToManyAnn.mappedBy(); collectionBinder.setTargetEntity( - context.getBuildingOptions().getReflectionManager().toXClass( oneToManyAnn.targetEntity() ) + context.getBootstrapContext().getReflectionManager().toXClass( oneToManyAnn.targetEntity() ) ); collectionBinder.setCascadeStrategy( getCascadeStrategy( @@ -2116,7 +2102,7 @@ else if ( elementCollectionAnn != null ) { mappedBy = ""; final Class targetElement = elementCollectionAnn.targetClass(); collectionBinder.setTargetEntity( - context.getBuildingOptions().getReflectionManager().toXClass( targetElement ) + context.getBootstrapContext().getReflectionManager().toXClass( targetElement ) ); //collectionBinder.setCascadeStrategy( getCascadeStrategy( embeddedCollectionAnn.cascade(), hibernateCascade ) ); collectionBinder.setOneToMany( true ); @@ -2124,7 +2110,7 @@ else if ( elementCollectionAnn != null ) { else if ( manyToManyAnn != null ) { mappedBy = manyToManyAnn.mappedBy(); collectionBinder.setTargetEntity( - context.getBuildingOptions().getReflectionManager().toXClass( manyToManyAnn.targetEntity() ) + context.getBootstrapContext().getReflectionManager().toXClass( manyToManyAnn.targetEntity() ) ); collectionBinder.setCascadeStrategy( getCascadeStrategy( @@ -2136,7 +2122,7 @@ else if ( manyToManyAnn != null ) { else if ( property.isAnnotationPresent( ManyToAny.class ) ) { mappedBy = ""; collectionBinder.setTargetEntity( - context.getBuildingOptions().getReflectionManager().toXClass( void.class ) + context.getBootstrapContext().getReflectionManager().toXClass( void.class ) ); collectionBinder.setCascadeStrategy( getCascadeStrategy( null, hibernateCascade, false, false ) ); collectionBinder.setOneToMany( false ); @@ -2162,7 +2148,7 @@ else if ( property.isAnnotationPresent( ManyToAny.class ) ) { } if ( property.isAnnotationPresent( CollectionId.class ) ) { //do not compute the generators unless necessary HashMap localGenerators = ( HashMap ) classGenerators.clone(); - localGenerators.putAll( buildLocalGenerators( property, context ) ); + localGenerators.putAll( buildGenerators( property, context ) ); collectionBinder.setLocalGenerators( localGenerators ); } @@ -2270,22 +2256,37 @@ else if ( !isId || !entityBinder.isIgnoreIdAnnotations() ) { final PropertyData mapsIdProperty = BinderHelper.getPropertyOverriddenByMapperOrMapsId( isId, propertyHolder, property.getName(), context ); - Map localGenerators = ( HashMap ) classGenerators.clone(); final IdentifierGeneratorDefinition.Builder foreignGeneratorBuilder = new IdentifierGeneratorDefinition.Builder(); foreignGeneratorBuilder.setName( "Hibernate-local--foreign generator" ); foreignGeneratorBuilder.setStrategy( "foreign" ); foreignGeneratorBuilder.addParam( "property", mapsIdProperty.getPropertyName() ); - final IdentifierGeneratorDefinition foreignGenerator = foreignGeneratorBuilder.build(); - localGenerators.put( foreignGenerator.getName(), foreignGenerator ); - BinderHelper.makeIdGenerator( - ( SimpleValue ) propertyBinder.getValue(), - foreignGenerator.getStrategy(), - foreignGenerator.getName(), - context, - localGenerators - ); + if ( isGlobalGeneratorNameGlobal( context ) ) { + SecondPass secondPass = new IdGeneratorResolverSecondPass( + (SimpleValue) propertyBinder.getValue(), + property, + foreignGenerator.getStrategy(), + foreignGenerator.getName(), + context, + foreignGenerator + ); + context.getMetadataCollector().addSecondPass( secondPass ); + } + else { + Map localGenerators = (HashMap) classGenerators + .clone(); + localGenerators.put( foreignGenerator.getName(), foreignGenerator ); + + BinderHelper.makeIdGenerator( + (SimpleValue) propertyBinder.getValue(), + property, + foreignGenerator.getStrategy(), + foreignGenerator.getName(), + context, + localGenerators + ); + } } if ( isId ) { //components and regular basic types create SimpleValue objects @@ -2304,7 +2305,7 @@ else if ( !isId || !entityBinder.isIgnoreIdAnnotations() ) { } } //init index - //process indexes afterQuery everything: in second pass, many to one has to be done beforeQuery indexes + //process indexes after everything: in second pass, many to one has to be done before indexes Index index = property.getAnnotation( Index.class ); if ( index != null ) { if ( joinColumns != null ) { @@ -2343,6 +2344,10 @@ else if ( !isId || !entityBinder.isIgnoreIdAnnotations() ) { } } + private static boolean isGlobalGeneratorNameGlobal(MetadataBuildingContext context) { + return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled(); + } + private static boolean isToManyAssociationWithinEmbeddableCollection(PropertyHolder propertyHolder) { if(propertyHolder instanceof ComponentPropertyHolder) { ComponentPropertyHolder componentPropertyHolder = (ComponentPropertyHolder) propertyHolder; @@ -2372,20 +2377,17 @@ private static void processId( + BinderHelper.getPath( propertyHolder, inferredData ) ); } - XClass returnedClass = inferredData.getClassOrElement(); - XProperty property = inferredData.getProperty(); - //clone classGenerator and override with local values - HashMap localGenerators = ( HashMap ) classGenerators.clone(); - localGenerators.putAll( buildLocalGenerators( property, buildingContext ) ); + XClass entityXClass = inferredData.getClassOrElement(); + XProperty idXProperty = inferredData.getProperty(); //manage composite related metadata //guess if its a component and find id data access (property, field etc) - final boolean isComponent = returnedClass.isAnnotationPresent( Embeddable.class ) - || property.isAnnotationPresent( EmbeddedId.class ); + final boolean isComponent = entityXClass.isAnnotationPresent( Embeddable.class ) + || idXProperty.isAnnotationPresent( EmbeddedId.class ); - GeneratedValue generatedValue = property.getAnnotation( GeneratedValue.class ); + GeneratedValue generatedValue = idXProperty.getAnnotation( GeneratedValue.class ); String generatorType = generatedValue != null - ? generatorType( generatedValue.strategy(), buildingContext, returnedClass ) + ? generatorType( generatedValue, buildingContext, entityXClass ) : "assigned"; String generatorName = generatedValue != null ? generatedValue.generator() @@ -2394,13 +2396,65 @@ private static void processId( //a component must not have any generator generatorType = "assigned"; } - BinderHelper.makeIdGenerator( idValue, generatorType, generatorName, buildingContext, localGenerators ); + + if ( isGlobalGeneratorNameGlobal( buildingContext ) ) { + buildGenerators( idXProperty, buildingContext ); + SecondPass secondPass = new IdGeneratorResolverSecondPass( + idValue, + idXProperty, + generatorType, + generatorName, + buildingContext + ); + buildingContext.getMetadataCollector().addSecondPass( secondPass ); + } + else { + //clone classGenerator and override with local values + HashMap localGenerators = (HashMap) classGenerators + .clone(); + localGenerators.putAll( buildGenerators( idXProperty, buildingContext ) ); + BinderHelper.makeIdGenerator( + idValue, + idXProperty, + generatorType, + generatorName, + buildingContext, + localGenerators + ); + } if ( LOG.isTraceEnabled() ) { LOG.tracev( "Bind {0} on {1}", ( isComponent ? "@EmbeddedId" : "@Id" ), inferredData.getPropertyName() ); } } + public static String generatorType( + GeneratedValue generatedValueAnn, + final MetadataBuildingContext buildingContext, + final XClass javaTypeXClass) { + return buildingContext.getBuildingOptions().getIdGenerationTypeInterpreter().determineGeneratorName( + generatedValueAnn.strategy(), + new IdGeneratorStrategyInterpreter.GeneratorNameDeterminationContext() { + Class javaType = null; + @Override + public Class getIdType() { + if ( javaType == null ) { + javaType = buildingContext + .getBootstrapContext() + .getReflectionManager() + .toClass( javaTypeXClass ); + } + return javaType; + } + + @Override + public String getGeneratedValueGeneratorName() { + return generatedValueAnn.generator(); + } + } + ); + } + //TODO move that to collection binder? private static void bindJoinedTableAssociation( @@ -2617,20 +2671,25 @@ public static Component fillComponent( propertyHolder.startingProperty( inferredData.getProperty() ); final XClass xClassProcessed = inferredData.getPropertyClass(); - List classElements = new ArrayList(); + List classElements = new ArrayList<>(); XClass returnedClassOrElement = inferredData.getClassOrElement(); List baseClassElements = null; - Map orderedBaseClassElements = new HashMap(); + Map orderedBaseClassElements = new HashMap<>(); XClass baseReturnedClassOrElement; if ( baseInferredData != null ) { - baseClassElements = new ArrayList(); + baseClassElements = new ArrayList<>(); baseReturnedClassOrElement = baseInferredData.getClassOrElement(); bindTypeDefs( baseReturnedClassOrElement, buildingContext ); - PropertyContainer propContainer = new PropertyContainer( baseReturnedClassOrElement, xClassProcessed, propertyAccessor ); - addElementsOfClass( baseClassElements, propContainer, buildingContext ); - for ( PropertyData element : baseClassElements ) { - orderedBaseClassElements.put( element.getPropertyName(), element ); + // iterate from base returned class up hierarchy to handle cases where the @Id attributes + // might be spread across the subclasses and super classes. + while ( !Object.class.getName().equals( baseReturnedClassOrElement.getName() ) ) { + PropertyContainer propContainer = new PropertyContainer( baseReturnedClassOrElement, xClassProcessed, propertyAccessor ); + addElementsOfClass( baseClassElements, propContainer, buildingContext ); + for ( PropertyData element : baseClassElements ) { + orderedBaseClassElements.put( element.getPropertyName(), element ); + } + baseReturnedClassOrElement = baseReturnedClassOrElement.getSuperclass(); } } @@ -2687,7 +2746,7 @@ public static Component fillComponent( ? Nullability.NO_CONSTRAINT : Nullability.FORCED_NOT_NULL, propertyAnnotatedElement, - new HashMap(), + new HashMap<>(), entityBinder, isIdentifierMapper, isComponentEmbedded, @@ -2699,25 +2758,38 @@ public static Component fillComponent( XProperty property = propertyAnnotatedElement.getProperty(); if ( property.isAnnotationPresent( GeneratedValue.class ) && property.isAnnotationPresent( Id.class ) ) { - //clone classGenerator and override with local values - Map localGenerators = new HashMap(); - localGenerators.putAll( buildLocalGenerators( property, buildingContext ) ); - GeneratedValue generatedValue = property.getAnnotation( GeneratedValue.class ); String generatorType = generatedValue != null - ? generatorType( generatedValue.strategy(), buildingContext, property.getType() ) + ? generatorType( generatedValue, buildingContext, property.getType() ) : "assigned"; - String generator = generatedValue != null ? generatedValue.generator() : BinderHelper.ANNOTATION_STRING_DEFAULT; - - BinderHelper.makeIdGenerator( - ( SimpleValue ) comp.getProperty( property.getName() ).getValue(), - generatorType, - generator, - buildingContext, - localGenerators - ); + String generator = generatedValue != null ? + generatedValue.generator() : + BinderHelper.ANNOTATION_STRING_DEFAULT; + + if ( isGlobalGeneratorNameGlobal( buildingContext ) ) { + buildGenerators( property, buildingContext ); + SecondPass secondPass = new IdGeneratorResolverSecondPass( + (SimpleValue) comp.getProperty( property.getName() ).getValue(), + property, + generatorType, + generator, + buildingContext + ); + buildingContext.getMetadataCollector().addSecondPass( secondPass ); + } + else { + Map localGenerators = new HashMap<>(); + localGenerators.putAll( buildGenerators( property, buildingContext ) ); + BinderHelper.makeIdGenerator( + (SimpleValue) comp.getProperty( property.getName() ).getValue(), + property, + generatorType, + generator, + buildingContext, + localGenerators + ); + } } - } return comp; } @@ -2728,7 +2800,7 @@ public static Component createComponent( boolean isComponentEmbedded, boolean isIdentifierMapper, MetadataBuildingContext context) { - Component comp = new Component( context.getMetadataCollector(), propertyHolder.getPersistentClass() ); + Component comp = new Component( context, propertyHolder.getPersistentClass() ); comp.setEmbedded( isComponentEmbedded ); //yuk comp.setTable( propertyHolder.getTable() ); @@ -2771,7 +2843,6 @@ private static void bindIdClass( String persistentClassName = rootClass.getClassName(); SimpleValue id; final String propertyName = inferredData.getPropertyName(); - HashMap localGenerators = new HashMap(); if ( isComposite ) { id = fillComponent( propertyHolder, @@ -2815,13 +2886,27 @@ private static void bindIdClass( id = value.make(); } rootClass.setIdentifier( id ); - BinderHelper.makeIdGenerator( - id, - generatorType, - generatorName, - buildingContext, - Collections.emptyMap() - ); + if ( isGlobalGeneratorNameGlobal( buildingContext ) ) { + SecondPass secondPass = new IdGeneratorResolverSecondPass( + id, + inferredData.getProperty(), + generatorType, + generatorName, + buildingContext + ); + buildingContext.getMetadataCollector().addSecondPass( secondPass ); + } + else { + BinderHelper.makeIdGenerator( + id, + inferredData.getProperty(), + generatorType, + generatorName, + buildingContext, + Collections.emptyMap() + ); + } + if ( isEmbedded ) { rootClass.setEmbeddedIdentifier( inferredData.getPropertyClass() == null ); } @@ -2854,7 +2939,7 @@ private static PropertyData getUniqueIdPropertyFromBaseClass( PropertyData baseInferredData, AccessType propertyAccessor, MetadataBuildingContext context) { - List baseClassElements = new ArrayList(); + List baseClassElements = new ArrayList<>(); XClass baseReturnedClassOrElement = baseInferredData.getClassOrElement(); PropertyContainer propContainer = new PropertyContainer( baseReturnedClassOrElement, @@ -2900,7 +2985,7 @@ private static void bindManyToOne( PropertyBinder propertyBinder, MetadataBuildingContext context) { //All FK columns should be in the same table - org.hibernate.mapping.ManyToOne value = new org.hibernate.mapping.ManyToOne( context.getMetadataCollector(), columns[0].getTable() ); + org.hibernate.mapping.ManyToOne value = new org.hibernate.mapping.ManyToOne( context, columns[0].getTable() ); // This is a @OneToOne mapped to a physical o.h.mapping.ManyToOne if ( unique ) { value.markAsLogicalOneToOne(); @@ -2924,7 +3009,7 @@ private static void bindManyToOne( column.setUpdatable( false ); } } - + final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class ); final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class ); @@ -3102,7 +3187,7 @@ private static void bindOneToOne( } else { Iterator idColumns = identifier.getColumnIterator(); - List idColumnNames = new ArrayList(); + List idColumnNames = new ArrayList<>(); org.hibernate.mapping.Column currentColumn; if ( identifier.getColumnSpan() != joinColumns.length ) { mapToPK = false; @@ -3211,27 +3296,6 @@ private static void bindAny( propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() ); } - private static String generatorType( - GenerationType generatorEnum, - final MetadataBuildingContext buildingContext, - final XClass javaTypeXClass) { - return buildingContext.getBuildingOptions().getIdGenerationTypeInterpreter().determineGeneratorName( - generatorEnum, - new IdGeneratorStrategyInterpreter.GeneratorNameDeterminationContext() { - Class javaType = null; - @Override - public Class getIdType() { - if ( javaType == null ) { - javaType = buildingContext.getBuildingOptions() - .getReflectionManager() - .toClass( javaTypeXClass ); - } - return javaType; - } - } - ); - } - private static EnumSet convertToHibernateCascadeType(javax.persistence.CascadeType[] ejbCascades) { EnumSet hibernateCascadeSet = EnumSet.noneOf( CascadeType.class ); if ( ejbCascades != null && ejbCascades.length > 0 ) { @@ -3337,28 +3401,64 @@ public static FetchMode getFetchMode(FetchType fetch) { } } - private static HashMap buildLocalGenerators(XAnnotatedElement annElt, MetadataBuildingContext context) { - HashMap generators = new HashMap(); + private static HashMap buildGenerators(XAnnotatedElement annElt, MetadataBuildingContext context) { + InFlightMetadataCollector metadataCollector = context.getMetadataCollector(); + HashMap generators = new HashMap<>(); + + TableGenerators tableGenerators = annElt.getAnnotation( TableGenerators.class ); + if ( tableGenerators != null ) { + for ( TableGenerator tableGenerator : tableGenerators.value() ) { + IdentifierGeneratorDefinition idGenerator = buildIdGenerator( + tableGenerator, + context + ); + generators.put( + idGenerator.getName(), + idGenerator + ); + metadataCollector.addIdentifierGenerator( idGenerator ); + } + } + + SequenceGenerators sequenceGenerators = annElt.getAnnotation( SequenceGenerators.class ); + if ( sequenceGenerators != null ) { + for ( SequenceGenerator sequenceGenerator : sequenceGenerators.value() ) { + IdentifierGeneratorDefinition idGenerator = buildIdGenerator( + sequenceGenerator, + context + ); + generators.put( + idGenerator.getName(), + idGenerator + ); + metadataCollector.addIdentifierGenerator( idGenerator ); + } + } + TableGenerator tabGen = annElt.getAnnotation( TableGenerator.class ); SequenceGenerator seqGen = annElt.getAnnotation( SequenceGenerator.class ); GenericGenerator genGen = annElt.getAnnotation( GenericGenerator.class ); if ( tabGen != null ) { IdentifierGeneratorDefinition idGen = buildIdGenerator( tabGen, context ); generators.put( idGen.getName(), idGen ); + metadataCollector.addIdentifierGenerator( idGen ); + } if ( seqGen != null ) { IdentifierGeneratorDefinition idGen = buildIdGenerator( seqGen, context ); generators.put( idGen.getName(), idGen ); + metadataCollector.addIdentifierGenerator( idGen ); } if ( genGen != null ) { IdentifierGeneratorDefinition idGen = buildIdGenerator( genGen, context ); generators.put( idGen.getName(), idGen ); + metadataCollector.addIdentifierGenerator( idGen ); } return generators; } public static boolean isDefault(XClass clazz, MetadataBuildingContext context) { - return context.getBuildingOptions().getReflectionManager().equals( clazz, void.class ); + return context.getBootstrapContext().getReflectionManager().equals( clazz, void.class ); } /** @@ -3372,8 +3472,7 @@ public static boolean isDefault(XClass clazz, MetadataBuildingContext context) { public static Map buildInheritanceStates( List orderedClasses, MetadataBuildingContext buildingContext) { - ReflectionManager reflectionManager = buildingContext.getBuildingOptions().getReflectionManager(); - Map inheritanceStatePerClass = new HashMap( + Map inheritanceStatePerClass = new HashMap<>( orderedClasses.size() ); for ( XClass clazz : orderedClasses ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java index eb9e63a07154..2453874042d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java @@ -17,15 +17,22 @@ import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; +import org.hibernate.boot.AttributeConverterInfo; +import org.hibernate.boot.model.convert.internal.InstanceBasedConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.spi.MetadataBuildingContext; /** * Externalized representation of an AttributeConverter * * @author Steve Ebersole * - * @see org.hibernate.boot.spi.AttributeConverterDescriptor + * @deprecated (since 5.3) forces the converter instance to be built too early, + * which precludes the ability to resolve them from CDI, etc. See + * {@link org.hibernate.boot.model.convert.spi.ConverterDescriptor} instead */ -public class AttributeConverterDefinition { +@Deprecated +public class AttributeConverterDefinition implements AttributeConverterInfo { private final AttributeConverter attributeConverter; private final boolean autoApply; private final Class entityAttributeType; @@ -152,7 +159,7 @@ public AttributeConverterDefinition(AttributeConverter attributeConverter, boole private ParameterizedType extractAttributeConverterParameterizedType(Type base) { if ( base != null ) { Class clazz = extractClass( base ); - List types = new ArrayList(); + List types = new ArrayList<>(); types.add( clazz.getGenericSuperclass() ); types.addAll( Arrays.asList( clazz.getGenericInterfaces() ) ); for ( Type type : types ) { @@ -236,15 +243,6 @@ public Class getDatabaseColumnType() { return databaseColumnType; } - private static Class extractType(TypeVariable typeVariable) { - java.lang.reflect.Type[] boundTypes = typeVariable.getBounds(); - if ( boundTypes == null || boundTypes.length != 1 ) { - return null; - } - - return (Class) boundTypes[0]; - } - private static Class extractClass(Type type) { if ( type instanceof Class ) { return (Class) type; @@ -255,6 +253,11 @@ else if ( type instanceof ParameterizedType ) { return null; } + @Override + public Class getConverterClass() { + return attributeConverter.getClass(); + } + @Override public String toString() { return String.format( @@ -265,4 +268,13 @@ public String toString() { databaseColumnType.getName() ); } + + @Override + public ConverterDescriptor toConverterDescriptor(MetadataBuildingContext context) { + return new InstanceBasedConverterDescriptor( + getAttributeConverter(), + isAutoApply(), + context.getBootstrapContext().getClassmateContext() + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 9afa4aba743b..3ee971e385d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -6,9 +6,19 @@ */ package org.hibernate.cfg; +import java.util.function.Supplier; +import javax.persistence.GeneratedValue; + +import org.hibernate.HibernateException; +import org.hibernate.Transaction; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.registry.classloading.internal.TcclLookupPrecedence; +import org.hibernate.cache.spi.TimestampsCacheFactory; +import org.hibernate.internal.log.DeprecationLogger; +import org.hibernate.jpa.spi.JpaCompliance; +import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; import org.hibernate.query.internal.ParameterMetadataImpl; +import org.hibernate.resource.beans.container.spi.ExtendedBeanManager; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; @@ -17,14 +27,14 @@ /** * @author Steve Ebersole */ -public interface AvailableSettings { +public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // JPA defined settings // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** - * THe name of the {@link javax.persistence.spi.PersistenceProvider} implementor + * The name of the {@link javax.persistence.spi.PersistenceProvider} implementor *

          * See JPA 2 sections 9.4.3 and 8.2.1.4 */ @@ -175,6 +185,25 @@ public interface AvailableSettings { /** * Used to pass along the CDI BeanManager, if any, to be used. + * + * According to JPA, strictly, the BeanManager should be passed in + * at boot-time and be ready for use at that time. However not all + * environments can do this (WildFly e.g.). To accommodate such + * environments, Hibernate provides 2 options: + * + * * a proprietary CDI extension SPI (that we have proposed to + * the CDI spec group as a standard option) that can be used + * to provide delayed BeanManager access. To use this solution, + * the reference passed as the BeanManager during bootstrap + * should be typed as {@link ExtendedBeanManager} + * * delayed access to the BeanManager reference. Here, Hibernate + * will not access the reference passed as the BeanManager during + * bootstrap until it is first needed. Note however that this has + * the effect of delaying any deployement problems until after + * bootstrapping. + * + * This setting is used to configure Hibernate ORM's access to + * the BeanManager (either directly or via {@link ExtendedBeanManager}). */ String CDI_BEAN_MANAGER = "javax.persistence.bean.manager"; @@ -194,11 +223,11 @@ public interface AvailableSettings { /** * Used to define how the current thread context {@link ClassLoader} must be used * for class lookup. - * + * * @see TcclLookupPrecedence */ String TC_CLASSLOADER = "hibernate.classLoader.tccl_lookup_precedence"; - + /** * Names the {@link ClassLoader} used to load user application classes. * @since 4.0 @@ -324,9 +353,12 @@ public interface AvailableSettings { String ISOLATION ="hibernate.connection.isolation"; /** - * Names the {@literal JDBC} autocommit mode + * Controls the autocommit mode of {@literal JDBC} Connections obtained + * from a non-DataSource ConnectionProvider - assuming the ConnectionProvider + * impl properly leverages this setting (the provided Hibernate impls all + * do). */ - String AUTOCOMMIT ="hibernate.connection.autocommit"; + String AUTOCOMMIT = "hibernate.connection.autocommit"; /** * Maximum number of inactive connections for the built-in Hibernate connection pool. @@ -342,6 +374,18 @@ public interface AvailableSettings { */ String DATASOURCE ="hibernate.connection.datasource"; + /** + * Allows a user to tell Hibernate that the Connections we obtain from the configured + * ConnectionProvider will already have auto-commit disabled when we acquire them from + * the provider. When we get connections already in auto-commit, this allows us to circumvent + * some operations in the interest of performance. + *

          + * Default value is {@code false} - do not skip, aka call setAutocommit + * + * @since 5.2.10 + */ + String CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT= "hibernate.connection.provider_disables_autocommit"; + /** * Names a prefix used to define arbitrary JDBC connection properties. These properties are passed along to * the {@literal JDBC} provider when creating a connection. @@ -379,6 +423,15 @@ public interface AvailableSettings { */ String DIALECT_RESOLVERS = "hibernate.dialect_resolvers"; + /** + * Defines the default storage engine for the relational databases that support multiple storage engines. + * This property must be set either as an Environment variable or JVM System Property. + * That is because the Dialect is bootstrapped prior to Hibernate property resolution. + * + * @since 5.2.9 + */ + String STORAGE_ENGINE = "hibernate.dialect.storage_engine"; + /** * Used to specify the {@link org.hibernate.tool.schema.spi.SchemaManagementTool} to use for performing * schema management. The default is to use {@link org.hibernate.tool.schema.internal.HibernateSchemaManagementTool} @@ -442,6 +495,20 @@ public interface AvailableSettings { */ String JTA_CACHE_UT = "hibernate.jta.cacheUserTransaction"; + /** + * `true` / `false - should zero be used as the base for JDBC-style parameters + * found in native-queries? + * + * @since 5.3 + * + * @see DeprecationLogger#logUseOfDeprecatedZeroBasedJdbcStyleParams + * + * @deprecated This is a temporary backwards-compatibility setting to help applications + * using versions prior to 5.3 in upgrading. Deprecation warnings are issued when this + * is set to `true`. + */ + @Deprecated + String JDBC_TYLE_PARAMS_ZERO_BASE = "hibernate.query.sql.jdbc_style_params_base"; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -783,6 +850,36 @@ public interface AvailableSettings { */ String USE_REFLECTION_OPTIMIZER = "hibernate.bytecode.use_reflection_optimizer"; + /** + * Configure the global BytecodeProvider implementation to generate class names matching the + * existing naming patterns. + * It is not a good idea to rely on a classname to check if a class is an Hibernate proxy, + * yet some frameworks are currently relying on this. + * This option is disabled by default and will log a deprecation warning when enabled. + */ + String ENFORCE_LEGACY_PROXY_CLASSNAMES = "hibernate.bytecode.enforce_legacy_proxy_classnames"; + + /** + * Should Hibernate use enhanced entities "as a proxy"? + * + * E.g., when an application uses {@link org.hibernate.Session#load} against an enhanced + * class, enabling this will allow Hibernate to create an "empty" instance of the enhanced + * class to act as the proxy - it contains just the identifier which is later used to + * trigger the base initialization but no other data is loaded + * + * Not enabling this (the legacy default behavior) would cause the "base" attributes to + * be loaded. Any lazy-group attributes would not be initialized. + * + * Applications using bytecode enhancement and switching to allowing this should be careful + * in use of the various {@link org.hibernate.Hibernate} methods such as + * {@link org.hibernate.Hibernate#isInitialized}, + * {@link org.hibernate.Hibernate#isPropertyInitialized}, etc - enabling this setting changes + * the results of those methods + * + * @implSpec See {@link org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor} + */ + String ALLOW_ENHANCEMENT_AS_PROXY = "hibernate.bytecode.allow_enhancement_as_proxy"; + /** * The classname of the HQL query parser factory */ @@ -825,6 +922,18 @@ public interface AvailableSettings { */ String WRAP_RESULT_SETS = "hibernate.jdbc.wrap_result_sets"; + /** + * Indicates if exception handling for a SessionFactory built via Hibernate's native bootstrapping + * should behave the same as native exception handling in Hibernate ORM 5.1, When set to {@code true}, + * {@link HibernateException} will not be wrapped or converted according to the JPA specification. + *

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

          + * Values are {@code true} or {@code false}. + * Default value is {@code false} + */ + String NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE = "hibernate.native_exception_handling_51_compliance"; + /** * Enable ordering of update statements by primary key value */ @@ -851,6 +960,19 @@ public interface AvailableSettings { */ String LOG_JDBC_WARNINGS = "hibernate.jdbc.log.warnings"; + /** + * Identifies an explicit {@link org.hibernate.resource.beans.container.spi.BeanContainer} + * to be used. + * + * Note that for CDI-based containers setting this is not necessary - simply + * pass the BeanManager to use via {@link #CDI_BEAN_MANAGER} and + * optionally specify {@link #DELAY_CDI_ACCESS}. This setting is more meant to + * integrate non-CDI bean containers such as Spring. + * + * @since 5.3 + */ + String BEAN_CONTAINER = "hibernate.resource.beans.container"; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -888,7 +1010,7 @@ public interface AvailableSettings { String C3P0_ACQUIRE_INCREMENT = "hibernate.c3p0.acquire_increment"; /** - * Idle time beforeQuery a C3P0 pooled connection is validated + * Idle time before a C3P0 pooled connection is validated */ String C3P0_IDLE_TEST_PERIOD = "hibernate.c3p0.idle_test_period"; @@ -976,7 +1098,7 @@ public interface AvailableSettings { String USE_QUERY_CACHE = "hibernate.cache.use_query_cache"; /** - * The {@link org.hibernate.cache.spi.QueryCacheFactory} implementation class. + * The {@link TimestampsCacheFactory} implementation class. */ String QUERY_CACHE_FACTORY = "hibernate.cache.query_cache_factory"; @@ -1045,6 +1167,9 @@ public interface AvailableSettings { String CHECK_NULLABILITY = "hibernate.check_nullability"; + /** + * Pick which bytecode enhancing library to use. Currently supports javassist and bytebuddy, bytebuddy being the default since version 5.3. + */ String BYTECODE_PROVIDER = "hibernate.bytecode.provider"; String JPAQL_STRICT_COMPLIANCE= "hibernate.query.jpaql_strict_compliance"; @@ -1086,7 +1211,7 @@ public interface AvailableSettings { *

        • {@link org.hibernate.engine.query.spi.FilterQueryPlan}
        • *
        • {@link org.hibernate.engine.query.spi.NativeSQLQueryPlan}
        • *
        - * + * * maintained by {@link org.hibernate.engine.query.spi.QueryPlanCache}. Default is 2048. */ String QUERY_PLAN_CACHE_MAX_SIZE = "hibernate.query.plan_cache_max_size"; @@ -1268,7 +1393,7 @@ public interface AvailableSettings { /** * Comma-separated names of the optional files containing SQL DML statements executed * during the SessionFactory creation. - * File order matters, the statements of a give file are executed beforeQuery the statements of the + * File order matters, the statements of a give file are executed before the statements of the * following files. *

        * These statements are only executed if the schema is created ie if hibernate.hbm2ddl.auto @@ -1338,6 +1463,8 @@ public interface AvailableSettings { * * Valid options are defined by the {@link JdbcMetadaAccessStrategy} enum. * + * {@link JdbcMetadaAccessStrategy#GROUPED} is the default value. + * * @see JdbcMetadaAccessStrategy */ String HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY = "hibernate.hbm2ddl.jdbc_metadata_extraction_strategy"; @@ -1374,6 +1501,22 @@ public interface AvailableSettings { */ String CUSTOM_ENTITY_DIRTINESS_STRATEGY = "hibernate.entity_dirtiness_strategy"; + /** + * Controls whether an entity's "where" clause, mapped using @Where(clause="....") + * or <entity ... where="...">, is taken into account when loading one-to-many + * or many-to-many collections of that type of entity. + *

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

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

        + * `false` indicates that the entity's "where" clause will be ignored when loading one-to-many or + * many-to-many collections of that type of entity. + */ + String USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = "hibernate.use_entity_where_clause_for_collections"; + /** * Strategy for multi-tenancy. @@ -1432,6 +1575,7 @@ public interface AvailableSettings { * Can reference

          *
        • Interceptor implementation {@link Class} reference
        • *
        • Interceptor implementation class name
        • + *
        • {@link Supplier} instance which is used to retrieve the interceptor
        • *
        * Note specifically that this setting cannot name an Interceptor instance. * @@ -1459,18 +1603,32 @@ public interface AvailableSettings { * Names the {@link org.hibernate.loader.BatchFetchStyle} to use. Can specify either the * {@link org.hibernate.loader.BatchFetchStyle} name (insensitively), or a * {@link org.hibernate.loader.BatchFetchStyle} instance. - * + * * {@code LEGACY} is the default value. */ String BATCH_FETCH_STYLE = "hibernate.batch_fetch_style"; - + + /** + * Controls how the individual Loaders for an entity are created. + * + * When `true` (the default), only the minimal set of Loaders are + * created. These include the handling for {@link org.hibernate.LockMode#READ} + * and {@link org.hibernate.LockMode#NONE} as well as specialized Loaders for + * merge and refresh handling. + * + * `false` indicates that all loaders should be created up front + * + * @since 5.3 + */ + String DELAY_ENTITY_LOADER_CREATIONS = "hibernate.loader.delay_entity_loader_creations"; + /** * A transaction can be rolled back by another thread ("tracking by thread") * -- not the original application. Examples of this include a JTA * transaction timeout handled by a background reaper thread. The ability * to handle this situation requires checking the Thread ID every time * Session is called. This can certainly have performance considerations. - * + * * Default is true (enabled). */ String JTA_TRACK_BY_THREAD = "hibernate.jta.track_by_thread"; @@ -1500,7 +1658,7 @@ public interface AvailableSettings { * SchemaUpdate needs to create these constraints, but DB's * support for finding existing constraints is extremely inconsistent. Further, * non-explicitly-named unique constraints use randomly generated characters. - * + * * Therefore, select from these strategies. * {@link org.hibernate.tool.hbm2ddl.UniqueConstraintSchemaUpdateStrategy#DROP_RECREATE_QUIETLY} (DEFAULT): * Attempt to drop, then (re-)create each unique constraint. @@ -1535,19 +1693,25 @@ public interface AvailableSettings { * Global setting for whether NULL parameter bindings should be passed to database * procedure/function calls as part of {@link org.hibernate.procedure.ProcedureCall} * handling. Implicitly Hibernate will not pass the NULL, the intention being to allow - * any default argumnet values to be applied. + * any default argument values to be applied. *

        * This defines a global setting, which can them be controlled per parameter via * {@link org.hibernate.procedure.ParameterRegistration#enablePassingNulls(boolean)} *

        * Values are {@code true} (pass the NULLs) or {@code false} (do not pass the NULLs). + * + * @deprecated (5.3) Hibernate determines it implicitly */ + @Deprecated String PROCEDURE_NULL_PARAM_PASSING = "hibernate.proc.param_null_passing"; /** - * Enable instantiation of composite/embedded objects when all of its attribute values are {@code null}. + * [EXPERIMENTAL] Enable instantiation of composite/embedded objects when all of its attribute values are {@code null}. * The default (and historical) behavior is that a {@code null} reference will be used to represent the * composite when all of its attributes are {@code null} + *

        + * This is an experimental feature that has known issues. It should not be used in production + * until it is stabilized. See Hibernate Jira issue HHH-11936 for details. * * @since 5.1 */ @@ -1637,4 +1801,191 @@ public interface AvailableSettings { * @since 5.2.5 */ String USE_LEGACY_LIMIT_HANDLERS = "hibernate.legacy_limit_handler"; + + + /** + * Setting which indicates if {@link org.hibernate.query.Query#setParameter} should not perform parameters validation + * + * This setting is applied only when the Session is bootstrapped via JPA {@link javax.persistence.EntityManagerFactory} + * + *

        + * Values are: {@code true} indicates the validation should be performed, {@code false} otherwise + *

        + * The default value is {@code true} when the Session is bootstrapped via JPA {@link javax.persistence.EntityManagerFactory}, + * otherwise is {@code false} + * + */ + String VALIDATE_QUERY_PARAMETERS = "hibernate.query.validate_parameters"; + + /** + * By default, Criteria queries uses bind parameters for any literal that is not a numeric value. + * + * However, to increase the likelihood of JDBC statement caching, + * you might want to use bind parameters for numeric values too. + * The {@link org.hibernate.query.criteria.LiteralHandlingMode#BIND} mode will use bind variables for any literal value. + * + * The {@link org.hibernate.query.criteria.LiteralHandlingMode#INLINE} mode will inline literal values as-is. + * To prevent SQL injection, never use {@link org.hibernate.query.criteria.LiteralHandlingMode#INLINE} with String variables. + * Always use constants with the {@link org.hibernate.query.criteria.LiteralHandlingMode#INLINE} mode. + *

        + * Valid options are defined by the {@link org.hibernate.query.criteria.LiteralHandlingMode} enum. + *

        + * The default value is {@link org.hibernate.query.criteria.LiteralHandlingMode#AUTO} + * + * @since 5.2.12 + * @see org.hibernate.query.criteria.LiteralHandlingMode + */ + String CRITERIA_LITERAL_HANDLING_MODE = "hibernate.criteria.literal_handling_mode"; + + /** + * True/false setting indicating whether the value specified for {@link GeneratedValue#generator()} + * should be used as the sequence/table name when no matching {@link javax.persistence.SequenceGenerator} + * or {@link javax.persistence.TableGenerator} is found. + * + * The default value is `true` meaning that {@link GeneratedValue#generator()} will be used as the + * sequence/table name by default. Users migrating from earlier versions using the legacy + * `hibernate_sequence` name should disable this setting. + */ + String PREFER_GENERATOR_NAME_AS_DEFAULT_SEQUENCE_NAME = "hibernate.model.generator_name_as_sequence_name"; + + /** + * Should Hibernate's {@link Transaction} behave as + * defined by the spec for JPA's {@link javax.persistence.EntityTransaction} + * since it extends the JPA one. + * + * @see JpaCompliance#isJpaTransactionComplianceEnabled() + * @since 5.3 + */ + String JPA_TRANSACTION_COMPLIANCE = "hibernate.jpa.compliance.transaction"; + + /** + * Controls whether Hibernate's handling of {@link javax.persistence.Query} + * (JPQL, Criteria and native-query) should strictly follow the JPA spec. + * This includes both in terms of parsing or translating a query as well + * as calls to the {@link javax.persistence.Query} methods throwing spec + * defined exceptions where as Hibernate might not. + * + * Deviations result in an exception if enabled + * + * @see JpaCompliance#isJpaQueryComplianceEnabled() + * @since 5.3 + */ + String JPA_QUERY_COMPLIANCE = "hibernate.jpa.compliance.query"; + + /** + * Controls whether Hibernate should recognize what it considers a "bag" + * ({@link org.hibernate.collection.internal.PersistentBag}) as a List + * ({@link org.hibernate.collection.internal.PersistentList}) or as a bag. + * + * If enabled, we will recognize it as a List where {@link javax.persistence.OrderColumn} + * is just missing (and its defaults will apply). + * + * @see JpaCompliance#isJpaListComplianceEnabled() + * @since 5.3 + */ + String JPA_LIST_COMPLIANCE = "hibernate.jpa.compliance.list"; + + /** + * JPA defines specific exceptions on specific methods when called on + * {@link javax.persistence.EntityManager} and {@link javax.persistence.EntityManagerFactory} + * when those objects have been closed. This setting controls + * whether the spec defined behavior or Hibernate's behavior will be used. + * + * If enabled Hibernate will operate in the JPA specified way throwing + * exceptions when the spec says it should. + * + * @see JpaCompliance#isJpaClosedComplianceEnabled() + * @since 5.3 + */ + String JPA_CLOSED_COMPLIANCE = "hibernate.jpa.compliance.closed"; + + /** + * The JPA spec says that a {@link javax.persistence.EntityNotFoundException} + * should be thrown when accessing an entity Proxy which does not have an associated + * table row in the database. + * + * Traditionally, Hibernate does not initialize an entity Proxy when accessing its + * identifier since we already know the identifier value, hence we can save a database roundtrip. + * + * If enabled Hibernate will initialize the entity Proxy even when accessing its identifier. + * + * @see JpaCompliance#isJpaProxyComplianceEnabled() + * @since 5.2.13 + */ + String JPA_PROXY_COMPLIANCE = "hibernate.jpa.compliance.proxy"; + + /** + * @see JpaCompliance#isJpaCacheComplianceEnabled() + * @since 5.3 + */ + String JPA_CACHING_COMPLIANCE = "hibernate.jpa.compliance.caching"; + + /** + * Determine if the scope of {@link javax.persistence.TableGenerator#name()} and {@link javax.persistence.SequenceGenerator#name()} should be + * considered globally or locally defined. + * + * If enabled, the names will considered globally scoped so defining two different generators with the same name + * will cause a name collision and an exception will be thrown during the bootstrap phase. + * + * @see JpaCompliance#isGlobalGeneratorScopeEnabled() + * @since 5.2.17 + */ + String JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE = "hibernate.jpa.compliance.global_id_generators"; + + /** + * True/False setting indicating if the value stored in the table used by the {@link javax.persistence.TableGenerator} + * is the last value generated or the next value to be used. + * + * The default value is true. + * + * @since 5.3 + */ + String TABLE_GENERATOR_STORE_LAST_USED = "hibernate.id.generator.stored_last_used"; + + /** + * Raises an exception when in-memory pagination over collection fetch is about to be performed. + * Disabled by default. Set to true to enable. + * + * @since 5.2.13 + */ + String FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH = "hibernate.query.fail_on_pagination_over_collection_fetch"; + + /** + * This setting defines how {@link org.hibernate.annotations.Immutable} entities are handled when executing a + * bulk update {@link javax.persistence.Query}. + * + * By default, the ({@link ImmutableEntityUpdateQueryHandlingMode#WARNING}) mode is used, meaning that + * a warning log message is issued when an {@link org.hibernate.annotations.Immutable} entity + * is to be updated via a bulk update statement. + * + * If the ({@link ImmutableEntityUpdateQueryHandlingMode#EXCEPTION}) mode is used, then a + * {@link HibernateException} is thrown instead. + *

        + * Valid options are defined by the {@link ImmutableEntityUpdateQueryHandlingMode} enum. + *

        + * The default value is {@link ImmutableEntityUpdateQueryHandlingMode#WARNING} + * + * @since 5.2.17 + * @see org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode + */ + String IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE = "hibernate.query.immutable_entity_update_query_handling_mode"; + + /** + * By default, the IN clause expands to include all bind parameter values. + *

        + * However, for database systems supporting execution plan caching, + * there's a better chance of hitting the cache if the number of possible IN clause parameters lowers. + *

        + * For this reason, we can expand the bind parameters to power-of-two: 4, 8, 16, 32, 64. + * This way, an IN clause with 5, 6, or 7 bind parameters will use the 8 IN clause, + * therefore reusing its execution plan. + *

        + * If you want to activate this feature, you need to set this property to {@code true}. + *

        + * The default value is {@code false}. + * + * @since 5.2.17 + */ + String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding"; + } 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 d2ef2c85e266..2ade91196c27 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java @@ -6,6 +6,24 @@ */ package org.hibernate.cfg; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Index; +import javax.persistence.SequenceGenerator; +import javax.persistence.TableGenerator; +import javax.persistence.UniqueConstraint; + import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; @@ -17,13 +35,17 @@ import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XPackage; +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.model.IdGeneratorStrategyInterpreter; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.EntityBinder; import org.hibernate.cfg.annotations.Nullability; import org.hibernate.cfg.annotations.TableBinder; +import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.MultipleHiLoPerTableGenerator; import org.hibernate.id.PersistentIdentifierGenerator; +import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Any; @@ -39,18 +61,8 @@ import org.hibernate.mapping.Table; import org.hibernate.mapping.ToOne; import org.hibernate.mapping.Value; -import org.jboss.logging.Logger; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.StringTokenizer; +import org.jboss.logging.Logger; /** * @author Emmanuel Bernard @@ -59,13 +71,14 @@ public class BinderHelper { public static final String ANNOTATION_STRING_DEFAULT = ""; - private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BinderHelper.class.getName() ); + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( BinderHelper.class ); + private static final Logger log = CoreLogging.logger( BinderHelper.class ); private BinderHelper() { } static { - Set primitiveNames = new HashSet(); + Set primitiveNames = new HashSet<>(); primitiveNames.add( byte.class.getName() ); primitiveNames.add( short.class.getName() ); primitiveNames.add( int.class.getName() ); @@ -260,12 +273,12 @@ public static void createSyntheticPropertyReference( Object columnOwner = findColumnOwner( ownerEntity, columns[0].getReferencedColumn(), context ); List properties = findPropertiesByColumns( columnOwner, columns, context ); //create an embeddable component - Property synthProp = null; + Property synthProp; if ( properties != null ) { //todo how about properties.size() == 1, this should be much simpler Component embeddedComp = columnOwner instanceof PersistentClass ? - new Component( context.getMetadataCollector(), (PersistentClass) columnOwner ) : - new Component( context.getMetadataCollector(), (Join) columnOwner ); + new Component( context, (PersistentClass) columnOwner ) : + new Component( context, (Join) columnOwner ); embeddedComp.setEmbedded( true ); embeddedComp.setComponentClassName( embeddedComp.getOwner().getClassName() ); for (Property property : properties) { @@ -358,9 +371,9 @@ private static List findPropertiesByColumns( Object columnOwner, Ejb3JoinColumn[] columns, MetadataBuildingContext context) { - Map> columnsToProperty = new HashMap>(); - List orderedColumns = new ArrayList( columns.length ); - Table referencedTable = null; + Map> columnsToProperty = new HashMap<>(); + List orderedColumns = new ArrayList<>( columns.length ); + Table referencedTable; if ( columnOwner instanceof PersistentClass ) { referencedTable = ( (PersistentClass) columnOwner ).getTable(); } @@ -383,7 +396,7 @@ else if ( columnOwner instanceof Join ) { ) ); orderedColumns.add( column ); - columnsToProperty.put( column, new HashSet() ); + columnsToProperty.put( column, new HashSet<>() ); } boolean isPersistentClass = columnOwner instanceof PersistentClass; Iterator it = isPersistentClass ? @@ -399,7 +412,7 @@ else if ( columnOwner instanceof Join ) { //first naive implementation //only check 1 columns properties //TODO make it smarter by checking correctly ordered multi column properties - List orderedProperties = new ArrayList(); + List orderedProperties = new ArrayList<>(); for (Column column : orderedColumns) { boolean found = false; for (Property property : columnsToProperty.get( column ) ) { @@ -623,10 +636,13 @@ public static Object findColumnOwner( */ public static void makeIdGenerator( SimpleValue id, + XProperty idXProperty, String generatorType, String generatorName, MetadataBuildingContext buildingContext, Map localGenerators) { + log.debugf( "#makeIdGenerator(%s, %s, %s, %s, ...)", id, idXProperty, generatorType, generatorName ); + Table table = id.getTable(); table.setIdentifierValue( id ); //generator settings @@ -656,12 +672,18 @@ public static void makeIdGenerator( } // YUCK! but cannot think of a clean way to do this given the string-config based scheme params.put( PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER, buildingContext.getObjectNameNormalizer() ); + params.put( IdentifierGenerator.GENERATOR_NAME, generatorName ); if ( !isEmptyAnnotationValue( generatorName ) ) { //we have a named generator - IdentifierGeneratorDefinition gen = getIdentifierGenerator( generatorName, localGenerators, buildingContext ); + IdentifierGeneratorDefinition gen = getIdentifierGenerator( + generatorName, + idXProperty, + localGenerators, + buildingContext + ); if ( gen == null ) { - throw new AnnotationException( "Unknown Id.generator: " + generatorName ); + throw new AnnotationException( "Unknown named generator (@GeneratedValue#generatorName): " + generatorName ); } //This is quite vague in the spec but a generator could override the generate choice String identifierGeneratorStrategy = gen.getStrategy(); @@ -676,6 +698,9 @@ public static void makeIdGenerator( //checkIfMatchingGenerator(gen, generatorType, generatorName); for ( Object o : gen.getParameters().entrySet() ) { Map.Entry elt = (Map.Entry) o; + if ( elt.getKey() == null ) { + continue; + } params.setProperty( (String) elt.getKey(), (String) elt.getValue() ); } } @@ -685,8 +710,27 @@ public static void makeIdGenerator( id.setIdentifierGeneratorProperties( params ); } - public static IdentifierGeneratorDefinition getIdentifierGenerator( + /** + * apply an id generator to a SimpleValue + */ + public static void makeIdGenerator( + SimpleValue id, + XProperty idXProperty, + String generatorType, + String generatorName, + MetadataBuildingContext buildingContext, + IdentifierGeneratorDefinition foreignKGeneratorDefinition) { + Map localIdentifiers = null; + if ( foreignKGeneratorDefinition != null ) { + localIdentifiers = new HashMap<>(); + localIdentifiers.put( foreignKGeneratorDefinition.getName(), foreignKGeneratorDefinition ); + } + makeIdGenerator( id, idXProperty, generatorType, generatorName, buildingContext, localIdentifiers ); + } + + private static IdentifierGeneratorDefinition getIdentifierGenerator( String name, + XProperty idXProperty, Map localGenerators, MetadataBuildingContext buildingContext) { if ( localGenerators != null ) { @@ -696,7 +740,190 @@ public static IdentifierGeneratorDefinition getIdentifierGenerator( } } - return buildingContext.getMetadataCollector().getIdentifierGenerator( name ); + final IdentifierGeneratorDefinition globalDefinition = buildingContext.getMetadataCollector().getIdentifierGenerator( name ); + if ( globalDefinition != null ) { + return globalDefinition; + } + + log.debugf( "Could not resolve explicit IdentifierGeneratorDefinition - using implicit interpretation (%s)", name ); + + // If we were unable to locate an actual matching named generator assume a sequence/table of the given name. + // this really needs access to the `javax.persistence.GenerationType` to work completely properly + // + // (the crux of HHH-12122) + + // temporarily, in lieu of having access to GenerationType, assume the EnhancedSequenceGenerator + // for the purpose of testing the feasibility of the approach + + final GeneratedValue generatedValueAnn = idXProperty.getAnnotation( GeneratedValue.class ); + if ( generatedValueAnn == null ) { + // this should really never happen, but its easy to protect against it... + return new IdentifierGeneratorDefinition( "assigned", "assigned" ); + } + + final IdGeneratorStrategyInterpreter generationInterpreter = buildingContext.getBuildingOptions().getIdGenerationTypeInterpreter(); + + final GenerationType generationType = interpretGenerationType( generatedValueAnn ); + + if ( generationType == null || generationType == GenerationType.SEQUENCE ) { + // NOTE : `null` will ultimately be interpreted as "hibernate_sequence" + log.debugf( "Building implicit sequence-based IdentifierGeneratorDefinition (%s)", name ); + final IdentifierGeneratorDefinition.Builder builder = new IdentifierGeneratorDefinition.Builder(); + generationInterpreter.interpretSequenceGenerator( + new SequenceGenerator() { + @Override + public String name() { + return name; + } + + @Override + public String sequenceName() { + return ""; + } + + @Override + public String catalog() { + return ""; + } + + @Override + public String schema() { + return ""; + } + + @Override + public int initialValue() { + return 1; + } + + @Override + public int allocationSize() { + return 50; + } + + @Override + public Class annotationType() { + return SequenceGenerator.class; + } + }, + builder + ); + + return builder.build(); + } + else if ( generationType == GenerationType.TABLE ) { + // NOTE : `null` will ultimately be interpreted as "hibernate_sequence" + log.debugf( "Building implicit table-based IdentifierGeneratorDefinition (%s)", name ); + final IdentifierGeneratorDefinition.Builder builder = new IdentifierGeneratorDefinition.Builder(); + generationInterpreter.interpretTableGenerator( + new TableGenerator() { + @Override + public String name() { + return name; + } + + @Override + public String table() { + return ""; + } + + @Override + public int initialValue() { + return 0; + } + + @Override + public int allocationSize() { + return 50; + } + + @Override + public String catalog() { + return ""; + } + + @Override + public String schema() { + return ""; + } + + @Override + public String pkColumnName() { + return ""; + } + + @Override + public String valueColumnName() { + return ""; + } + + @Override + public String pkColumnValue() { + return ""; + } + + @Override + public UniqueConstraint[] uniqueConstraints() { + return new UniqueConstraint[0]; + } + + @Override + public Index[] indexes() { + return new Index[0]; + } + + @Override + public Class annotationType() { + return TableGenerator.class; + } + }, + builder + ); + + return builder.build(); + } + + + // really AUTO and IDENTITY work the same in this respect, aside from the actual strategy name + final String strategyName; + if ( generationType == GenerationType.IDENTITY ) { + strategyName = "identity"; + } + else { + strategyName = generationInterpreter.determineGeneratorName( + generationType, + new IdGeneratorStrategyInterpreter.GeneratorNameDeterminationContext() { + @Override + public Class getIdType() { + return buildingContext + .getBootstrapContext() + .getReflectionManager() + .toClass( idXProperty.getType() ); + } + + @Override + public String getGeneratedValueGeneratorName() { + return generatedValueAnn.generator(); + } + } + ); + } + + log.debugf( "Building implicit generic IdentifierGeneratorDefinition (%s) : %s", name, strategyName ); + return new IdentifierGeneratorDefinition( + name, + strategyName, + Collections.singletonMap( IdentifierGenerator.GENERATOR_NAME, name ) + ); + } + + @SuppressWarnings("ConstantConditions") + private static GenerationType interpretGenerationType(GeneratedValue generatedValueAnn) { + if ( generatedValueAnn.strategy() == null ) { + return GenerationType.AUTO; + } + + return generatedValueAnn.strategy(); } public static boolean isEmptyAnnotationValue(String annotationString) { @@ -720,7 +947,7 @@ public static Any buildAnyValue( boolean optional, MetadataBuildingContext context) { //All FK columns should be in the same table - Any value = new Any( context.getMetadataCollector(), columns[0].getTable() ); + Any value = new Any( context, columns[0].getTable() ); AnyMetaDef metaAnnDef = inferredData.getProperty().getAnnotation( AnyMetaDef.class ); if ( metaAnnDef != null ) { @@ -851,7 +1078,7 @@ public static MappedSuperclass getMappedSuperclassOrNull( if ( retrieve ) { return context.getMetadataCollector().getMappedSuperclass( - context.getBuildingOptions().getReflectionManager().toClass( declaringClass ) + context.getBootstrapContext().getReflectionManager().toClass( declaringClass ) ); } else { @@ -870,7 +1097,7 @@ static PropertyData getPropertyOverriddenByMapperOrMapsId( MetadataBuildingContext buildingContext) { final XClass persistentXClass; try { - persistentXClass = buildingContext.getBuildingOptions().getReflectionManager() + persistentXClass = buildingContext.getBootstrapContext().getReflectionManager() .classForName( propertyHolder.getPersistentClass().getClassName() ); } catch ( ClassLoadingException e ) { @@ -894,7 +1121,7 @@ static PropertyData getPropertyOverriddenByMapperOrMapsId( } public static Map toAliasTableMap(SqlFragmentAlias[] aliases){ - Map ret = new HashMap(); + Map ret = new HashMap<>(); for ( int i = 0; i < aliases.length; i++ ){ if ( StringHelper.isNotEmpty( aliases[i].table() ) ){ ret.put( aliases[i].alias(), aliases[i].table() ); @@ -904,7 +1131,7 @@ public static Map toAliasTableMap(SqlFragmentAlias[] aliases){ } public static Map toAliasEntityMap(SqlFragmentAlias[] aliases){ - Map ret = new HashMap(); + Map ret = new HashMap<>(); for (int i = 0; i < aliases.length; i++){ if (aliases[i].entity() != void.class){ ret.put( aliases[i].alias(), aliases[i].entity().getName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java index 025c2fe484e9..a3958341fcb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java @@ -231,7 +231,7 @@ private void addPropertyToPersistentClass(Property prop, XClass declaringClass) } private void addPropertyToMappedSuperclass(Property prop, XClass declaringClass) { - final Class type = getContext().getBuildingOptions().getReflectionManager().toClass( declaringClass ); + final Class type = getContext().getBootstrapContext().getReflectionManager().toClass( declaringClass ); MappedSuperclass superclass = getContext().getMetadataCollector().getMappedSuperclass( type ); superclass.addDeclaredProperty( prop ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java index 36d66a0897c8..b0a120ca1864 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java @@ -26,7 +26,7 @@ import org.hibernate.annotations.MapKeyType; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.boot.spi.AttributeConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -66,8 +66,8 @@ public CollectionPropertyHolder( this.collection = collection; setCurrentProperty( property ); - this.elementAttributeConversionInfoMap = new HashMap(); - this.keyAttributeConversionInfoMap = new HashMap(); + this.elementAttributeConversionInfoMap = new HashMap<>(); + this.keyAttributeConversionInfoMap = new HashMap<>(); } public Collection getCollectionBinding() { @@ -381,7 +381,7 @@ else if ( collectionProperty.isAnnotationPresent( CollectionType.class ) ) { } } - public AttributeConverterDescriptor resolveElementAttributeConverterDescriptor(XProperty collectionXProperty, XClass elementXClass) { + public ConverterDescriptor resolveElementAttributeConverterDescriptor(XProperty collectionXProperty, XClass elementXClass) { AttributeConversionInfo info = locateAttributeConversionInfo( "element" ); if ( info != null ) { if ( info.isConversionDisabled() ) { @@ -412,7 +412,7 @@ public AttributeConverterDescriptor resolveElementAttributeConverterDescriptor(X private Class determineElementClass(XClass elementXClass) { if ( elementXClass != null ) { try { - return getContext().getBuildingOptions().getReflectionManager().toClass( elementXClass ); + return getContext().getBootstrapContext().getReflectionManager().toClass( elementXClass ); } catch (Exception e) { log.debugf( @@ -438,7 +438,7 @@ private Class determineElementClass(XClass elementXClass) { return null; } - public AttributeConverterDescriptor mapKeyAttributeConverterDescriptor(XProperty mapXProperty, XClass keyXClass) { + public ConverterDescriptor mapKeyAttributeConverterDescriptor(XProperty mapXProperty, XClass keyXClass) { AttributeConversionInfo info = locateAttributeConversionInfo( "key" ); if ( info != null ) { if ( info.isConversionDisabled() ) { @@ -469,7 +469,7 @@ public AttributeConverterDescriptor mapKeyAttributeConverterDescriptor(XProperty private Class determineKeyClass(XClass keyXClass) { if ( keyXClass != null ) { try { - return getContext().getBuildingOptions().getReflectionManager().toClass( keyXClass ); + return getContext().getBootstrapContext().getReflectionManager().toClass( keyXClass ); } catch (Exception e) { log.debugf( diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java index b9d3f47983aa..9482f4282641 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java @@ -185,7 +185,7 @@ Ejb3JoinColumn[] buildDefaultJoinColumnsForXToOne(XProperty property, PropertyDa } Ejb3JoinColumn[] buildExplicitJoinColumns(XProperty property, PropertyData inferredData) { - //process @JoinColumn(s) beforeQuery @Column(s) to handle collection of entities properly + //process @JoinColumn(s) before @Column(s) to handle collection of entities properly JoinColumn[] joinColumnAnnotations = null; if ( property.isAnnotationPresent( JoinColumn.class ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index cfe387eea679..85b63d24a4fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -639,7 +639,7 @@ public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver c /** * Create a {@link SessionFactory} using the properties and mappings in this configuration. The - * SessionFactory will be immutable, so changes made to this Configuration afterQuery building the + * SessionFactory will be immutable, so changes made to this Configuration after building the * SessionFactory will not affect it. * * @param serviceRegistry The registry of services to be used in creating this session factory. @@ -650,7 +650,6 @@ public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver c */ public SessionFactory buildSessionFactory(ServiceRegistry serviceRegistry) throws HibernateException { log.debug( "Building session factory using provided StandardServiceRegistry" ); - final MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder( (StandardServiceRegistry) serviceRegistry ); if ( implicitNamingStrategy != null ) { metadataBuilder.applyImplicitNamingStrategy( implicitNamingStrategy ); @@ -687,7 +686,6 @@ public SessionFactory buildSessionFactory(ServiceRegistry serviceRegistry) throw } } - final Metadata metadata = metadataBuilder.build(); final SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder(); @@ -713,7 +711,7 @@ public SessionFactory buildSessionFactory(ServiceRegistry serviceRegistry) throw /** * Create a {@link SessionFactory} using the properties and mappings in this configuration. The - * {@link SessionFactory} will be immutable, so changes made to {@code this} {@link Configuration} afterQuery + * {@link SessionFactory} will be immutable, so changes made to {@code this} {@link Configuration} after * building the {@link SessionFactory} will not affect it. * * @return The build {@link SessionFactory} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java index ee5bc1331ba3..69a628eb088d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java @@ -66,7 +66,7 @@ public void doSecondPass(Map persistentClasses) throws MappingException { //prepare column name structure boolean isExplicitReference = true; - Map columnByReferencedName = new HashMap(joinColumns.length); + Map columnByReferencedName = new HashMap<>(joinColumns.length); for (Ejb3JoinColumn joinColumn : joinColumns) { final String referencedColumnName = joinColumn.getReferencedColumn(); if ( referencedColumnName == null || BinderHelper.isEmptyAnnotationValue( referencedColumnName ) ) { @@ -111,7 +111,7 @@ private Property createComponentProperty( //property.setOptional( property.isOptional() ); property.setPersistentClass( component.getOwner() ); property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() ); - Component value = new Component( buildingContext.getMetadataCollector(), component.getOwner() ); + Component value = new Component( buildingContext, component.getOwner() ); property.setValue( value ); final Component referencedValue = (Component) referencedProperty.getValue(); @@ -150,7 +150,7 @@ private Property createSimpleProperty( //property.setOptional( property.isOptional() ); property.setPersistentClass( component.getOwner() ); property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() ); - SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() ); + SimpleValue value = new SimpleValue( buildingContext, component.getTable() ); property.setValue( value ); final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue(); value.setTypeName( referencedValue.getTypeName() ); @@ -198,6 +198,7 @@ private Property createSimpleProperty( .getName(); value.addColumn( new Column( columnName ) ); if ( joinColumn != null ) { + applyComponentColumnSizeValueToJoinColumn( column, joinColumn ); joinColumn.linkWithValue( value ); } column.setValue( value ); @@ -206,6 +207,13 @@ private Property createSimpleProperty( return property; } + private void applyComponentColumnSizeValueToJoinColumn(Column column, Ejb3JoinColumn joinColumn) { + Column mappingColumn = joinColumn.getMappingColumn(); + mappingColumn.setLength( column.getLength() ); + mappingColumn.setPrecision( column.getPrecision() ); + mappingColumn.setScale( column.getScale() ); + } + public boolean dependentUpon( CopyIdentifierComponentSecondPass other ) { return this.referencedEntityName.equals( other.component.getOwner().getEntityName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java b/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java index 8fa3956e220c..4adec44fdffa 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java @@ -41,13 +41,27 @@ public boolean isResolved() { public InputSource resolveEntity(String publicId, String systemId) { LOG.tracev( "Resolving XML entity {0} : {1}", publicId, systemId ); if ( systemId != null ) { - if ( systemId.endsWith( "orm_2_1.xsd" ) ) { + if ( systemId.endsWith( "orm_3_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "orm_3_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); + if ( source != null ) { + return source; + } + } + else if ( systemId.endsWith( "orm_2_1.xsd" ) ) { InputStream dtdStream = getStreamFromClasspath( "orm_2_1.xsd" ); final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); if ( source != null ) { return source; } } + else if ( systemId.endsWith( "orm_2_2.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "orm_2_2.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); + if ( source != null ) { + return source; + } + } else if ( systemId.endsWith( "orm_2_0.xsd" ) ) { InputStream dtdStream = getStreamFromClasspath( "orm_2_0.xsd" ); final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); @@ -62,6 +76,20 @@ else if ( systemId.endsWith( "orm_1_0.xsd" ) ) { return source; } } + else if ( systemId.endsWith( "persistence_3_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "persistence_3_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); + if ( source != null ) { + return source; + } + } + else if ( systemId.endsWith( "persistence_2_2.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "persistence_2_2.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); + if ( source != null ) { + return source; + } + } else if ( systemId.endsWith( "persistence_2_1.xsd" ) ) { InputStream dtdStream = getStreamFromClasspath( "persistence_2_1.xsd" ); final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java index 62b7a08bb5b8..a893119a2da2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java @@ -632,7 +632,7 @@ private static void applyColumnDefault(Ejb3Column column, PropertyData inferredD } } - //must only be called afterQuery all setters are defined and beforeQuery bind + //must only be called after all setters are defined and before bind private void extractDataFromPropertyData(PropertyData inferredData) { if ( inferredData != null ) { XProperty property = inferredData.getProperty(); @@ -649,10 +649,17 @@ private void extractDataFromPropertyData(PropertyData inferredData) { } private void processExpression(ColumnTransformer annotation) { - String nonNullLogicalColumnName = logicalColumnName != null ? logicalColumnName : ""; //use the default for annotations - if ( annotation != null && - ( StringHelper.isEmpty( annotation.forColumn() ) - || annotation.forColumn().equals( nonNullLogicalColumnName ) ) ) { + if ( annotation == null ) { + return; + } + + final String nonNullLogicalColumnName = logicalColumnName != null + ? logicalColumnName + //use the default for annotations + : ""; + + if ( StringHelper.isEmpty( annotation.forColumn() ) + || annotation.forColumn().equals( nonNullLogicalColumnName ) ) { readExpression = annotation.read(); if ( StringHelper.isEmpty( readExpression ) ) { readExpression = null; 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 0c546a06d2e5..a6c617a80151 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java @@ -498,7 +498,7 @@ private String buildDefaultColumnName(final PersistentClass referencedEntity, fi boolean mappedBySide = mappedByTableName != null || mappedByPropertyName != null; boolean ownerSide = getPropertyName() != null; - Boolean isRefColumnQuoted = StringHelper.isQuoted( logicalReferencedColumn ); + boolean isRefColumnQuoted = StringHelper.isQuoted( logicalReferencedColumn ); final String unquotedLogicalReferenceColumn = isRefColumnQuoted ? StringHelper.unquote( logicalReferencedColumn ) : logicalReferencedColumn; @@ -704,6 +704,12 @@ public MetadataBuildingContext getBuildingContext() { } ); + // HHH-11826 magic. See Ejb3Column and the HHH-6005 comments + if ( columnIdentifier.getText().contains( "_collection&&element_" ) ) { + columnIdentifier = Identifier.toIdentifier( columnIdentifier.getText().replace( "_collection&&element_", "_" ), + columnIdentifier.isQuoted() ); + } + //one element was quoted so we quote if ( isRefColumnQuoted || StringHelper.isQuoted( logicalTableName ) ) { columnIdentifier = Identifier.quote( columnIdentifier ); @@ -955,7 +961,7 @@ public static Ejb3JoinColumn[] buildJoinTableJoinColumns( currentJoinColumn.setMappedBy( mappedBy ); currentJoinColumn.setJoinAnnotation( annJoin, propertyName ); currentJoinColumn.setNullable( false ); //I break the spec, but it's for good - //done afterQuery the annotation to override it + //done after the annotation to override it currentJoinColumn.bind(); joinColumns[index] = currentJoinColumn; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java b/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java index 6c37f6ee4bc4..900a9502b592 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.io.InputStream; -import java.sql.Timestamp; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -18,6 +17,7 @@ import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.log.UnsupportedLogger; import org.hibernate.internal.util.ConfigHelper; import org.hibernate.internal.util.config.ConfigurationHelper; @@ -112,7 +112,7 @@ *
    * * + * natively generated keys after insert. Requires JDBC3+ driver and JRE1.4+ * * * @@ -156,7 +156,7 @@ public final class Environment implements AvailableSettings { private static final BytecodeProvider BYTECODE_PROVIDER_INSTANCE; private static final boolean ENABLE_BINARY_STREAMS; private static final boolean ENABLE_REFLECTION_OPTIMIZER; - private static final boolean JVM_HAS_TIMESTAMP_BUG; + private static final boolean ENABLE_LEGACY_PROXY_CLASSNAMES; private static final Properties GLOBAL_PROPERTIES; @@ -237,30 +237,24 @@ public static void verifyProperties(Map configurationValues) { LOG.usingReflectionOptimizer(); } - BYTECODE_PROVIDER_INSTANCE = buildBytecodeProvider( GLOBAL_PROPERTIES ); - - long x = 123456789; - JVM_HAS_TIMESTAMP_BUG = new Timestamp(x).getTime() != x; - if ( JVM_HAS_TIMESTAMP_BUG ) { - LOG.usingTimestampWorkaround(); + ENABLE_LEGACY_PROXY_CLASSNAMES = ConfigurationHelper.getBoolean( ENFORCE_LEGACY_PROXY_CLASSNAMES, GLOBAL_PROPERTIES ); + if ( ENABLE_LEGACY_PROXY_CLASSNAMES ) { + final UnsupportedLogger unsupportedLogger = Logger.getMessageLogger( UnsupportedLogger.class, Environment.class.getName() ); + unsupportedLogger.usingLegacyClassnamesForProxies(); } - } - public static BytecodeProvider getBytecodeProvider() { - return BYTECODE_PROVIDER_INSTANCE; + BYTECODE_PROVIDER_INSTANCE = buildBytecodeProvider( GLOBAL_PROPERTIES ); } /** - * Does this JVM's implementation of {@link java.sql.Timestamp} have a bug in which the following is true: - * new java.sql.Timestamp( x ).getTime() != x - * - *

    - * NOTE : IBM JDK 1.3.1 the only known JVM to exhibit this behavior. - * - * @return True if the JVM's {@link Timestamp} implementa + * This will be removed soon; currently just returns false as no known JVM exibits this bug + * and is also able to run this version of Hibernate ORM. + * @deprecated removed as unneccessary + * @return false */ + @Deprecated public static boolean jvmHasTimestampBug() { - return JVM_HAS_TIMESTAMP_BUG; + return false; } /** @@ -269,7 +263,14 @@ public static boolean jvmHasTimestampBug() { * @return True if streams should be used for binary data handling; false otherwise. * * @see #USE_STREAMS_FOR_BINARY + * + * @deprecated Deprecated to indicate that the method will be moved to + * {@link org.hibernate.boot.spi.SessionFactoryOptions} / + * {@link org.hibernate.boot.SessionFactoryBuilder} - probably in 6.0. + * See HHH-12194 and + * HHH-12193 for details */ + @Deprecated public static boolean useStreamsForBinary() { return ENABLE_BINARY_STREAMS; } @@ -282,11 +283,39 @@ public static boolean useStreamsForBinary() { * @see #USE_REFLECTION_OPTIMIZER * @see #getBytecodeProvider() * @see BytecodeProvider#getReflectionOptimizer + * + * @deprecated Deprecated to indicate that the method will be moved to + * {@link org.hibernate.boot.spi.SessionFactoryOptions} / + * {@link org.hibernate.boot.SessionFactoryBuilder} - probably in 6.0. + * See HHH-12194 and + * HHH-12193 for details */ + @Deprecated public static boolean useReflectionOptimizer() { return ENABLE_REFLECTION_OPTIMIZER; } + /** + * @deprecated Deprecated to indicate that the method will be moved to + * {@link org.hibernate.boot.spi.SessionFactoryOptions} / + * {@link org.hibernate.boot.SessionFactoryBuilder} - probably in 6.0. + * See HHH-12194 and + * HHH-12193 for details + */ + @Deprecated + public static BytecodeProvider getBytecodeProvider() { + return BYTECODE_PROVIDER_INSTANCE; + } + + /** + * @return True if global option org.hibernate.cfg.AvailableSettings#ENFORCE_LEGACY_PROXY_CLASSNAMES was enabled + * @deprecated This option will be removed soon and should not be relied on. + */ + @Deprecated + public static boolean useLegacyProxyClassnames() { + return ENABLE_LEGACY_PROXY_CLASSNAMES; + } + /** * Disallow instantiation */ @@ -316,7 +345,7 @@ public static String isolationLevelToString(int isolation) { public static final String BYTECODE_PROVIDER_NAME_JAVASSIST = "javassist"; public static final String BYTECODE_PROVIDER_NAME_BYTEBUDDY = "bytebuddy"; - public static final String BYTECODE_PROVIDER_NAME_DEFAULT = BYTECODE_PROVIDER_NAME_JAVASSIST; + public static final String BYTECODE_PROVIDER_NAME_DEFAULT = BYTECODE_PROVIDER_NAME_BYTEBUDDY; public static BytecodeProvider buildBytecodeProvider(Properties properties) { String provider = ConfigurationHelper.getString( BYTECODE_PROVIDER, properties, BYTECODE_PROVIDER_NAME_DEFAULT ); @@ -338,6 +367,6 @@ private static BytecodeProvider buildBytecodeProvider(String providerName) { // currently we assume it is only ever the Strings "javassist" or "bytebuddy"... LOG.unknownBytecodeProvider( providerName, BYTECODE_PROVIDER_NAME_DEFAULT ); - return new org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl(); + return new org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/IdGeneratorResolverSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/IdGeneratorResolverSecondPass.java new file mode 100644 index 000000000000..0799cc927252 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/IdGeneratorResolverSecondPass.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.cfg; + +import java.util.Map; + +import org.hibernate.MappingException; +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.model.IdentifierGeneratorDefinition; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.mapping.SimpleValue; + +/** + * @author Andrea Boriero + */ +public class IdGeneratorResolverSecondPass implements SecondPass { + private SimpleValue id; + private XProperty idXProperty; + private String generatorType; + private String generatorName; + private MetadataBuildingContext buildingContext; + private IdentifierGeneratorDefinition localIdentifierGeneratorDefinition; + + public IdGeneratorResolverSecondPass( + SimpleValue id, + XProperty idXProperty, + String generatorType, + String generatorName, + MetadataBuildingContext buildingContext) { + this.id = id; + this.idXProperty = idXProperty; + this.generatorType = generatorType; + this.generatorName = generatorName; + this.buildingContext = buildingContext; + } + + public IdGeneratorResolverSecondPass( + SimpleValue id, + XProperty idXProperty, + String generatorType, + String generatorName, + MetadataBuildingContext buildingContext, + IdentifierGeneratorDefinition localIdentifierGeneratorDefinition) { + this(id,idXProperty,generatorType,generatorName,buildingContext); + this.localIdentifierGeneratorDefinition = localIdentifierGeneratorDefinition; + } + + @Override + public void doSecondPass(Map idGeneratorDefinitionMap) throws MappingException { + BinderHelper.makeIdGenerator( id, idXProperty, generatorType, generatorName, buildingContext, localIdentifierGeneratorDefinition ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java index 4056d9182f7d..6bdc9edef3bf 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java @@ -62,6 +62,7 @@ public IndexOrUniqueKeySecondPass(String indexName, Ejb3Column column, MetadataB this.buildingContext = buildingContext; this.unique = unique; } + @Override public void doSecondPass(Map persistentClasses) throws MappingException { if ( columns != null ) { @@ -72,11 +73,17 @@ public void doSecondPass(Map persistentClasses) throws MappingException { if ( column != null ) { this.table = column.getTable(); - PersistentClass persistentClass = (PersistentClass) persistentClasses.get( column.getPropertyHolder().getEntityName() ); - Property property = persistentClass.getProperty( column.getPropertyName() ); + final PropertyHolder propertyHolder = column.getPropertyHolder(); + + String entityName = ( propertyHolder.isComponent() ) ? + propertyHolder.getPersistentClass().getEntityName() : + propertyHolder.getEntityName(); + + final PersistentClass persistentClass = (PersistentClass) persistentClasses.get( entityName ); + final Property property = persistentClass.getProperty( column.getPropertyName() ); if ( property.getValue() instanceof Component ) { - Component component = (Component) property.getValue(); + final Component component = (Component) property.getValue(); List columns = new ArrayList<>(); component.getColumnIterator().forEachRemaining( selectable -> { @@ -89,7 +96,7 @@ public void doSecondPass(Map persistentClasses) throws MappingException { else { addConstraintToColumn( buildingContext.getMetadataCollector() - .getLogicalColumnName( table, column.getMappingColumn().getQuotedName() ) + .getLogicalColumnName( table, column.getMappingColumn().getQuotedName() ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/InheritanceState.java b/hibernate-core/src/main/java/org/hibernate/cfg/InheritanceState.java index 3eb476e6fcc4..3a49dd778e78 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/InheritanceState.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/InheritanceState.java @@ -279,7 +279,7 @@ private void getMappedSuperclassesTillNextEntityOrdered() { superclassState = inheritanceStatePerClass.get( superClass ); } while ( superClass != null - && !buildingContext.getBuildingOptions().getReflectionManager().equals( superClass, Object.class ) + && !buildingContext.getBootstrapContext().getReflectionManager().equals( superClass, Object.class ) && superclassState == null ); currentClassInHierarchy = superClass; @@ -300,7 +300,7 @@ private void addMappedSuperClassInMetadata(PersistentClass persistentClass) { final int lastMappedSuperclass = classesToProcessForMappedSuperclass.size() - 1; for ( int index = 0; index < lastMappedSuperclass; index++ ) { org.hibernate.mapping.MappedSuperclass parentSuperclass = mappedSuperclass; - final Class type = buildingContext.getBuildingOptions().getReflectionManager() + final Class type = buildingContext.getBootstrapContext().getReflectionManager() .toClass( classesToProcessForMappedSuperclass.get( index ) ); //add MAppedSuperclass if not already there mappedSuperclass = buildingContext.getMetadataCollector().getMappedSuperclass( type ); 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 459b036e460e..7a1f3baa7b95 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -8,12 +8,12 @@ import java.util.Iterator; import java.util.Map; - import javax.persistence.ConstraintMode; import org.hibernate.AnnotationException; import org.hibernate.MappingException; import org.hibernate.annotations.ForeignKey; +import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.PropertyBinder; @@ -78,7 +78,7 @@ public OneToOneSecondPass( //TODO refactor this code, there is a lot of duplication in this method public void doSecondPass(Map persistentClasses) throws MappingException { org.hibernate.mapping.OneToOne value = new org.hibernate.mapping.OneToOne( - buildingContext.getMetadataCollector(), + buildingContext, propertyHolder.getTable(), propertyHolder.getPersistentClass() ); @@ -91,21 +91,32 @@ public void doSecondPass(Map persistentClasses) throws MappingException { value.setCascadeDeleteEnabled( cascadeOnDelete ); //value.setLazy( fetchMode != FetchMode.JOIN ); - if ( !optional ) value.setConstrained( true ); - value.setForeignKeyType( - value.isConstrained() - ? ForeignKeyDirection.FROM_PARENT - : ForeignKeyDirection.TO_PARENT - ); + if ( !optional ) { + value.setConstrained( true ); + } + if ( value.isReferenceToPrimaryKey() ) { + value.setForeignKeyType( ForeignKeyDirection.TO_PARENT ); + } + else { + value.setForeignKeyType( + value.isConstrained() + ? ForeignKeyDirection.FROM_PARENT + : ForeignKeyDirection.TO_PARENT + ); + } PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); binder.setValue( value ); binder.setCascade( cascadeStrategy ); binder.setAccessType( inferredData.getDefaultAccess() ); - Property prop = binder.makeProperty(); - if ( ignoreNotFound ) { - prop.setOptional( true ); + + final LazyGroup lazyGroupAnnotation = inferredData.getProperty().getAnnotation( LazyGroup.class ); + if ( lazyGroupAnnotation != null ) { + binder.setLazyGroup( lazyGroupAnnotation.value() ); } + + Property prop = binder.makeProperty(); + prop.setOptional( optional ); if ( BinderHelper.isEmptyAnnotationValue( mappedBy ) ) { /* * we need to check if the columns are in the right order @@ -175,7 +186,7 @@ else if ( otherSideProperty.getValue() instanceof ManyToOne ) { Join mappedByJoin = buildJoinFromMappedBySide( (PersistentClass) persistentClasses.get( ownerEntity ), otherSideProperty, otherSideJoin ); - ManyToOne manyToOne = new ManyToOne( buildingContext.getMetadataCollector(), mappedByJoin.getTable() ); + ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() ); //FIXME use ignore not found here manyToOne.setIgnoreNotFound( ignoreNotFound ); manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() ); @@ -284,7 +295,7 @@ private Join buildJoinFromMappedBySide(PersistentClass persistentClass, Property //no check constraints available on joins join.setTable( originalJoin.getTable() ); join.setInverse( true ); - SimpleValue key = new DependantValue( buildingContext.getMetadataCollector(), join.getTable(), persistentClass.getIdentifier() ); + SimpleValue key = new DependantValue( buildingContext, join.getTable(), persistentClass.getIdentifier() ); //TODO support @ForeignKey join.setKey( key ); join.setSequentialSelect( false ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java index 2c616a06bf35..e01aba7b7d94 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolder.java @@ -13,7 +13,7 @@ import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.boot.spi.AttributeConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.PersistentClass; @@ -106,5 +106,5 @@ default ForeignKey getOverriddenForeignKey(String propertyName) { * @param property * @return */ - AttributeConverterDescriptor resolveAttributeConverterDescriptor(XProperty property); + ConverterDescriptor resolveAttributeConverterDescriptor(XProperty property); } 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 732e3971bde8..3c693cb43b8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java @@ -16,8 +16,8 @@ import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.spi.QueryCacheFactory; import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.hql.spi.QueryTranslatorFactory; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; @@ -34,6 +34,7 @@ * * @deprecated Use {@link org.hibernate.boot.spi.SessionFactoryOptions} instead. */ +@SuppressWarnings("unused") @Deprecated public final class Settings { private static final Logger LOG = Logger.getLogger( Settings.class ); @@ -89,12 +90,11 @@ public Settings(SessionFactoryOptions sessionFactoryOptions, String defaultCatal LOG.debugf( "JTA Track by Thread: %s", enabledDisabled( sessionFactoryOptions.isJtaTrackByThread() ) ); LOG.debugf( "Query language substitutions: %s", sessionFactoryOptions.getQuerySubstitutions() ); - LOG.debugf( "JPA query language strict compliance: %s", enabledDisabled( sessionFactoryOptions.isStrictJpaQueryLanguageCompliance() ) ); LOG.debugf( "Named query checking : %s", enabledDisabled( sessionFactoryOptions.isNamedQueryStartupCheckingEnabled() ) ); LOG.debugf( "Second-level cache: %s", enabledDisabled( sessionFactoryOptions.isSecondLevelCacheEnabled() ) ); LOG.debugf( "Second-level query cache: %s", enabledDisabled( sessionFactoryOptions.isQueryCacheEnabled() ) ); - LOG.debugf( "Second-level query cache factory: %s", sessionFactoryOptions.getQueryCacheFactory() ); + LOG.debugf( "Second-level query cache factory: %s", sessionFactoryOptions.getTimestampsCacheFactory() ); LOG.debugf( "Second-level cache region prefix: %s", sessionFactoryOptions.getCacheRegionPrefix() ); LOG.debugf( "Optimize second-level cache for minimal puts: %s", enabledDisabled( sessionFactoryOptions.isMinimalPutsEnabled() ) ); LOG.debugf( "Structured second-level cache entries: %s", enabledDisabled( sessionFactoryOptions.isStructuredCacheEntriesEnabled() ) ); @@ -109,6 +109,11 @@ public Settings(SessionFactoryOptions sessionFactoryOptions, String defaultCatal LOG.debugf( "JDBC result set fetch size: %s", sessionFactoryOptions.getJdbcFetchSize() ); LOG.debugf( "Connection release mode: %s", sessionFactoryOptions.getConnectionReleaseMode() ); LOG.debugf( "Generate SQL with comments: %s", enabledDisabled( sessionFactoryOptions.isCommentsEnabled() ) ); + + LOG.debugf( "JPA compliance - query : ", enabledDisabled( sessionFactoryOptions.getJpaCompliance().isJpaQueryComplianceEnabled() ) ); + LOG.debugf( "JPA compliance - closed-handling : ", enabledDisabled( sessionFactoryOptions.getJpaCompliance().isJpaClosedComplianceEnabled() ) ); + LOG.debugf( "JPA compliance - lists : ", enabledDisabled( sessionFactoryOptions.getJpaCompliance().isJpaListComplianceEnabled() ) ); + LOG.debugf( "JPA compliance - transactions : ", enabledDisabled( sessionFactoryOptions.getJpaCompliance().isJpaTransactionComplianceEnabled() ) ); } } @@ -203,14 +208,14 @@ public boolean isJtaTrackByThread() { return sessionFactoryOptions.isJtaTrackByThread(); } - public Map getQuerySubstitutions() { - return sessionFactoryOptions.getQuerySubstitutions(); - } - public boolean isStrictJPAQLCompliance() { return sessionFactoryOptions.isStrictJpaQueryLanguageCompliance(); } + public Map getQuerySubstitutions() { + return sessionFactoryOptions.getQuerySubstitutions(); + } + public boolean isNamedQueryStartupCheckingEnabled() { return sessionFactoryOptions.isNamedQueryStartupCheckingEnabled(); } @@ -223,8 +228,8 @@ public boolean isQueryCacheEnabled() { return sessionFactoryOptions.isQueryCacheEnabled(); } - public QueryCacheFactory getQueryCacheFactory() { - return sessionFactoryOptions.getQueryCacheFactory(); + public TimestampsCacheFactory getTimestampsCacheFactory() { + return sessionFactoryOptions.getTimestampsCacheFactory(); } public String getCacheRegionPrefix() { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java index 3bcb721eda72..dde6e145df3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java @@ -5,6 +5,7 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.cfg; + import javax.persistence.ManyToOne; import javax.persistence.OneToOne; @@ -42,7 +43,7 @@ public static String getReferenceEntityName(PropertyData propertyData, MetadataB public static XClass getTargetEntity(PropertyData propertyData, MetadataBuildingContext buildingContext) { XProperty property = propertyData.getProperty(); - return buildingContext.getBuildingOptions().getReflectionManager().toXClass( getTargetEntityClass( property ) ); + return buildingContext.getBootstrapContext().getReflectionManager().toXClass( getTargetEntityClass( property ) ); } private static Class getTargetEntityClass(XProperty property) { 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 586237810da0..906d1827b0b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java @@ -97,6 +97,7 @@ public void doSecondPass(java.util.Map persistentClasses) throws MappingExceptio + manyToOne.getReferencedEntityName() ); } + manyToOne.setPropertyName( path ); BinderHelper.createSyntheticPropertyReference( columns, ref, null, manyToOne, false, buildingContext ); TableBinder.bindFk( ref, null, columns, manyToOne, unique, buildingContext ); /* diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java index 9855532dfe46..01ad54919b89 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java @@ -5,6 +5,7 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.cfg.annotations; + import org.hibernate.mapping.Array; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; @@ -20,6 +21,6 @@ public ArrayBinder() { } protected Collection createCollection(PersistentClass persistentClass) { - return new Array( getBuildingContext().getMetadataCollector(), persistentClass ); + return new Array( getBuildingContext(), persistentClass ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java index f50334769fb7..64f680630309 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java @@ -19,6 +19,6 @@ public BagBinder() { } protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.Bag( getBuildingContext().getMetadataCollector(), persistentClass ); + return new org.hibernate.mapping.Bag( getBuildingContext(), persistentClass ); } } 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 55b984fb9b4e..554c13cfc068 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 @@ -46,6 +46,7 @@ import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.Loader; import org.hibernate.annotations.ManyToAny; +import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OptimisticLock; import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.Parameter; @@ -69,6 +70,7 @@ import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AnnotatedClassType; import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.BinderHelper; import org.hibernate.cfg.CollectionPropertyHolder; import org.hibernate.cfg.CollectionSecondPass; @@ -83,9 +85,11 @@ import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.cfg.SecondPass; import org.hibernate.criterion.Junction; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.Any; import org.hibernate.mapping.Backref; import org.hibernate.mapping.Collection; @@ -111,7 +115,7 @@ * @author inger * @author Emmanuel Bernard */ -@SuppressWarnings({"unchecked", "serial"}) +@SuppressWarnings({"unchecked", "serial", "WeakerAccess", "deprecation"}) public abstract class CollectionBinder { private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, CollectionBinder.class.getName()); @@ -120,14 +124,14 @@ public abstract class CollectionBinder { protected Collection collection; protected String propertyName; PropertyHolder propertyHolder; - int batchSize; + private int batchSize; private String mappedBy; private XClass collectionType; private XClass targetEntity; private Ejb3JoinColumn[] inverseJoinColumns; private String cascadeStrategy; - String cacheConcurrencyStrategy; - String cacheRegionName; + private String cacheConcurrencyStrategy; + private String cacheRegionName; private boolean oneToMany; protected IndexColumn indexColumn; protected boolean cascadeDeleteEnabled; @@ -160,6 +164,10 @@ public abstract class CollectionBinder { private String explicitType; private final Properties explicitTypeParameters = new Properties(); + protected CollectionBinder(boolean isSortedCollection) { + this.isSortedCollection = isSortedCollection; + } + protected MetadataBuildingContext getBuildingContext() { return buildingContext; } @@ -172,7 +180,7 @@ public boolean isMap() { return false; } - public void setIsHibernateExtensionMapping(boolean hibernateExtensionMapping) { + protected void setIsHibernateExtensionMapping(boolean hibernateExtensionMapping) { this.hibernateExtensionMapping = hibernateExtensionMapping; } @@ -346,10 +354,6 @@ else if ( property.isAnnotationPresent( CollectionId.class ) ) { return result; } - protected CollectionBinder(boolean isSortedCollection) { - this.isSortedCollection = isSortedCollection; - } - public void setMappedBy(String mappedBy) { this.mappedBy = mappedBy; } @@ -480,6 +484,15 @@ public void bind() { throw new AnnotationException( message ); } + if (!isMappedBy + && oneToMany + && property.isAnnotationPresent( OnDelete.class ) + && !property.isAnnotationPresent( JoinColumn.class )) { + String message = "Unidirectional one-to-many associations annotated with @OnDelete must define @JoinColumn: "; + message += StringHelper.qualify( propertyHolder.getPath(), propertyName ); + throw new AnnotationException( message ); + } + collection.setInverse( isMappedBy ); //many to many may need some second pass informations @@ -523,7 +536,7 @@ public void bind() { binder.setName( propertyName ); binder.setValue( collection ); binder.setCascade( cascadeStrategy ); - if ( cascadeStrategy != null && cascadeStrategy.indexOf( "delete-orphan" ) >= 0 ) { + if ( cascadeStrategy != null && cascadeStrategy.contains( "delete-orphan" ) ) { collection.setOrphanDelete( true ); } binder.setLazy( collection.isLazy() ); @@ -542,8 +555,6 @@ public void bind() { } private void applySortingAndOrdering(Collection collection) { - boolean isSorted = isSortedCollection; - boolean hadOrderBy = false; boolean hadExplicitSort = false; @@ -557,10 +568,10 @@ private void applySortingAndOrdering(Collection collection) { } hadExplicitSort = deprecatedSort.type() != SortType.UNSORTED; if ( deprecatedSort.type() == SortType.NATURAL ) { - isSorted = true; + isSortedCollection = true; } else if ( deprecatedSort.type() == SortType.COMPARATOR ) { - isSorted = true; + isSortedCollection = true; comparatorClass = deprecatedSort.comparator(); } } @@ -639,7 +650,7 @@ private void defineFetchingStrategy() { Fetch fetch = property.getAnnotation( Fetch.class ); OneToMany oneToMany = property.getAnnotation( OneToMany.class ); ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); - ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); //jpa 2 + ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); ManyToAny manyToAny = property.getAnnotation( ManyToAny.class ); FetchType fetchType; if ( oneToMany != null ) { @@ -765,14 +776,14 @@ protected boolean bindStarToManySecondPass( ); } catch (MappingException e) { - StringBuilder error = new StringBuilder( 80 ); - error.append( "mappedBy reference an unknown target entity property: " ) - .append( collType ).append( "." ).append( this.mappedBy ) - .append( " in " ) - .append( collection.getOwnerEntityName() ) - .append( "." ) - .append( property.getName() ); - throw new AnnotationException( error.toString() ); + throw new AnnotationException( + "mappedBy reference an unknown target entity property: " + + collType + "." + this.mappedBy + + " in " + + collection.getOwnerEntityName() + + "." + + property.getName() + ); } } if ( persistentClass != null @@ -834,7 +845,7 @@ protected void bindOneToManySecondPass( "CollectionSecondPass for oneToMany should not be called with null mappings" ); } - org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( buildingContext.getMetadataCollector(), collection.getOwner() ); + org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( buildingContext, collection.getOwner() ); collection.setElement( oneToMany ); oneToMany.setReferencedEntityName( collectionType.getName() ); oneToMany.setIgnoreNotFound( ignoreNotFound ); @@ -945,42 +956,58 @@ private void bindFilters(boolean hasAssociationTable) { } } - StringBuilder whereBuffer = new StringBuilder(); - if ( property.getElementClass() != null ) { + final boolean useEntityWhereClauseForCollections = ConfigurationHelper.getBoolean( + AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + buildingContext + .getBuildingOptions() + .getServiceRegistry() + .getService( ConfigurationService.class ) + .getSettings(), + true + ); + + // There are 2 possible sources of "where" clauses that apply to the associated entity table: + // 1) from the associated entity mapping; i.e., @Entity @Where(clause="...") + // (ignored if useEntityWhereClauseForCollections == false) + // 2) from the collection mapping; + // for one-to-many, e.g., @OneToMany @JoinColumn @Where(clause="...") public Set getRatings(); + // for many-to-many e.g., @ManyToMany @Where(clause="...") public Set getRatings(); + String whereOnClassClause = null; + if ( useEntityWhereClauseForCollections && property.getElementClass() != null ) { Where whereOnClass = property.getElementClass().getAnnotation( Where.class ); if ( whereOnClass != null ) { - String clause = whereOnClass.clause(); - if ( StringHelper.isNotEmpty( clause ) ) { - whereBuffer.append( clause ); - } + whereOnClassClause = whereOnClass.clause(); } } Where whereOnCollection = property.getAnnotation( Where.class ); + String whereOnCollectionClause = null; if ( whereOnCollection != null ) { - String clause = whereOnCollection.clause(); - if ( StringHelper.isNotEmpty( clause ) ) { - if ( whereBuffer.length() > 0 ) { - whereBuffer.append( ' ' ); - whereBuffer.append( Junction.Nature.AND.getOperator() ); - whereBuffer.append( ' ' ); - } - whereBuffer.append( clause ); - } + whereOnCollectionClause = whereOnCollection.clause(); } - if ( whereBuffer.length() > 0 ) { - String whereClause = whereBuffer.toString(); - if ( hasAssociationTable ) { - collection.setManyToManyWhere( whereClause ); - } - else { - collection.setWhere( whereClause ); - } + final String whereClause = StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + whereOnClassClause, + whereOnCollectionClause + ); + if ( hasAssociationTable ) { + // A many-to-many association has an association (join) table + // Collection#setManytoManyWhere is used to set the "where" clause that applies to + // to the many-to-many associated entity table (not the join table). + collection.setManyToManyWhere( whereClause ); + } + else { + // A one-to-many association does not have an association (join) table. + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the associated entity table for a one-to-many association). + collection.setWhere( whereClause ); } WhereJoinTable whereJoinTable = property.getAnnotation( WhereJoinTable.class ); String whereJoinTableClause = whereJoinTable == null ? null : whereJoinTable.clause(); if ( StringHelper.isNotEmpty( whereJoinTableClause ) ) { if ( hasAssociationTable ) { + // This is a many-to-many association. + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-many association). collection.setWhere( whereJoinTableClause ); } else { @@ -1001,13 +1028,6 @@ private void bindFilters(boolean hasAssociationTable) { // } } - private String getCondition(FilterJoinTable filter) { - //set filtering - String name = filter.name(); - String cond = filter.condition(); - return getCondition( cond, name ); - } - private String getCondition(Filter filter) { //set filtering String name = filter.name(); @@ -1066,17 +1086,28 @@ else if ( "desc".equals( orderByFragment ) ) { return orderByFragment; } - private static String adjustUserSuppliedValueCollectionOrderingFragment(String orderByFragment) { + public static String adjustUserSuppliedValueCollectionOrderingFragment(String orderByFragment) { if ( orderByFragment != null ) { - // NOTE: "$element$" is a specially recognized collection property recognized by the collection persister - if ( orderByFragment.length() == 0 ) { - //order by element + orderByFragment = orderByFragment.trim(); + if ( orderByFragment.length() == 0 || orderByFragment.equalsIgnoreCase( "asc" ) ) { + // This indicates something like either: + // `@OrderBy()` + // `@OrderBy("asc") + // + // JPA says this should indicate an ascending natural ordering of the elements - id for + // entity associations or the value(s) for "element collections" return "$element$ asc"; } - else if ( "desc".equals( orderByFragment ) ) { + else if ( orderByFragment.equalsIgnoreCase( "desc" ) ) { + // This indicates: + // `@OrderBy("desc")` + // + // JPA says this should indicate a descending natural ordering of the elements - id for + // entity associations or the value(s) for "element collections" return "$element$ desc"; } } + return orderByFragment; } @@ -1113,7 +1144,7 @@ private static SimpleValue buildCollectionKey( .getReferencedProperty( propRef ) .getValue(); } - DependantValue key = new DependantValue( buildingContext.getMetadataCollector(), collValue.getCollectionTable(), keyVal ); + DependantValue key = new DependantValue( buildingContext, collValue.getCollectionTable(), keyVal ); key.setTypeName( null ); Ejb3Column.checkPropertyConsistency( joinColumns, collValue.getOwnerEntityName() ); key.setNullable( joinColumns.length == 0 || joinColumns[0].isNullable() ); @@ -1134,6 +1165,13 @@ private static SimpleValue buildCollectionKey( else { key.setForeignKeyName( StringHelper.nullIfEmpty( collectionTableAnn.foreignKey().name() ) ); key.setForeignKeyDefinition( StringHelper.nullIfEmpty( collectionTableAnn.foreignKey().foreignKeyDefinition() ) ); + if ( key.getForeignKeyName() == null && + key.getForeignKeyDefinition() == null && + collectionTableAnn.joinColumns().length == 1 ) { + JoinColumn joinColumn = collectionTableAnn.joinColumns()[0]; + key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumn.foreignKey().name() ) ); + key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) ); + } } } else { @@ -1191,7 +1229,7 @@ else if ( fkOverride != null ) { return key; } - protected void bindManyToManySecondPass( + private void bindManyToManySecondPass( Collection collValue, Map persistentClasses, Ejb3JoinColumn[] joinColumns, @@ -1259,14 +1297,12 @@ else if ( anyAnn != null ) { boolean mappedBy = !BinderHelper.isEmptyAnnotationValue( joinColumns[0].getMappedBy() ); if ( mappedBy ) { if ( !isCollectionOfEntities ) { - StringBuilder error = new StringBuilder( 80 ) - .append( - "Collection of elements must not have mappedBy or association reference an unmapped entity: " - ) - .append( collValue.getOwnerEntityName() ) - .append( "." ) - .append( joinColumns[0].getPropertyName() ); - throw new AnnotationException( error.toString() ); + throw new AnnotationException( + "Collection of elements must not have mappedBy or association reference an unmapped entity: " + + collValue.getOwnerEntityName() + + "." + + joinColumns[0].getPropertyName() + ); } Property otherSideProperty; try { @@ -1336,7 +1372,7 @@ else if ( anyAnn != null ) { ManyToOne element = null; if ( isCollectionOfEntities ) { - element = new ManyToOne( buildingContext.getMetadataCollector(), collValue.getCollectionTable() ); + element = new ManyToOne( buildingContext, collValue.getCollectionTable() ); collValue.setElement( element ); element.setReferencedEntityName( collType.getName() ); //element.setFetchMode( fetchMode ); @@ -1385,7 +1421,12 @@ else if ( anyAnn != null ) { else if ( anyAnn != null ) { //@ManyToAny //Make sure that collTyp is never used during the @ManyToAny branch: it will be set to void.class - PropertyData inferredData = new PropertyInferredData(null, property, "unsupported", buildingContext.getBuildingOptions().getReflectionManager() ); + PropertyData inferredData = new PropertyInferredData( + null, + property, + "unsupported", + buildingContext.getBootstrapContext().getReflectionManager() + ); //override the table for (Ejb3Column column : inverseJoinColumns) { column.setTable( collValue.getCollectionTable() ); @@ -1408,7 +1449,7 @@ else if ( anyAnn != null ) { XClass elementClass; AnnotatedClassType classType; - CollectionPropertyHolder holder = null; + CollectionPropertyHolder holder; if ( BinderHelper.PRIMITIVE_NAMES.contains( collType.getName() ) ) { classType = AnnotatedClassType.NONE; elementClass = null; @@ -1505,7 +1546,6 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get collValue.setElement( component ); if ( StringHelper.isNotEmpty( hqlOrderBy ) ) { - String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName(); String orderBy = adjustUserSuppliedValueCollectionOrderingFragment( hqlOrderBy ); if ( orderBy != null ) { collValue.setOrderBy( orderBy ); @@ -1527,7 +1567,7 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get column.setLength( Ejb3Column.DEFAULT_COLUMN_LENGTH ); column.setLogicalColumnName( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); //TODO create an EMPTY_JOINS collection - column.setJoins( new HashMap() ); + column.setJoins( new HashMap<>() ); column.setBuildingContext( buildingContext ); column.bind(); elementColumns[0] = column; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java index 1a7d84c465ee..ad96518092ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java @@ -6,15 +6,16 @@ */ package org.hibernate.cfg.annotations; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import javax.persistence.Access; +import javax.persistence.Cacheable; import javax.persistence.ConstraintMode; import javax.persistence.Entity; -import javax.persistence.InheritanceType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.NamedEntityGraph; @@ -22,6 +23,7 @@ import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.SecondaryTable; import javax.persistence.SecondaryTables; +import javax.persistence.SharedCacheMode; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; @@ -62,6 +64,7 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ImplicitEntityNameSource; import org.hibernate.boot.model.naming.NamingStrategyHelper; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -124,19 +127,21 @@ public class EntityBinder { private String where; // todo : we should defer to InFlightMetadataCollector.EntityTableXref for secondary table tracking; // atm we use both from here; HBM binding solely uses InFlightMetadataCollector.EntityTableXref - private java.util.Map secondaryTables = new HashMap(); - private java.util.Map secondaryTableJoins = new HashMap(); - private String cacheConcurrentStrategy; - private String cacheRegion; - private String naturalIdCacheRegion; - private List filters = new ArrayList(); + private java.util.Map secondaryTables = new HashMap<>(); + private java.util.Map secondaryTableJoins = new HashMap<>(); + private List filters = new ArrayList<>(); private InheritanceState inheritanceState; private boolean ignoreIdAnnotations; - private boolean cacheLazyProperty; private AccessType propertyAccessType = AccessType.DEFAULT; private boolean wrapIdsInEmbeddedComponents; private String subselect; + private boolean isCached; + private String cacheConcurrentStrategy; + private String cacheRegion; + private boolean cacheLazyProperty; + private String naturalIdCacheRegion; + public boolean wrapIdsInEmbeddedComponents() { return wrapIdsInEmbeddedComponents; } @@ -162,7 +167,7 @@ public EntityBinder( /** * For the most part, this is a simple delegation to {@link PersistentClass#isPropertyDefinedInHierarchy}, - * afterQuery verifying that PersistentClass is indeed set here. + * after verifying that PersistentClass is indeed set here. * * @param name The name of the property to check * @@ -280,18 +285,26 @@ public void bindEntity() { } rootClass.setMutable( mutable ); rootClass.setExplicitPolymorphism( isExplicitPolymorphism( polymorphismType ) ); - if ( StringHelper.isNotEmpty( where ) ) rootClass.setWhere( where ); + + if ( StringHelper.isNotEmpty( where ) ) { + rootClass.setWhere( where ); + } + if ( cacheConcurrentStrategy != null ) { rootClass.setCacheConcurrencyStrategy( cacheConcurrentStrategy ); rootClass.setCacheRegionName( cacheRegion ); rootClass.setLazyPropertiesCacheable( cacheLazyProperty ); } + rootClass.setNaturalIdCacheRegionName( naturalIdCacheRegion ); + boolean forceDiscriminatorInSelects = forceDiscriminator == null ? context.getBuildingOptions().shouldImplicitlyForceDiscriminatorInSelect() : forceDiscriminator; + rootClass.setForceDiscriminator( forceDiscriminatorInSelects ); - if( insertableDiscriminator != null) { + + if ( insertableDiscriminator != null ) { rootClass.setDiscriminatorInsertable( insertableDiscriminator ); } } @@ -303,6 +316,9 @@ public void bindEntity() { LOG.immutableAnnotationOnNonRoot(annotatedClass.getName()); } } + + persistentClass.setCached( isCached ); + persistentClass.setOptimisticLockStyle( getVersioning( optimisticLockType ) ); persistentClass.setSelectBeforeUpdate( selectBeforeUpdate ); @@ -316,7 +332,7 @@ public void bindEntity() { org.hibernate.annotations.Entity entityAnn = annotatedClass.getAnnotation( org.hibernate.annotations.Entity.class ); if ( entityAnn != null && !BinderHelper.isEmptyAnnotationValue( entityAnn.persister() ) ) { try { - persister = context.getClassLoaderAccess().classForName( entityAnn.persister() ); + persister = context.getBootstrapContext().getClassLoaderAccess().classForName( entityAnn.persister() ); } catch (ClassLoadingException e) { throw new AnnotationException( "Could not find persister class: " + entityAnn.persister(), e ); @@ -512,7 +528,7 @@ public void setProxy(Proxy proxy) { proxyClass = null; } else { - final ReflectionManager reflectionManager = context.getBuildingOptions().getReflectionManager(); + final ReflectionManager reflectionManager = context.getBootstrapContext().getReflectionManager(); if ( AnnotationBinder.isDefault( reflectionManager.toXClass( proxy.proxyClass() ), context ) ) { proxyClass = annotatedClass; } @@ -537,6 +553,173 @@ public void setWrapIdsInEmbeddedComponents(boolean wrapIdsInEmbeddedComponents) this.wrapIdsInEmbeddedComponents = wrapIdsInEmbeddedComponents; } + public void applyCaching( + XClass clazzToProcess, + SharedCacheMode sharedCacheMode, + MetadataBuildingContext context) { + final Cache explicitCacheAnn = clazzToProcess.getAnnotation( Cache.class ); + final Cacheable explicitCacheableAnn = clazzToProcess.getAnnotation( Cacheable.class ); + + isCached = false; + cacheConcurrentStrategy = null; + cacheRegion = null; + cacheLazyProperty = true; + + if ( persistentClass instanceof RootClass ) { + Cache effectiveCacheAnn = explicitCacheAnn; + + if ( explicitCacheAnn != null ) { + // preserve legacy behavior of circumventing SharedCacheMode when Hibernate's @Cache is used. + isCached = true; + } + else { + effectiveCacheAnn = buildCacheMock( clazzToProcess.getName(), context ); + + switch ( sharedCacheMode ) { + case ALL: { + // all entities should be cached + isCached = true; + break; + } + case ENABLE_SELECTIVE: { + if ( explicitCacheableAnn != null && explicitCacheableAnn.value() ) { + isCached = true; + } + break; + } + case DISABLE_SELECTIVE: { + if ( explicitCacheableAnn == null || explicitCacheableAnn.value() ) { + isCached = true; + } + break; + } + default: { + // treat both NONE and UNSPECIFIED the same + isCached = false; + break; + } + } + } + + cacheConcurrentStrategy = resolveCacheConcurrencyStrategy( effectiveCacheAnn.usage() ); + cacheRegion = effectiveCacheAnn.region(); + switch ( effectiveCacheAnn.include().toLowerCase( Locale.ROOT ) ) { + case "all": { + cacheLazyProperty = true; + break; + } + case "non-lazy": { + cacheLazyProperty = false; + break; + } + default: { + throw new AnnotationException( + "Unknown @Cache.include value [" + effectiveCacheAnn.include() + "] : " + + annotatedClass.getName() + ); + } + } + } + else { + if ( explicitCacheAnn != null ) { + LOG.cacheOrCacheableAnnotationOnNonRoot( + persistentClass.getClassName() == null + ? annotatedClass.getName() + : persistentClass.getClassName() + ); + } + else if ( explicitCacheableAnn == null && persistentClass.getSuperclass() != null ) { + // we should inherit our super's caching config + isCached = persistentClass.getSuperclass().isCached(); + } + else { + switch ( sharedCacheMode ) { + case ALL: { + // all entities should be cached + isCached = true; + break; + } + case ENABLE_SELECTIVE: { + // only entities with @Cacheable(true) should be cached + if ( explicitCacheableAnn != null && explicitCacheableAnn.value() ) { + isCached = true; + } + break; + } + case DISABLE_SELECTIVE: { + if ( explicitCacheableAnn == null || !explicitCacheableAnn.value() ) { + isCached = true; + } + break; + } + default: { + // treat both NONE and UNSPECIFIED the same + isCached = false; + break; + } + } + } + } + + naturalIdCacheRegion = null; + + final NaturalIdCache naturalIdCacheAnn = clazzToProcess.getAnnotation( NaturalIdCache.class ); + if ( naturalIdCacheAnn != null ) { + if ( BinderHelper.isEmptyAnnotationValue( naturalIdCacheAnn.region() ) ) { + if ( explicitCacheAnn != null && StringHelper.isNotEmpty( explicitCacheAnn.region() ) ) { + naturalIdCacheRegion = explicitCacheAnn.region() + NATURAL_ID_CACHE_SUFFIX; + } + else { + naturalIdCacheRegion = clazzToProcess.getName() + NATURAL_ID_CACHE_SUFFIX; + } + } + else { + naturalIdCacheRegion = naturalIdCacheAnn.region(); + } + } + } + + private static String resolveCacheConcurrencyStrategy(CacheConcurrencyStrategy strategy) { + final org.hibernate.cache.spi.access.AccessType accessType = strategy.toAccessType(); + return accessType == null ? null : accessType.getExternalName(); + } + + private static Cache buildCacheMock(String region, MetadataBuildingContext context) { + return new LocalCacheAnnotationStub( region, determineCacheConcurrencyStrategy( context ) ); + } + + @SuppressWarnings({ "ClassExplicitlyAnnotation" }) + private static class LocalCacheAnnotationStub implements Cache { + private final String region; + private final CacheConcurrencyStrategy usage; + + private LocalCacheAnnotationStub(String region, CacheConcurrencyStrategy usage) { + this.region = region; + this.usage = usage; + } + + public CacheConcurrencyStrategy usage() { + return usage; + } + + public String region() { + return region; + } + + public String include() { + return "all"; + } + + public Class annotationType() { + return Cache.class; + } + } + + private static CacheConcurrencyStrategy determineCacheConcurrencyStrategy(MetadataBuildingContext context) { + return CacheConcurrencyStrategy.fromAccessType( + context.getBuildingOptions().getImplicitCacheAccessType() + ); + } private static class EntityTableObjectNameSource implements ObjectNameSource { private final String explicitName; @@ -697,8 +880,8 @@ public void bindTable( public void finalSecondaryTableBinding(PropertyHolder propertyHolder) { /* - * Those operations has to be done afterQuery the id definition of the persistence class. - * ie afterQuery the properties parsing + * Those operations has to be done after the id definition of the persistence class. + * ie after the properties parsing */ Iterator joins = secondaryTables.values().iterator(); Iterator joinColumns = secondaryTableJoins.values().iterator(); @@ -782,7 +965,7 @@ private void createPrimaryColumnsToSecondaryTable(Object uncastedColumn, Propert } private void bindJoinToPersistentClass(Join join, Ejb3JoinColumn[] ejb3JoinColumns, MetadataBuildingContext buildingContext) { - SimpleValue key = new DependantValue( buildingContext.getMetadataCollector(), join.getTable(), persistentClass.getIdentifier() ); + SimpleValue key = new DependantValue( buildingContext, join.getTable(), persistentClass.getIdentifier() ); join.setKey( key ); setFKNameIfDefined( join ); key.setCascadeDeleteEnabled( false ); @@ -823,12 +1006,11 @@ private SecondaryTable findMatchingSecondaryTable(Join join) { SecondaryTables secondaryTables = annotatedClass.getAnnotation( SecondaryTables.class ); if ( secondaryTables != null ) { - for ( SecondaryTable secondaryTable2 : secondaryTables.value() ) { - if ( secondaryTable != null && nameToMatch.equals( secondaryTable.name() ) ) { - return secondaryTable; + for ( SecondaryTable secondaryTablesEntry : secondaryTables.value() ) { + if ( secondaryTablesEntry != null && nameToMatch.equals( secondaryTablesEntry.name() ) ) { + return secondaryTablesEntry; } } - } return null; @@ -874,23 +1056,6 @@ public Join addJoin(JoinTable joinTable, PropertyHolder holder, boolean noDelayI return addJoin( null, joinTable, holder, noDelayInPkColumnCreation ); } - private static class SecondaryTableNameSource implements ObjectNameSource { - // always has an explicit name - private final String explicitName; - - private SecondaryTableNameSource(String explicitName) { - this.explicitName = explicitName; - } - - public String getExplicitName() { - return explicitName; - } - - public String getLogicalName() { - return explicitName; - } - } - private static class SecondaryTableNamingStrategyHelper implements NamingStrategyHelper { @Override public Identifier determineImplicitName(MetadataBuildingContext buildingContext) { @@ -929,30 +1094,37 @@ private Join addJoin( final String schema; final String catalog; - final SecondaryTableNameSource secondaryTableNameContext; final Object joinColumns; final List uniqueConstraintHolders; - final Identifier logicalName; + final QualifiedTableName logicalName; if ( secondaryTable != null ) { schema = secondaryTable.schema(); catalog = secondaryTable.catalog(); - logicalName = context.getMetadataCollector() + logicalName = new QualifiedTableName( + Identifier.toIdentifier( catalog ), + Identifier.toIdentifier( schema ), + context.getMetadataCollector() .getDatabase() .getJdbcEnvironment() .getIdentifierHelper() - .toIdentifier( secondaryTable.name() ); + .toIdentifier( secondaryTable.name() ) + ); joinColumns = secondaryTable.pkJoinColumns(); uniqueConstraintHolders = TableBinder.buildUniqueConstraintHolders( secondaryTable.uniqueConstraints() ); } else if ( joinTable != null ) { schema = joinTable.schema(); catalog = joinTable.catalog(); - logicalName = context.getMetadataCollector() - .getDatabase() - .getJdbcEnvironment() - .getIdentifierHelper() - .toIdentifier( joinTable.name() ); + logicalName = new QualifiedTableName( + Identifier.toIdentifier( catalog ), + Identifier.toIdentifier( schema ), + context.getMetadataCollector() + .getDatabase() + .getJdbcEnvironment() + .getIdentifierHelper() + .toIdentifier( joinTable.name() ) + ); joinColumns = joinTable.joinColumns(); uniqueConstraintHolders = TableBinder.buildUniqueConstraintHolders( joinTable.uniqueConstraints() ); } @@ -963,7 +1135,7 @@ else if ( joinTable != null ) { final Table table = TableBinder.buildAndFillTable( schema, catalog, - logicalName, + logicalName.getTableName(), false, uniqueConstraintHolders, null, @@ -1039,48 +1211,6 @@ public java.util.Map getSecondaryTables() { return secondaryTables; } - public void setCache(Cache cacheAnn) { - if ( cacheAnn != null ) { - cacheRegion = BinderHelper.isEmptyAnnotationValue( cacheAnn.region() ) ? - null : - cacheAnn.region(); - cacheConcurrentStrategy = getCacheConcurrencyStrategy( cacheAnn.usage() ); - if ( "all".equalsIgnoreCase( cacheAnn.include() ) ) { - cacheLazyProperty = true; - } - else if ( "non-lazy".equalsIgnoreCase( cacheAnn.include() ) ) { - cacheLazyProperty = false; - } - else { - throw new AnnotationException( "Unknown lazy property annotations: " + cacheAnn.include() ); - } - } - else { - cacheConcurrentStrategy = null; - cacheRegion = null; - cacheLazyProperty = true; - } - } - - public void setNaturalIdCache(XClass clazzToProcess, NaturalIdCache naturalIdCacheAnn) { - if ( naturalIdCacheAnn != null ) { - if ( BinderHelper.isEmptyAnnotationValue( naturalIdCacheAnn.region() ) ) { - if (cacheRegion != null) { - naturalIdCacheRegion = cacheRegion + NATURAL_ID_CACHE_SUFFIX; - } - else { - naturalIdCacheRegion = clazzToProcess.getName() + NATURAL_ID_CACHE_SUFFIX; - } - } - else { - naturalIdCacheRegion = naturalIdCacheAnn.region(); - } - } - else { - naturalIdCacheRegion = null; - } - } - public static String getCacheConcurrencyStrategy(CacheConcurrencyStrategy strategy) { org.hibernate.cache.spi.access.AccessType accessType = strategy.toAccessType(); return accessType == null ? null : accessType.getExternalName(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/HCANNHelper.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/HCANNHelper.java index c2c4ca737cd0..4e872f4c3bd2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/HCANNHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/HCANNHelper.java @@ -28,6 +28,7 @@ public class HCANNHelper { final Class javaXMemberClass = JavaXMember.class; try { getMemberMethod = javaXMemberClass.getDeclaredMethod( "getMember" ); + // NOTE : no need to check accessibility here - we know it is protected getMemberMethod.setAccessible( true ); } catch (NoSuchMethodException e) { 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 9167e701d921..09d26222e9c4 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 @@ -18,8 +18,10 @@ import org.hibernate.cfg.BinderHelper; import org.hibernate.cfg.Ejb3Column; import org.hibernate.cfg.Ejb3JoinColumn; +import org.hibernate.cfg.IdGeneratorResolverSecondPass; import org.hibernate.cfg.PropertyData; import org.hibernate.cfg.PropertyInferredData; +import org.hibernate.cfg.SecondPass; import org.hibernate.cfg.WrappedInferredData; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Collection; @@ -33,7 +35,7 @@ */ public class IdBagBinder extends BagBinder { protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.IdentifierBag( getBuildingContext().getMetadataCollector(), persistentClass ); + return new org.hibernate.mapping.IdentifierBag( getBuildingContext(), persistentClass ); } @Override @@ -63,7 +65,7 @@ property, unique, associationTableBinder, ignoreNotFound, getBuildingContext() null, property, null, //default access should not be useful - buildingContext.getBuildingOptions().getReflectionManager() + buildingContext.getBootstrapContext().getReflectionManager() ), "id" ); @@ -104,7 +106,27 @@ property, unique, associationTableBinder, ignoreNotFound, getBuildingContext() else { generatorType = null; } - BinderHelper.makeIdGenerator( id, generatorType, generator, getBuildingContext(), localGenerators ); + + if ( buildingContext.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled() ) { + SecondPass secondPass = new IdGeneratorResolverSecondPass( + id, + property, + generatorType, + generator, + getBuildingContext() + ); + buildingContext.getMetadataCollector().addSecondPass( secondPass ); + } + else { + BinderHelper.makeIdGenerator( + id, + property, + generatorType, + generator, + getBuildingContext(), + localGenerators + ); + } } return result; } 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 0834cc22ac46..69aa6c597699 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 @@ -48,7 +48,7 @@ public ListBinder() { @Override protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.List( getBuildingContext().getMetadataCollector(), persistentClass ); + return new org.hibernate.mapping.List( getBuildingContext(), persistentClass ); } @Override 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 876e864c6a14..336616e7c8bf 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 @@ -15,6 +15,7 @@ import javax.persistence.ConstraintMode; import javax.persistence.InheritanceType; import javax.persistence.MapKeyClass; +import javax.persistence.MapKeyColumn; import javax.persistence.MapKeyJoinColumn; import javax.persistence.MapKeyJoinColumns; @@ -72,7 +73,7 @@ public boolean isMap() { } protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.Map( getBuildingContext().getMetadataCollector(), persistentClass ); + return new org.hibernate.mapping.Map( getBuildingContext(), persistentClass ); } @Override @@ -102,10 +103,55 @@ public void secondPass(Map persistentClasses, Map inheritedMetas) mapKeyColumns, mapKeyManyToManyColumns, inverseColumns != null ? inverseColumns[0].getPropertyName() : null ); + makeOneToManyMapKeyColumnNullableIfNotInProperty( property ); } }; } + private void makeOneToManyMapKeyColumnNullableIfNotInProperty( + final XProperty property) { + final org.hibernate.mapping.Map map = (org.hibernate.mapping.Map) this.collection; + if ( map.isOneToMany() && + property.isAnnotationPresent( MapKeyColumn.class ) ) { + final Value indexValue = map.getIndex(); + if ( indexValue.getColumnSpan() != 1 ) { + throw new AssertionFailure( "Map key mapped by @MapKeyColumn does not have 1 column" ); + } + final Selectable selectable = indexValue.getColumnIterator().next(); + if ( selectable.isFormula() ) { + throw new AssertionFailure( "Map key mapped by @MapKeyColumn is a Formula" ); + } + Column column = (Column) map.getIndex().getColumnIterator().next(); + if ( !column.isNullable() ) { + final PersistentClass persistentClass = ( ( OneToMany ) map.getElement() ).getAssociatedClass(); + // check if the index column has been mapped by the associated entity to a property; + // @MapKeyColumn only maps a column to the primary table for the one-to-many, so we only + // need to check "un-joined" properties. + if ( !propertyIteratorContainsColumn( persistentClass.getUnjoinedPropertyIterator(), column ) ) { + // The index column is not mapped to an associated entity property so we can + // safely make the index column nullable. + column.setNullable( true ); + } + } + } + } + + private boolean propertyIteratorContainsColumn(Iterator propertyIterator, Column column) { + for ( Iterator it = propertyIterator; it.hasNext(); ) { + final Property property = (Property) it.next(); + for ( Iterator selectableIterator = property.getColumnIterator(); selectableIterator.hasNext(); ) { + final Selectable selectable = selectableIterator.next(); + if ( column.equals( selectable ) ) { + final Column iteratedColumn = (Column) selectable; + if ( column.getValue().getTable().equals( iteratedColumn.getValue().getTable() ) ) { + return true; + } + } + } + } + return false; + } + private void bindKeyFromAssociationTable( XClass collType, Map persistentClasses, @@ -160,7 +206,7 @@ private void bindKeyFromAssociationTable( ManyToOne element = null; org.hibernate.mapping.Map mapValue = (org.hibernate.mapping.Map) this.collection; if ( isIndexOfEntities ) { - element = new ManyToOne( buildingContext.getMetadataCollector(), mapValue.getCollectionTable() ); + element = new ManyToOne( buildingContext, mapValue.getCollectionTable() ); mapValue.setIndex( element ); element.setReferencedEntityName( mapKeyType ); //element.setFetchMode( fetchMode ); @@ -179,7 +225,7 @@ private void bindKeyFromAssociationTable( } else { try { - keyXClass = buildingContext.getBuildingOptions().getReflectionManager().classForName( mapKeyType ); + keyXClass = buildingContext.getBootstrapContext().getReflectionManager().classForName( mapKeyType ); } catch (ClassLoadingException e) { throw new AnnotationException( "Unable to find class: " + mapKeyType, e ); @@ -411,7 +457,7 @@ else if ( element instanceof DependantValue ) { if ( value instanceof Component ) { Component component = (Component) value; Iterator properties = component.getPropertyIterator(); - Component indexComponent = new Component( getBuildingContext().getMetadataCollector(), collection ); + Component indexComponent = new Component( getBuildingContext(), collection ); indexComponent.setComponentClassName( component.getComponentClassName() ); while ( properties.hasNext() ) { Property current = (Property) properties.next(); @@ -442,7 +488,7 @@ else if ( value instanceof SimpleValue ) { SimpleValue targetValue; if ( value instanceof ManyToOne ) { ManyToOne sourceManyToOne = (ManyToOne) sourceValue; - ManyToOne targetManyToOne = new ManyToOne( getBuildingContext().getMetadataCollector(), collection.getCollectionTable() ); + ManyToOne targetManyToOne = new ManyToOne( getBuildingContext(), collection.getCollectionTable() ); targetManyToOne.setFetchMode( FetchMode.DEFAULT ); targetManyToOne.setLazy( true ); //targetValue.setIgnoreNotFound( ); does not make sense for a map key @@ -450,7 +496,7 @@ else if ( value instanceof SimpleValue ) { targetValue = targetManyToOne; } else { - targetValue = new SimpleValue( getBuildingContext().getMetadataCollector(), collection.getCollectionTable() ); + targetValue = new SimpleValue( getBuildingContext(), collection.getCollectionTable() ); targetValue.copyTypeFrom( sourceValue ); } Iterator columns = sourceValue.getColumnIterator(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java index 0bacc5b4b801..f13b16bba7c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java @@ -5,6 +5,7 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.cfg.annotations; + import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PrimitiveArray; @@ -15,6 +16,6 @@ public class PrimitiveArrayBinder extends ArrayBinder { @Override protected Collection createCollection(PersistentClass persistentClass) { - return new PrimitiveArray( getBuildingContext().getMetadataCollector(), persistentClass ); + return new PrimitiveArray( getBuildingContext(), persistentClass ); } } 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 b942a956ce54..83c3f0c34ad1 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 @@ -14,6 +14,7 @@ import org.hibernate.AnnotationException; import org.hibernate.HibernateException; +import org.hibernate.annotations.AttributeAccessor; import org.hibernate.annotations.Generated; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.NaturalId; @@ -160,7 +161,7 @@ private void validateBind() { ); } if ( !declaringClassSet ) { - throw new AssertionFailure( "declaringClass has not been set beforeQuery a bind" ); + throw new AssertionFailure( "declaringClass has not been set before a bind" ); } } @@ -273,6 +274,11 @@ public Property makeProperty() { if ( property != null ) { prop.setValueGenerationStrategy( determineValueGenerationStrategy( property ) ); + + if ( property.isAnnotationPresent( AttributeAccessor.class ) ) { + final AttributeAccessor accessor = property.getAnnotation( AttributeAccessor.class ); + prop.setPropertyAccessorName( accessor.value() ); + } } NaturalId naturalId = property != null ? property.getAnnotation( NaturalId.class ) : null; @@ -420,7 +426,7 @@ private AnnotationValueGeneration instantiateAndInitia AnnotationValueGeneration valueGeneration = (AnnotationValueGeneration) generationType.newInstance(); valueGeneration.initialize( annotation, - buildingContext.getBuildingOptions().getReflectionManager().toClass( property.getType() ) + buildingContext.getBootstrapContext().getReflectionManager().toClass( property.getType() ) ); return valueGeneration; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java index 11e918bdac33..6478a90cb12c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java @@ -48,11 +48,15 @@ public static void bindQuery( NamedQuery queryAnn, MetadataBuildingContext context, boolean isDefault) { - if ( queryAnn == null ) return; + if ( queryAnn == null ) { + return; + } + if ( BinderHelper.isEmptyAnnotationValue( queryAnn.name() ) ) { throw new AnnotationException( "A named query must have a name when used in class or package level" ); } - //EJBQL Query + + // JPA-QL Query QueryHintDefinition hints = new QueryHintDefinition( queryAnn.hints() ); String queryName = queryAnn.query(); NamedQueryDefinition queryDefinition = new NamedQueryDefinitionBuilder( queryAnn.name() ) @@ -112,14 +116,17 @@ public static void bindNativeQuery( if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) { //sql result set usage - builder.setResultSetRef( resultSetMapping ) - .createNamedQueryDefinition(); + builder.setResultSetRef( resultSetMapping ).createNamedQueryDefinition(); } else if ( !void.class.equals( queryAnn.resultClass() ) ) { //class mapping usage //FIXME should be done in a second pass due to entity name? - final NativeSQLQueryRootReturn entityQueryReturn = - new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ ); + final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn( + "alias1", + queryAnn.resultClass().getName(), + new HashMap(), + LockMode.READ + ); builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ); } else { @@ -151,59 +158,50 @@ public static void bindNativeQuery( throw new AnnotationException( "A named query must have a name when used in class or package level" ); } - NamedSQLQueryDefinition query; - String resultSetMapping = queryAnn.resultSetMapping(); + final String resultSetMapping = queryAnn.resultSetMapping(); + + final NamedSQLQueryDefinitionBuilder builder = new NamedSQLQueryDefinitionBuilder() + .setName( queryAnn.name() ) + .setQuery( queryAnn.query() ) + .setCacheable( queryAnn.cacheable() ) + .setCacheRegion( + BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) + ? null + : queryAnn.cacheRegion() + ) + .setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() ) + .setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() ) + .setFlushMode( getFlushMode( queryAnn.flushMode() ) ) + .setCacheMode( getCacheMode( queryAnn.cacheMode() ) ) + .setReadOnly( queryAnn.readOnly() ) + .setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() ) + .setParameterTypes( null ) + .setCallable( queryAnn.callable() ); + + if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) { //sql result set usage - query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() ) - .setQuery( queryAnn.query() ) - .setResultSetRef( resultSetMapping ) - .setQuerySpaces( null ) - .setCacheable( queryAnn.cacheable() ) - .setCacheRegion( - BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ? - null : - queryAnn.cacheRegion() - ) - .setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() ) - .setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() ) - .setFlushMode( getFlushMode( queryAnn.flushMode() ) ) - .setCacheMode( getCacheMode( queryAnn.cacheMode() ) ) - .setReadOnly( queryAnn.readOnly() ) - .setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() ) - .setParameterTypes( null ) - .setCallable( queryAnn.callable() ) - .createNamedQueryDefinition(); + builder.setResultSetRef( resultSetMapping ); } - else if ( !void.class.equals( queryAnn.resultClass() ) ) { + else if ( ! void.class.equals( queryAnn.resultClass() ) ) { //class mapping usage //FIXME should be done in a second pass due to entity name? - final NativeSQLQueryRootReturn entityQueryReturn = - new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ ); - query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() ) - .setQuery( queryAnn.query() ) - .setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ) - .setQuerySpaces( null ) - .setCacheable( queryAnn.cacheable() ) - .setCacheRegion( - BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ? - null : - queryAnn.cacheRegion() - ) - .setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() ) - .setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() ) - .setFlushMode( getFlushMode( queryAnn.flushMode() ) ) - .setCacheMode( getCacheMode( queryAnn.cacheMode() ) ) - .setReadOnly( queryAnn.readOnly() ) - .setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() ) - .setParameterTypes( null ) - .setCallable( queryAnn.callable() ) - .createNamedQueryDefinition(); + final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn( + "alias1", + queryAnn.resultClass().getName(), + new HashMap(), + LockMode.READ + ); + builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ); } else { - throw new NotYetImplementedException( "Pure native scalar queries are not yet supported" ); + LOG.debugf( "Raw scalar native-query (no explicit result mappings) found : %s", queryAnn.name() ); } + + final NamedSQLQueryDefinition query = builder.createNamedQueryDefinition(); + context.getMetadataCollector().addNamedNativeQuery( query ); + if ( LOG.isDebugEnabled() ) { LOG.debugf( "Binding named native query: %s => %s", query.getName(), queryAnn.query() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java index c908a1636927..4735be1d89be 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java @@ -22,7 +22,7 @@ public SetBinder(boolean sorted) { @Override protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.Set( getBuildingContext().getMetadataCollector(), persistentClass ); + return new org.hibernate.mapping.Set( getBuildingContext(), persistentClass ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java index 17132c122529..5b55c0a2cf94 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java @@ -30,7 +30,7 @@ import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.model.TypeDefinition; -import org.hibernate.boot.spi.AttributeConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.AccessType; import org.hibernate.cfg.BinderHelper; @@ -87,7 +87,7 @@ public class SimpleValueBinder { private XProperty xproperty; private AccessType accessType; - private AttributeConverterDescriptor attributeConverterDescriptor; + private ConverterDescriptor attributeConverterDescriptor; public void setReferencedEntityName(String referencedEntityName) { this.referencedEntityName = referencedEntityName; @@ -135,7 +135,7 @@ public void setPersistentClassName(String persistentClassName) { //TODO execute it lazily to be order safe - public void setType(XProperty property, XClass returnedClass, String declaringClassName, AttributeConverterDescriptor attributeConverterDescriptor) { + public void setType(XProperty property, XClass returnedClass, String declaringClassName, ConverterDescriptor attributeConverterDescriptor) { if ( returnedClass == null ) { // we cannot guess anything return; @@ -177,10 +177,10 @@ else if ( ( !key && property.isAnnotationPresent( Temporal.class ) ) || ( key && property.isAnnotationPresent( MapKeyTemporal.class ) ) ) { boolean isDate; - if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, Date.class ) ) { + if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, Date.class ) ) { isDate = true; } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, Calendar.class ) ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, Calendar.class ) ) { isDate = false; } else { @@ -213,39 +213,39 @@ else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( re } else if ( !key && property.isAnnotationPresent( Lob.class ) ) { isLob = true; - if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, java.sql.Clob.class ) ) { + if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, java.sql.Clob.class ) ) { type = isNationalized ? StandardBasicTypes.NCLOB.getName() : StandardBasicTypes.CLOB.getName(); } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, java.sql.NClob.class ) ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, java.sql.NClob.class ) ) { type = StandardBasicTypes.NCLOB.getName(); } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, java.sql.Blob.class ) ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, java.sql.Blob.class ) ) { type = "blob"; } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, String.class ) ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, String.class ) ) { type = isNationalized ? StandardBasicTypes.MATERIALIZED_NCLOB.getName() : StandardBasicTypes.MATERIALIZED_CLOB.getName(); } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, Character.class ) && isArray ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, Character.class ) && isArray ) { type = isNationalized ? CharacterArrayNClobType.class.getName() : CharacterArrayClobType.class.getName(); } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, char.class ) && isArray ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, char.class ) && isArray ) { type = isNationalized ? PrimitiveCharacterArrayNClobType.class.getName() : PrimitiveCharacterArrayClobType.class.getName(); } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, Byte.class ) && isArray ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, Byte.class ) && isArray ) { type = WrappedMaterializedBlobType.class.getName(); } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, byte.class ) && isArray ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, byte.class ) && isArray ) { type = StandardBasicTypes.MATERIALIZED_BLOB.getName(); } - else if ( buildingContext.getBuildingOptions().getReflectionManager() + else if ( buildingContext.getBootstrapContext().getReflectionManager() .toXClass( Serializable.class ) .isAssignableFrom( returnedClassOrElement ) ) { type = SerializableToBlobType.class.getName(); @@ -261,7 +261,7 @@ else if ( buildingContext.getBuildingOptions().getReflectionManager() } else if ( ( !key && property.isAnnotationPresent( Enumerated.class ) ) || ( key && property.isAnnotationPresent( MapKeyEnumerated.class ) ) ) { - final Class attributeJavaType = buildingContext.getBuildingOptions().getReflectionManager().toClass( returnedClassOrElement ); + final Class attributeJavaType = buildingContext.getBootstrapContext().getReflectionManager().toClass( returnedClassOrElement ); if ( !Enum.class.isAssignableFrom( attributeJavaType ) ) { throw new AnnotationException( String.format( @@ -276,13 +276,13 @@ else if ( ( !key && property.isAnnotationPresent( Enumerated.class ) ) explicitType = type; } else if ( isNationalized ) { - if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, String.class ) ) { + if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, String.class ) ) { // nvarchar type = StringNVarcharType.INSTANCE.getName(); explicitType = type; } - else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, Character.class ) || - buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, char.class ) ) { + else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, Character.class ) || + buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, char.class ) ) { if ( isArray ) { // nvarchar type = StringNVarcharType.INSTANCE.getName(); @@ -297,7 +297,7 @@ else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( re // implicit type will check basic types and Serializable classes if ( columns == null ) { - throw new AssertionFailure( "SimpleValueBinder.setColumns should be set beforeQuery SimpleValueBinder.setType" ); + throw new AssertionFailure( "SimpleValueBinder.setColumns should be set before SimpleValueBinder.setType" ); } if ( BinderHelper.ANNOTATION_STRING_DEFAULT.equals( type ) ) { @@ -320,7 +320,7 @@ private Dialect getDialect() { .getDialect(); } - private void applyAttributeConverter(XProperty property, AttributeConverterDescriptor attributeConverterDescriptor) { + private void applyAttributeConverter(XProperty property, ConverterDescriptor attributeConverterDescriptor) { if ( attributeConverterDescriptor == null ) { return; } @@ -412,7 +412,7 @@ public SimpleValue make() { if ( table == null ) { table = columns[0].getTable(); } - simpleValue = new SimpleValue( buildingContext.getMetadataCollector(), table ); + simpleValue = new SimpleValue( buildingContext, table ); if ( isVersion ) { simpleValue.makeVersion(); } @@ -535,7 +535,7 @@ public void fillSimpleValue() { if ( simpleValue.getTypeName() != null && simpleValue.getTypeName().length() > 0 && simpleValue.getMetadata().getTypeResolver().basic( simpleValue.getTypeName() ) == null ) { try { - Class typeClass = buildingContext.getClassLoaderAccess().classForName( simpleValue.getTypeName() ); + Class typeClass = buildingContext.getBootstrapContext().getClassLoaderAccess().classForName( simpleValue.getTypeName() ); if ( typeClass != null && DynamicParameterizedType.class.isAssignableFrom( typeClass ) ) { Properties parameters = simpleValue.getTypeParameters(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/AttributeConverterDefinitionCollector.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/AttributeConverterDefinitionCollector.java index 02b785c0f384..2f2ab7bdae5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/AttributeConverterDefinitionCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/AttributeConverterDefinitionCollector.java @@ -6,11 +6,13 @@ */ package org.hibernate.cfg.annotations.reflection; -import org.hibernate.cfg.AttributeConverterDefinition; +import org.hibernate.boot.AttributeConverterInfo; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; /** * @author Steve Ebersole */ public interface AttributeConverterDefinitionCollector { - void addAttributeConverter(AttributeConverterDefinition definition); + void addAttributeConverter(AttributeConverterInfo info); + void addAttributeConverter(ConverterDescriptor descriptor); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAMetadataProvider.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAMetadataProvider.java index 58f6fa93295b..1e2bcc9d6b98 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAMetadataProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAMetadataProvider.java @@ -25,6 +25,7 @@ import org.hibernate.boot.internal.ClassLoaderAccessImpl; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.boot.spi.ClassLoaderAccessDelegateImpl; import org.hibernate.boot.spi.MetadataBuildingOptions; @@ -38,17 +39,21 @@ */ @SuppressWarnings("unchecked") public class JPAMetadataProvider implements MetadataProvider { - private final MetadataProvider delegate = new JavaMetadataProvider(); private final ClassLoaderAccess classLoaderAccess; private final XMLContext xmlContext; + private Map defaults; private Map cache = new HashMap(100); + /** + * @deprecated Use {@link JPAMetadataProvider#JPAMetadataProvider(BootstrapContext)} instead. + */ + @Deprecated public JPAMetadataProvider(final MetadataBuildingOptions metadataBuildingOptions) { - classLoaderAccess = new ClassLoaderAccessDelegateImpl() { + this( new ClassLoaderAccessDelegateImpl() { ClassLoaderAccess delegate; @Override @@ -61,10 +66,17 @@ protected ClassLoaderAccess getDelegate() { } return delegate; } - }; + } ); + } - xmlContext = new XMLContext( classLoaderAccess ); + public JPAMetadataProvider(BootstrapContext bootstrapContext) { + this( bootstrapContext.getClassLoaderAccess() ); + } + JPAMetadataProvider(ClassLoaderAccess classLoaderAccess) { + this.classLoaderAccess = classLoaderAccess; + this.xmlContext = new XMLContext( classLoaderAccess ); + ; } //all of the above can be safely rebuilt from XMLContext: only XMLContext this object is serialized @@ -85,12 +97,13 @@ public AnnotationReader getAnnotationReader(AnnotatedElement annotatedElement) { @Override public Map getDefaults() { if ( defaults == null ) { - defaults = new HashMap(); + defaults = new HashMap<>(); XMLContext.Default xmlDefaults = xmlContext.getDefault( null ); defaults.put( "schema", xmlDefaults.getSchema() ); defaults.put( "catalog", xmlDefaults.getCatalog() ); defaults.put( "delimited-identifier", xmlDefaults.getDelimitedIdentifier() ); + defaults.put( "cascade-persist", xmlDefaults.getCascadePersist() ); List entityListeners = new ArrayList(); for ( String className : xmlContext.getDefaultEntityListeners() ) { try { @@ -106,7 +119,7 @@ public Map getDefaults() { List elements = element.elements( "sequence-generator" ); List sequenceGenerators = ( List ) defaults.get( SequenceGenerator.class ); if ( sequenceGenerators == null ) { - sequenceGenerators = new ArrayList(); + sequenceGenerators = new ArrayList<>(); defaults.put( SequenceGenerator.class, sequenceGenerators ); } for ( Element subelement : elements ) { @@ -116,7 +129,7 @@ public Map getDefaults() { elements = element.elements( "table-generator" ); List tableGenerators = ( List ) defaults.get( TableGenerator.class ); if ( tableGenerators == null ) { - tableGenerators = new ArrayList(); + tableGenerators = new ArrayList<>(); defaults.put( TableGenerator.class, tableGenerators ); } for ( Element subelement : elements ) { @@ -129,7 +142,7 @@ public Map getDefaults() { List namedQueries = ( List ) defaults.get( NamedQuery.class ); if ( namedQueries == null ) { - namedQueries = new ArrayList(); + namedQueries = new ArrayList<>(); defaults.put( NamedQuery.class, namedQueries ); } List currentNamedQueries = JPAOverriddenAnnotationReader.buildNamedQueries( @@ -142,7 +155,7 @@ public Map getDefaults() { List namedNativeQueries = ( List ) defaults.get( NamedNativeQuery.class ); if ( namedNativeQueries == null ) { - namedNativeQueries = new ArrayList(); + namedNativeQueries = new ArrayList<>(); defaults.put( NamedNativeQuery.class, namedNativeQueries ); } List currentNamedNativeQueries = JPAOverriddenAnnotationReader.buildNamedQueries( @@ -157,7 +170,7 @@ public Map getDefaults() { SqlResultSetMapping.class ); if ( sqlResultSetMappings == null ) { - sqlResultSetMappings = new ArrayList(); + sqlResultSetMappings = new ArrayList<>(); defaults.put( SqlResultSetMapping.class, sqlResultSetMappings ); } List currentSqlResultSetMappings = JPAOverriddenAnnotationReader.buildSqlResultsetMappings( @@ -169,7 +182,7 @@ public Map getDefaults() { List namedStoredProcedureQueries = (List)defaults.get( NamedStoredProcedureQuery.class ); if(namedStoredProcedureQueries==null){ - namedStoredProcedureQueries = new ArrayList( ); + namedStoredProcedureQueries = new ArrayList<>( ); defaults.put( NamedStoredProcedureQuery.class, namedStoredProcedureQueries ); } List currentNamedStoredProcedureQueries = JPAOverriddenAnnotationReader.buildNamedStoreProcedureQueries( diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java index 26bae1298662..fe429ccfcf77 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java @@ -13,6 +13,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -123,6 +124,7 @@ import org.hibernate.annotations.common.reflection.AnnotationReader; import org.hibernate.annotations.common.reflection.ReflectionUtil; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -146,7 +148,7 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { private static final String SCHEMA_VALIDATION = "Activate schema validation for more information"; private static final String WORD_SEPARATOR = "-"; - private static enum PropertyType { + private enum PropertyType { PROPERTY, FIELD, METHOD @@ -155,7 +157,7 @@ private static enum PropertyType { private static final Map annotationToXml; static { - annotationToXml = new HashMap(); + annotationToXml = new HashMap<>(); annotationToXml.put( Entity.class, "entity" ); annotationToXml.put( MappedSuperclass.class, "mapped-superclass" ); annotationToXml.put( Embeddable.class, "embeddable" ); @@ -247,7 +249,13 @@ private static enum PropertyType { private transient List elementsForProperty; private AccessibleObject mirroredAttribute; - public JPAOverriddenAnnotationReader(AnnotatedElement el, XMLContext xmlContext, ClassLoaderAccess classLoaderAccess) { + /** + * @deprecated Use {@link JPAMetadataProvider(AnnotatedElement, XMLContext, BootstrapContext)} instead. + */ + public JPAOverriddenAnnotationReader( + AnnotatedElement el, + XMLContext xmlContext, + ClassLoaderAccess classLoaderAccess) { this.element = el; this.xmlContext = xmlContext; this.classLoaderAccess = classLoaderAccess; @@ -305,6 +313,14 @@ else if ( propertyName.startsWith( "is" ) ) { } } + public JPAOverriddenAnnotationReader( + AnnotatedElement el, + XMLContext xmlContext, + BootstrapContext bootstrapContext) { + this( el, xmlContext, bootstrapContext.getClassLoaderAccess() ); + } + + public T getAnnotation(Class annotationType) { initAnnotations(); return (T) annotationsMap.get( annotationType ); @@ -331,8 +347,8 @@ private void initAnnotations() { //is a class Element tree = xmlContext.getXMLTree( className ); Annotation[] annotations = getPhysicalAnnotations(); - List annotationList = new ArrayList( annotations.length + 5 ); - annotationsMap = new HashMap( annotations.length + 5 ); + List annotationList = new ArrayList<>( annotations.length + 5 ); + annotationsMap = new HashMap<>( annotations.length + 5 ); for ( Annotation annotation : annotations ) { if ( !annotationToXml.containsKey( annotation.annotationType() ) ) { //unknown annotations are left over @@ -374,8 +390,8 @@ private void initAnnotations() { else if ( className != null ) { //&& propertyName != null ) { //always true but less confusing Element tree = xmlContext.getXMLTree( className ); Annotation[] annotations = getPhysicalAnnotations(); - List annotationList = new ArrayList( annotations.length + 5 ); - annotationsMap = new HashMap( annotations.length + 5 ); + List annotationList = new ArrayList<>( annotations.length + 5 ); + annotationsMap = new HashMap<>( annotations.length + 5 ); for ( Annotation annotation : annotations ) { if ( !annotationToXml.containsKey( annotation.annotationType() ) ) { //unknown annotations are left over @@ -417,7 +433,7 @@ else if ( className != null ) { //&& propertyName != null ) { //always true but } else { this.annotations = getPhysicalAnnotations(); - annotationsMap = new HashMap( annotations.length + 5 ); + annotationsMap = new HashMap<>( annotations.length + 5 ); for ( Annotation ann : this.annotations ) { annotationsMap.put( ann.annotationType(), ann ); } @@ -431,15 +447,14 @@ private Annotation getConvertsForAttribute(List elementsForProperty, XM // todo : revisit this // although bear in mind that this code is no longer used in 5.0... - final Map convertAnnotationsMap = new HashMap(); + final Map convertAnnotationsMap = new HashMap<>(); for ( Element element : elementsForProperty ) { final boolean isBasic = "basic".equals( element.getName() ); final boolean isEmbedded = "embedded".equals( element.getName() ); + final boolean isElementCollection = "element-collection".equals(element.getName()); - // todo : can be collections too - - final boolean canHaveConverts = isBasic || isEmbedded; + final boolean canHaveConverts = isBasic || isEmbedded || isElementCollection; if ( !canHaveConverts ) { continue; @@ -469,7 +484,7 @@ private Annotation getConvertsForAttribute(List elementsForProperty, XM private Converts getConverts(Element tree, XMLContext.Default defaults) { // NOTE : we use a map here to make sure that an xml and annotation referring to the same attribute // properly overrides. Bit sparse, but easy... - final Map convertAnnotationsMap = new HashMap(); + final Map convertAnnotationsMap = new HashMap<>(); if ( tree != null ) { applyXmlDefinedConverts( tree, defaults, null, convertAnnotationsMap ); @@ -578,7 +593,7 @@ private void checkForOrphanProperties(Element tree) { if ( element != null ) { //precompute the list of properties //TODO is it really useful... - Set properties = new HashSet(); + Set properties = new HashSet<>(); for ( Field field : clazz.getFields() ) { properties.add( field.getName() ); } @@ -707,7 +722,7 @@ else if ( "post-load".equals( elementName ) ) { private EntityListeners getEntityListeners(Element tree, XMLContext.Default defaults) { Element element = tree != null ? tree.element( "entity-listeners" ) : null; if ( element != null ) { - List entityListenerClasses = new ArrayList(); + List entityListenerClasses = new ArrayList<>(); for ( Element subelement : (List) element.elements( "entity-listener" ) ) { String className = subelement.attributeValue( "class" ); try { @@ -779,6 +794,52 @@ else if ( defaults.canUseJavaAnnotations() ) { } } + private Annotation overridesDefaultCascadePersist(Annotation annotation, XMLContext.Default defaults) { + if ( Boolean.TRUE.equals( defaults.getCascadePersist() ) ) { + final Class annotationType = annotation.annotationType(); + + if ( annotationType == ManyToOne.class ) { + ManyToOne manyToOne = (ManyToOne) annotation; + List cascades = new ArrayList<>( Arrays.asList( manyToOne.cascade() ) ); + if ( !cascades.contains( CascadeType.ALL ) && !cascades.contains( CascadeType.PERSIST ) ) { + cascades.add( CascadeType.PERSIST ); + } + else { + return annotation; + } + + AnnotationDescriptor ad = new AnnotationDescriptor( annotationType ); + ad.setValue( "cascade", cascades.toArray( new CascadeType[] {} ) ); + ad.setValue( "targetEntity", manyToOne.targetEntity() ); + ad.setValue( "fetch", manyToOne.fetch() ); + ad.setValue( "optional", manyToOne.optional() ); + + return AnnotationFactory.create( ad ); + } + else if ( annotationType == OneToOne.class ) { + OneToOne oneToOne = (OneToOne) annotation; + List cascades = new ArrayList<>( Arrays.asList( oneToOne.cascade() ) ); + if ( !cascades.contains( CascadeType.ALL ) && !cascades.contains( CascadeType.PERSIST ) ) { + cascades.add( CascadeType.PERSIST ); + } + else { + return annotation; + } + + AnnotationDescriptor ad = new AnnotationDescriptor( annotationType ); + ad.setValue( "cascade", cascades.toArray( new CascadeType[] {} ) ); + ad.setValue( "targetEntity", oneToOne.targetEntity() ); + ad.setValue( "fetch", oneToOne.fetch() ); + ad.setValue( "optional", oneToOne.optional() ); + ad.setValue( "mappedBy", oneToOne.mappedBy() ); + ad.setValue( "orphanRemoval", oneToOne.orphanRemoval() ); + + return AnnotationFactory.create( ad ); + } + } + return annotation; + } + private void getJoinTable(List annotationList, Element tree, XMLContext.Default defaults) { addIfNotNull( annotationList, buildJoinTable( tree, defaults ) ); } @@ -857,6 +918,7 @@ private void getAssociation( if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { Annotation annotation = getPhysicalAnnotation( annotationType ); if ( annotation != null ) { + annotation = overridesDefaultCascadePersist( annotation, defaults ); annotationList.add( annotation ); annotation = overridesDefaultsInJoinTable( annotation, defaults ); addIfNotNull( annotationList, annotation ); @@ -961,7 +1023,7 @@ private void buildMapKeyJoinColumns(List annotationList, Element ele private MapKeyJoinColumn[] getMapKeyJoinColumns(Element element) { List subelements = element != null ? element.elements( "map-key-join-column" ) : null; - List joinColumns = new ArrayList(); + List joinColumns = new ArrayList<>(); if ( subelements != null ) { for ( Element subelement : subelements ) { AnnotationDescriptor column = new AnnotationDescriptor( MapKeyJoinColumn.class ); @@ -973,7 +1035,7 @@ private MapKeyJoinColumn[] getMapKeyJoinColumns(Element element) { copyBooleanAttribute( column, subelement, "updatable" ); copyStringAttribute( column, subelement, "column-definition", false ); copyStringAttribute( column, subelement, "table", false ); - joinColumns.add( (MapKeyJoinColumn) AnnotationFactory.create( column ) ); + joinColumns.add( AnnotationFactory.create( column ) ); } } return joinColumns.toArray( new MapKeyJoinColumn[joinColumns.size()] ); @@ -1120,7 +1182,7 @@ private void getElementCollection(List annotationList, XMLContext.De //Both map-key-attribute-overrides and attribute-overrides //translate into AttributeOverride annotations, which need //need to be wrapped in the same AttributeOverrides annotation. - List attributes = new ArrayList(); + List attributes = new ArrayList<>(); attributes.addAll( buildAttributeOverrides( element, "map-key-attribute-override" ) ); attributes.addAll( buildAttributeOverrides( element, "attribute-override" ) ); annotation = mergeAttributeOverrides( defaults, attributes, false ); @@ -1229,8 +1291,8 @@ private void buildJoinColumns(List annotationList, Element element) } private void getCascades(AnnotationDescriptor ad, Element element, XMLContext.Default defaults) { - List elements = element != null ? element.elements( "cascade" ) : new ArrayList( 0 ); - List cascades = new ArrayList(); + List elements = element != null ? element.elements( "cascade" ) : new ArrayList<>( 0 ); + List cascades = new ArrayList<>(); for ( Element subelement : elements ) { if ( subelement.element( "cascade-all" ) != null ) { cascades.add( CascadeType.ALL ); @@ -1449,7 +1511,7 @@ private void getEmbeddedId(List annotationList, XMLContext.Default d } private void preCalculateElementsForProperty(Element tree) { - elementsForProperty = new ArrayList(); + elementsForProperty = new ArrayList<>(); Element element = tree != null ? tree.element( "attributes" ) : null; //put entity.attributes elements if ( element != null ) { @@ -1538,7 +1600,7 @@ private boolean isProcessingId(XMLContext.Default defaults) { private Columns buildColumns(Element element) { List subelements = element.elements( "column" ); - List columns = new ArrayList( subelements.size() ); + List columns = new ArrayList<>( subelements.size() ); for ( Element subelement : subelements ) { columns.add( getColumn( subelement, false, element ) ); } @@ -1656,7 +1718,7 @@ private AssociationOverrides getAssociationOverrides(Element tree, XMLContext.De private List buildAssociationOverrides(Element element, XMLContext.Default defaults) { List subelements = element == null ? null : element.elements( "association-override" ); - List overrides = new ArrayList(); + List overrides = new ArrayList<>(); if ( subelements != null && subelements.size() > 0 ) { for ( Element current : subelements ) { AnnotationDescriptor override = new AnnotationDescriptor( AssociationOverride.class ); @@ -1666,7 +1728,7 @@ private List buildAssociationOverrides(Element element, XML if ( joinTable != null ) { override.setValue( "joinTable", joinTable ); } - overrides.add( (AssociationOverride) AnnotationFactory.create( override ) ); + overrides.add( AnnotationFactory.create( override ) ); } } return overrides; @@ -1676,7 +1738,7 @@ private JoinColumn[] getJoinColumns(Element element, boolean isInverse) { List subelements = element != null ? element.elements( isInverse ? "inverse-join-column" : "join-column" ) : null; - List joinColumns = new ArrayList(); + List joinColumns = new ArrayList<>(); if ( subelements != null ) { for ( Element subelement : subelements ) { AnnotationDescriptor column = new AnnotationDescriptor( JoinColumn.class ); @@ -1688,7 +1750,7 @@ private JoinColumn[] getJoinColumns(Element element, boolean isInverse) { copyBooleanAttribute( column, subelement, "updatable" ); copyStringAttribute( column, subelement, "column-definition", false ); copyStringAttribute( column, subelement, "table", false ); - joinColumns.add( (JoinColumn) AnnotationFactory.create( column ) ); + joinColumns.add( AnnotationFactory.create( column ) ); } } return joinColumns.toArray( new JoinColumn[joinColumns.size()] ); @@ -1754,7 +1816,7 @@ private List buildAttributeOverrides(Element element, String } private List buildAttributeOverrides(List subelements, String nodeName) { - List overrides = new ArrayList(); + List overrides = new ArrayList<>(); if ( subelements != null && subelements.size() > 0 ) { for ( Element current : subelements ) { if ( !current.getName().equals( nodeName ) ) { @@ -1764,7 +1826,7 @@ private List buildAttributeOverrides(List subelement copyStringAttribute( override, current, "name", true ); Element column = current.element( "column" ); override.setValue( "column", getColumn( column, true, current ) ); - overrides.add( (AttributeOverride) AnnotationFactory.create( override ) ); + overrides.add( AnnotationFactory.create( override ) ); } } return overrides; @@ -1888,9 +1950,9 @@ public static List buildNamedEntityGraph( XMLContext.Default defaults, ClassLoaderAccess classLoaderAccess) { if ( element == null ) { - return new ArrayList(); + return new ArrayList<>(); } - List namedEntityGraphList = new ArrayList(); + List namedEntityGraphList = new ArrayList<>(); List namedEntityGraphElements = element.elements( "named-entity-graph" ); for ( Element subElement : namedEntityGraphElements ) { AnnotationDescriptor ann = new AnnotationDescriptor( NamedEntityGraph.class ); @@ -1904,7 +1966,7 @@ public static List buildNamedEntityGraph( subgraphNodes.addAll( subclassSubgraphNodes ); } bindNamedSubgraph( defaults, ann, subgraphNodes, classLoaderAccess ); - namedEntityGraphList.add( (NamedEntityGraph) AnnotationFactory.create( ann ) ); + namedEntityGraphList.add( AnnotationFactory.create( ann ) ); } //TODO return namedEntityGraphList; @@ -1915,7 +1977,7 @@ private static void bindNamedSubgraph( AnnotationDescriptor ann, List subgraphNodes, ClassLoaderAccess classLoaderAccess) { - List annSubgraphNodes = new ArrayList( ); + List annSubgraphNodes = new ArrayList<>( ); for(Element subgraphNode : subgraphNodes){ AnnotationDescriptor annSubgraphNode = new AnnotationDescriptor( NamedSubgraph.class ); copyStringAttribute( annSubgraphNode, subgraphNode, "name", true ); @@ -1931,7 +1993,7 @@ private static void bindNamedSubgraph( } annSubgraphNode.setValue( "type", clazz ); bindNamedAttributeNodes(subgraphNode, annSubgraphNode); - annSubgraphNodes.add( (NamedSubgraph) AnnotationFactory.create( annSubgraphNode ) ); + annSubgraphNodes.add( AnnotationFactory.create( annSubgraphNode ) ); } ann.setValue( "subgraphs", annSubgraphNodes.toArray( new NamedSubgraph[annSubgraphNodes.size()] ) ); @@ -1939,13 +2001,13 @@ private static void bindNamedSubgraph( private static void bindNamedAttributeNodes(Element subElement, AnnotationDescriptor ann) { List namedAttributeNodes = subElement.elements("named-attribute-node"); - List annNamedAttributeNodes = new ArrayList( ); + List annNamedAttributeNodes = new ArrayList<>( ); for(Element namedAttributeNode : namedAttributeNodes){ AnnotationDescriptor annNamedAttributeNode = new AnnotationDescriptor( NamedAttributeNode.class ); copyStringAttribute( annNamedAttributeNode, namedAttributeNode, "value", "name", true ); copyStringAttribute( annNamedAttributeNode, namedAttributeNode, "subgraph", false ); copyStringAttribute( annNamedAttributeNode, namedAttributeNode, "key-subgraph", false ); - annNamedAttributeNodes.add( (NamedAttributeNode) AnnotationFactory.create( annNamedAttributeNode ) ); + annNamedAttributeNodes.add( AnnotationFactory.create( annNamedAttributeNode ) ); } ann.setValue( "attributeNodes", annNamedAttributeNodes.toArray( new NamedAttributeNode[annNamedAttributeNodes.size()] ) ); } @@ -1955,10 +2017,10 @@ public static List buildNamedStoreProcedureQueries( XMLContext.Default defaults, ClassLoaderAccess classLoaderAccess) { if ( element == null ) { - return new ArrayList(); + return new ArrayList<>(); } List namedStoredProcedureElements = element.elements( "named-stored-procedure-query" ); - List namedStoredProcedureQueries = new ArrayList(); + List namedStoredProcedureQueries = new ArrayList<>(); for ( Object obj : namedStoredProcedureElements ) { Element subElement = (Element) obj; AnnotationDescriptor ann = new AnnotationDescriptor( NamedStoredProcedureQuery.class ); @@ -1966,7 +2028,7 @@ public static List buildNamedStoreProcedureQueries( copyStringAttribute( ann, subElement, "procedure-name", true ); List elements = subElement.elements( "parameter" ); - List storedProcedureParameters = new ArrayList(); + List storedProcedureParameters = new ArrayList<>(); for ( Element parameterElement : elements ) { AnnotationDescriptor parameterDescriptor = new AnnotationDescriptor( StoredProcedureParameter.class ); @@ -1989,7 +2051,7 @@ public static List buildNamedStoreProcedureQueries( throw new AnnotationException( "Unable to find entity-class: " + clazzName, e ); } parameterDescriptor.setValue( "type", clazz ); - storedProcedureParameters.add( (StoredProcedureParameter) AnnotationFactory.create( parameterDescriptor ) ); + storedProcedureParameters.add( AnnotationFactory.create( parameterDescriptor ) ); } ann.setValue( @@ -1998,7 +2060,7 @@ public static List buildNamedStoreProcedureQueries( ); elements = subElement.elements( "result-class" ); - List returnClasses = new ArrayList(); + List returnClasses = new ArrayList<>(); for ( Element classElement : elements ) { String clazzName = classElement.getTextTrim(); Class clazz; @@ -2016,14 +2078,14 @@ public static List buildNamedStoreProcedureQueries( elements = subElement.elements( "result-set-mapping" ); - List resultSetMappings = new ArrayList(); + List resultSetMappings = new ArrayList<>(); for ( Element resultSetMappingElement : elements ) { resultSetMappings.add( resultSetMappingElement.getTextTrim() ); } ann.setValue( "resultSetMappings", resultSetMappings.toArray( new String[resultSetMappings.size()] ) ); elements = subElement.elements( "hint" ); buildQueryHints( elements, ann ); - namedStoredProcedureQueries.add( (NamedStoredProcedureQuery) AnnotationFactory.create( ann ) ); + namedStoredProcedureQueries.add( AnnotationFactory.create( ann ) ); } return namedStoredProcedureQueries; @@ -2033,7 +2095,7 @@ public static List buildSqlResultsetMappings( Element element, XMLContext.Default defaults, ClassLoaderAccess classLoaderAccess) { - final List builtResultSetMappings = new ArrayList(); + final List builtResultSetMappings = new ArrayList<>(); if ( element == null ) { return builtResultSetMappings; } @@ -2059,20 +2121,20 @@ public static List buildSqlResultsetMappings( if ( "entity-result".equals( resultElement.getName() ) ) { if ( entityResultAnnotations == null ) { - entityResultAnnotations = new ArrayList(); + entityResultAnnotations = new ArrayList<>(); } // process the entityResultAnnotations.add( buildEntityResult( resultElement, defaults, classLoaderAccess ) ); } else if ( "column-result".equals( resultElement.getName() ) ) { if ( columnResultAnnotations == null ) { - columnResultAnnotations = new ArrayList(); + columnResultAnnotations = new ArrayList<>(); } columnResultAnnotations.add( buildColumnResult( resultElement, defaults, classLoaderAccess ) ); } else if ( "constructor-result".equals( resultElement.getName() ) ) { if ( constructorResultAnnotations == null ) { - constructorResultAnnotations = new ArrayList(); + constructorResultAnnotations = new ArrayList<>(); } constructorResultAnnotations.add( buildConstructorResult( resultElement, defaults, classLoaderAccess ) ); } @@ -2121,7 +2183,7 @@ else if ( "constructor-result".equals( resultElement.getName() ) ) { // this was part of the old code too, but could never figure out what it is supposed to do... // copyStringAttribute( ann, subelement, "result-set-mapping", false ); - builtResultSetMappings.add( (SqlResultSetMapping) AnnotationFactory.create( resultSetMappingAnnotation ) ); + builtResultSetMappings.add( AnnotationFactory.create( resultSetMappingAnnotation ) ); } return builtResultSetMappings; @@ -2139,12 +2201,12 @@ private static EntityResult buildEntityResult( copyStringAttribute( entityResultDescriptor, entityResultElement, "discriminator-column", false ); // process the sub-elements - List fieldResultAnnotations = new ArrayList(); + List fieldResultAnnotations = new ArrayList<>(); for ( Element fieldResult : (List) entityResultElement.elements( "field-result" ) ) { AnnotationDescriptor fieldResultDescriptor = new AnnotationDescriptor( FieldResult.class ); copyStringAttribute( fieldResultDescriptor, fieldResult, "name", true ); copyStringAttribute( fieldResultDescriptor, fieldResult, "column", true ); - fieldResultAnnotations.add( (FieldResult) AnnotationFactory.create( fieldResultDescriptor ) ); + fieldResultAnnotations.add( AnnotationFactory.create( fieldResultDescriptor ) ); } entityResultDescriptor.setValue( "fields", fieldResultAnnotations.toArray( new FieldResult[fieldResultAnnotations.size()] ) @@ -2195,7 +2257,7 @@ private static ConstructorResult buildConstructorResult( final Class entityClass = resolveClassReference( constructorResultElement.attributeValue( "target-class" ), defaults, classLoaderAccess ); constructorResultDescriptor.setValue( "targetClass", entityClass ); - List columnResultAnnotations = new ArrayList(); + List columnResultAnnotations = new ArrayList<>(); for ( Element columnResultElement : (List) constructorResultElement.elements( "column" ) ) { columnResultAnnotations.add( buildColumnResult( columnResultElement, defaults, classLoaderAccess ) ); } @@ -2381,7 +2443,7 @@ private void addNamedNativeQueryIfNeeded(NamedNativeQuery annotation, List elements, AnnotationDescriptor ann){ - List queryHints = new ArrayList( elements.size() ); + List queryHints = new ArrayList<>( elements.size() ); for ( Element hint : elements ) { AnnotationDescriptor hintDescriptor = new AnnotationDescriptor( QueryHint.class ); String value = hint.attributeValue( "name" ); @@ -2394,7 +2456,7 @@ private static void buildQueryHints(List elements, AnnotationDescriptor throw new AnnotationException( " without value. " + SCHEMA_VALIDATION ); } hintDescriptor.setValue( "value", value ); - queryHints.add( (QueryHint) AnnotationFactory.create( hintDescriptor ) ); + queryHints.add( AnnotationFactory.create( hintDescriptor ) ); } ann.setValue( "hints", queryHints.toArray( new QueryHint[queryHints.size()] ) ); } @@ -2625,8 +2687,7 @@ private IdClass getIdClass(Element tree, XMLContext.Default defaults) { AnnotationDescriptor ad = new AnnotationDescriptor( IdClass.class ); Class clazz; try { - clazz = classLoaderAccess.classForName( - XMLContext.buildSafeClassName( attr.getValue(), defaults ) + clazz = classLoaderAccess.classForName( XMLContext.buildSafeClassName( attr.getValue(), defaults ) ); } catch ( ClassLoadingException e ) { @@ -2786,9 +2847,9 @@ else if ( defaults.canUseJavaAnnotations() ) { private SecondaryTables getSecondaryTables(Element tree, XMLContext.Default defaults) { List elements = tree == null ? - new ArrayList() : + new ArrayList<>() : (List) tree.elements( "secondary-table" ); - List secondaryTables = new ArrayList( 3 ); + List secondaryTables = new ArrayList<>( 3 ); for ( Element element : elements ) { AnnotationDescriptor annotation = new AnnotationDescriptor( SecondaryTable.class ); copyStringAttribute( annotation, element, "name", false ); @@ -2805,7 +2866,7 @@ private SecondaryTables getSecondaryTables(Element tree, XMLContext.Default defa buildUniqueConstraints( annotation, element ); buildIndex( annotation, element ); annotation.setValue( "pkJoinColumns", buildPrimaryKeyJoinColumns( element ) ); - secondaryTables.add( (SecondaryTable) AnnotationFactory.create( annotation ) ); + secondaryTables.add( AnnotationFactory.create( annotation ) ); } /* * You can't have both secondary table in XML and Java, @@ -2852,7 +2913,7 @@ private void overridesDefaultInSecondaryTable( && StringHelper.isNotEmpty( defaults.getCatalog() ) ) { annotation.setValue( "catalog", defaults.getCatalog() ); } - secondaryTables.add( (SecondaryTable) AnnotationFactory.create( annotation ) ); + secondaryTables.add( AnnotationFactory.create( annotation ) ); } else { secondaryTables.add( secTableAnn ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/XMLContext.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/XMLContext.java index 6365a30140b4..f46279b485e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/XMLContext.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/XMLContext.java @@ -15,7 +15,9 @@ import javax.persistence.AttributeConverter; import org.hibernate.AnnotationException; +import org.hibernate.boot.AttributeConverterInfo; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.internal.CoreLogging; @@ -37,16 +39,24 @@ public class XMLContext implements Serializable { private final ClassLoaderAccess classLoaderAccess; private Default globalDefaults; - private Map classOverriding = new HashMap(); - private Map defaultsOverriding = new HashMap(); - private List defaultElements = new ArrayList(); - private List defaultEntityListeners = new ArrayList(); + private Map classOverriding = new HashMap<>(); + private Map defaultsOverriding = new HashMap<>(); + private List defaultElements = new ArrayList<>(); + private List defaultEntityListeners = new ArrayList<>(); private boolean hasContext = false; + /** + * @deprecated Use {@link XMLContext#XMLContext(BootstrapContext)} instead. + */ + @Deprecated public XMLContext(ClassLoaderAccess classLoaderAccess) { this.classLoaderAccess = classLoaderAccess; } + public XMLContext(BootstrapContext bootstrapContext) { + this.classLoaderAccess = bootstrapContext.getClassLoaderAccess(); + } + /** * @param doc The xml document to add * @return Add a xml document to this context and return the list of added class names. @@ -54,7 +64,7 @@ public XMLContext(ClassLoaderAccess classLoaderAccess) { @SuppressWarnings( "unchecked" ) public List addDocument(Document doc) { hasContext = true; - List addedClasses = new ArrayList(); + List addedClasses = new ArrayList<>(); Element root = doc.getRootElement(); //global defaults Element metadata = root.element( "persistence-unit-metadata" ); @@ -157,7 +167,7 @@ private void addClass(List entities, String packageName, Default defaul } private List addEntityListenerClasses(Element element, String packageName, List addedClasses) { - List localAddedClasses = new ArrayList(); + List localAddedClasses = new ArrayList<>(); Element listeners = element.element( "entity-listeners" ); if ( listeners != null ) { @SuppressWarnings( "unchecked" ) @@ -192,7 +202,7 @@ private void setLocalAttributeConverterDefinitions(List converterElemen final Class attributeConverterClass = classLoaderAccess.classForName( className ); - attributeConverterDefinitions.add( + attributeConverterInfoList.add( new AttributeConverterDefinition( attributeConverterClass.newInstance(), autoApply ) ); } @@ -238,13 +248,13 @@ public boolean hasContext() { return hasContext; } - private List attributeConverterDefinitions = new ArrayList(); + private List attributeConverterInfoList = new ArrayList<>(); public void applyDiscoveredAttributeConverters(AttributeConverterDefinitionCollector collector) { - for ( AttributeConverterDefinition definition : attributeConverterDefinitions ) { - collector.addAttributeConverter( definition ); + for ( AttributeConverterInfo info : attributeConverterInfoList ) { + collector.addAttributeConverter( info ); } - attributeConverterDefinitions.clear(); + attributeConverterInfoList.clear(); } public static class Default implements Serializable { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java index 2d12fc898d92..94dcbe2e2e52 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java @@ -52,7 +52,6 @@ public static void validateFactory(Object object) { final Class activatorClass = BeanValidationIntegrator.class.getClassLoader().loadClass( ACTIVATOR_CLASS_NAME ); try { final Method validateMethod = activatorClass.getMethod( VALIDATE_SUPPLIED_FACTORY_METHOD_NAME, Object.class ); - validateMethod.setAccessible( true ); try { validateMethod.invoke( null, object ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java index 82984c0d0c04..50e3ee850fb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java @@ -282,9 +282,16 @@ private static void applyMin(Property property, ConstraintDescriptor descript ConstraintDescriptor minConstraint = (ConstraintDescriptor) descriptor; long min = minConstraint.getAnnotation().value(); - Column col = (Column) property.getColumnIterator().next(); - String checkConstraint = col.getQuotedName(dialect) + ">=" + min; - applySQLCheck( col, checkConstraint ); + @SuppressWarnings("unchecked") + final Iterator itor = property.getColumnIterator(); + if ( itor.hasNext() ) { + final Selectable selectable = itor.next(); + if ( Column.class.isInstance( selectable ) ) { + Column col = (Column) selectable; + String checkConstraint = col.getQuotedName(dialect) + ">=" + min; + applySQLCheck( col, checkConstraint ); + } + } } } @@ -293,9 +300,17 @@ private static void applyMax(Property property, ConstraintDescriptor descript @SuppressWarnings("unchecked") ConstraintDescriptor maxConstraint = (ConstraintDescriptor) descriptor; long max = maxConstraint.getAnnotation().value(); - Column col = (Column) property.getColumnIterator().next(); - String checkConstraint = col.getQuotedName(dialect) + "<=" + max; - applySQLCheck( col, checkConstraint ); + + @SuppressWarnings("unchecked") + final Iterator itor = property.getColumnIterator(); + if ( itor.hasNext() ) { + final Selectable selectable = itor.next(); + if ( Column.class.isInstance( selectable ) ) { + Column col = (Column) selectable; + String checkConstraint = col.getQuotedName( dialect ) + "<=" + max; + applySQLCheck( col, checkConstraint ); + } + } } } @@ -344,9 +359,18 @@ private static void applyDigits(Property property, ConstraintDescriptor descr ConstraintDescriptor digitsConstraint = (ConstraintDescriptor) descriptor; int integerDigits = digitsConstraint.getAnnotation().integer(); int fractionalDigits = digitsConstraint.getAnnotation().fraction(); - Column col = (Column) property.getColumnIterator().next(); - col.setPrecision( integerDigits + fractionalDigits ); - col.setScale( fractionalDigits ); + + @SuppressWarnings("unchecked") + final Iterator itor = property.getColumnIterator(); + if ( itor.hasNext() ) { + final Selectable selectable = itor.next(); + if ( Column.class.isInstance( selectable ) ) { + Column col = (Column) selectable; + col.setPrecision( integerDigits + fractionalDigits ); + col.setScale( fractionalDigits ); + } + } + } } @@ -356,9 +380,15 @@ private static void applySize(Property property, ConstraintDescriptor descrip @SuppressWarnings("unchecked") ConstraintDescriptor sizeConstraint = (ConstraintDescriptor) descriptor; int max = sizeConstraint.getAnnotation().max(); - Column col = (Column) property.getColumnIterator().next(); - if ( max < Integer.MAX_VALUE ) { - col.setLength( max ); + + @SuppressWarnings("unchecked") + final Iterator itor = property.getColumnIterator(); + if ( itor.hasNext() ) { + final Selectable selectable = itor.next(); + Column col = (Column) selectable; + if ( max < Integer.MAX_VALUE ) { + col.setLength( max ); + } } } } @@ -370,9 +400,17 @@ private static void applyLength(Property property, ConstraintDescriptor descr && String.class.equals( propertyDescriptor.getElementClass() ) ) { @SuppressWarnings("unchecked") int max = (Integer) descriptor.getAttributes().get( "max" ); - Column col = (Column) property.getColumnIterator().next(); - if ( max < Integer.MAX_VALUE ) { - col.setLength( max ); + + @SuppressWarnings("unchecked") + final Iterator itor = property.getColumnIterator(); + if ( itor.hasNext() ) { + final Selectable selectable = itor.next(); + if ( Column.class.isInstance( selectable ) ) { + Column col = (Column) selectable; + if ( max < Integer.MAX_VALUE ) { + col.setLength( max ); + } + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/classic/Lifecycle.java b/hibernate-core/src/main/java/org/hibernate/classic/Lifecycle.java index a6b942792cef..8c2f622f9865 100644 --- a/hibernate-core/src/main/java/org/hibernate/classic/Lifecycle.java +++ b/hibernate-core/src/main/java/org/hibernate/classic/Lifecycle.java @@ -15,11 +15,11 @@ * Persistent classes may implement this interface but they are not * required to.
    *
    - * onSave: called just beforeQuery the object is saved
    - * onUpdate: called just beforeQuery an object is updated, + * onSave: called just before the object is saved
    + * onUpdate: called just before an object is updated, * ie. when Session.update() is called
    - * onDelete: called just beforeQuery an object is deleted
    - * onLoad: called just afterQuery an object is loaded
    + * onDelete: called just before an object is deleted
    + * onLoad: called just after an object is loaded
    *
    * onLoad() may be used to initialize transient properties of the * object from its persistent state. It may not be used to load @@ -35,7 +35,7 @@ * CallbackException is thrown, the operation is vetoed and the * exception is passed back to the application.
    *
    - * Note that onSave() is called afterQuery an identifier is assigned + * Note that onSave() is called after an identifier is assigned * to the object, except when identity column key generation is used. * * @see CallbackException @@ -80,7 +80,7 @@ public interface Lifecycle { public boolean onDelete(Session s) throws CallbackException; /** - * Called afterQuery an entity is loaded. It is illegal to + * Called after an entity is loaded. It is illegal to * access the Session from inside this method. * However, the object may keep a reference to the session * for later use. diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index 43d5c2482ce7..dd80a81621dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -26,6 +26,7 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.TypedValue; @@ -33,11 +34,11 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.internal.util.MarkerObject; -import org.hibernate.internal.util.collections.EmptyIterator; import org.hibernate.internal.util.collections.IdentitySet; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; +import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.type.CompositeType; import org.hibernate.type.IntegerType; import org.hibernate.type.LongType; @@ -69,6 +70,7 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers // collections detect changes made via their public interface and mark // themselves as dirty as a performance optimization private boolean dirty; + protected boolean elementRemoved; private Serializable storedSnapshot; private String sessionFactoryUuid; @@ -85,6 +87,14 @@ protected AbstractPersistentCollection(SharedSessionContractImplementor session) this.session = session; } + /** + * * @deprecated {@link #AbstractPersistentCollection(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + protected AbstractPersistentCollection(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + @Override public final String getRole() { return role; @@ -105,9 +115,15 @@ public final boolean isDirty() { return dirty; } + @Override + public boolean isElementRemoved() { + return elementRemoved; + } + @Override public final void clearDirty() { dirty = false; + elementRemoved = false; } @Override @@ -201,7 +217,7 @@ private T withTemporarySessionIfNeeded(LazyInitializationWork lazyInitial throwLazyInitializationException( "could not initialize proxy - no Session" ); } } - else if ( !session.isOpen() ) { + else if ( !session.isOpenOrWaitingForAutoClose() ) { if ( allowLoadOutsideTransaction ) { tempSession = openTemporarySessionForLoading(); } @@ -497,6 +513,7 @@ protected final void performQueuedOperations() { for ( DelayedOperation operation : operationQueue ) { operation.operate(); } + clearOperationQueue(); } @Override @@ -508,11 +525,15 @@ public void setSnapshot(Serializable key, String role, Serializable snapshot) { @Override public void postAction() { - operationQueue = null; + clearOperationQueue(); cachedSize = -1; clearDirty(); } + public final void clearOperationQueue() { + operationQueue = null; + } + @Override public Object getValue() { return this; @@ -533,10 +554,9 @@ public boolean endRead() { @Override public boolean afterInitialize() { setInitialized(); - //do this bit afterQuery setting initialized to true or it will recurse - if ( operationQueue != null ) { + //do this bit after setting initialized to true or it will recurse + if ( hasQueuedOperations() ) { performQueuedOperations(); - operationQueue = null; cachedSize = -1; return false; } @@ -605,6 +625,33 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio prepareForPossibleLoadingOutsideTransaction(); if ( currentSession == this.session ) { if ( !isTempSession ) { + if ( hasQueuedOperations() ) { + final String collectionInfoString = MessageHelper.collectionInfoString( getRole(), getKey() ); + try { + final TransactionStatus transactionStatus = + session.getTransactionCoordinator().getTransactionDriverControl().getStatus(); + if ( transactionStatus.isOneOf( + TransactionStatus.ROLLED_BACK, + TransactionStatus.MARKED_ROLLBACK, + TransactionStatus.FAILED_COMMIT, + TransactionStatus.FAILED_ROLLBACK, + TransactionStatus.ROLLING_BACK + ) ) { + // It was due to a rollback. + LOG.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString ); + } + else { + // We don't know why the collection is being detached. + // Just log the info. + LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); + } + } + catch (Exception e) { + // We don't know why the collection is being detached. + // Just log the info. + LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); + } + } this.session = null; } return true; @@ -632,25 +679,22 @@ public final boolean setCurrentSession(SharedSessionContractImplementor session) if ( session == this.session ) { return false; } - else { - if ( this.session != null ) { - final String msg = generateUnexpectedSessionStateMessage( session ); - if ( isConnectedToSession() ) { - throw new HibernateException( - "Illegal attempt to associate a collection with two open sessions. " + msg - ); - } - else { - LOG.logUnexpectedSessionInCollectionNotConnected( msg ); - this.session = session; - return true; - } + else if ( this.session != null ) { + final String msg = generateUnexpectedSessionStateMessage( session ); + if ( isConnectedToSession() ) { + throw new HibernateException( + "Illegal attempt to associate a collection with two open sessions. " + msg + ); } else { - this.session = session; - return true; + LOG.logUnexpectedSessionInCollectionNotConnected( msg ); } } + if ( hasQueuedOperations() ) { + LOG.queuedOperationWhenAttachToSession( MessageHelper.collectionInfoString( getRole(), getKey() ) ); + } + this.session = session; + return true; } private String generateUnexpectedSessionStateMessage(SharedSessionContractImplementor session) { @@ -777,7 +821,7 @@ public void remove() { }; } else { - return EmptyIterator.INSTANCE; + return Collections.emptyIterator(); } } @@ -1275,6 +1319,26 @@ public static void identityRemove( } } + /** + * Removes entity entries that have an equal identifier with the incoming entity instance + * + * @param list The list containing the entity instances + * @param entityInstance The entity instance to match elements. + * @param entityName The entity name + * @param session The session + * + * @deprecated {@link #identityRemove(Collection, Object, String, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + public static void identityRemove( + Collection list, + Object entityInstance, + String entityName, + SessionImplementor session) { + identityRemove( list, entityInstance, entityName, (SharedSessionContractImplementor) session ); + } + @Override public Object getIdentifier(Object entry, int i) { throw new UnsupportedOperationException(); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java index bb36565ab7db..866766afef5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java @@ -16,6 +16,7 @@ import java.util.Iterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.loader.CollectionAliases; @@ -55,6 +56,20 @@ public PersistentArrayHolder(SharedSessionContractImplementor session, Object ar setInitialized(); } + /** + * Constructs a PersistentCollection instance for holding an array. + * + * @param session The session + * @param array The array (the persistent "collection"). + * + * @deprecated {@link #PersistentArrayHolder(SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + public PersistentArrayHolder(SessionImplementor session, Object array) { + this( (SharedSessionContractImplementor) session, array ); + } + /** * Constructs a PersistentCollection instance for holding an array. * @@ -66,7 +81,19 @@ public PersistentArrayHolder(SharedSessionContractImplementor session, Collectio elementClass = persister.getElementClass(); } - + /** + * Constructs a PersistentCollection instance for holding an array. + * + * @param session The session + * @param persister The persister for the array + * + * @deprecated {@link #PersistentArrayHolder(SharedSessionContractImplementor, CollectionPersister)} + * should be used instead. + */ + @Deprecated + public PersistentArrayHolder(SessionImplementor session, CollectionPersister persister) { + this( (SharedSessionContractImplementor) session, persister ); + } @Override public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java index c3c9d1b6a831..20cc23f4404e 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java @@ -11,11 +11,15 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -33,6 +37,9 @@ public class PersistentBag extends AbstractPersistentCollection implements List protected List bag; + // The Collection provided to a PersistentBag constructor, + private Collection providedCollection; + /** * Constructs a PersistentBag. Needed for SOAP libraries, etc */ @@ -49,6 +56,18 @@ public PersistentBag(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentBag + * + * @param session The session + * + * @deprecated {@link #PersistentBag(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentBag(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentBag * @@ -58,6 +77,7 @@ public PersistentBag(SharedSessionContractImplementor session) { @SuppressWarnings("unchecked") public PersistentBag(SharedSessionContractImplementor session, Collection coll) { super( session ); + providedCollection = coll; if ( coll instanceof List ) { bag = (List) coll; } @@ -71,9 +91,28 @@ public PersistentBag(SharedSessionContractImplementor session, Collection coll) setDirectlyAccessible( true ); } + /** + * Constructs a PersistentBag + * + * @param session The session + * @param coll The base elements. + * + * @deprecated {@link #PersistentBag(SharedSessionContractImplementor, Collection)} + * should be used instead. + */ + @Deprecated + public PersistentBag(SessionImplementor session, Collection coll) { + this( (SharedSessionContractImplementor) session, coll ); + } + @Override public boolean isWrapper(Object collection) { - return bag==collection; + return bag == collection; + } + + @Override + public boolean isDirectlyProvidedCollection(Object collection) { + return isDirectlyAccessible() && providedCollection == collection; } @Override @@ -92,7 +131,7 @@ public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAl throws HibernateException, SQLException { // note that if we load this collection from a cartesian product // the multiplicity would be broken ... so use an idbag instead - final Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ) ; + final Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ); if ( element != null ) { bag.add( element ); } @@ -105,38 +144,112 @@ public void beforeInitialize(CollectionPersister persister, int anticipatedSize) } @Override + @SuppressWarnings("unchecked") public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException { final Type elementType = persister.getElementType(); - final List sn = (List) getSnapshot(); + final List sn = (List) getSnapshot(); if ( sn.size() != bag.size() ) { return false; } - for ( Object elt : bag ) { - final boolean unequal = countOccurrences( elt, bag, elementType ) != countOccurrences( elt, sn, elementType ); - if ( unequal ) { + + // HHH-11032 - Group objects by Type.getHashCode() to reduce the complexity of the search + final Map> hashToInstancesBag = groupByEqualityHash( bag, elementType ); + final Map> hashToInstancesSn = groupByEqualityHash( sn, elementType ); + if ( hashToInstancesBag.size() != hashToInstancesSn.size() ) { + return false; + } + + // First iterate over the hashToInstancesBag entries to see if the number + // of List values is different for any hash value. + for ( Map.Entry> hashToInstancesBagEntry : hashToInstancesBag.entrySet() ) { + final Integer hash = hashToInstancesBagEntry.getKey(); + final List instancesBag = hashToInstancesBagEntry.getValue(); + final List instancesSn = hashToInstancesSn.get( hash ); + if ( instancesSn == null || ( instancesBag.size() != instancesSn.size() ) ) { return false; } } + + // We already know that both hashToInstancesBag and hashToInstancesSn have: + // 1) the same hash values; + // 2) the same number of values with the same hash value. + + // Now check if the number of occurrences of each element is the same. + for ( Map.Entry> hashToInstancesBagEntry : hashToInstancesBag.entrySet() ) { + final Integer hash = hashToInstancesBagEntry.getKey(); + final List instancesBag = hashToInstancesBagEntry.getValue(); + final List instancesSn = hashToInstancesSn.get( hash ); + for ( Object instance : instancesBag ) { + if ( !expectOccurrences( + instance, + instancesBag, + elementType, + countOccurrences( instance, instancesSn, elementType ) + ) ) { + return false; + } + } + } return true; } + /** + * Groups items in searchedBag according to persistence "equality" as defined in Type.isSame and Type.getHashCode + * + * @return Map of "equality" hashCode to List of objects + */ + private Map> groupByEqualityHash(List searchedBag, Type elementType) { + if ( searchedBag.isEmpty() ) { + return Collections.emptyMap(); + } + Map> map = new HashMap<>(); + for ( Object o : searchedBag ) { + map.computeIfAbsent( nullableHashCode( o, elementType ), k -> new ArrayList<>() ).add( o ); + } + return map; + } + + /** + * @param o + * @param elementType + * @return the default elementType hashcode of the object o, or null if the object is null + */ + private Integer nullableHashCode(Object o, Type elementType) { + if ( o == null ) { + return null; + } + else { + return elementType.getHashCode( o ); + } + } + @Override public boolean isSnapshotEmpty(Serializable snapshot) { return ( (Collection) snapshot ).isEmpty(); } - private int countOccurrences(Object element, List list, Type elementType) - throws HibernateException { - final Iterator iter = list.iterator(); + private int countOccurrences(Object element, List list, Type elementType) { int result = 0; - while ( iter.hasNext() ) { - if ( elementType.isSame( element, iter.next() ) ) { + for ( Object listElement : list ) { + if ( elementType.isSame( element, listElement ) ) { result++; } } return result; } + private boolean expectOccurrences(Object element, List list, Type elementType, int expected) { + int result = 0; + for ( Object listElement : list ) { + if ( elementType.isSame( element, listElement ) ) { + if ( result++ > expected ) { + return false; + } + } + } + return result == expected; + } + @Override @SuppressWarnings("unchecked") public Serializable getSnapshot(CollectionPersister persister) @@ -159,7 +272,7 @@ public Serializable disassemble(CollectionPersister persister) throws HibernateException { final int length = bag.size(); final Serializable[] result = new Serializable[length]; - for ( int i=0; ii && elementType.isSame( old, bag.get( i++ ) ) ) { - //a shortcut if its location didn't change! + if ( bag.size() > i && elementType.isSame( old, bag.get( i++ ) ) ) { + //a shortcut if its location didn't change! found = true; } else { @@ -263,7 +376,7 @@ public int size() { @Override public boolean isEmpty() { - return readSize() ? getCachedSize()==0 : bag.isEmpty(); + return readSize() ? getCachedSize() == 0 : bag.isEmpty(); } @Override @@ -307,6 +420,7 @@ public boolean add(Object object) { public boolean remove(Object o) { initialize( true ); if ( bag.remove( o ) ) { + elementRemoved = true; dirty(); return true; } @@ -325,7 +439,7 @@ public boolean containsAll(Collection c) { @Override @SuppressWarnings("unchecked") public boolean addAll(Collection values) { - if ( values.size()==0 ) { + if ( values.size() == 0 ) { return false; } if ( !isOperationQueueEnabled() ) { @@ -336,16 +450,17 @@ public boolean addAll(Collection values) { for ( Object value : values ) { queueOperation( new SimpleAdd( value ) ); } - return values.size()>0; + return values.size() > 0; } } @Override @SuppressWarnings("unchecked") public boolean removeAll(Collection c) { - if ( c.size()>0 ) { + if ( c.size() > 0 ) { initialize( true ); if ( bag.removeAll( c ) ) { + elementRemoved = true; dirty(); return true; } @@ -379,7 +494,7 @@ public void clear() { } else { initialize( true ); - if ( ! bag.isEmpty() ) { + if ( !bag.isEmpty() ) { bag.clear(); dirty(); } @@ -388,7 +503,7 @@ public void clear() { @Override public Object getIndex(Object entry, int i, CollectionPersister persister) { - throw new UnsupportedOperationException("Bags don't have indexes"); + throw new UnsupportedOperationException( "Bags don't have indexes" ); } @Override @@ -501,7 +616,7 @@ public List subList(int start, int end) { @Override public boolean entryExists(Object entry, int i) { - return entry!=null; + return entry != null; } @Override @@ -515,8 +630,9 @@ public String toString() { * JVM instance comparison to do the equals. * The semantic is broken not to have to initialize a * collection for a simple equals() operation. - * @see java.lang.Object#equals(java.lang.Object) * + * @see java.lang.Object#equals(java.lang.Object) + *

    * {@inheritDoc} */ @Override @@ -542,7 +658,7 @@ public Object getAddedInstance() { @Override public Object getOrphan() { - throw new UnsupportedOperationException("queued clear cannot be used with orphan delete"); + throw new UnsupportedOperationException( "queued clear cannot be used with orphan delete" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java index 8938d9799588..217c330cf38d 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java @@ -18,6 +18,7 @@ import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -39,6 +40,9 @@ public class PersistentIdentifierBag extends AbstractPersistentCollection implem protected List values; protected Map identifiers; + // The Collection provided to a PersistentIdentifierBag constructor, + private Collection providedValues; + /** * Constructs a PersistentIdentifierBag. This form needed for SOAP libraries, etc */ @@ -55,6 +59,17 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentIdentifierBag. + * + * @param session The session + * @deprecated {@link #PersistentIdentifierBag(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentIdentifierBag(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentIdentifierBag. * @@ -64,6 +79,7 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session) { @SuppressWarnings("unchecked") public PersistentIdentifierBag(SharedSessionContractImplementor session, Collection coll) { super( session ); + providedValues = coll; if (coll instanceof List) { values = (List) coll; } @@ -78,6 +94,18 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session, Collect identifiers = new HashMap<>(); } + /** + * Constructs a PersistentIdentifierBag. + * + * @param session The session + * @param coll The base elements + * @deprecated {@link #PersistentIdentifierBag(SharedSessionContractImplementor, Collection)} should be used instead. + */ + @Deprecated + public PersistentIdentifierBag(SessionImplementor session, Collection coll) { + this( (SharedSessionContractImplementor) session, coll ); + } + @Override public void initializeFromCache(CollectionPersister persister, Serializable disassembled, Object owner) throws HibernateException { @@ -100,7 +128,12 @@ public Object getIdentifier(Object entry, int i) { @Override public boolean isWrapper(Object collection) { - return values==collection; + return values == collection; + } + + @Override + public boolean isDirectlyProvidedCollection(Object collection) { + return isDirectlyAccessible() && providedValues == collection; } @Override @@ -150,6 +183,7 @@ public boolean remove(Object o) { if ( index >= 0 ) { beforeRemove( index ); values.remove( index ); + elementRemoved = true; dirty(); return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java index a1825b598f1c..8a0015665224 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java @@ -16,6 +16,7 @@ import java.util.ListIterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -46,6 +47,17 @@ public PersistentList(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentList. + * + * @param session The session + * @deprecated {@link #PersistentList(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentList(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentList. * @@ -59,6 +71,18 @@ public PersistentList(SharedSessionContractImplementor session, List list) { setDirectlyAccessible( true ); } + /** + * Constructs a PersistentList. + * + * @param session The session + * @param list The raw list + * @deprecated {@link #PersistentList(SharedSessionContractImplementor, List)} should be used instead. + */ + @Deprecated + public PersistentList(SessionImplementor session, List list) { + this( (SharedSessionContractImplementor) session, list ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { @@ -163,6 +187,7 @@ public boolean remove(Object value) { if ( exists == null ) { initialize( true ); if ( list.remove( value ) ) { + elementRemoved = true; dirty(); return true; } @@ -171,6 +196,7 @@ public boolean remove(Object value) { } } else if ( exists ) { + elementRemoved = true; queueOperation( new SimpleRemove( value ) ); return true; } @@ -222,6 +248,7 @@ public boolean removeAll(Collection coll) { if ( coll.size()>0 ) { initialize( true ); if ( list.removeAll( coll ) ) { + elementRemoved = true; dirty(); return true; } @@ -298,8 +325,10 @@ public Object remove(int index) { throw new ArrayIndexOutOfBoundsException( "negative index" ); } final Object old = isPutQueueEnabled() ? readElementByIndex( index ) : UNKNOWN; + elementRemoved = true; if ( old == UNKNOWN ) { write(); + dirty(); return list.remove( index ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java index 6a0dbb1080d7..0976a3cf7e1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java @@ -18,6 +18,7 @@ import java.util.Set; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -54,6 +55,17 @@ public PersistentMap(SharedSessionContractImplementor session) { super( session ); } + /** + * Instantiates a lazy map (the underlying map is un-initialized). + * + * @param session The session to which this map will belong. + * @deprecated {@link #PersistentMap(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentMap(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Instantiates a non-lazy map (the underlying map is constructed * from the incoming map reference). @@ -68,6 +80,19 @@ public PersistentMap(SharedSessionContractImplementor session, Map map) { setDirectlyAccessible( true ); } + /** + * Instantiates a non-lazy map (the underlying map is constructed + * from the incoming map reference). + * + * @param session The session to which this map will belong. + * @param map The underlying map data. + * @deprecated {@link #PersistentMap(SharedSessionContractImplementor, Map)} should be used instead. + */ + @Deprecated + public PersistentMap(SessionImplementor session, Map map) { + this( (SharedSessionContractImplementor) session, map ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { @@ -178,6 +203,7 @@ public Object remove(Object key) { if ( isPutQueueEnabled() ) { final Object old = readElementByIndex( key ); if ( old != UNKNOWN ) { + elementRemoved = true; queueOperation( new Remove( key, old ) ); return old; } @@ -185,6 +211,7 @@ public Object remove(Object key) { // TODO : safe to interpret "map.remove(key) == null" as non-dirty? initialize( true ); if ( map.containsKey( key ) ) { + elementRemoved = true; dirty(); } return map.remove( key ); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java index 47e5b8930ff9..0f9a77f72f52 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java @@ -17,6 +17,7 @@ import java.util.Set; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -54,6 +55,17 @@ public PersistentSet(SharedSessionContractImplementor session) { super( session ); } + /** + * Instantiates a lazy set (the underlying set is un-initialized). + * + * @param session The session to which this set will belong. + * @deprecated {@link #PersistentSet(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSet(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Instantiates a non-lazy set (the underlying set is constructed * from the incoming set reference). @@ -72,6 +84,19 @@ public PersistentSet(SharedSessionContractImplementor session, java.util.Set set setDirectlyAccessible( true ); } + /** + * Instantiates a non-lazy set (the underlying set is constructed + * from the incoming set reference). + * + * @param session The session to which this set will belong. + * @param set The underlying set data. + * @deprecated {@link #PersistentSet(SharedSessionContractImplementor, java.util.Set)} should be used instead. + */ + @Deprecated + public PersistentSet(SessionImplementor session, java.util.Set set) { + this( (SharedSessionContractImplementor) session, set ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { @@ -206,6 +231,7 @@ public boolean remove(Object value) { if ( exists == null ) { initialize( true ); if ( set.remove( value ) ) { + elementRemoved = true; dirty(); return true; } @@ -214,6 +240,7 @@ public boolean remove(Object value) { } } else if ( exists ) { + elementRemoved = true; queueOperation( new SimpleRemove( value ) ); return true; } @@ -266,6 +293,7 @@ public boolean removeAll(Collection coll) { if ( coll.size() > 0 ) { initialize( true ); if ( set.removeAll( coll ) ) { + elementRemoved = true; dirty(); return true; } @@ -326,8 +354,8 @@ public void beginRead() { public boolean endRead() { set.addAll( tempList ); tempList = null; - setInitialized(); - return true; + // ensure that operationQueue is considered + return super.endRead(); } @Override @@ -384,7 +412,7 @@ public boolean needsInserting(Object entry, int i, Type elemType) throws Hiberna // note that it might be better to iterate the snapshot but this is safe, // assuming the user implements equals() properly, as required by the Set // contract! - return oldValue == null || elemType.isDirty( oldValue, entry, getSession() ); + return ( oldValue == null && entry != null ) || elemType.isDirty( oldValue, entry, getSession() ); } @Override @@ -434,7 +462,7 @@ public int hashCode() { @Override @SuppressWarnings("unchecked") public boolean entryExists(Object key, int i) { - return true; + return key != null; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java index b3e67b28da41..f47825d0963f 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java @@ -46,6 +46,17 @@ public PersistentSortedMap(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentSortedMap. + * + * @param session The session + * @deprecated {@link #PersistentSortedMap(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSortedMap(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentSortedMap. * @@ -57,6 +68,18 @@ public PersistentSortedMap(SharedSessionContractImplementor session, SortedMap m comparator = map.comparator(); } + /** + * Constructs a PersistentSortedMap. + * + * @param session The session + * @param map The underlying map data + * @deprecated {@link #PersistentSortedMap(SharedSessionContractImplementor, SortedMap)} should be used instead. + */ + @Deprecated + public PersistentSortedMap(SessionImplementor session, SortedMap map) { + this( (SharedSessionContractImplementor) session, map ); + } + @SuppressWarnings({"unchecked", "UnusedParameters"}) protected Serializable snapshot(BasicCollectionPersister persister, EntityMode entityMode) throws HibernateException { final TreeMap clonedMap = new TreeMap( comparator ); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java index 75d3606bbdeb..b52e6bff31f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java @@ -43,6 +43,17 @@ public PersistentSortedSet(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentSortedSet + * + * @param session The session + * @deprecated {@link #PersistentSortedSet(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSortedSet(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentSortedSet * @@ -54,6 +65,18 @@ public PersistentSortedSet(SharedSessionContractImplementor session, SortedSet s comparator = set.comparator(); } + /** + * Constructs a PersistentSortedSet + * + * @param session The session + * @param set The underlying set data + * @deprecated {@link #PersistentSortedSet(SharedSessionContractImplementor, SortedSet)} should be used instead. + */ + @Deprecated + public PersistentSortedSet(SessionImplementor session, SortedSet set) { + this( (SharedSessionContractImplementor) session, set ); + } + @SuppressWarnings({"unchecked", "UnusedParameters"}) protected Serializable snapshot(BasicCollectionPersister persister, EntityMode entityMode) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java index 97debb566b59..24032cee229b 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java @@ -83,7 +83,7 @@ public interface PersistentCollection { * database state is now synchronized with the memory state. */ void postAction(); - + /** * Return the user-visible collection (or array) instance * @@ -92,19 +92,19 @@ public interface PersistentCollection { Object getValue(); /** - * Called just beforeQuery reading any rows from the JDBC result set + * Called just before reading any rows from the JDBC result set */ void beginRead(); /** - * Called afterQuery reading all rows from the JDBC result set + * Called after reading all rows from the JDBC result set * * @return Whether to end the read. */ boolean endRead(); - + /** - * Called afterQuery initializing from cache + * Called after initializing from cache * * @return ?? */ @@ -184,18 +184,18 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The identifier value */ Object getIdentifier(Object entry, int i); - + /** * Get the index of the given collection entry * * @param entry The collection entry/element * @param i The assumed index - * @param persister it was more elegant beforeQuery we added this... + * @param persister it was more elegant before we added this... * * @return The index value */ Object getIndex(Object entry, int i, CollectionPersister persister); - + /** * Get the value of the given collection entry. Generally the given entry parameter value will just be returned. * Might get a different value for a duplicate entries in a Set. @@ -205,7 +205,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The corresponding object that is part of the collection elements. */ Object getElement(Object entry); - + /** * Get the snapshot value of the given collection entry * @@ -217,11 +217,11 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri Object getSnapshotElement(Object entry, int i); /** - * Called beforeQuery any elements are read into the collection, + * Called before any elements are read into the collection, * allowing appropriate initializations to occur. * * @param persister The underlying collection persister. - * @param anticipatedSize The anticipated size of the collection afterQuery initialization is complete. + * @param anticipatedSize The anticipated size of the collection after initialization is complete. */ void beforeInitialize(CollectionPersister persister, int anticipatedSize); @@ -243,7 +243,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return {@code true} if the given snapshot is empty */ boolean isSnapshotEmpty(Serializable snapshot); - + /** * Disassemble the collection to get it ready for the cache * @@ -355,7 +355,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The iterator */ Iterator queuedAdditionIterator(); - + /** * Get the "queued" orphans * @@ -364,58 +364,77 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The orphaned elements */ Collection getQueuedOrphans(String entityName); - + /** * Get the current collection key value * * @return the current collection key value */ Serializable getKey(); - + /** * Get the current role name * * @return the collection role name */ String getRole(); - + /** * Is the collection unreferenced? * * @return {@code true} if the collection is no longer referenced by an owner */ boolean isUnreferenced(); - + /** * Is the collection dirty? Note that this is only - * reliable during the flush cycle, afterQuery the + * reliable during the flush cycle, after the * collection elements are dirty checked against * the snapshot. * * @return {@code true} if the collection is dirty */ boolean isDirty(); - + + default boolean isElementRemoved(){ + return false; + } + + /** + * Was {@code collection} provided directly to this PersistentCollection + * (i.e., provided as an argument to a constructor)? + *

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

    + * @param collection The collection + * @return true, if {@code collection} was provided directly to this + * PersistentCollection; false, otherwise. + */ + default boolean isDirectlyProvidedCollection(Object collection) { + return isDirectlyAccessible() && isWrapper( collection ); + } + /** - * Clear the dirty flag, afterQuery flushing changes + * Clear the dirty flag, after flushing changes * to the database. */ void clearDirty(); - + /** * Get the snapshot cached by the collection instance * * @return The internally stored snapshot state */ Serializable getStoredSnapshot(); - + /** * Mark the collection as dirty */ void dirty(); - + /** - * Called beforeQuery inserting rows, to ensure that any surrogate keys + * Called before inserting rows, to ensure that any surrogate keys * are fully generated * * @param persister The collection persister @@ -423,7 +442,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri void preInsert(CollectionPersister persister); /** - * Called afterQuery inserting a row, to fetch the natively generated id + * Called after inserting a row, to fetch the natively generated id * * @param persister The collection persister * @param entry The collection element just inserted @@ -440,5 +459,5 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The orphans */ Collection getOrphans(Serializable snapshot, String entityName); - + } diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java index 902d648db78c..903f6aca6103 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java @@ -25,7 +25,7 @@ /** * An implementation of {@link org.hibernate.context.spi.CurrentSessionContext} which scopes the notion - * of a current session to a JTA transaction. Because JTA gives us a nice tie-in to clean up afterQuery + * of a current session to a JTA transaction. Because JTA gives us a nice tie-in to clean up after * ourselves, this implementation will generate Sessions as needed provided a JTA transaction is in * effect. If a session is not already associated with the current JTA transaction at the time * {@link #currentSession()} is called, a new session will be opened and it will be associated with that @@ -36,7 +36,7 @@ * {@link org.hibernate.cfg.Environment#AUTO_CLOSE_SESSION auto-close} attributes set to true, meaning * that the Session will be automatically flushed and closed as part of the lifecycle for the JTA * transaction to which it is associated. Additionally, it will also be configured to aggressively - * release JDBC connections afterQuery each statement is executed. These settings are governed by the + * release JDBC connections after each statement is executed. These settings are governed by the * {@link #isAutoFlushEnabled()}, {@link #isAutoCloseEnabled()}, and {@link #getConnectionReleaseMode()} * methods; these are provided (along with the {@link #buildOrObtainSession()} method) for easier * subclassing for custom JTA-based session tracking logic (like maybe long-session semantics). @@ -118,7 +118,7 @@ public Session currentSession() throws HibernateException { } /** - * Builds a {@link org.hibernate.context.internal.JTASessionContext.CleanupSync} capable of cleaning up the the current session map as an afterQuery transaction + * Builds a {@link org.hibernate.context.internal.JTASessionContext.CleanupSync} capable of cleaning up the the current session map as an after transaction * callback. * * @param transactionIdentifier The transaction identifier under which the current session is registered. diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/ManagedSessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/ManagedSessionContext.java index b6da073da623..cd954eb172e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/ManagedSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/ManagedSessionContext.java @@ -103,12 +103,12 @@ public static Session unbind(SessionFactory factory) { } private static Session existingSession(SessionFactory factory) { - final Map sessionMap = sessionMap(); + final Map sessionMap = sessionMap(); if ( sessionMap == null ) { return null; } else { - return (Session) sessionMap.get( factory ); + return sessionMap.get( factory ); } } @@ -116,7 +116,7 @@ protected static Map sessionMap() { return sessionMap( false ); } - private static synchronized Map sessionMap(boolean createMap) { + private static Map sessionMap(boolean createMap) { Map sessionMap = CONTEXT_TL.get(); if ( sessionMap == null && createMap ) { sessionMap = new HashMap(); @@ -125,11 +125,11 @@ private static synchronized Map sessionMap(boolean creat return sessionMap; } - private static synchronized void doCleanup() { + private static void doCleanup() { final Map sessionMap = sessionMap( false ); if ( sessionMap != null ) { if ( sessionMap.isEmpty() ) { - CONTEXT_TL.set( null ); + CONTEXT_TL.remove(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java index 0a11b2018eb2..b87cab0e14a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java @@ -23,6 +23,7 @@ import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; import org.hibernate.context.spi.AbstractCurrentSessionContext; import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -38,9 +39,9 @@ * session by the current thread of execution. Unlike the JTA counterpart, threads do not give us a nice * hook to perform any type of cleanup making it questionable for this impl to actually generate Session * instances. In the interest of usability, it was decided to have this default impl actually generate - * a session upon first request and then clean it up afterQuery the {@link org.hibernate.Transaction} + * a session upon first request and then clean it up after the {@link org.hibernate.Transaction} * associated with that session is committed/rolled-back. In order for ensuring that happens, the - * sessions generated here are unusable until afterQuery {@link Session#beginTransaction()} has been + * sessions generated here are unusable until after {@link Session#beginTransaction()} has been * called. If close() is called on a session managed by this class, it will be automatically * unbound. * @@ -54,6 +55,7 @@ * subclassing (for long-running session scenarios, for example). * * @author Steve Ebersole + * @author Sanne Grinovero */ public class ThreadLocalSessionContext extends AbstractCurrentSessionContext { private static final CoreMessageLogger LOG = Logger.getMessageLogger( @@ -74,7 +76,7 @@ public class ThreadLocalSessionContext extends AbstractCurrentSessionContext { * the possibility for multiple SessionFactory instances being used during execution * of the given thread. */ - private static final ThreadLocal CONTEXT_TL = new ThreadLocal(); + private static final ThreadLocal> CONTEXT_TL = ThreadLocal.withInitial( HashMap::new ); /** * Constructs a ThreadLocal @@ -107,12 +109,10 @@ public final Session currentSession() throws HibernateException { private boolean needsWrapping(Session session) { // try to make sure we don't wrap and already wrapped session - if ( session != null ) { - if ( Proxy.isProxyClass( session.getClass() ) ) { - final InvocationHandler invocationHandler = Proxy.getInvocationHandler( session ); - if ( invocationHandler != null && TransactionProtectionWrapper.class.isInstance( invocationHandler ) ) { - return false; - } + if ( Proxy.isProxyClass( session.getClass() ) ) { + final InvocationHandler invocationHandler = Proxy.getInvocationHandler( session ); + if ( invocationHandler != null && TransactionProtectionWrapper.class.isInstance( invocationHandler ) ) { + return false; } } return true; @@ -194,28 +194,32 @@ protected Session wrap(Session session) { */ public static void bind(org.hibernate.Session session) { final SessionFactory factory = session.getSessionFactory(); - cleanupAnyOrphanedSession( factory ); doBind( session, factory ); } - private static void cleanupAnyOrphanedSession(SessionFactory factory) { - final Session orphan = doUnbind( factory, false ); + private static void terminateOrphanedSession(Session orphan) { if ( orphan != null ) { LOG.alreadySessionBound(); try { - if ( orphan.getTransaction() != null && orphan.getTransaction().getStatus() == TransactionStatus.ACTIVE ) { + final Transaction orphanTransaction = orphan.getTransaction(); + if ( orphanTransaction != null && orphanTransaction.getStatus() == TransactionStatus.ACTIVE ) { try { - orphan.getTransaction().rollback(); + orphanTransaction.rollback(); } catch( Throwable t ) { LOG.debug( "Unable to rollback transaction for orphaned session", t ); } } - orphan.close(); } - catch( Throwable t ) { - LOG.debug( "Unable to close orphaned session", t ); + finally { + try { + orphan.close(); + } + catch( Throwable t ) { + LOG.debug( "Unable to close orphaned session", t ); + } } + } } @@ -230,35 +234,25 @@ public static Session unbind(SessionFactory factory) { } private static Session existingSession(SessionFactory factory) { - final Map sessionMap = sessionMap(); - if ( sessionMap == null ) { - return null; - } - return (Session) sessionMap.get( factory ); + return sessionMap().get( factory ); } - protected static Map sessionMap() { + protected static Map sessionMap() { return CONTEXT_TL.get(); } @SuppressWarnings({"unchecked"}) private static void doBind(org.hibernate.Session session, SessionFactory factory) { - Map sessionMap = sessionMap(); - if ( sessionMap == null ) { - sessionMap = new HashMap(); - CONTEXT_TL.set( sessionMap ); - } - sessionMap.put( factory, session ); + Session orphanedPreviousSession = sessionMap().put( factory, session ); + terminateOrphanedSession( orphanedPreviousSession ); } private static Session doUnbind(SessionFactory factory, boolean releaseMapIfEmpty) { - Session session = null; - final Map sessionMap = sessionMap(); - if ( sessionMap != null ) { - session = (Session) sessionMap.remove( factory ); - if ( releaseMapIfEmpty && sessionMap.isEmpty() ) { - CONTEXT_TL.set( null ); - } + final Map sessionMap = sessionMap(); + final Session session = sessionMap.remove( factory ); + if ( releaseMapIfEmpty && sessionMap.isEmpty() ) { + //Do not use set(null) as it would prevent the initialValue to be invoked again in case of need. + CONTEXT_TL.remove(); } return session; } @@ -348,7 +342,8 @@ else if ( "reconnect".equals( methodName ) || "disconnect".equals( methodName ) LOG.tracef( "Allowing invocation [%s] to proceed to real (non-transacted) session - deprecated methods", methodName ); } else { - throw new HibernateException( methodName + " is not valid without active transaction" ); + throw new HibernateException( "Calling method '" + methodName + "' is not valid without an active transaction (Current status: " + + realSession.getTransaction().getStatus() + ")" ); } } LOG.tracef( "Allowing proxy invocation [%s] to proceed to real session", methodName ); diff --git a/hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java index 59ed888c1e05..75b223ab12ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java @@ -6,11 +6,12 @@ */ package org.hibernate.context.spi; +import java.util.Objects; + import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.context.TenantIdentifierMismatchException; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.compare.EqualsHelper; /** * Base support for {@link CurrentSessionContext} implementors. @@ -46,7 +47,7 @@ protected void validateExistingSession(Session existingSession) { final CurrentTenantIdentifierResolver resolver = factory.getCurrentTenantIdentifierResolver(); if ( resolver != null && resolver.validateExistingCurrentSessions() ) { final String current = resolver.resolveCurrentTenantIdentifier(); - if ( ! EqualsHelper.equals( existingSession.getTenantIdentifier(), current ) ) { + if ( !Objects.equals( existingSession.getTenantIdentifier(), current ) ) { throw new TenantIdentifierMismatchException( String.format( "Reported current tenant identifier [%s] did not match tenant identifier from " + diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/BetweenExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/BetweenExpression.java index 2a339af575d9..cd295af32e27 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/BetweenExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/BetweenExpression.java @@ -18,33 +18,33 @@ */ public class BetweenExpression implements Criterion { private final String propertyName; - private final Object lo; - private final Object hi; + private final Object low; + private final Object high; - protected BetweenExpression(String propertyName, Object lo, Object hi) { + protected BetweenExpression(String propertyName, Object low, Object high) { this.propertyName = propertyName; - this.lo = lo; - this.hi = hi; + this.low = low; + this.high = high; } @Override public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { final String[] columns = criteriaQuery.findColumns( propertyName, criteria ); final String[] expressions = StringHelper.suffix( columns, " between ? and ?" ); - return StringHelper.join( " and ", expressions ); + return String.join( " and ", expressions ); } @Override public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { return new TypedValue[] { - criteriaQuery.getTypedValue( criteria, propertyName, lo ), - criteriaQuery.getTypedValue( criteria, propertyName, hi ) + criteriaQuery.getTypedValue( criteria, propertyName, low), + criteriaQuery.getTypedValue( criteria, propertyName, high) }; } @Override public String toString() { - return propertyName + " between " + lo + " and " + hi; + return propertyName + " between " + low + " and " + high; } } diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Criterion.java b/hibernate-core/src/main/java/org/hibernate/criterion/Criterion.java index 0be4da6108f9..b790a514ea9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Criterion.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Criterion.java @@ -5,6 +5,7 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.criterion; + import java.io.Serializable; import org.hibernate.Criteria; diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/DetachedCriteria.java b/hibernate-core/src/main/java/org/hibernate/criterion/DetachedCriteria.java index 1f166ae0d54d..f81a5fcfec03 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/DetachedCriteria.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/DetachedCriteria.java @@ -274,7 +274,7 @@ public DetachedCriteria createAlias(String associationPath, String alias, int jo } /** - * Creates an nested DetachedCriteria representing the association path. + * Creates a nested DetachedCriteria representing the association path. * * @param associationPath The association path * @param alias The alias to apply to that association path @@ -286,7 +286,7 @@ public DetachedCriteria createCriteria(String associationPath, String alias) { } /** - * Creates an nested DetachedCriteria representing the association path. + * Creates a nested DetachedCriteria representing the association path. * * @param associationPath The association path * @@ -297,7 +297,7 @@ public DetachedCriteria createCriteria(String associationPath) { } /** - * Creates an nested DetachedCriteria representing the association path, specifying the type of join to use. + * Creates a nested DetachedCriteria representing the association path, specifying the type of join to use. * * @param associationPath The association path * @param joinType The type of join to use @@ -309,7 +309,7 @@ public DetachedCriteria createCriteria(String associationPath, JoinType joinType } /** - * Creates an nested DetachedCriteria representing the association path, specifying the type of join to use. + * Creates a nested DetachedCriteria representing the association path, specifying the type of join to use. * * @param associationPath The association path * @param alias The alias to associate with this "join". @@ -322,7 +322,7 @@ public DetachedCriteria createCriteria(String associationPath, String alias, Joi } /** - * Creates an nested DetachedCriteria representing the association path, specifying the type of join to use and + * Creates a nested DetachedCriteria representing the association path, specifying the type of join to use and * an additional join restriction. * * @param associationPath The association path diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierEqExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierEqExpression.java index ca74274b202c..0be51d5ce173 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierEqExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierEqExpression.java @@ -33,7 +33,7 @@ protected IdentifierEqExpression(Object value) { public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) { final String[] columns = criteriaQuery.getIdentifierColumns( criteria ); - String result = StringHelper.join( " and ", StringHelper.suffix( columns, " = ?" ) ); + String result = String.join( " and ", StringHelper.suffix( columns, " = ?" ) ); if ( columns.length > 1) { result = '(' + result + ')'; } diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierProjection.java b/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierProjection.java index 497ddf3e151b..5d45c4d6dce0 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierProjection.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/IdentifierProjection.java @@ -7,7 +7,6 @@ package org.hibernate.criterion; import org.hibernate.Criteria; -import org.hibernate.internal.util.StringHelper; import org.hibernate.type.Type; /** @@ -67,7 +66,7 @@ public String toGroupSqlString(Criteria criteria, CriteriaQuery criteriaQuery) { return super.toGroupSqlString( criteria, criteriaQuery ); } else { - return StringHelper.join( ", ", criteriaQuery.getIdentifierColumns( criteria ) ); + return String.join( ", ", criteriaQuery.getIdentifierColumns( criteria ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/InExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/InExpression.java index 5a5edefe205e..d255529399e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/InExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/InExpression.java @@ -49,14 +49,14 @@ public String toSqlString( Criteria criteria, CriteriaQuery criteriaQuery ) { final String params = values.length > 0 ? StringHelper.repeat( singleValueParam + ", ", values.length - 1 ) + singleValueParam : ""; - String cols = StringHelper.join( ", ", columns ); + String cols = String.join( ", ", columns ); if ( columns.length > 1 ) { cols = '(' + cols + ')'; } return cols + " in (" + params + ')'; } else { - String cols = " ( " + StringHelper.join( " = ? and ", columns ) + "= ? ) "; + String cols = " ( " + String.join( " = ? and ", columns ) + "= ? ) "; cols = values.length > 0 ? StringHelper.repeat( cols + "or ", values.length - 1 ) + cols : ""; diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/MatchMode.java b/hibernate-core/src/main/java/org/hibernate/criterion/MatchMode.java index 4dec81af0bc3..73caa7b4c823 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/MatchMode.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/MatchMode.java @@ -7,7 +7,7 @@ package org.hibernate.criterion; /** - * Represents an strategy for matching strings using "like". + * Represents a strategy for matching strings using "like". * * @author Gavin King * @see Example#enableLike(MatchMode) diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/NotNullExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/NotNullExpression.java index d68ec54c516c..ce4e4ea57bb3 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/NotNullExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/NotNullExpression.java @@ -28,7 +28,7 @@ protected NotNullExpression(String propertyName) { @Override public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { final String[] columns = criteriaQuery.findColumns( propertyName, criteria ); - String result = StringHelper.join( + String result = String.join( " or ", StringHelper.suffix( columns, " is not null" ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/NullExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/NullExpression.java index 639cfef9a834..8a28f717c4f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/NullExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/NullExpression.java @@ -34,7 +34,7 @@ protected NullExpression(String propertyName) { @Override public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { final String[] columns = criteriaQuery.findColumns( propertyName, criteria ); - String result = StringHelper.join( + String result = String.join( " and ", StringHelper.suffix( columns, " is null" ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ParameterInfoCollector.java b/hibernate-core/src/main/java/org/hibernate/criterion/ParameterInfoCollector.java new file mode 100644 index 000000000000..419543f0a687 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/criterion/ParameterInfoCollector.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 . + */ +package org.hibernate.criterion; + +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public interface ParameterInfoCollector { + void addNamedParameter(String name, Type type); + void addPositionalParameter(int label, Type type); +} diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Projection.java b/hibernate-core/src/main/java/org/hibernate/criterion/Projection.java old mode 100755 new mode 100644 index f04e1d4b1ad5..e41c9584ea38 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Projection.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Projection.java @@ -29,7 +29,7 @@ public interface Projection extends Serializable { * Render the SQL fragment to be used in the SELECT clause. * * @param criteria The local criteria to which this project is attached (for resolution). - * @param position The number of columns rendered in the SELECT clause beforeQuery this projection. Generally + * @param position The number of columns rendered in the SELECT clause before this projection. Generally * speaking this is useful to ensure uniqueness of the individual columns aliases. * @param criteriaQuery The overall criteria query instance. * @return The SQL fragment to plug into the SELECT diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ProjectionList.java b/hibernate-core/src/main/java/org/hibernate/criterion/ProjectionList.java old mode 100755 new mode 100644 index c34fba82e137..f54b6b889b61 --- a/hibernate-core/src/main/java/org/hibernate/criterion/ProjectionList.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/ProjectionList.java @@ -57,7 +57,7 @@ public ProjectionList add(Projection projection) { } /** - * Adds a projection to this list of projections afterQuery wrapping it with an alias + * Adds a projection to this list of projections after wrapping it with an alias * * @param projection The projection to add * @param alias The alias to apply to the projection diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/PropertiesSubqueryExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/PropertiesSubqueryExpression.java index a7a26574e397..e6b9c4b6fa55 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/PropertiesSubqueryExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/PropertiesSubqueryExpression.java @@ -7,7 +7,6 @@ package org.hibernate.criterion; import org.hibernate.Criteria; -import org.hibernate.internal.util.StringHelper; /** * A comparison between several properties value in the outer query and the result of a multicolumn subquery. @@ -29,7 +28,7 @@ protected String toLeftSqlString(Criteria criteria, CriteriaQuery outerQuery) { for ( int i = 0; i < sqlColumnNames.length; ++i ) { sqlColumnNames[i] = outerQuery.getColumn( criteria, propertyNames[i] ); } - left.append( StringHelper.join( ", ", sqlColumnNames ) ); + left.append( String.join( ", ", sqlColumnNames ) ); return left.append( ")" ).toString(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Property.java b/hibernate-core/src/main/java/org/hibernate/criterion/Property.java index 33f5c8069fa0..9d1478fd4012 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Property.java @@ -132,7 +132,7 @@ public Criterion eqOrIsNull(Object value) { } /** - * Creates an non-equality restriction. + * Creates a non-equality restriction. * * @param value The value to check against * @@ -145,7 +145,7 @@ public SimpleExpression ne(Object value) { } /** - * Creates an non-equality restriction capable of also rendering as IS NOT NULL if the given value is {@code null} + * Creates a non-equality restriction capable of also rendering as IS NOT NULL if the given value is {@code null} * * @param value The value to check against * @@ -241,7 +241,7 @@ public PropertyExpression eqProperty(String other) { } /** - * Creates an non-equality restriction between 2 properties + * Creates a non-equality restriction between 2 properties * * @param other The other property to compare against * @@ -255,7 +255,7 @@ public PropertyExpression neProperty(Property other) { } /** - * Creates an non-equality restriction between 2 properties + * Creates a non-equality restriction between 2 properties * * @param other The other property to compare against * @@ -269,7 +269,7 @@ public PropertyExpression neProperty(String other) { } /** - * Creates an less-than-or-equal-to restriction between 2 properties + * Creates a less-than-or-equal-to restriction between 2 properties * * @param other The other property to compare against * @@ -283,7 +283,7 @@ public PropertyExpression leProperty(Property other) { } /** - * Creates an less-than-or-equal-to restriction between 2 properties + * Creates a less-than-or-equal-to restriction between 2 properties * * @param other The other property to compare against * @@ -297,7 +297,7 @@ public PropertyExpression leProperty(String other) { } /** - * Creates an greater-than-or-equal-to restriction between 2 properties + * Creates a greater-than-or-equal-to restriction between 2 properties * * @param other The other property to compare against * @@ -311,7 +311,7 @@ public PropertyExpression geProperty(Property other) { } /** - * Creates an greater-than-or-equal-to restriction between 2 properties + * Creates a greater-than-or-equal-to restriction between 2 properties * * @param other The other property to compare against * @@ -325,7 +325,7 @@ public PropertyExpression geProperty(String other) { } /** - * Creates an less-than restriction between 2 properties + * Creates a less-than restriction between 2 properties * * @param other The other property to compare against * @@ -339,7 +339,7 @@ public PropertyExpression ltProperty(Property other) { } /** - * Creates an less-than restriction between 2 properties + * Creates a less-than restriction between 2 properties * * @param other The other property to compare against * @@ -353,7 +353,7 @@ public PropertyExpression ltProperty(String other) { } /** - * Creates an greater-than restriction between 2 properties + * Creates a greater-than restriction between 2 properties * * @param other The other property to compare against * @@ -367,7 +367,7 @@ public PropertyExpression gtProperty(Property other) { } /** - * Creates an greater-than restriction between 2 properties + * Creates a greater-than restriction between 2 properties * * @param other The other property to compare against * diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/PropertyExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/PropertyExpression.java index a92a77ee54ef..5f2c546404c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/PropertyExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/PropertyExpression.java @@ -40,7 +40,7 @@ public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws final String[] comparisons = StringHelper.add( lhsColumns, getOp(), rhsColumns ); if ( comparisons.length > 1 ) { - return '(' + StringHelper.join( " and ", comparisons ) + ')'; + return '(' + String.join( " and ", comparisons ) + ')'; } else { return comparisons[0]; diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/PropertyProjection.java b/hibernate-core/src/main/java/org/hibernate/criterion/PropertyProjection.java index df3f027eacdd..a34e70ae4269 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/PropertyProjection.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/PropertyProjection.java @@ -7,7 +7,6 @@ package org.hibernate.criterion; import org.hibernate.Criteria; import org.hibernate.HibernateException; -import org.hibernate.internal.util.StringHelper; import org.hibernate.type.Type; /** @@ -65,7 +64,7 @@ public String toGroupSqlString(Criteria criteria, CriteriaQuery criteriaQuery) t return super.toGroupSqlString( criteria, criteriaQuery ); } else { - return StringHelper.join( ", ", criteriaQuery.getColumns( propertyName, criteria ) ); + return String.join( ", ", criteriaQuery.getColumns( propertyName, criteria ) ); } } 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 160e3a6bcb71..264f7f8a9d3a 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java @@ -225,15 +225,15 @@ public static SimpleExpression ge(String propertyName, Object value) { * Apply a "between" constraint to the named property * * @param propertyName The name of the property - * @param lo The low value - * @param hi The high value + * @param low The low value + * @param high The high value * * @return The Criterion * * @see BetweenExpression */ - public static Criterion between(String propertyName, Object lo, Object hi) { - return new BetweenExpression( propertyName, lo, hi ); + public static Criterion between(String propertyName, Object low, Object high) { + return new BetweenExpression( propertyName, low, high ); } /** 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 0752f41e61c3..bf94c4f40382 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -6,64 +6,121 @@ */ package org.hibernate.dialect; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; import java.io.FilterReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; import java.sql.Types; -import java.util.Iterator; -import java.util.Map; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.MappingException; import org.hibernate.ScrollMode; +import org.hibernate.boot.model.TypeContributions; +import org.hibernate.boot.model.naming.Identifier; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.function.AnsiTrimFunction; 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.identity.HANAIdentityColumnSupport; +import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.AbstractLimitHandler; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.LimitHelper; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.config.spi.ConfigurationService.Converter; +import org.hibernate.engine.config.spi.StandardConverters; +import org.hibernate.engine.jdbc.BinaryStream; +import org.hibernate.engine.jdbc.BlobImplementer; import org.hibernate.engine.jdbc.CharacterStream; import org.hibernate.engine.jdbc.ClobImplementer; import org.hibernate.engine.jdbc.NClobImplementer; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords; +import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; +import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.spi.RowSelection; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.SQLGrammarException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; -import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; -import org.hibernate.hql.spi.id.local.AfterUseAction; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.mapping.Table; +import org.hibernate.procedure.internal.StandardCallableStatementSupport; +import org.hibernate.procedure.spi.CallableStatementSupport; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.tool.schema.internal.StandardTableExporter; +import org.hibernate.tool.schema.spi.Exporter; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.DataHelper; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.sql.BasicBinder; +import org.hibernate.type.descriptor.sql.BasicExtractor; import org.hibernate.type.descriptor.sql.BitTypeDescriptor; +import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; +import org.hibernate.type.descriptor.sql.BooleanTypeDescriptor; +import org.hibernate.type.descriptor.sql.CharTypeDescriptor; import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.NCharTypeDescriptor; import org.hibernate.type.descriptor.sql.NClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.NVarcharTypeDescriptor; import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; /** - * An abstract base class for HANA dialects.
    - * SAP HANA Reference
    - * - * NOTE: This dialect is currently configured to create foreign keys with - * on update cascade. + * An abstract base class for SAP HANA dialects. + *

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

    + * Note: This dialect is configured to create foreign keys with {@code on update cascade}. * * @author Andrew Clemons + * @author Jonathan Bregler */ public abstract class AbstractHANADialect extends Dialect { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractHANADialect.class ); + private static final AbstractLimitHandler LIMIT_HANDLER = new AbstractLimitHandler() { + @Override public String processSql(String sql, RowSelection selection) { final boolean hasOffset = LimitHelper.hasFirstRow( selection ); @@ -79,9 +136,11 @@ public boolean supportsLimit() { public boolean bindLimitParametersInReverseOrder() { return true; } + }; private static class CloseSuppressingReader extends FilterReader { + protected CloseSuppressingReader(final Reader in) { super( in ); } @@ -92,30 +151,309 @@ public void close() { } } + private static class CloseSuppressingInputStream extends FilterInputStream { + + protected CloseSuppressingInputStream(final InputStream in) { + super( in ); + } + + @Override + public void close() { + // do not close + } + } + + private static class MaterializedBlob implements Blob { + + private byte[] bytes = null; + + public MaterializedBlob(byte[] bytes) { + this.setBytes( bytes ); + } + + @Override + public long length() throws SQLException { + return this.getBytes().length; + } + + @Override + public byte[] getBytes(long pos, int length) throws SQLException { + return Arrays.copyOfRange( this.bytes, (int) ( pos - 1 ), (int) ( pos - 1 + length ) ); + } + + @Override + public InputStream getBinaryStream() throws SQLException { + return new ByteArrayInputStream( this.getBytes() ); + } + + @Override + public long position(byte[] pattern, long start) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public long position(Blob pattern, long start) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public int setBytes(long pos, byte[] bytes) throws SQLException { + int bytesSet = 0; + if ( this.bytes.length < pos - 1 + bytes.length ) { + this.bytes = Arrays.copyOf( this.bytes, (int) ( pos - 1 + bytes.length ) ); + } + for ( int i = 0; i < bytes.length && i < this.bytes.length; i++, bytesSet++ ) { + this.bytes[(int) ( i + pos - 1 )] = bytes[i]; + } + return bytesSet; + } + + @Override + public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { + int bytesSet = 0; + if ( this.bytes.length < pos - 1 + len ) { + this.bytes = Arrays.copyOf( this.bytes, (int) ( pos - 1 + len ) ); + } + for ( int i = offset; i < len && i < this.bytes.length; i++, bytesSet++ ) { + this.bytes[(int) ( i + pos - 1 )] = bytes[i]; + } + return bytesSet; + } + + @Override + public OutputStream setBinaryStream(long pos) throws SQLException { + return new ByteArrayOutputStream() { + + { + this.buf = getBytes(); + } + }; + } + + @Override + public void truncate(long len) throws SQLException { + this.setBytes( Arrays.copyOf( this.getBytes(), (int) len ) ); + } + + @Override + public void free() throws SQLException { + this.setBytes( null ); + } + + @Override + public InputStream getBinaryStream(long pos, long length) throws SQLException { + return new ByteArrayInputStream( this.getBytes(), (int) ( pos - 1 ), (int) length ); + } + + byte[] getBytes() { + return this.bytes; + } + + void setBytes(byte[] bytes) { + this.bytes = bytes; + } + + } + + private static class MaterializedNClob implements NClob { + + private String data = null; + + public MaterializedNClob(String data) { + this.data = data; + } + + @Override + public void truncate(long len) throws SQLException { + this.data = new String(); + } + + @Override + public int setString(long pos, String str, int offset, int len) throws SQLException { + this.data = this.data.substring( 0, (int) ( pos - 1 ) ) + str.substring( offset, offset + len ) + + this.data.substring( (int) ( pos - 1 + len ) ); + return len; + } + + @Override + public int setString(long pos, String str) throws SQLException { + this.data = this.data.substring( 0, (int) ( pos - 1 ) ) + str + this.data.substring( (int) ( pos - 1 + str.length() ) ); + return str.length(); + } + + @Override + public Writer setCharacterStream(long pos) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public OutputStream setAsciiStream(long pos) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public long position(Clob searchstr, long start) throws SQLException { + return this.data.indexOf( DataHelper.extractString( searchstr ), (int) ( start - 1 ) ); + } + + @Override + public long position(String searchstr, long start) throws SQLException { + return this.data.indexOf( searchstr, (int) ( start - 1 ) ); + } + + @Override + public long length() throws SQLException { + return this.data.length(); + } + + @Override + public String getSubString(long pos, int length) throws SQLException { + return this.data.substring( (int) ( pos - 1 ), (int) ( pos - 1 + length ) ); + } + + @Override + public Reader getCharacterStream(long pos, long length) throws SQLException { + return new StringReader( this.data.substring( (int) ( pos - 1 ), (int) ( pos - 1 + length ) ) ); + } + + @Override + public Reader getCharacterStream() throws SQLException { + return new StringReader( this.data ); + } + + @Override + public InputStream getAsciiStream() throws SQLException { + return new ByteArrayInputStream( this.data.getBytes( StandardCharsets.ISO_8859_1 ) ); + } + + @Override + public void free() throws SQLException { + this.data = null; + } + } + + private static class HANAStreamBlobTypeDescriptor implements SqlTypeDescriptor { + + private static final long serialVersionUID = -2476600722093442047L; + + final int maxLobPrefetchSize; + + public HANAStreamBlobTypeDescriptor(int maxLobPrefetchSize) { + this.maxLobPrefetchSize = maxLobPrefetchSize; + } + + @Override + public int getSqlType() { + return Types.BLOB; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + final BinaryStream binaryStream = javaTypeDescriptor.unwrap( value, BinaryStream.class, options ); + if ( value instanceof BlobImplementer ) { + try ( InputStream is = new CloseSuppressingInputStream( binaryStream.getInputStream() ) ) { + st.setBinaryStream( index, is, binaryStream.getLength() ); + } + catch (IOException e) { + // can't happen => ignore + } + } + else { + st.setBinaryStream( index, binaryStream.getInputStream(), binaryStream.getLength() ); + } + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { + final BinaryStream binaryStream = javaTypeDescriptor.unwrap( value, BinaryStream.class, options ); + if ( value instanceof BlobImplementer ) { + try ( InputStream is = new CloseSuppressingInputStream( binaryStream.getInputStream() ) ) { + st.setBinaryStream( name, is, binaryStream.getLength() ); + } + catch (IOException e) { + // can't happen => ignore + } + } + else { + st.setBinaryStream( name, binaryStream.getInputStream(), binaryStream.getLength() ); + } + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + Blob rsBlob = rs.getBlob( name ); + if ( rsBlob == null || rsBlob.length() < HANAStreamBlobTypeDescriptor.this.maxLobPrefetchSize ) { + return javaTypeDescriptor.wrap( rsBlob, options ); + } + Blob blob = new MaterializedBlob( DataHelper.extractBytes( rsBlob.getBinaryStream() ) ); + return javaTypeDescriptor.wrap( blob, options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( name ), options ); + } + }; + } + + } + // the ClobTypeDescriptor and NClobTypeDescriptor for HANA are slightly // changed from the standard ones. The HANA JDBC driver currently closes any // stream passed in via // PreparedStatement.setCharacterStream(int,Reader,long) - // afterQuery the stream has been processed. this causes problems later if we are + // after the stream has been processed. this causes problems later if we are // using non-contexual lob creation and HANA then closes our StringReader. // see test case LobLocatorTest - private static final ClobTypeDescriptor HANA_CLOB_STREAM_BINDING = new ClobTypeDescriptor() { + private static class HANAClobTypeDescriptor extends ClobTypeDescriptor { + /** serial version uid. */ private static final long serialVersionUID = -379042275442752102L; + final int maxLobPrefetchSize; + final boolean useUnicodeStringTypes; + + public HANAClobTypeDescriptor(int maxLobPrefetchSize, boolean useUnicodeStringTypes) { + this.maxLobPrefetchSize = maxLobPrefetchSize; + this.useUnicodeStringTypes = useUnicodeStringTypes; + } + @Override public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { + @Override - protected void doBind(final PreparedStatement st, final X value, final int index, - final WrapperOptions options) throws SQLException { - final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, - options ); + protected void doBind(final PreparedStatement st, final X value, final int index, final WrapperOptions options) throws SQLException { + final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); if ( value instanceof ClobImplementer ) { - st.setCharacterStream( index, new CloseSuppressingReader( characterStream.asReader() ), - characterStream.getLength() ); + try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { + st.setCharacterStream( index, r, characterStream.getLength() ); + } + catch (IOException e) { + // can't happen => ignore + } } else { st.setCharacterStream( index, characterStream.asReader(), characterStream.getLength() ); @@ -124,20 +462,16 @@ protected void doBind(final PreparedStatement st, final X value, final int index } @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { - final CharacterStream characterStream = javaTypeDescriptor.unwrap( - value, - CharacterStream.class, - options - ); + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { + final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); if ( value instanceof ClobImplementer ) { - st.setCharacterStream( - name, - new CloseSuppressingReader( characterStream.asReader() ), - characterStream.getLength() - ); + try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { + st.setCharacterStream( name, r, characterStream.getLength() ); + } + catch (IOException e) { + // can't happen => ignore + } } else { st.setCharacterStream( name, characterStream.asReader(), characterStream.getLength() ); @@ -145,27 +479,75 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions } }; } - }; - private static final NClobTypeDescriptor HANA_NCLOB_STREAM_BINDING = new NClobTypeDescriptor() { + @Override + public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + Clob rsClob; + if ( HANAClobTypeDescriptor.this.useUnicodeStringTypes ) { + rsClob = rs.getNClob( name ); + } + else { + rsClob = rs.getClob( name ); + } + + if ( rsClob == null || rsClob.length() < HANAClobTypeDescriptor.this.maxLobPrefetchSize ) { + return javaTypeDescriptor.wrap( rsClob, options ); + } + Clob clob = new MaterializedNClob( DataHelper.extractString( rsClob ) ); + return javaTypeDescriptor.wrap( clob, options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getClob( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getClob( name ), options ); + } + }; + } + + public int getMaxLobPrefetchSize() { + return this.maxLobPrefetchSize; + } + + public boolean isUseUnicodeStringTypes() { + return this.useUnicodeStringTypes; + } + } + + private static class HANANClobTypeDescriptor extends NClobTypeDescriptor { + /** serial version uid. */ private static final long serialVersionUID = 5651116091681647859L; + final int maxLobPrefetchSize; + + public HANANClobTypeDescriptor(int maxLobPrefetchSize) { + this.maxLobPrefetchSize = maxLobPrefetchSize; + } + @Override public BasicBinder getNClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { + @Override - protected void doBind(final PreparedStatement st, final X value, final int index, - final WrapperOptions options) throws SQLException { - final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, - options ); + protected void doBind(final PreparedStatement st, final X value, final int index, final WrapperOptions options) throws SQLException { + final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); if ( value instanceof NClobImplementer ) { - st.setCharacterStream( - index, - new CloseSuppressingReader( characterStream.asReader() ), - characterStream.getLength() - ); + try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { + st.setCharacterStream( index, r, characterStream.getLength() ); + } + catch (IOException e) { + // can't happen => ignore + } } else { st.setCharacterStream( index, characterStream.asReader(), characterStream.getLength() ); @@ -174,20 +556,16 @@ protected void doBind(final PreparedStatement st, final X value, final int index } @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { - final CharacterStream characterStream = javaTypeDescriptor.unwrap( - value, - CharacterStream.class, - options - ); + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { + final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); if ( value instanceof NClobImplementer ) { - st.setCharacterStream( - name, - new CloseSuppressingReader( characterStream.asReader() ), - characterStream.getLength() - ); + try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { + st.setCharacterStream( name, r, characterStream.getLength() ); + } + catch (IOException e) { + // can't happen => ignore + } } else { st.setCharacterStream( name, characterStream.asReader(), characterStream.getLength() ); @@ -195,12 +573,187 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions } }; } + + @Override + public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + NClob rsNClob = rs.getNClob( name ); + if ( rsNClob == null || rsNClob.length() < HANANClobTypeDescriptor.this.maxLobPrefetchSize ) { + return javaTypeDescriptor.wrap( rsNClob, options ); + } + NClob nClob = new MaterializedNClob( DataHelper.extractString( rsNClob ) ); + return javaTypeDescriptor.wrap( nClob, options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getNClob( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getNClob( name ), options ); + } + }; + } + + public int getMaxLobPrefetchSize() { + return this.maxLobPrefetchSize; + } + } + + public static class HANABlobTypeDescriptor implements SqlTypeDescriptor { + + private static final long serialVersionUID = 5874441715643764323L; + + final int maxLobPrefetchSize; + + final HANAStreamBlobTypeDescriptor hanaStreamBlobTypeDescriptor; + + public HANABlobTypeDescriptor(int maxLobPrefetchSize) { + this.maxLobPrefetchSize = maxLobPrefetchSize; + this.hanaStreamBlobTypeDescriptor = new HANAStreamBlobTypeDescriptor( maxLobPrefetchSize ); + } + + @Override + public int getSqlType() { + return Types.BLOB; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + Blob rsBlob = rs.getBlob( name ); + if ( rsBlob == null || rsBlob.length() < HANABlobTypeDescriptor.this.maxLobPrefetchSize ) { + return javaTypeDescriptor.wrap( rsBlob, options ); + } + Blob blob = new MaterializedBlob( DataHelper.extractBytes( rsBlob.getBinaryStream() ) ); + return javaTypeDescriptor.wrap( blob, options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( name ), options ); + } + }; + } + + @Override + public BasicBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + SqlTypeDescriptor descriptor = BlobTypeDescriptor.BLOB_BINDING; + if ( byte[].class.isInstance( value ) ) { + // performance shortcut for binding BLOB data in byte[] format + descriptor = BlobTypeDescriptor.PRIMITIVE_ARRAY_BINDING; + } + else if ( options.useStreamForLobBinding() ) { + descriptor = HANABlobTypeDescriptor.this.hanaStreamBlobTypeDescriptor; + } + descriptor.getBinder( javaTypeDescriptor ).bind( st, value, index, options ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { + SqlTypeDescriptor descriptor = BlobTypeDescriptor.BLOB_BINDING; + if ( byte[].class.isInstance( value ) ) { + // performance shortcut for binding BLOB data in byte[] format + descriptor = BlobTypeDescriptor.PRIMITIVE_ARRAY_BINDING; + } + else if ( options.useStreamForLobBinding() ) { + descriptor = HANABlobTypeDescriptor.this.hanaStreamBlobTypeDescriptor; + } + descriptor.getBinder( javaTypeDescriptor ).bind( st, value, name, options ); + } + }; + } + + public int getMaxLobPrefetchSize() { + return this.maxLobPrefetchSize; + } + } + + private static final String MAX_LOB_PREFETCH_SIZE_PARAMETER_NAME = new String( "hibernate.dialect.hana.max_lob_prefetch_size" ); + private static final String USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME = new String( "hibernate.dialect.hana.use_legacy_boolean_type" ); + private static final String USE_UNICODE_STRING_TYPES_PARAMETER_NAME = new String( "hibernate.dialect.hana.use_unicode_string_types" ); + + private static final int MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE = 1024; + private static final Boolean USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE = Boolean.FALSE; + private static final Boolean USE_UNICODE_STRING_TYPES_DEFAULT_VALUE = Boolean.FALSE; + + private HANANClobTypeDescriptor nClobTypeDescriptor = new HANANClobTypeDescriptor( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE ); + + private HANABlobTypeDescriptor blobTypeDescriptor = new HANABlobTypeDescriptor( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE ); + + private HANAClobTypeDescriptor clobTypeDescriptor = new HANAClobTypeDescriptor( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE, + USE_UNICODE_STRING_TYPES_DEFAULT_VALUE ); + + private boolean useLegacyBooleanType = USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE.booleanValue(); + private boolean useUnicodeStringTypes = USE_UNICODE_STRING_TYPES_DEFAULT_VALUE.booleanValue(); + + /* + * Tables named "TYPE" need to be quoted + */ + 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 ); + return quoteTypeIfNecessary( table, sqlCreateStrings, getCreateTableString() ); + } + + @Override + public String[] getSqlDropStrings(Table table, org.hibernate.boot.Metadata metadata) { + String[] sqlDropStrings = super.getSqlDropStrings( table, metadata ); + return quoteTypeIfNecessary( table, sqlDropStrings, "drop table" ); + } + + private String[] quoteTypeIfNecessary(org.hibernate.mapping.Table table, String[] strings, String prefix) { + if ( table.getNameIdentifier() == null || table.getNameIdentifier().isQuoted() + || !"type".equals( table.getNameIdentifier().getText().toLowerCase() ) ) { + return strings; + } + + Pattern createTableTypePattern = Pattern.compile( "(" + prefix + "\\s+)(" + table.getNameIdentifier().getText() + ")(.+)" ); + Pattern commentOnTableTypePattern = Pattern.compile( "(comment\\s+on\\s+table\\s+)(" + table.getNameIdentifier().getText() + ")(.+)" ); + for ( int i = 0; i < strings.length; i++ ) { + Matcher createTableTypeMatcher = createTableTypePattern.matcher( strings[i] ); + Matcher commentOnTableTypeMatcher = commentOnTableTypePattern.matcher( strings[i] ); + if ( createTableTypeMatcher.matches() ) { + strings[i] = createTableTypeMatcher.group( 1 ) + "\"TYPE\"" + createTableTypeMatcher.group( 3 ); + } + if ( commentOnTableTypeMatcher.matches() ) { + strings[i] = commentOnTableTypeMatcher.group( 1 ) + "\"TYPE\"" + commentOnTableTypeMatcher.group( 3 ); + } + } + + return strings; + } }; public AbstractHANADialect() { super(); registerColumnType( Types.DECIMAL, "decimal($p, $s)" ); + registerColumnType( Types.NUMERIC, "decimal($p, $s)" ); registerColumnType( Types.DOUBLE, "double" ); // varbinary max length 5000 @@ -214,23 +767,29 @@ public AbstractHANADialect() { registerColumnType( Types.LONGVARBINARY, "blob" ); registerColumnType( Types.CHAR, "varchar(1)" ); + registerColumnType( Types.NCHAR, "nvarchar(1)" ); registerColumnType( Types.VARCHAR, 5000, "varchar($l)" ); registerColumnType( Types.LONGVARCHAR, 5000, "varchar($l)" ); registerColumnType( Types.NVARCHAR, 5000, "nvarchar($l)" ); + registerColumnType( Types.LONGNVARCHAR, 5000, "nvarchar($l)" ); // for longer values map to clob/nclob registerColumnType( Types.LONGVARCHAR, "clob" ); registerColumnType( Types.VARCHAR, "clob" ); + registerColumnType( Types.LONGNVARCHAR, "nclob" ); registerColumnType( Types.NVARCHAR, "nclob" ); registerColumnType( Types.CLOB, "clob" ); + registerColumnType( Types.NCLOB, "nclob" ); - registerColumnType( Types.BOOLEAN, "tinyint" ); + registerColumnType( Types.BOOLEAN, "boolean" ); // map bit/tinyint to smallint since tinyint is unsigned on HANA registerColumnType( Types.BIT, "smallint" ); registerColumnType( Types.TINYINT, "smallint" ); - registerHibernateType( Types.NCLOB, StandardBasicTypes.NCLOB.getName() ); + registerHibernateType( Types.NCLOB, StandardBasicTypes.MATERIALIZED_NCLOB.getName() ); + registerHibernateType( Types.CLOB, StandardBasicTypes.MATERIALIZED_CLOB.getName() ); + registerHibernateType( Types.BLOB, StandardBasicTypes.MATERIALIZED_BLOB.getName() ); registerHibernateType( Types.NVARCHAR, StandardBasicTypes.STRING.getName() ); registerFunction( "to_date", new StandardSQLFunction( "to_date", StandardBasicTypes.DATE ) ); @@ -240,12 +799,12 @@ public AbstractHANADialect() { registerFunction( "current_date", new NoArgSQLFunction( "current_date", StandardBasicTypes.DATE, false ) ); registerFunction( "current_time", new NoArgSQLFunction( "current_time", StandardBasicTypes.TIME, false ) ); - registerFunction( "current_timestamp", new NoArgSQLFunction( "current_timestamp", StandardBasicTypes.TIMESTAMP, - false ) ); + registerFunction( "current_timestamp", + new NoArgSQLFunction( "current_timestamp", StandardBasicTypes.TIMESTAMP, false ) ); registerFunction( "current_utcdate", new NoArgSQLFunction( "current_utcdate", StandardBasicTypes.DATE, false ) ); registerFunction( "current_utctime", new NoArgSQLFunction( "current_utctime", StandardBasicTypes.TIME, false ) ); - registerFunction( "current_utctimestamp", new NoArgSQLFunction( "current_utctimestamp", - StandardBasicTypes.TIMESTAMP, false ) ); + registerFunction( "current_utctimestamp", + new NoArgSQLFunction( "current_utctimestamp", StandardBasicTypes.TIMESTAMP, false ) ); registerFunction( "add_days", new StandardSQLFunction( "add_days" ) ); registerFunction( "add_months", new StandardSQLFunction( "add_months" ) ); @@ -279,8 +838,7 @@ public AbstractHANADialect() { registerFunction( "to_int", new StandardSQLFunction( "to_int", StandardBasicTypes.INTEGER ) ); registerFunction( "to_integer", new StandardSQLFunction( "to_integer", StandardBasicTypes.INTEGER ) ); registerFunction( "to_real", new StandardSQLFunction( "to_real", StandardBasicTypes.FLOAT ) ); - registerFunction( "to_smalldecimal", - new StandardSQLFunction( "to_smalldecimal", StandardBasicTypes.BIG_DECIMAL ) ); + registerFunction( "to_smalldecimal", new StandardSQLFunction( "to_smalldecimal", StandardBasicTypes.BIG_DECIMAL ) ); registerFunction( "to_smallint", new StandardSQLFunction( "to_smallint", StandardBasicTypes.SHORT ) ); registerFunction( "to_tinyint", new StandardSQLFunction( "to_tinyint", StandardBasicTypes.BYTE ) ); @@ -321,8 +879,8 @@ public AbstractHANADialect() { registerFunction( "concat", new VarArgsSQLFunction( StandardBasicTypes.STRING, "(", "||", ")" ) ); registerFunction( "lcase", new StandardSQLFunction( "lcase", StandardBasicTypes.STRING ) ); registerFunction( "left", new StandardSQLFunction( "left", StandardBasicTypes.STRING ) ); - registerFunction( "length", new StandardSQLFunction( "length", StandardBasicTypes.LONG ) ); - registerFunction( "locate", new StandardSQLFunction( "locate", StandardBasicTypes.INTEGER ) ); + registerFunction( "length", new StandardSQLFunction( "length", StandardBasicTypes.INTEGER ) ); + registerFunction( "locate", new SQLFunctionTemplate( StandardBasicTypes.INTEGER, "locate(?2, ?1, ?3)" ) ); registerFunction( "lpad", new StandardSQLFunction( "lpad", StandardBasicTypes.STRING ) ); registerFunction( "ltrim", new StandardSQLFunction( "ltrim", StandardBasicTypes.STRING ) ); registerFunction( "nchar", new StandardSQLFunction( "nchar", StandardBasicTypes.STRING ) ); @@ -343,8 +901,8 @@ public AbstractHANADialect() { registerFunction( "to_nclob", new StandardSQLFunction( "to_nclob", StandardBasicTypes.NCLOB ) ); registerFunction( "coalesce", new StandardSQLFunction( "coalesce" ) ); - registerFunction( "current_connection", new NoArgSQLFunction( "current_connection", StandardBasicTypes.INTEGER, - false ) ); + registerFunction( "current_connection", + new NoArgSQLFunction( "current_connection", StandardBasicTypes.INTEGER, false ) ); registerFunction( "current_schema", new NoArgSQLFunction( "current_schema", StandardBasicTypes.STRING, false ) ); registerFunction( "current_user", new NoArgSQLFunction( "current_user", StandardBasicTypes.STRING, false ) ); registerFunction( "grouping_id", new VarArgsSQLFunction( StandardBasicTypes.INTEGER, "(", ",", ")" ) ); @@ -357,8 +915,12 @@ public AbstractHANADialect() { registerHanaKeywords(); - // createBlob() and createClob() are not supported by the HANA JDBC driver + // createBlob() and createClob() are not supported by the HANA JDBC + // driver getDefaultProperties().setProperty( AvailableSettings.NON_CONTEXTUAL_LOB_CREATION, "true" ); + + // getGeneratedKeys() is not supported by the HANA JDBC driver + getDefaultProperties().setProperty( AvailableSettings.USE_GET_GENERATED_KEYS, "false" ); } @Override @@ -369,6 +931,7 @@ public boolean bindLimitParametersInReverseOrder() { @Override public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { return new SQLExceptionConversionDelegate() { + @Override public JDBCException convert(final SQLException sqlException, final String message, final String sql) { @@ -406,10 +969,11 @@ public JDBCException convert(final SQLException sqlException, final String messa // 257 - Cannot insert NULL or update to NULL // 301 - Unique constraint violated // 461 - foreign key constraint violation - // 462 - failed on update or delete by foreign key constraint violation + // 462 - failed on update or delete by foreign key constraint + // violation if ( errorCode == 287 || errorCode == 301 || errorCode == 461 || errorCode == 462 ) { - final String constraintName = getViolatedConstraintNameExtracter().extractConstraintName( - sqlException ); + final String constraintName = getViolatedConstraintNameExtracter() + .extractConstraintName( sqlException ); return new ConstraintViolationException( message, sqlException, sql, constraintName ); } @@ -445,21 +1009,30 @@ public String getCreateSequenceString(final String sequenceName) { } @Override - public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() { - return new GlobalTemporaryTableBulkIdStrategy( - new IdTableSupportStandardImpl() { - @Override - public String getCreateIdTableCommand() { - return "create global temporary table"; - } - }, - AfterUseAction.CLEAN - ); + protected String getCreateSequenceString(String sequenceName, int initialValue, int incrementSize) throws MappingException { + if ( incrementSize == 0 ) { + throw new MappingException( "Unable to create the sequence [" + sequenceName + "]: the increment size must not be 0" ); + } + + String createSequenceString = getCreateSequenceString( sequenceName ) + " start with " + initialValue + " increment by " + incrementSize; + if ( incrementSize > 0 ) { + if ( initialValue < 1 ) { + // default minvalue for an ascending sequence is 1 + createSequenceString += " minvalue " + initialValue; + } + } + else if ( incrementSize < 0 ) { + if ( initialValue > -1 ) { + // default maxvalue for a descending sequence is -1 + createSequenceString += " maxvalue " + initialValue; + } + } + return createSequenceString; } @Override public String getCurrentTimestampSelectString() { - return "select current_timestamp from dummy"; + return "select current_timestamp from sys.dummy"; } @Override @@ -474,52 +1047,46 @@ public String getForUpdateString(final String aliases) { @Override public String getForUpdateString(final String aliases, final LockOptions lockOptions) { - LockMode lockMode = lockOptions.getLockMode(); - final Iterator> itr = lockOptions.getAliasLockIterator(); - while ( itr.hasNext() ) { - // seek the highest lock mode - final Map.Entry entry = itr.next(); - final LockMode lm = entry.getValue(); - if ( lm.greaterThan( lockMode ) ) { - lockMode = lm; - } - } + LockMode lockMode = lockOptions.findGreatestLockMode(); + lockOptions.setLockMode( lockMode ); // not sure why this is sometimes empty - if ( aliases == null || "".equals( aliases ) ) { - return getForUpdateString( lockMode ); + if ( aliases == null || aliases.isEmpty() ) { + return getForUpdateString( lockOptions ); } - String clause = getForUpdateString( lockMode ) + " of " + aliases; - if(lockOptions.getTimeOut() == LockOptions.NO_WAIT) { - clause += " nowait"; - } - return clause; + return getForUpdateString( aliases, lockMode, lockOptions.getTimeOut() ); } - public String getForUpdateNowaitString() { - return getForUpdateString() + " nowait"; - } - - @Override - public String getReadLockString(int timeout) { - return getWriteLockString( timeout ); + @SuppressWarnings({ "deprecation" }) + private String getForUpdateString(String aliases, LockMode lockMode, int timeout) { + switch ( lockMode ) { + case UPGRADE: + return getForUpdateString( aliases ); + case PESSIMISTIC_READ: + return getReadLockString( aliases, timeout ); + case PESSIMISTIC_WRITE: + return getWriteLockString( aliases, timeout ); + case UPGRADE_NOWAIT: + case FORCE: + case PESSIMISTIC_FORCE_INCREMENT: + return getForUpdateNowaitString( aliases ); + case UPGRADE_SKIPLOCKED: + return getForUpdateSkipLockedString( aliases ); + default: + return ""; + } } @Override - public String getWriteLockString(int timeout) { - if ( timeout == LockOptions.NO_WAIT ) { - return getForUpdateNowaitString(); - } - else { - return getForUpdateString(); - } + public String getForUpdateNowaitString() { + return getForUpdateString() + " nowait"; } @Override public String getLimitString(final String sql, final boolean hasOffset) { - return new StringBuilder( sql.length() + 20 ).append( sql ) - .append( hasOffset ? " limit ? offset ?" : " limit ?" ).toString(); + return new StringBuilder( sql.length() + 20 ).append( sql ).append( hasOffset ? " limit ? offset ?" : " limit ?" ) + .toString(); } @Override @@ -539,23 +1106,29 @@ public String getSelectSequenceNextValString(final String sequenceName) { @Override public String getSequenceNextValString(final String sequenceName) { - return "select " + getSelectSequenceNextValString( sequenceName ) + " from dummy"; + return "select " + getSelectSequenceNextValString( sequenceName ) + " from sys.dummy"; } @Override protected SqlTypeDescriptor getSqlTypeDescriptorOverride(final int sqlCode) { switch ( sqlCode ) { - case Types.BOOLEAN: - return BitTypeDescriptor.INSTANCE; - case Types.CLOB: - return HANA_CLOB_STREAM_BINDING; - case Types.NCLOB: - return HANA_NCLOB_STREAM_BINDING; - case Types.TINYINT: - // tinyint is unsigned on HANA - return SmallIntTypeDescriptor.INSTANCE; - default: - return super.getSqlTypeDescriptorOverride( sqlCode ); + case Types.CLOB: + return this.clobTypeDescriptor; + case Types.NCLOB: + return this.nClobTypeDescriptor; + case Types.BLOB: + return this.blobTypeDescriptor; + case Types.TINYINT: + // tinyint is unsigned on HANA + return SmallIntTypeDescriptor.INSTANCE; + case Types.BOOLEAN: + return this.useLegacyBooleanType ? BitTypeDescriptor.INSTANCE : BooleanTypeDescriptor.INSTANCE; + case Types.VARCHAR: + return this.useUnicodeStringTypes ? NVarcharTypeDescriptor.INSTANCE : VarcharTypeDescriptor.INSTANCE; + case Types.CHAR: + return this.useUnicodeStringTypes ? NCharTypeDescriptor.INSTANCE : CharTypeDescriptor.INSTANCE; + default: + return super.getSqlTypeDescriptorOverride( sqlCode ); } } @@ -568,7 +1141,7 @@ protected void registerHanaKeywords() { registerKeyword( "all" ); registerKeyword( "alter" ); registerKeyword( "as" ); - registerKeyword( "beforeQuery" ); + registerKeyword( "before" ); registerKeyword( "begin" ); registerKeyword( "both" ); registerKeyword( "case" ); @@ -582,6 +1155,7 @@ protected void registerHanaKeywords() { registerKeyword( "current_schema" ); registerKeyword( "current_time" ); registerKeyword( "current_timestamp" ); + registerKeyword( "current_transaction_isolation_level" ); registerKeyword( "current_user" ); registerKeyword( "current_utcdate" ); registerKeyword( "current_utctime" ); @@ -589,14 +1163,15 @@ protected void registerHanaKeywords() { registerKeyword( "currval" ); registerKeyword( "cursor" ); registerKeyword( "declare" ); + registerKeyword( "deferred" ); registerKeyword( "distinct" ); registerKeyword( "else" ); registerKeyword( "elseif" ); - registerKeyword( "elsif" ); registerKeyword( "end" ); registerKeyword( "except" ); registerKeyword( "exception" ); registerKeyword( "exec" ); + registerKeyword( "false" ); registerKeyword( "for" ); registerKeyword( "from" ); registerKeyword( "full" ); @@ -616,6 +1191,7 @@ protected void registerHanaKeywords() { registerKeyword( "loop" ); registerKeyword( "minus" ); registerKeyword( "natural" ); + registerKeyword( "nchar" ); registerKeyword( "nextval" ); registerKeyword( "null" ); registerKeyword( "on" ); @@ -629,19 +1205,18 @@ protected void registerHanaKeywords() { registerKeyword( "rollup" ); registerKeyword( "rowid" ); registerKeyword( "select" ); + registerKeyword( "session_user" ); registerKeyword( "set" ); registerKeyword( "sql" ); registerKeyword( "start" ); - registerKeyword( "sysdate" ); - registerKeyword( "systime" ); - registerKeyword( "systimestamp" ); registerKeyword( "sysuuid" ); + registerKeyword( "tablesample" ); registerKeyword( "top" ); registerKeyword( "trailing" ); + registerKeyword( "true" ); registerKeyword( "union" ); + registerKeyword( "unknown" ); registerKeyword( "using" ); - registerKeyword( "utcdate" ); - registerKeyword( "utctime" ); registerKeyword( "utctimestamp" ); registerKeyword( "values" ); registerKeyword( "when" ); @@ -650,12 +1225,6 @@ protected void registerHanaKeywords() { registerKeyword( "with" ); } - @Override - public boolean supportsCircularCascadeDeleteConstraints() { - // HANA does not support circular constraints - return false; - } - @Override public ScrollMode defaultScrollMode() { return ScrollMode.FORWARD_ONLY; @@ -690,6 +1259,11 @@ public boolean supportsExpectedLobUsagePattern() { return false; } + @Override + public boolean supportsUnboundedLobLocatorMaterialization() { + return false; + } + @Override public boolean supportsLimit() { return true; @@ -707,12 +1281,12 @@ public boolean supportsSequences() { @Override public boolean supportsTableCheck() { - return false; + return true; } @Override public boolean supportsTupleDistinctCounts() { - return false; + return true; } @Override @@ -740,20 +1314,306 @@ public int getMaxAliasLength() { return 128; } - /** - * The default behaviour for 'on update restrict' on HANA is currently - * to not allow any updates to any column of a row if the row has a - * foreign key. Make the default for foreign keys have 'on update cascade' - * to work around the issue. + @Override + public LimitHandler getLimitHandler() { + return LIMIT_HANDLER; + } + + @Override + public String getSelectGUIDString() { + return "select sysuuid from sys.dummy"; + } + + @Override + public NameQualifierSupport getNameQualifierSupport() { + return NameQualifierSupport.SCHEMA; + } + + @SuppressWarnings("deprecation") + @Override + public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) + throws SQLException { + /* + * Copied from Dialect + */ + builder.applyIdentifierCasing( dbMetaData ); + + builder.applyReservedWords( dbMetaData ); + builder.applyReservedWords( AnsiSqlKeywords.INSTANCE.sql2003() ); + builder.applyReservedWords( getKeywords() ); + + builder.setNameQualifierSupport( getNameQualifierSupport() ); + + /* + * HANA-specific extensions + */ + builder.setQuotedCaseStrategy( IdentifierCaseStrategy.MIXED ); + builder.setUnquotedCaseStrategy( IdentifierCaseStrategy.UPPER ); + + final IdentifierHelper identifierHelper = builder.build(); + + return new IdentifierHelper() { + + private final IdentifierHelper helper = identifierHelper; + + @Override + public String toMetaDataSchemaName(Identifier schemaIdentifier) { + return this.helper.toMetaDataSchemaName( schemaIdentifier ); + } + + @Override + public String toMetaDataObjectName(Identifier identifier) { + return this.helper.toMetaDataObjectName( identifier ); + } + + @Override + public String toMetaDataCatalogName(Identifier catalogIdentifier) { + return this.helper.toMetaDataCatalogName( catalogIdentifier ); + } + + @Override + public Identifier toIdentifier(String text) { + return normalizeQuoting( Identifier.toIdentifier( text ) ); + } + + @Override + public Identifier toIdentifier(String text, boolean quoted) { + return normalizeQuoting( Identifier.toIdentifier( text, quoted ) ); + } + + @Override + public Identifier normalizeQuoting(Identifier identifier) { + Identifier normalizedIdentifier = this.helper.normalizeQuoting( identifier ); + + if ( normalizedIdentifier == null ) { + return null; + } + + // need to quote names containing special characters like ':' + if ( !normalizedIdentifier.isQuoted() && !normalizedIdentifier.getText().matches( "\\w+" ) ) { + normalizedIdentifier = Identifier.quote( normalizedIdentifier ); + } + + return normalizedIdentifier; + } + + @Override + public boolean isReservedWord(String word) { + return this.helper.isReservedWord( word ); + } + + @Override + public Identifier applyGlobalQuoting(String text) { + return this.helper.applyGlobalQuoting( text ); + } + }; + } + + @Override + public String getCurrentSchemaCommand() { + return "select current_schema from sys.dummy"; + } + + @Override + public String getForUpdateNowaitString(String aliases) { + return getForUpdateString( aliases ) + " nowait"; + } + + @Override + public String getReadLockString(int timeout) { + return getWriteLockString( timeout ); + } + + @Override + public String getReadLockString(String aliases, int timeout) { + return getWriteLockString( aliases, timeout ); + } + + @Override + public String getWriteLockString(int timeout) { + if ( timeout > 0 ) { + return getForUpdateString() + " wait " + timeout; + } + else if ( timeout == 0 ) { + return getForUpdateNowaitString(); + } + else { + return getForUpdateString(); + } + } + + @Override + public String getWriteLockString(String aliases, int timeout) { + if ( timeout > 0 ) { + return getForUpdateString( aliases ) + " wait " + timeout; + } + else if ( timeout == 0 ) { + return getForUpdateNowaitString( aliases ); + } + else { + return getForUpdateString( aliases ); + } + } + + @Override + public String getQueryHintString(String query, List hints) { + return query + " with hint (" + String.join( ",", hints ) + ")"; + } + + @Override + public String getTableComment(String comment) { + return "comment '" + comment + "'"; + } + + @Override + public String getColumnComment(String comment) { + return "comment '" + comment + "'"; + } + + @Override + public boolean supportsCommentOn() { + return true; + } + + @Override + public boolean supportsPartitionBy() { + return true; + } + + @Override + public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.contributeTypes( typeContributions, serviceRegistry ); + + final ConnectionProvider connectionProvider = serviceRegistry.getService( ConnectionProvider.class ); + + int maxLobPrefetchSizeDefault = MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE; + if ( connectionProvider != null ) { + Connection conn = null; + try { + conn = connectionProvider.getConnection(); + try ( Statement statement = conn.createStatement() ) { + try ( ResultSet rs = statement.executeQuery( + "SELECT TOP 1 VALUE, MAP(LAYER_NAME, 'DEFAULT', 1, 'SYSTEM', 2, 'DATABASE', 3, 4) AS LAYER FROM SYS.M_INIFILE_CONTENTS WHERE FILE_NAME='indexserver.ini' AND SECTION='session' AND KEY='max_lob_prefetch_size' ORDER BY LAYER DESC" ) ) { + // This only works if the current user has the privilege INIFILE ADMIN + if ( rs.next() ) { + maxLobPrefetchSizeDefault = rs.getInt( 1 ); + } + } + } + } + catch (Exception e) { + LOG.debug( + "An error occurred while trying to determine the value of the HANA parameter indexserver.ini / session / max_lob_prefetch_size. Using the default value " + + maxLobPrefetchSizeDefault, + e ); + } + finally { + if ( conn != null ) { + try { + connectionProvider.closeConnection( conn ); + } + catch (SQLException e) { + // ignore + } + } + } + } + + final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class ); + int maxLobPrefetchSize = configurationService.getSetting( + MAX_LOB_PREFETCH_SIZE_PARAMETER_NAME, + new Converter() { + + @Override + public Integer convert(Object value) { + return Integer.valueOf( value.toString() ); + } + + }, + Integer.valueOf( maxLobPrefetchSizeDefault ) ).intValue(); + + if ( this.nClobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize ) { + this.nClobTypeDescriptor = new HANANClobTypeDescriptor( maxLobPrefetchSize ); + } + + if ( this.blobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize ) { + this.blobTypeDescriptor = new HANABlobTypeDescriptor( maxLobPrefetchSize ); + } + + boolean useUnicodeStringTypes = configurationService.getSetting( USE_UNICODE_STRING_TYPES_PARAMETER_NAME, StandardConverters.BOOLEAN, + USE_UNICODE_STRING_TYPES_DEFAULT_VALUE ).booleanValue(); + + if ( useUnicodeStringTypes ) { + registerColumnType( Types.CHAR, "nvarchar(1)" ); + registerColumnType( Types.VARCHAR, 5000, "nvarchar($l)" ); + registerColumnType( Types.LONGVARCHAR, 5000, "nvarchar($l)" ); + + // for longer values map to clob/nclob + registerColumnType( Types.LONGVARCHAR, "nclob" ); + registerColumnType( Types.VARCHAR, "nclob" ); + registerColumnType( Types.CLOB, "nclob" ); + } + + if ( this.clobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize + || this.clobTypeDescriptor.isUseUnicodeStringTypes() != useUnicodeStringTypes ) { + this.clobTypeDescriptor = new HANAClobTypeDescriptor( maxLobPrefetchSize, useUnicodeStringTypes ); + } + + this.useLegacyBooleanType = configurationService.getSetting( USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME, StandardConverters.BOOLEAN, + USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE ).booleanValue(); + + if ( this.useLegacyBooleanType ) { + registerColumnType( Types.BOOLEAN, "tinyint" ); + } + } + + public SqlTypeDescriptor getBlobTypeDescriptor() { + return this.blobTypeDescriptor; + } + + @Override + public String toBooleanValueString(boolean bool) { + if ( this.useLegacyBooleanType ) { + return bool ? "1" : "0"; + } + return bool ? "true" : "false"; + } + + @Override + public IdentityColumnSupport getIdentityColumnSupport() { + return new HANAIdentityColumnSupport(); + } + + @Override + public Exporter

    hibernate.jdbc.use_getGeneratedKeysenable use of JDBC3 PreparedStatement.getGeneratedKeys() to retrieve - * natively generated keys afterQuery insert. Requires JDBC3+ driver and JRE1.4+
    hibernate.hbm2ddl.auto
    getTableExporter() { + return this.hanaTableExporter; + } + + /* + * HANA doesn't really support REF_CURSOR returns from a procedure, but REF_CURSOR support can be emulated by using + * procedures or functions with an OUT parameter of type TABLE. The results will be returned as result sets on the + * callable statement. */ @Override - public String getAddForeignKeyConstraintString(final String constraintName, final String[] foreignKey, - final String referencedTable, final String[] primaryKey, final boolean referencesPrimaryKey) { - return super.getAddForeignKeyConstraintString(constraintName, foreignKey, referencedTable, primaryKey, referencesPrimaryKey) + " on update cascade"; + public CallableStatementSupport getCallableStatementSupport() { + return StandardCallableStatementSupport.REF_CURSOR_INSTANCE; } @Override - public LimitHandler getLimitHandler() { - return LIMIT_HANDLER; + public int registerResultSetOutParameter(CallableStatement statement, int position) throws SQLException { + // Result set (TABLE) OUT parameters don't need to be registered + return position; + } + + @Override + public int registerResultSetOutParameter(CallableStatement statement, String name) throws SQLException { + // Result set (TABLE) OUT parameters don't need to be registered + return 0; } + + @Override + public boolean supportsNoWait() { + return true; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java index 28be86986421..36f9cd928f72 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -221,7 +221,7 @@ public String generateIdTableName(String baseName) { return "#" + baseName; } }, - // sql-server, at least needed this dropped afterQuery use; strange! + // sql-server, at least needed this dropped after use; strange! AfterUseAction.DROP, TempTableDdlTransactionHandling.NONE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java index 966cb8caba59..652b7bb6ca64 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java @@ -41,7 +41,6 @@ import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.local.AfterUseAction; -import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.Lockable; import org.hibernate.sql.CacheJoinFragment; import org.hibernate.sql.JoinFragment; @@ -398,11 +397,11 @@ public String getAddForeignKeyConstraintString( .append( " FOREIGN KEY " ) .append( constraintName ) .append( " (" ) - .append( StringHelper.join( ", ", foreignKey ) ) + .append( String.join( ", ", foreignKey ) ) .append( ") REFERENCES " ) .append( referencedTable ) .append( " (" ) - .append( StringHelper.join( ", ", primaryKey ) ) + .append( String.join( ", ", primaryKey ) ) .append( ") " ) .toString(); } @@ -431,7 +430,7 @@ public String getCascadeConstraintsString() { @Override public boolean dropConstraints() { - // Do we need to drop constraints beforeQuery dropping tables in this dialect? + // Do we need to drop constraints before dropping tables in this dialect? return true; } @@ -516,7 +515,7 @@ public boolean supportsOuterJoinForUpdate() { @Override public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // InterSystems Cache' does not current support "SELECT ... FOR UPDATE" syntax... - // Set your transaction mode to READ_COMMITTED beforeQuery using + // Set your transaction mode to READ_COMMITTED before using if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2390Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2390Dialect.java index dbedf85d255c..622de5be788e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2390Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2390Dialect.java @@ -58,6 +58,11 @@ public boolean supportsLimit() { return true; } + @Override + public boolean supportsLimitOffset() { + return false; + } + @Override public boolean useMaxForLimit() { return true; @@ -74,6 +79,11 @@ public boolean supportsSequences() { return false; } + @Override + public String getQuerySequencesString() { + return null; + } + @Override public boolean supportsLimit() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2390V8Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2390V8Dialect.java new file mode 100644 index 000000000000..e5e2b1464174 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2390V8Dialect.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 . + */ +package org.hibernate.dialect; + + +/** + * An SQL dialect for DB2/390 version 8. + * + * @author Tobias Sternvik + */ +public class DB2390V8Dialect extends DB2390Dialect { + + @Override + public boolean supportsSequences() { + return true; + } + + public String getSequenceNextValString(String sequenceName) { + return "select nextval for " + sequenceName + " from sysibm.sysdummy1"; + } + + public String getCreateSequenceString(String sequenceName) { + return "create sequence " + sequenceName + " as integer start with 1 increment by 1 minvalue 1 nomaxvalue nocycle nocache"; //simple default settings.. + } + + public String getDropSequenceString(String sequenceName) { + return "drop sequence " + sequenceName + " restrict"; + } + + public String getQuerySequencesString() { + return "select name from sysibm.syssequences"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2400Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2400Dialect.java index fce64e4d3c52..c85faa3a4ffd 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2400Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2400Dialect.java @@ -59,6 +59,11 @@ public boolean supportsLimit() { return true; } + @Override + public String getQuerySequencesString() { + return null; + } + @Override @SuppressWarnings("deprecation") public boolean supportsLimitOffset() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java new file mode 100644 index 000000000000..e7841d241776 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.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 . + */ +package org.hibernate.dialect; + +import java.sql.Types; + +import org.hibernate.dialect.function.DB2SubstringFunction; +import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; +import org.hibernate.hql.spi.id.local.AfterUseAction; +import org.hibernate.type.descriptor.sql.CharTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; + +/** + * An SQL dialect for DB2 9.7. + * + * @author Gail Badner + */ +public class DB297Dialect extends DB2Dialect { + + public DB297Dialect() { + super(); + registerFunction( "substring", new DB2SubstringFunction() ); + } + + @Override + public String getCrossJoinSeparator() { + // DB2 9.7 and later support "cross join" + return " cross join "; + } + + @Override + public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() { + // Starting in DB2 9.7, "real" global temporary tables that can be shared between sessions + // are supported; (obviously) data is not shared between sessions. + return new GlobalTemporaryTableBulkIdStrategy( + new IdTableSupportStandardImpl() { + @Override + public String generateIdTableName(String baseName) { + return super.generateIdTableName( baseName ); + } + + @Override + public String getCreateIdTableCommand() { + return "create global temporary table"; + } + + @Override + public String getCreateIdTableStatementOptions() { + return "not logged"; + } + }, + AfterUseAction.CLEAN + ); + } + + @Override + protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { + // See HHH-12753 + // It seems that DB2's JDBC 4.0 support as of 9.5 does not support the N-variant methods like + // NClob or NString. Therefore here we overwrite the sql type descriptors to use the non-N variants + // which are supported. + switch ( sqlCode ) { + case Types.NCHAR: + return CharTypeDescriptor.INSTANCE; + + case Types.NCLOB: + if ( useInputStreamToInsertBlob() ) { + return ClobTypeDescriptor.STREAM_BINDING; + } + else { + return ClobTypeDescriptor.CLOB_BINDING; + } + + case Types.NVARCHAR: + return VarcharTypeDescriptor.INSTANCE; + + default: + return super.getSqlTypeDescriptorOverride( sqlCode ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 6564635b392c..12b95f4e4d64 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -35,10 +35,10 @@ import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.local.AfterUseAction; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.sql.DecimalTypeDescriptor; import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -48,7 +48,6 @@ * @author Gavin King */ public class DB2Dialect extends Dialect { - private static final CoreMessageLogger log = CoreLogging.messageLogger( DB2Dialect.class ); private static final AbstractLimitHandler LIMIT_HANDLER = new AbstractLimitHandler() { @Override @@ -99,7 +98,12 @@ public DB2Dialect() { registerColumnType( Types.TIME, "time" ); registerColumnType( Types.TIMESTAMP, "timestamp" ); registerColumnType( Types.VARBINARY, "varchar($l) for bit data" ); - registerColumnType( Types.NUMERIC, "numeric($p,$s)" ); + // DB2 converts numeric to decimal under the hood + // Note that the type returned by DB2 for a numeric column will be Types.DECIMAL. Thus, we have an issue when + // comparing the types during the schema validation, defining the type to decimal here as the type names will + // also be compared and there will be a match. See HHH-12827 for the details. + registerColumnType( Types.NUMERIC, "decimal($p,$s)" ); + registerColumnType( Types.DECIMAL, "decimal($p,$s)" ); registerColumnType( Types.BLOB, "blob($l)" ); registerColumnType( Types.CLOB, "clob($l)" ); registerColumnType( Types.LONGVARCHAR, "long varchar" ); @@ -209,7 +213,7 @@ public DB2Dialect() { registerKeyword( "only" ); getDefaultProperties().setProperty( Environment.STATEMENT_BATCH_SIZE, NO_BATCH ); - + uniqueDelegate = new DB2UniqueDelegate( this ); } @@ -348,7 +352,7 @@ public String getSelectClauseNullString(int sqlType) { default: literal = "0"; } - return "nullif(" + literal + ',' + literal + ')'; + return "nullif(" + literal + ", " + literal + ')'; } @Override @@ -364,7 +368,7 @@ public int registerResultSetOutParameter(CallableStatement statement, int col) t @Override public ResultSet getResultSet(CallableStatement ps) throws SQLException { boolean isResultSet = ps.execute(); - // This assumes you will want to ignore any update counts + // This assumes you will want to ignore any update counts while ( !isResultSet && ps.getUpdateCount() != -1 ) { isResultSet = ps.getMoreResults(); } @@ -379,7 +383,10 @@ public boolean supportsCommentOn() { @Override public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() { - return new GlobalTemporaryTableBulkIdStrategy( + // Prior to DB2 9.7, "real" global temporary tables that can be shared between sessions + // are *not* supported; even though the DB2 command says to declare a "global" temp table + // Hibernate treats it as a "local" temp table. + return new LocalTemporaryTableBulkIdStrategy( new IdTableSupportStandardImpl() { @Override public String generateIdTableName(String baseName) { @@ -396,7 +403,8 @@ public String getCreateIdTableStatementOptions() { return "not logged"; } }, - AfterUseAction.CLEAN + AfterUseAction.DROP, + null ); } @@ -473,7 +481,14 @@ public boolean supportsTupleDistinctCounts() { @Override protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { - return sqlCode == Types.BOOLEAN ? SmallIntTypeDescriptor.INSTANCE : super.getSqlTypeDescriptorOverride( sqlCode ); + if ( sqlCode == Types.BOOLEAN ) { + return SmallIntTypeDescriptor.INSTANCE; + } + else if ( sqlCode == Types.NUMERIC ) { + return DecimalTypeDescriptor.INSTANCE; + } + + return super.getSqlTypeDescriptorOverride( sqlCode ); } @Override @@ -491,12 +506,12 @@ public JDBCException convert(SQLException sqlException, String message, String s } }; } - + @Override public UniqueDelegate getUniqueDelegate() { return uniqueDelegate; } - + @Override public String getNotExpression( String expression ) { return "not (" + expression + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Database.java b/hibernate-core/src/main/java/org/hibernate/dialect/Database.java new file mode 100644 index 000000000000..ace32856e736 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Database.java @@ -0,0 +1,564 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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 org.hibernate.HibernateException; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; + +/** + * List all supported relational database systems. + * + * @author Vlad Mihalcea + */ +public enum Database { + + CACHE { + @Override + public Class latestDialect() { + return Cache71Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + CUBRID { + @Override + public Class latestDialect() { + return CUBRIDDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "CUBRID".equalsIgnoreCase( databaseName ) ) { + return latestDialectInstance( this ); + } + + return null; + } + }, + DB2 { + @Override + public Class latestDialect() { + return DB2400Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "DB2 UDB for AS/400".equals( databaseName ) ) { + return new DB2400Dialect(); + } + + if ( databaseName.startsWith( "DB2/" ) ) { + return new DB2Dialect(); + } + + return null; + } + }, + DERBY { + @Override + public Class latestDialect() { + return DerbyTenSevenDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "Apache Derby".equals( databaseName ) ) { + final int majorVersion = info.getDatabaseMajorVersion(); + final int minorVersion = info.getDatabaseMinorVersion(); + + if ( majorVersion > 10 || ( majorVersion == 10 && minorVersion >= 7 ) ) { + return latestDialectInstance( this ); + } + else if ( majorVersion == 10 && minorVersion == 6 ) { + return new DerbyTenSixDialect(); + } + else if ( majorVersion == 10 && minorVersion == 5 ) { + return new DerbyTenFiveDialect(); + } + else { + return new DerbyDialect(); + } + } + + return null; + } + }, + ENTERPRISEDB { + @Override + public Class latestDialect() { + return PostgresPlusDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "EnterpriseDB".equals( databaseName ) ) { + return latestDialectInstance( this ); + } + + return null; + } + }, + FIREBIRD { + @Override + public Class latestDialect() { + return FirebirdDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( databaseName.startsWith( "Firebird" ) ) { + return latestDialectInstance( this ); + } + + return null; + } + }, + FRONTBASE { + @Override + public Class latestDialect() { + return FrontBaseDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + H2 { + @Override + public Class latestDialect() { + return H2Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "H2".equals( databaseName ) ) { + return latestDialectInstance( this ); + } + + return null; + } + }, + HANA { + @Override + public Class latestDialect() { + return HANAColumnStoreDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "HDB".equals( databaseName ) ) { + // SAP recommends defaulting to column store. + return latestDialectInstance( this ); + } + + return null; + } + }, + HSQL { + @Override + public Class latestDialect() { + return HSQLDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "HSQL Database Engine".equals( databaseName ) ) { + return latestDialectInstance( this ); + } + + return null; + } + }, + INFORMIX { + @Override + public Class latestDialect() { + return Informix10Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "Informix Dynamic Server".equals( databaseName ) ) { + return latestDialectInstance( this ); + } + + return null; + } + }, + INGRES { + @Override + public Class latestDialect() { + return Ingres10Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "ingres".equalsIgnoreCase( databaseName ) ) { + final int majorVersion = info.getDatabaseMajorVersion(); + final int minorVersion = info.getDatabaseMinorVersion(); + + if ( majorVersion < 9 ) { + return new IngresDialect(); + } + else if ( majorVersion == 9 ) { + if ( minorVersion > 2 ) { + return new Ingres9Dialect(); + } + return new IngresDialect(); + } + else if ( majorVersion == 10 ) { + return new Ingres10Dialect(); + } + + return latestDialectInstance( this ); + } + + return null; + } + }, + INTERBASE { + @Override + public Class latestDialect() { + return InterbaseDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + MARIADB { + @Override + public Class latestDialect() { + return MariaDB102Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + + if ( info.getDriverName() != null && info.getDriverName().startsWith( "MariaDB" ) ) { + final int majorVersion = info.getDatabaseMajorVersion(); + final int minorVersion = info.getDatabaseMinorVersion(); + + if ( majorVersion == 10 ) { + if ( minorVersion >= 3 ) { + return new MariaDB103Dialect(); + } + else if ( minorVersion == 2 ) { + return new MariaDB102Dialect(); + } + else if ( minorVersion >= 0 ) { + return new MariaDB10Dialect(); + } + return new MariaDB53Dialect(); + } + else if ( majorVersion > 5 || ( majorVersion == 5 && minorVersion >= 3 ) ) { + return new MariaDB53Dialect(); + } + return new MariaDBDialect(); + } + + return null; + } + }, + MAXDB { + @Override + public Class latestDialect() { + return SAPDBDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + MCKOI { + @Override + public Class latestDialect() { + return MckoiDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + MIMERSQL { + @Override + public Class latestDialect() { + return MimerSQLDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + MYSQL { + @Override + public Class latestDialect() { + return MySQL8Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "MySQL".equals( databaseName ) ) { + final int majorVersion = info.getDatabaseMajorVersion(); + final int minorVersion = info.getDatabaseMinorVersion(); + + if ( majorVersion < 5 ) { + return new MySQLDialect(); + } + else if ( majorVersion == 5 ) { + if ( minorVersion < 5 ) { + return new MySQL5Dialect(); + } + else if ( minorVersion < 7 ) { + return new MySQL55Dialect(); + } + else { + return new MySQL57Dialect(); + } + } + else if ( majorVersion < 8) { + // There is no MySQL 6 or 7. + // Adding this just in case. + return new MySQL57Dialect(); + } + else if ( majorVersion == 8 ) { + return new MySQL8Dialect(); + } + + return latestDialectInstance( this ); + } + + return null; + } + }, + ORACLE { + @Override + public Class latestDialect() { + return Oracle12cDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "Oracle".equals( databaseName ) ) { + final int majorVersion = info.getDatabaseMajorVersion(); + + switch ( majorVersion ) { + case 12: + return new Oracle12cDialect(); + case 11: + // fall through + case 10: + return new Oracle10gDialect(); + case 9: + return new Oracle9iDialect(); + case 8: + return new Oracle8iDialect(); + default: + return latestDialectInstance( this ); + + } + } + + return null; + } + }, + POINTBASE { + @Override + public Class latestDialect() { + return PointbaseDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + POSTGRESQL { + @Override + public Class latestDialect() { + return PostgreSQL95Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "PostgreSQL".equals( databaseName ) ) { + final int majorVersion = info.getDatabaseMajorVersion(); + final int minorVersion = info.getDatabaseMinorVersion(); + + if ( majorVersion < 8 ) { + return new PostgreSQL81Dialect(); + } + + if ( majorVersion == 8 ) { + return minorVersion >= 2 ? new PostgreSQL82Dialect() : new PostgreSQL81Dialect(); + } + + if ( majorVersion == 9 ) { + if ( minorVersion < 2 ) { + return new PostgreSQL9Dialect(); + } + else if ( minorVersion < 4 ) { + return new PostgreSQL92Dialect(); + } + else if ( minorVersion < 5 ) { + return new PostgreSQL94Dialect(); + } + else if ( minorVersion < 6 ) { + return new PostgreSQL95Dialect(); + } + } + + return latestDialectInstance( this ); + } + + return null; + } + }, + PROGRESS { + @Override + public Class latestDialect() { + return ProgressDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + SQLSERVER { + @Override + public Class latestDialect() { + return SQLServer2012Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( databaseName.startsWith( "Microsoft SQL Server" ) ) { + final int majorVersion = info.getDatabaseMajorVersion(); + + switch ( majorVersion ) { + case 8: { + return new SQLServerDialect(); + } + case 9: { + return new SQLServer2005Dialect(); + } + case 10: { + return new SQLServer2008Dialect(); + } + case 11: + case 12: + case 13: { + return new SQLServer2012Dialect(); + } + default: { + if ( majorVersion < 8 ) { + return new SQLServerDialect(); + } + else { + // assume `majorVersion > 13` + return latestDialectInstance( this ); + } + } + } + } + + return null; + } + }, + SYBASE { + @Override + public Class latestDialect() { + return SybaseASE15Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + final String databaseName = info.getDatabaseName(); + + if ( "Sybase SQL Server".equals( databaseName ) || "Adaptive Server Enterprise".equals( databaseName ) ) { + return latestDialectInstance( this ); + } + + if ( databaseName.startsWith( "Adaptive Server Anywhere" ) ) { + return new SybaseAnywhereDialect(); + } + + return null; + } + }, + TERADATA { + @Override + public Class latestDialect() { + return Teradata14Dialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }, + TIMESTEN { + @Override + public Class latestDialect() { + return TimesTenDialect.class; + } + + @Override + public Dialect resolveDialect(DialectResolutionInfo info) { + return null; + } + }; + + public abstract Class latestDialect(); + + public abstract Dialect resolveDialect(DialectResolutionInfo info); + + private static Dialect latestDialectInstance(Database database) { + try { + return database.latestDialect().newInstance(); + } + catch (InstantiationException | IllegalAccessException e) { + throw new HibernateException( e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index b7792a9726a6..63ef4e50a2c9 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -133,7 +133,7 @@ public String getQuerySequencesString() { return "select SEQUENCENAME from SYS.SYSSEQUENCES"; } else { - throw new MappingException( "Derby does not support sequence prior to release 10.6.1.0" ); + return null; } } 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 5c955d44284f..ef18c7ac66ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Pattern; import org.hibernate.HibernateException; import org.hibernate.LockMode; @@ -140,6 +141,9 @@ public abstract class Dialect implements ConversionContext { */ public static final String CLOSED_QUOTE = "`\"]"; + private static final Pattern ESCAPE_CLOSING_COMMENT_PATTERN = Pattern.compile( "\\*/" ); + private static final Pattern ESCAPE_OPENING_COMMENT_PATTERN = Pattern.compile( "/\\*" ); + private final TypeNames typeNames = new TypeNames(); private final TypeNames hibernateTypeNames = new TypeNames(); @@ -256,7 +260,6 @@ public static Dialect getDialect() throws HibernateException { return instantiateDialect( Environment.getProperties().getProperty( Environment.DIALECT ) ); } - /** * Get an instance of the dialect specified by the given properties or by * the current System properties. @@ -1032,7 +1035,7 @@ public boolean bindLimitParametersInReverseOrder() { * Does the LIMIT clause come at the start of the * SELECT statement, rather than at the end? * - * @return true if limit parameters should come beforeQuery other parameters + * @return true if limit parameters should come before other parameters * @deprecated {@link #getLimitHandler()} should be overridden instead. */ @Deprecated @@ -1452,6 +1455,22 @@ public String getCreateTableString() { return "create table"; } + /** + * Command used to alter a table. + * + * @param tableName The name of the table to alter + * @return The command used to alter a table. + * @since 5.2.11 + */ + public String getAlterTableString(String tableName) { + final StringBuilder sb = new StringBuilder( "alter table " ); + if ( supportsIfExistsAfterAlterTable() ) { + sb.append( "if exists " ); + } + sb.append( tableName ); + return sb.toString(); + } + /** * Slight variation on {@link #getCreateTableString}. Here, we have the * command used to create a table when there is no primary key and @@ -2046,7 +2065,7 @@ public boolean hasAlterTable() { } /** - * Do we need to drop constraints beforeQuery dropping tables in this dialect? + * Do we need to drop constraints before dropping tables in this dialect? * * @return True if constraints must be dropped prior to dropping * the table; false otherwise. @@ -2115,13 +2134,13 @@ public String getAddForeignKeyConstraintString( res.append( " add constraint " ) .append( quote( constraintName ) ) .append( " foreign key (" ) - .append( StringHelper.join( ", ", foreignKey ) ) + .append( String.join( ", ", foreignKey ) ) .append( ") references " ) .append( referencedTable ); if ( !referencesPrimaryKey ) { res.append( " (" ) - .append( StringHelper.join( ", ", primaryKey ) ) + .append( String.join( ", ", primaryKey ) ) .append( ')' ); } @@ -2199,49 +2218,59 @@ public String getColumnComment(String comment) { } /** - * For dropping a table, can the phrase "if exists" be applied beforeQuery the table name? + * For dropping a table, can the phrase "if exists" be applied before the table name? *

    * NOTE : Only one or the other (or neither) of this and {@link #supportsIfExistsAfterTableName} should return true * - * @return {@code true} if the "if exists" can be applied beforeQuery the table name + * @return {@code true} if the "if exists" can be applied before the table name */ public boolean supportsIfExistsBeforeTableName() { return false; } /** - * For dropping a table, can the phrase "if exists" be applied afterQuery the table name? + * For dropping a table, can the phrase "if exists" be applied after the table name? *

    * NOTE : Only one or the other (or neither) of this and {@link #supportsIfExistsBeforeTableName} should return true * - * @return {@code true} if the "if exists" can be applied afterQuery the table name + * @return {@code true} if the "if exists" can be applied after the table name */ public boolean supportsIfExistsAfterTableName() { return false; } /** - * For dropping a constraint with an "alter table", can the phrase "if exists" be applied beforeQuery the constraint name? + * For dropping a constraint with an "alter table", can the phrase "if exists" be applied before the constraint name? *

    * NOTE : Only one or the other (or neither) of this and {@link #supportsIfExistsAfterConstraintName} should return true * - * @return {@code true} if the "if exists" can be applied beforeQuery the constraint name + * @return {@code true} if the "if exists" can be applied before the constraint name */ public boolean supportsIfExistsBeforeConstraintName() { return false; } /** - * For dropping a constraint with an "alter table", can the phrase "if exists" be applied afterQuery the constraint name? + * For dropping a constraint with an "alter table", can the phrase "if exists" be applied after the constraint name? *

    * NOTE : Only one or the other (or neither) of this and {@link #supportsIfExistsBeforeConstraintName} should return true * - * @return {@code true} if the "if exists" can be applied afterQuery the constraint name + * @return {@code true} if the "if exists" can be applied after the constraint name */ public boolean supportsIfExistsAfterConstraintName() { return false; } + /** + * For an "alter table", can the phrase "if exists" be applied? + * + * @return {@code true} if the "if exists" can be applied after ALTER TABLE + * @since 5.2.11 + */ + public boolean supportsIfExistsAfterAlterTable() { + return false; + } + /** * Generate a DROP TABLE statement * @@ -2646,7 +2675,7 @@ public boolean requiresParensForTupleDistinctCounts() { } /** - * Return the limit that the underlying database places on the number elements in an {@code IN} predicate. + * Return the limit that the underlying database places on the number of elements in an {@code IN} predicate. * If the database defines no such limits, simply return zero or less-than-zero. * * @return int The limit, or zero-or-less to indicate no limit. @@ -2659,7 +2688,7 @@ public int getInExpressionCountLimit() { * HHH-4635 * Oracle expects all Lob values to be last in inserts and updates. * - * @return boolean True of Lob values should be last, false if it + * @return boolean True if Lob values should be last, false if it * does not matter. */ public boolean forceLobAsLastValue() { @@ -2763,6 +2792,24 @@ public boolean supportsNotNullUnique() { return true; } + /** + * Apply a hint to the query. The entire query is provided, allowing the Dialect full control over the placement + * and syntax of the hint. By default, ignore the hint and simply return the query. + * + * @param query The query to which to apply the hint. + * @param hintList The hints to apply + * @return The modified SQL + */ + public String getQueryHintString(String query, List hintList) { + final String hints = String.join( ", ", hintList ); + + if ( StringHelper.isEmpty( hints ) ) { + return query; + } + + return getQueryHintString( query, hints ); + } + /** * Apply a hint to the query. The entire query is provided, allowing the Dialect full control over the placement * and syntax of the hint. By default, ignore the hint and simply return the query. @@ -2771,7 +2818,7 @@ public boolean supportsNotNullUnique() { * @param hints The hints to apply * @return The modified SQL */ - public String getQueryHintString(String query, List hints) { + public String getQueryHintString(String query, String hints) { return query; } @@ -2883,10 +2930,46 @@ public boolean supportsValuesList() { return false; } + /** + * Does this dialect/database support SKIP_LOCKED timeout. + * + * @return {@code true} if SKIP_LOCKED is supported + */ + public boolean supportsSkipLocked() { + return false; + } + + /** + * Does this dialect/database support NO_WAIT timeout. + * + * @return {@code true} if NO_WAIT is supported + */ + public boolean supportsNoWait() { + return false; + } + public boolean isLegacyLimitHandlerBehaviorEnabled() { return legacyLimitHandlerBehavior; } + /** + * Inline String literal. + * + * @return escaped String + */ + public String inlineLiteral(String literal) { + return String.format( "\'%s\'", escapeLiteral( literal ) ); + } + + /** + * Escape String literal. + * + * @return escaped String + */ + protected String escapeLiteral(String literal) { + return literal.replace("'", "''"); + } + private void resolveLegacyLimitHandlerBehavior(ServiceRegistry serviceRegistry) { // HHH-11194 // Temporary solution to set whether legacy limit handler behavior should be used. @@ -2897,4 +2980,40 @@ private void resolveLegacyLimitHandlerBehavior(ServiceRegistry serviceRegistry) false ); } + + /** + * Modify the SQL, adding hints or comments, if necessary + * + * @param sql original sql + * @param parameters query parameters + * @param commentsEnabled if comments are enabled + */ + public String addSqlHintOrComment( + String sql, + QueryParameters parameters, + boolean commentsEnabled) { + + // Keep this here, rather than moving to Select. Some Dialects may need the hint to be appended to the very + // end or beginning of the finalized SQL statement, so wait until everything is processed. + if ( parameters.getQueryHints() != null && parameters.getQueryHints().size() > 0 ) { + sql = getQueryHintString( sql, parameters.getQueryHints() ); + } + if ( commentsEnabled && parameters.getComment() != null ){ + sql = prependComment( sql, parameters.getComment() ); + } + + return sql; + } + + protected String prependComment(String sql, String comment) { + return "/* " + escapeComment( comment ) + " */ " + sql; + } + + public static String escapeComment(String comment) { + if ( StringHelper.isNotEmpty( comment ) ) { + final String escaped = ESCAPE_CLOSING_COMMENT_PATTERN.matcher( comment ).replaceAll( "*\\\\/" ); + return ESCAPE_OPENING_COMMENT_PATTERN.matcher( escaped ).replaceAll( "/\\\\*" ); + } + return comment; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 5bf643ac4adc..fea16552b57a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -17,6 +17,7 @@ import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.dialect.function.VarArgsSQLFunction; +import org.hibernate.dialect.hint.IndexQueryHintHandler; import org.hibernate.dialect.identity.H2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.AbstractLimitHandler; @@ -432,7 +433,7 @@ public boolean supportsTuplesInSubqueries() { @Override public boolean dropConstraints() { - // We don't need to drop constraints beforeQuery dropping tables, that just leads to error + // We don't need to drop constraints before dropping tables, that just leads to error // messages about missing tables when we don't have a schema in the database return false; } @@ -441,4 +442,9 @@ public boolean dropConstraints() { public IdentityColumnSupport getIdentityColumnSupport() { return new H2IdentityColumnSupport(); } + + @Override + public String getQueryHintString(String query, String hints) { + return IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java index d257763f0f06..2cafa3aa1299 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java @@ -6,12 +6,24 @@ */ package org.hibernate.dialect; +import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; +import org.hibernate.hql.spi.id.local.AfterUseAction; + /** - * An SQL dialect for HANA.
    - * SAP HANA Reference
    + * An SQL dialect for the SAP HANA column store. + *

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

    * Column tables are created by this dialect when using the auto-ddl feature. * * @author Andrew Clemons + * @author Jonathan Bregler */ public class HANAColumnStoreDialect extends AbstractHANADialect { @@ -23,4 +35,21 @@ public HANAColumnStoreDialect() { public String getCreateTableString() { return "create column table"; } + + @Override + public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() { + return new GlobalTemporaryTableBulkIdStrategy( new IdTableSupportStandardImpl() { + + @Override + public String getCreateIdTableCommand() { + return "create global temporary column table"; + } + + @Override + public String getTruncateIdTableCommand() { + return "truncate table"; + } + + }, AfterUseAction.CLEAN ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java index 615c6b557fb4..dc87a926458d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java @@ -6,19 +6,44 @@ */ package org.hibernate.dialect; +import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; +import org.hibernate.hql.spi.id.local.AfterUseAction; + /** - * An SQL dialect for HANA.
    - * SAP HANA Reference
    + * An SQL dialect for the SAP HANA row store. + *

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

    * Row tables are created by this dialect when using the auto-ddl feature. * * @author Andrew Clemons + * @author Jonathan Bregler */ public class HANARowStoreDialect extends AbstractHANADialect { - // Even though it's currently pointless, provide this structure in case HANA row store merits additional - // differences in the future. - public HANARowStoreDialect() { super(); } + + @Override + public String getCreateTableString() { + return "create row table"; + } + + @Override + public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() { + return new GlobalTemporaryTableBulkIdStrategy( new IdTableSupportStandardImpl() { + + @Override + public String getCreateIdTableCommand() { + return "create global temporary row table"; + } + }, AfterUseAction.CLEAN ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index 32ca3a724423..5ac0bbe54dc2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -240,6 +240,7 @@ public HSQLDialect() { registerFunction( "round", new StandardSQLFunction( "round" ) ); registerFunction( "roundmagic", new StandardSQLFunction( "roundmagic" ) ); registerFunction( "truncate", new StandardSQLFunction( "truncate" ) ); + registerFunction( "trunc", new StandardSQLFunction( "trunc" ) ); registerFunction( "ceiling", new StandardSQLFunction( "ceiling" ) ); registerFunction( "floor", new StandardSQLFunction( "floor" ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Informix10Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Informix10Dialect.java new file mode 100644 index 000000000000..54fe89f94b29 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Informix10Dialect.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.dialect; + +import org.hibernate.dialect.pagination.Informix10LimitHandler; +import org.hibernate.dialect.pagination.LimitHandler; + +/** + * Since version 10.00.xC3 Informix has limit/offset support which was introduced in July 2005. + */ +public class Informix10Dialect extends InformixDialect { + + @Override + public LimitHandler getLimitHandler() { + return Informix10LimitHandler.INSTANCE; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java index 7ef27c98d98b..8ed37b7abb08 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java @@ -28,7 +28,6 @@ import org.hibernate.hql.spi.id.local.AfterUseAction; import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; import org.hibernate.internal.util.JdbcExceptionHelper; -import org.hibernate.internal.util.StringHelper; import org.hibernate.type.StandardBasicTypes; /** @@ -106,13 +105,13 @@ public String getAddForeignKeyConstraintString( final StringBuilder result = new StringBuilder( 30 ) .append( " add constraint " ) .append( " foreign key (" ) - .append( StringHelper.join( ", ", foreignKey ) ) + .append( String.join( ", ", foreignKey ) ) .append( ") references " ) .append( referencedTable ); if ( !referencesPrimaryKey ) { result.append( " (" ) - .append( StringHelper.join( ", ", primaryKey ) ) + .append( String.join( ", ", primaryKey ) ) .append( ')' ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InnoDBStorageEngine.java b/hibernate-core/src/main/java/org/hibernate/dialect/InnoDBStorageEngine.java new file mode 100644 index 000000000000..b576b0fe148f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InnoDBStorageEngine.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 . + */ +package org.hibernate.dialect; + +/** + * Represents the InnoDB storage engine. + * + * @author Vlad Mihalcea + */ +public class InnoDBStorageEngine implements MySQLStorageEngine{ + + public static final MySQLStorageEngine INSTANCE = new InnoDBStorageEngine(); + + @Override + public boolean supportsCascadeDelete() { + return true; + } + + @Override + public String getTableTypeString(String engineKeyword) { + return String.format( " %s=InnoDB", engineKeyword ); + } + + @Override + public boolean hasSelfReferentialForeignKeyBug() { + return true; + } + + @Override + public boolean dropConstraints() { + return true; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB102Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB102Dialect.java new file mode 100644 index 000000000000..2dba8193b3a8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB102Dialect.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.dialect; + +import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.type.StandardBasicTypes; + +import java.sql.Types; + +public class MariaDB102Dialect extends MariaDB10Dialect { + + public MariaDB102Dialect() { + super(); + + this.registerColumnType( Types.JAVA_OBJECT, "json" ); + this.registerFunction( "json_valid", new StandardSQLFunction( "json_valid", StandardBasicTypes.NUMERIC_BOOLEAN ) ); + + } + + @Override + public boolean supportsColumnCheck() { + return true; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB103Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB103Dialect.java new file mode 100644 index 000000000000..9c0abaca3803 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB103Dialect.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.dialect; + + +import org.hibernate.LockOptions; +import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.type.StandardBasicTypes; + +/** + * An SQL dialect for MariaDB 10.3 and later, provides sequence support, lock-timeouts, etc. + * + * @author Philippe Marschall + */ +public class MariaDB103Dialect extends MariaDB102Dialect { + + public MariaDB103Dialect() { + super(); + + this.registerFunction( "chr", new StandardSQLFunction( "chr", StandardBasicTypes.CHARACTER) ); + } + + @Override + public boolean supportsSequences() { + return true; + } + + @Override + public boolean supportsPooledSequences() { + return true; + } + + @Override + public String getCreateSequenceString(String sequenceName) { + return "create sequence " + sequenceName; + } + + @Override + public String getDropSequenceString(String sequenceName) { + return "drop sequence " + sequenceName; + } + + @Override + public String getSequenceNextValString(String sequenceName) { + return "select " + getSelectSequenceNextValString( sequenceName ); + } + + @Override + public String getSelectSequenceNextValString(String sequenceName) { + return "nextval(" + sequenceName + ")"; + } + + @Override + public String getQuerySequencesString() { + return "select table_name from information_schema.TABLES where table_type='SEQUENCE'"; + } + + @Override + public String getWriteLockString(int timeout) { + if ( timeout == LockOptions.NO_WAIT ) { + return getForUpdateNowaitString(); + } + + if ( timeout > 0 ) { + return getForUpdateString() + " wait " + timeout; + } + + return getForUpdateString(); + } + + @Override + public String getForUpdateNowaitString() { + return getForUpdateString() + " nowait"; + } + + @Override + public String getForUpdateNowaitString(String aliases) { + return getForUpdateString( aliases ) + " nowait"; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB10Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB10Dialect.java new file mode 100644 index 000000000000..11028b68eaff --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDB10Dialect.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 . + */ +package org.hibernate.dialect; + +import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.type.StandardBasicTypes; + +public class MariaDB10Dialect extends MariaDB53Dialect { + + public MariaDB10Dialect() { + super(); + + registerFunction( "regexp_replace", new StandardSQLFunction( "regexp_replace", StandardBasicTypes.STRING ) ); + registerFunction( "regexp_instr", new StandardSQLFunction( "regexp_instr", StandardBasicTypes.INTEGER ) ); + registerFunction( "regexp_substr", new StandardSQLFunction( "regexp_substr", StandardBasicTypes.STRING ) ); + registerFunction( "weight_string", new StandardSQLFunction( "weight_string", StandardBasicTypes.STRING ) ); + registerFunction( "to_base64", new StandardSQLFunction( "to_base64", StandardBasicTypes.STRING ) ); + registerFunction( "from_base64", new StandardSQLFunction( "from_base64", StandardBasicTypes.STRING ) ); + } + + @Override + public boolean supportsIfExistsBeforeConstraintName() { + return true; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java index b5775645669b..db6b85813419 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java @@ -9,7 +9,7 @@ /** * @author Vlad Mihalcea */ -public class MariaDBDialect extends MySQL5InnoDBDialect { +public class MariaDBDialect extends MySQL5Dialect { public MariaDBDialect() { super(); } @@ -17,4 +17,9 @@ public MariaDBDialect() { public boolean supportsRowValueConstructorSyntaxInInList() { return true; } + + @Override + protected MySQLStorageEngine getDefaultMySQLStorageEngine() { + return InnoDBStorageEngine.INSTANCE; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MyISAMStorageEngine.java b/hibernate-core/src/main/java/org/hibernate/dialect/MyISAMStorageEngine.java new file mode 100644 index 000000000000..d62e3fe0a1b5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MyISAMStorageEngine.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 . + */ +package org.hibernate.dialect; + +/** + * Represents the MyISAM storage engine. + * + * @author Vlad Mihalcea + */ +public class MyISAMStorageEngine implements MySQLStorageEngine{ + + public static final MySQLStorageEngine INSTANCE = new MyISAMStorageEngine(); + + @Override + public boolean supportsCascadeDelete() { + return false; + } + + @Override + public String getTableTypeString(String engineKeyword) { + return String.format( " %s=MyISAM", engineKeyword ); + } + + @Override + public boolean hasSelfReferentialForeignKeyBug() { + return false; + } + + @Override + public boolean dropConstraints() { + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL55Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL55Dialect.java new file mode 100644 index 000000000000..fb4ce0e490ea --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL55Dialect.java @@ -0,0 +1,20 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +/** + * An SQL dialect for MySQL 5.5.x specific features. + * + * @author Vlad Mihalcea + */ +public class MySQL55Dialect extends MySQL5Dialect { + + @Override + protected MySQLStorageEngine getDefaultMySQLStorageEngine() { + return InnoDBStorageEngine.INSTANCE; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL57Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL57Dialect.java new file mode 100644 index 000000000000..2adf0514bc78 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL57Dialect.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import java.sql.Types; + +import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.dialect.function.StaticPrecisionFspTimestampFunction; + +/** + * @author Gail Badner + */ +public class MySQL57Dialect extends MySQL55Dialect { + public MySQL57Dialect() { + super(); + + // For details about MySQL 5.7 support for fractional seconds + // precision (fsp): http://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html + // Regarding datetime(fsp), "The fsp value, if given, must be + // in the range 0 to 6. A value of 0 signifies that there is + // no fractional part. If omitted, the default precision is 0. + // (This differs from the standard SQL default of 6, for + // compatibility with previous MySQL versions.)". + + // The following is defined because Hibernate currently expects + // the SQL 1992 default of 6 (which is inconsistent with the MySQL + // default). + registerColumnType( Types.TIMESTAMP, "datetime(6)" ); + + // MySQL 5.7 brings JSON native support with a dedicated datatype. + // For more details about MySql new JSON datatype support, see: + // https://dev.mysql.com/doc/refman/5.7/en/json.html + registerColumnType( Types.JAVA_OBJECT, "json" ); + + // MySQL also supports fractional seconds precision for time values + // (time(fsp)). According to SQL 1992, the default for

    * NOTE : Also, the DROP statement syntax used by Hibernate to drop constraints is * not compatible with ASA. 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 19020e932900..b20284657c69 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -43,4 +43,9 @@ protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { public String getNullColumnString() { return " null"; } + + @Override + public String getCurrentSchemaCommand() { + return "select db_name()"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java index ed157d7578c8..e27281c2176c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java @@ -144,6 +144,11 @@ public String getCreateIdTableStatementOptions() { public String getDropIdTableCommand() { return "drop table"; } + + @Override + public String getTruncateIdTableCommand() { + return "delete from"; + } /** * Get the name of the database type associated with the given diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java b/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java index a5bfacbbf56b..dc74d4d31f24 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java @@ -43,17 +43,17 @@ * * @author Christoph Beck */ -public class TypeNames { +public final class TypeNames { /** * Holds default type mappings for a typeCode. This is the non-sized mapping */ - private Map defaults = new HashMap(); + private final Map defaults = new HashMap(); /** * Holds the weighted mappings for a typeCode. The nested map is a TreeMap to sort its contents * based on the key (the weighting) to ensure proper iteration ordering during {@link #get(int, long, int, int)} */ - private Map> weighted = new HashMap>(); + private final Map> weighted = new HashMap>(); /** * get default type name for specified type @@ -64,8 +64,9 @@ public class TypeNames { * * @throws MappingException Indicates that no registrations were made for that typeCode */ - public String get(int typeCode) throws MappingException { - final String result = defaults.get( typeCode ); + public String get(final int typeCode) throws MappingException { + final Integer integer = Integer.valueOf( typeCode ); + final String result = defaults.get( integer ); if ( result == null ) { throw new MappingException( "No Dialect mapping for JDBC type: " + typeCode ); } @@ -85,7 +86,8 @@ public String get(int typeCode) throws MappingException { * @throws MappingException Indicates that no registrations were made for that typeCode */ public String get(int typeCode, long size, int precision, int scale) throws MappingException { - final Map map = weighted.get( typeCode ); + final Integer integer = Integer.valueOf( typeCode ); + final Map map = weighted.get( integer ); if ( map != null && map.size() > 0 ) { // iterate entries ordered by capacity to find first fit for ( Map.Entry entry: map.entrySet() ) { @@ -115,11 +117,12 @@ private static String replace(String type, long size, int precision, int scale) * @param value The mapping (type name) */ public void put(int typeCode, long capacity, String value) { - Map map = weighted.get( typeCode ); + final Integer integer = Integer.valueOf( typeCode ); + Map map = weighted.get( integer ); if ( map == null ) { // add new ordered map map = new TreeMap(); - weighted.put( typeCode, map ); + weighted.put( integer, map ); } map.put( capacity, value ); } @@ -131,7 +134,8 @@ public void put(int typeCode, long capacity, String value) { * @param value The mapping (type name) */ public void put(int typeCode, String value) { - defaults.put( typeCode, value ); + final Integer integer = Integer.valueOf( typeCode ); + defaults.put( integer, value ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java new file mode 100644 index 000000000000..fd9c5033aabe --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.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 . + */ +package org.hibernate.dialect.function; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.type.StandardBasicTypes; + +/** + * When "substring" function is used for DB2, this implementation of {@link StandardSQLFunction} + * will render "substr" or "substring", depending on the last argument being used. If the last + * argument is a string unit ("CODEUNITS16", "CODEUNITS32", or "OCTETS"), then the function + * will be rendered as "substring"; otherwise, it will be rendered as "substr". + *

    + * ANSI SQL-92 standard defines "substring" without string units, which is more similar to DB2's "substr", + * so it makes sense to use DB2's "substr" function when string units are not provided. + *

    + * Background: DB2 has both "substr" and "substring", which are different functions that are not + * interchangeable. Prior to DB2 11.1, DB2's "substring" function requires an argument for string + * units; without this argument, DB2 throws an exception. DB2's "substr" function throws an exception + * if string unit is provided as an argument. + * + * @author Gail Badner + */ +public class DB2SubstringFunction extends StandardSQLFunction { + private static final Set possibleStringUnits = new HashSet<>( + Arrays.asList( "CODEUNITS16", "CODEUNITS32", "OCTETS" ) + ); + + public DB2SubstringFunction() { + super( "substring", StandardBasicTypes.STRING ); + } + + @Override + protected String getRenderedName(List arguments) { + final String lastArgument = (String) arguments.get( arguments.size() - 1 ); + if ( lastArgument != null && possibleStringUnits.contains( lastArgument.toUpperCase() ) ) { + return getName(); + } + else{ + return "substr"; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java index d3fd714b9d51..bdd98d3a39b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java @@ -86,7 +86,7 @@ public Type getReturnType(Type firstArgumentType, Mapping mapping) { @Override public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { final StringBuilder buf = new StringBuilder(); - buf.append( name ).append( '(' ); + buf.append( getRenderedName( arguments) ).append( '(' ); for ( int i = 0; i < arguments.size(); i++ ) { buf.append( arguments.get( i ) ); if ( i < arguments.size() - 1 ) { @@ -96,6 +96,10 @@ public String render(Type firstArgumentType, List arguments, SessionFactoryImple return buf.append( ')' ).toString(); } + protected String getRenderedName(List arguments) { + return getName(); + } + @Override public String toString() { return name; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/hint/IndexQueryHintHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/hint/IndexQueryHintHandler.java new file mode 100644 index 000000000000..36ce81083a9a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/hint/IndexQueryHintHandler.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 . + */ +package org.hibernate.dialect.hint; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Adds an INDEX query hint as follows: + * + * + * SELECT * + * FROM TEST + * USE INDEX (hint1, hint2) + * WHERE X=1 + * + * + * @author Vlad Mihalcea + */ +public class IndexQueryHintHandler implements QueryHintHandler { + + public static final IndexQueryHintHandler INSTANCE = new IndexQueryHintHandler(); + + private static final Pattern QUERY_PATTERN = Pattern.compile( "^(select.*?from.*?)(where.*?)$" ); + + @Override + public String addQueryHints(String query, String hints) { + Matcher matcher = QUERY_PATTERN.matcher( query ); + if ( matcher.matches() && matcher.groupCount() > 1 ) { + String startToken = matcher.group( 1 ); + String endToken = matcher.group( 2 ); + + return new StringBuilder( startToken ) + .append( " USE INDEX (" ) + .append( hints ) + .append( ") " ) + .append( endToken ) + .toString(); + } + else { + return query; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/hint/QueryHintHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/hint/QueryHintHandler.java new file mode 100644 index 000000000000..4067fcdad957 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/hint/QueryHintHandler.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 . + */ +package org.hibernate.dialect.hint; + +/** + * Contract defining how query hints get applied. + * + * @author Vlad Mihalcea + */ +public interface QueryHintHandler { + + /** + * Add query hints to the given query. + * + * @param query original query + * @param hints hints to be applied + * @return query with hints + */ + String addQueryHints(String query, String hints); +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/HANAIdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/HANAIdentityColumnSupport.java new file mode 100644 index 000000000000..00cd124969d8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/HANAIdentityColumnSupport.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 . + */ +package org.hibernate.dialect.identity; + +import org.hibernate.MappingException; + +public class HANAIdentityColumnSupport extends IdentityColumnSupportImpl { + + @Override + public boolean supportsIdentityColumns() { + return true; + } + + @Override + public String getIdentitySelectString(String table, String column, int type) throws MappingException { + return "select current_identity_value() from " + table; + } + + @Override + public String getIdentityColumnString(int type) { + // implicitly start with 1 increment by 1 + return "generated by default as identity"; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java index 34bc3500f163..d957b4444659 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java @@ -17,7 +17,7 @@ import org.hibernate.persister.entity.Lockable; /** - * An optimistic locking strategy that forces an increment of the version (afterQuery verifying that version hasn't changed). + * An optimistic locking strategy that forces an increment of the version (after verifying that version hasn't changed). * This takes place just prior to transaction commit. *

    * This strategy is valid for LockMode.OPTIMISTIC_FORCE_INCREMENT @@ -50,7 +50,7 @@ public void lock(Serializable id, Object version, Object object, int timeout, Sh } final EntityEntry entry = session.getPersistenceContext().getEntry( object ); // Register the EntityIncrementVersionProcess action to run just prior to transaction commit. - ( (EventSource) session ).getActionQueue().registerProcess( new EntityIncrementVersionProcess( object, entry ) ); + ( (EventSource) session ).getActionQueue().registerProcess( new EntityIncrementVersionProcess( object ) ); } protected LockMode getLockMode() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java index 262149c2aa3a..dceea8792602 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java @@ -12,7 +12,6 @@ import org.hibernate.LockMode; import org.hibernate.OptimisticLockException; import org.hibernate.action.internal.EntityVerifyVersionProcess; -import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.Lockable; @@ -49,9 +48,8 @@ public void lock(Serializable id, Object version, Object object, int timeout, Sh if ( !lockable.isVersioned() ) { throw new OptimisticLockException( object, "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); } - final EntityEntry entry = session.getPersistenceContext().getEntry( object ); // Register the EntityVerifyVersionProcess action to run just prior to transaction commit. - ( (EventSource) session ).getActionQueue().registerProcess( new EntityVerifyVersionProcess( object, entry ) ); + ( (EventSource) session ).getActionQueue().registerProcess( new EntityVerifyVersionProcess( object ) ); } protected LockMode getLockMode() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java index b2b9100f5453..33e8d35b82bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java @@ -56,7 +56,7 @@ public boolean bindLimitParametersInReverseOrder() { * Does the LIMIT clause come at the start of the * SELECT statement, rather than at the end? * - * @return true if limit parameters should come beforeQuery other parameters + * @return true if limit parameters should come before other parameters */ public boolean bindLimitParametersFirst() { return false; @@ -169,6 +169,13 @@ protected final int bindLimitParameters(RowSelection selection, PreparedStatemen protected final int getMaxOrLimit(RowSelection selection) { final int firstRow = convertToFirstRowValue( LimitHelper.getFirstRow( selection ) ); final int lastRow = selection.getMaxRows(); - return useMaxForLimit() ? lastRow + firstRow : lastRow; + final int maxRows = useMaxForLimit() ? lastRow + firstRow : lastRow; + // Use Integer.MAX_VALUE on overflow + if ( maxRows < 0 ) { + return Integer.MAX_VALUE; + } + else { + return maxRows; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Informix10LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Informix10LimitHandler.java new file mode 100644 index 000000000000..eecf677f4273 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Informix10LimitHandler.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect.pagination; + +import java.util.Locale; + +import org.hibernate.engine.spi.RowSelection; + +public class Informix10LimitHandler extends AbstractLimitHandler { + + public static final Informix10LimitHandler INSTANCE = new Informix10LimitHandler(); + + private Informix10LimitHandler() { + // Disallow instantiation + } + + @Override + public String processSql(String sql, RowSelection selection) { + final boolean hasOffset = LimitHelper.hasFirstRow( selection ); + String sqlOffset = hasOffset ? " SKIP " + selection.getFirstRow() : ""; + String sqlLimit = " FIRST " + getMaxOrLimit( selection ); + String sqlOffsetLimit = sqlOffset + sqlLimit; + String result = new StringBuilder( sql.length() + 10 ) + .append( sql ) + .insert( sql.toLowerCase( Locale.ROOT ).indexOf( "select" ) + 6, sqlOffsetLimit ).toString(); + return result; + } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean bindLimitParametersFirst() { + return true; + } + + @Override + public boolean useMaxForLimit() { + return false; + } + + @Override + public boolean supportsLimitOffset() { + return true; + } + + @Override + public boolean supportsVariableLimit() { + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java index b68a99ec44e5..3013254bee5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java @@ -45,7 +45,7 @@ public interface LimitHandler { String processSql(String sql, RowSelection selection); /** - * Bind parameter values needed by the LIMIT clause beforeQuery original SELECT statement. + * Bind parameter values needed by the LIMIT clause before original SELECT statement. * * @param selection the selection criteria for rows. * @param statement Statement to which to bind limit parameter values. @@ -56,7 +56,7 @@ public interface LimitHandler { int bindLimitParametersAtStartOfQuery(RowSelection selection, PreparedStatement statement, int index) throws SQLException; /** - * Bind parameter values needed by the LIMIT clause afterQuery original SELECT statement. + * Bind parameter values needed by the LIMIT clause after original SELECT statement. * * @param selection the selection criteria for rows. * @param statement Statement to which to bind limit parameter values. diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java index 5c5c14b7a528..b67a670dc276 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java @@ -42,7 +42,14 @@ public int bindLimitParametersAtEndOfQuery(RowSelection selection, PreparedState @Override public void setMaxRows(RowSelection selection, PreparedStatement statement) throws SQLException { if ( LimitHelper.hasMaxRows( selection ) ) { - statement.setMaxRows( selection.getMaxRows() + convertToFirstRowValue( LimitHelper.getFirstRow( selection ) ) ); + int maxRows = selection.getMaxRows() + convertToFirstRowValue( LimitHelper.getFirstRow( selection ) ); + // Use Integer.MAX_VALUE on overflow + if ( maxRows < 0 ) { + statement.setMaxRows( Integer.MAX_VALUE ); + } + else { + statement.setMaxRows( maxRows ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java index ac6a83c086a5..7241927ab771 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -42,8 +43,18 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { private static final Pattern ALIAS_PATTERN = Pattern.compile( "(?![^\\[]*(\\]))\\S+\\s*(\\s(?i)as\\s)\\s*(\\S+)*\\s*$|(?![^\\[]*(\\]))\\s+(\\S+)$" ); + // CTE pattern support + private static final String SPACE_NEWLINE_LINEFEED = "[\\s\\t\\n\\r]*"; + private static final Pattern WITH_CTE = Pattern.compile( "(^" + SPACE_NEWLINE_LINEFEED + "WITH" + SPACE_NEWLINE_LINEFEED +")", Pattern.CASE_INSENSITIVE ); + private static final Pattern WITH_EXPRESSION_NAME = Pattern.compile( "(^" + SPACE_NEWLINE_LINEFEED + "[a-zA-Z0-9]*" + SPACE_NEWLINE_LINEFEED +")", Pattern.CASE_INSENSITIVE ); + private static final Pattern WITH_COLUMN_NAMES_START = Pattern.compile( "(^" + SPACE_NEWLINE_LINEFEED + "\\()", Pattern.CASE_INSENSITIVE ); + private static final Pattern WITH_COLUMN_NAMES_END = Pattern.compile( "(\\))", Pattern.CASE_INSENSITIVE ); + private static final Pattern WITH_AS = Pattern.compile( "(^" + SPACE_NEWLINE_LINEFEED + "AS" + SPACE_NEWLINE_LINEFEED +")", Pattern.CASE_INSENSITIVE ); + private static final Pattern WITH_COMMA = Pattern.compile( "(^" + SPACE_NEWLINE_LINEFEED + ",)", Pattern.CASE_INSENSITIVE ); + // Flag indicating whether TOP(?) expression has been added to the original query. private boolean topAdded; + private boolean isCTE; /** * Constructs a SQLServer2005LimitHandler @@ -103,24 +114,27 @@ public String processSql(String sql, RowSelection selection) { sb.setLength( sb.length() - 1 ); } - if ( LimitHelper.hasFirstRow( selection ) ) { - final String selectClause = fillAliasInSelectClause( sb ); + // checks the query buffer for CTE queries or simple SELECT statements + // returns the index where the injection should start. + final int offset = getStatementIndex( sb ); + + if ( !LimitHelper.hasFirstRow( selection ) ) { + addTopExpression( sb, offset ); + } + else { + final String selectClause = fillAliasInSelectClause( sb, offset ); - final int orderByIndex = shallowIndexOfPattern( sb, ORDER_BY_PATTERN, 0 ); - if ( orderByIndex > 0 ) { - // ORDER BY requires using TOP. - addTopExpression( sb ); + if ( shallowIndexOfPattern( sb, ORDER_BY_PATTERN, offset ) > 0 ) { + // ORDER BY requires using TOP + addTopExpression( sb, offset ); } - encloseWithOuterQuery( sb ); + encloseWithOuterQuery( sb, offset ); - // Wrap the query within a with statement: - sb.insert( 0, "WITH query AS (" ).append( ") SELECT " ).append( selectClause ).append( " FROM query " ); + sb.insert( offset, !isCTE ? "WITH query AS (" : ", query AS (" ); + sb.append( ") SELECT " ).append( selectClause ).append( " FROM query " ); sb.append( "WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?" ); } - else { - addTopExpression( sb ); - } return sb.toString(); } @@ -146,13 +160,14 @@ public int bindLimitParametersAtEndOfQuery(RowSelection selection, PreparedState * method returns {@literal *}. * * @param sb SQL query. + * @param offset the starting offset. * * @return List of aliases separated with comas or {@literal *}. */ - protected String fillAliasInSelectClause(StringBuilder sb) { + protected String fillAliasInSelectClause(StringBuilder sb, int offset) { final String separator = System.lineSeparator(); final List aliases = new LinkedList(); - final int startPos = getSelectColumnsStartPosition( sb ); + final int startPos = getSelectColumnsStartPosition( sb, offset ); int endPos = shallowIndexOfPattern( sb, FROM_PATTERN, startPos ); int nextComa = startPos; @@ -206,17 +221,18 @@ protected String fillAliasInSelectClause(StringBuilder sb) { } // In case of '*' or '{table}.*' expressions adding an alias breaks SQL syntax, returning '*'. - return selectsMultipleColumns ? "*" : StringHelper.join( ", ", aliases.iterator() ); + return selectsMultipleColumns ? "*" : String.join( ", ", aliases ); } /** * Get the start position for where the column list begins. * * @param sb the string builder sql. + * @param offset the starting offset. * @return the start position where the column list begins. */ - private int getSelectColumnsStartPosition(StringBuilder sb) { - final int startPos = getSelectStartPosition( sb ); + private int getSelectColumnsStartPosition(StringBuilder sb, int offset) { + final int startPos = getSelectStartPosition( sb, offset ); // adjustment for 'select distinct ' and 'select '. final String sql = sb.toString().substring( startPos ).toLowerCase(); if ( sql.startsWith( SELECT_DISTINCT_SPACE ) ) { @@ -232,10 +248,11 @@ else if ( sql.startsWith( SELECT_SPACE ) ) { * Get the select start position. * * @param sb the string builder sql. + * @param offset the starting offset in buffer. * @return the position where {@code select} is found. */ - private int getSelectStartPosition(StringBuilder sb) { - return shallowIndexOfPattern( sb, SELECT_PATTERN, 0 ); + private int getSelectStartPosition(StringBuilder sb, int offset) { + return shallowIndexOfPattern( sb, SELECT_PATTERN, offset ); } /** @@ -284,9 +301,10 @@ private String getAlias(String expression) { * Encloses original SQL statement with outer query that provides {@literal __hibernate_row_nr__} column. * * @param sql SQL query. + * @param offset SQL query offset. */ - protected void encloseWithOuterQuery(StringBuilder sql) { - sql.insert( 0, "SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " ); + protected void encloseWithOuterQuery(StringBuilder sql, int offset) { + sql.insert( offset, "SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " ); sql.append( " ) inner_query " ); } @@ -295,11 +313,12 @@ protected void encloseWithOuterQuery(StringBuilder sql) { * {@link #bindLimitParametersAtStartOfQuery(RowSelection, PreparedStatement, int)} method. * * @param sql SQL query. + * @param offset the offset where top expression pattern matching should begin. */ - protected void addTopExpression(StringBuilder sql) { + protected void addTopExpression(StringBuilder sql, int offset) { // We should use either of these which come first (SELECT or SELECT DISTINCT). - final int selectPos = shallowIndexOfPattern( sql, SELECT_PATTERN, 0 ); - final int selectDistinctPos = shallowIndexOfPattern( sql, SELECT_DISTINCT_PATTERN, 0 ); + final int selectPos = shallowIndexOfPattern( sql, SELECT_PATTERN, offset ); + final int selectDistinctPos = shallowIndexOfPattern( sql, SELECT_DISTINCT_PATTERN, offset ); if ( selectPos == selectDistinctPos ) { // Place TOP after SELECT DISTINCT sql.insert( selectDistinctPos + SELECT_DISTINCT.length(), " TOP(?)" ); @@ -387,15 +406,19 @@ private static List generateIgnoreRanges(String sql) { int depth = 0; int start = -1; + boolean insideAStringValue = false; for ( int i = 0; i < sql.length(); ++i ) { final char ch = sql.charAt( i ); - if ( ch == '(' ) { + if ( ch == '\'' ) { + insideAStringValue = !insideAStringValue; + } + else if ( ch == '(' && !insideAStringValue ) { depth++; if ( depth == 1 ) { start = i; } } - else if ( ch == ')' ) { + else if ( ch == ')' && !insideAStringValue ) { if ( depth > 0 ) { if ( depth == 1 ) { ignoreRangeList.add( new IgnoreRange( start, i ) ); @@ -445,4 +468,146 @@ boolean isWithinRange(int position) { return position >= start && position <= end; } } + + /** + * Get the starting point for the limit handler to begin injecting and transforming the SQL. + * For non-CTE queries, this is offset 0. For CTE queries, this will be where the CTE's + * SELECT clause begins (skipping all query definitions, column definitions and expressions). + * + * This method also sets {@code isCTE} if the query is parsed as a CTE query. + * + * @param sql The sql buffer. + * @return the index where to begin parsing. + */ + private int getStatementIndex(StringBuilder sql) { + final Matcher matcher = WITH_CTE.matcher( sql.toString() ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + isCTE = true; + return locateQueryInCTEStatement( sql, matcher.end() ); + } + return 0; + } + + /** + * Steps through the SQL buffer from the specified offset and performs a series of pattern matches. + * The method locates where the CTE SELECT clause begins and returns that offset from the SQL buffer. + * + * @param sql The sql buffer. + * @param offset The offset to begin pattern matching. + * + * @return the offset where the CTE SELECT clause begins. + * @throws IllegalArgumentException if the parse of the CTE query fails. + */ + private int locateQueryInCTEStatement(StringBuilder sql, int offset) { + while ( true ) { + Matcher matcher = WITH_EXPRESSION_NAME.matcher( sql.substring( offset ) ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + offset += matcher.end(); + matcher = WITH_COLUMN_NAMES_START.matcher( sql.substring( offset ) ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + offset += matcher.end(); + matcher = WITH_COLUMN_NAMES_END.matcher( sql.substring( offset ) ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + offset += matcher.end(); + offset += advanceOverCTEInnerQuery( sql, offset ); + matcher = WITH_COMMA.matcher( sql.substring( offset ) ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + // another CTE fragment exists, re-start parse of CTE + offset += matcher.end(); + } + else { + // last CTE fragment, we're at the start of the SQL. + return offset; + } + } + else { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Failed to parse CTE expression columns at offset %d, SQL [%s]", + offset, + sql.toString() + ) + ); + } + } + else { + matcher = WITH_AS.matcher( sql.substring( offset ) ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + offset += matcher.end(); + offset += advanceOverCTEInnerQuery( sql, offset ); + matcher = WITH_COMMA.matcher( sql.substring( offset ) ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + // another CTE fragment exists, re-start parse of CTE + offset += matcher.end(); + } + else { + // last CTE fragment, we're at the start of the SQL. + return offset; + } + } + else { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Failed to locate AS keyword in CTE query at offset %d, SQL [%s]", + offset, + sql.toString() + ) + ); + } + } + } + else { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Failed to locate CTE expression name at offset %d, SQL [%s]", + offset, + sql.toString() + ) + ); + } + } + } + + /** + * Advances over the CTE inner query that is contained inside matching '(' and ')'. + * + * @param sql The sql buffer. + * @param offset The offset where to begin advancing the position from. + * @return the position immediately after the CTE inner query plus 1. + * + * @throws IllegalArgumentException if the matching parenthesis aren't detected at the end of the parse. + */ + private int advanceOverCTEInnerQuery(StringBuilder sql, int offset) { + int brackets = 0; + int index = offset; + boolean inString = false; + for ( ; index < sql.length(); ++index ) { + if ( sql.charAt( index ) == '\'' ) { + inString = true; + } + else if ( sql.charAt( index ) == '\'' && inString ) { + inString = false; + } + else if ( sql.charAt( index ) == '(' && !inString ) { + brackets++; + } + else if ( sql.charAt( index ) == ')' && !inString ) { + brackets--; + if ( brackets == 0 ) { + break; + } + } + } + + if ( brackets > 0 ) { + throw new IllegalArgumentException( + "Failed to parse the CTE query inner query because closing ')' was not found." + ); + } + + return index - offset + 1; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java index 5a6bafd51dd6..d4828a3fd499 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java @@ -6,6 +6,9 @@ */ package org.hibernate.dialect.pagination; +import java.sql.PreparedStatement; +import java.sql.SQLException; + import org.hibernate.engine.spi.RowSelection; /** @@ -14,9 +17,10 @@ * @author Chris Cranford */ public class SQLServer2012LimitHandler extends SQLServer2005LimitHandler { - public static final SQLServer2012LimitHandler INSTANCE = new SQLServer2012LimitHandler(); + // determines whether the limit handler used offset/fetch or 2005 behavior. + private boolean usedOffsetFetch; - private SQLServer2012LimitHandler() { + public SQLServer2012LimitHandler() { } @@ -27,7 +31,7 @@ public boolean supportsLimit() { @Override public boolean supportsVariableLimit() { - return false; + return true; } @Override @@ -40,16 +44,77 @@ public String processSql(String sql, RowSelection selection) { if ( !LimitHelper.useLimit( this, selection ) ) { return sql; } - int firstRow = LimitHelper.hasFirstRow( selection ) ? selection.getFirstRow() : 0; - return sql + String.format( " offset %d rows fetch next %d rows only", firstRow, selection.getMaxRows() ); + return applyOffsetFetch( selection, sql, getInsertPosition( sql ) ); } return super.processSql( sql, selection ); } + @Override + public boolean useMaxForLimit() { + // when using the offset fetch clause, the max value is passed as-is. + // SQLServer2005LimitHandler uses start + max values. + return usedOffsetFetch ? false : super.useMaxForLimit(); + } + + @Override + public int convertToFirstRowValue(int zeroBasedFirstResult) { + // When using the offset/fetch clause, the first row is passed as-is + // SQLServer2005LimitHandler uses zeroBasedFirstResult + 1 + if ( usedOffsetFetch ) { + return zeroBasedFirstResult; + } + return super.convertToFirstRowValue( zeroBasedFirstResult ); + } + + @Override + public int bindLimitParametersAtEndOfQuery(RowSelection selection, PreparedStatement statement, int index) + throws SQLException { + if ( usedOffsetFetch && !LimitHelper.hasFirstRow( selection ) ) { + // apply just the max value when offset fetch applied + statement.setInt( index, getMaxOrLimit( selection ) ); + return 1; + } + return super.bindLimitParametersAtEndOfQuery( selection, statement, index ); + } + + private String getOffsetFetch(RowSelection selection) { + if ( !LimitHelper.hasFirstRow( selection ) ) { + return " offset 0 rows fetch next ? rows only"; + } + return " offset ? rows fetch next ? rows only"; + } + + private int getInsertPosition(String sql) { + int position = sql.length() - 1; + for ( ; position > 0; --position ) { + char ch = sql.charAt( position ); + if ( ch != ';' && ch != ' ' && ch != '\r' && ch != '\n' ) { + break; + } + } + return position + 1; + } + + private String applyOffsetFetch(RowSelection selection, String sql, int position) { + usedOffsetFetch = true; + + StringBuilder sb = new StringBuilder(); + sb.append( sql.substring( 0, position ) ); + sb.append( getOffsetFetch( selection ) ); + if ( position > sql.length() ) { + sb.append( sql.substring( position - 1 ) ); + } + + return sb.toString(); + } + private boolean hasOrderBy(String sql) { int depth = 0; - for ( int i = 0; i < sql.length(); ++i ) { - char ch = sql.charAt( i ); + + String lowerCaseSQL = sql.toLowerCase(); + + for ( int i = lowerCaseSQL.length() - 1; i >= 0; --i ) { + char ch = lowerCaseSQL.charAt( i ); if ( ch == '(' ) { depth++; } @@ -57,7 +122,7 @@ else if ( ch == ')' ) { depth--; } if ( depth == 0 ) { - if ( sql.substring( i ).toLowerCase().startsWith( "order by " ) ) { + if ( lowerCaseSQL.startsWith( "order by ", i ) ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java new file mode 100644 index 000000000000..5137385a0505 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java @@ -0,0 +1,83 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect.pagination; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hibernate.engine.spi.RowSelection; + +/** + * This limit handler is very conservative and is only triggered in simple cases involving a select or select distinct. + *

    + * Note that if the query already contains "top" just after the select or select distinct, we don't add anything to the + * query. It might just be a column name but, in any case, we just don't add the top clause and default to the previous + * behavior so it's not an issue. + */ +public class SybaseASE157LimitHandler extends AbstractLimitHandler { + + private static final Pattern SELECT_DISTINCT_PATTERN = Pattern.compile( "^(\\s*select\\s+distinct\\s+).*", + Pattern.CASE_INSENSITIVE ); + private static final Pattern SELECT_PATTERN = Pattern.compile( "^(\\s*select\\s+).*", Pattern.CASE_INSENSITIVE ); + private static final Pattern TOP_PATTERN = Pattern.compile( "^\\s*top\\s+.*", Pattern.CASE_INSENSITIVE ); + + @Override + public String processSql(String sql, RowSelection selection) { + if ( selection.getMaxRows() == null ) { + return sql; + } + + int top = getMaxOrLimit( selection ); + if ( top == Integer.MAX_VALUE ) { + return sql; + } + + Matcher selectDistinctMatcher = SELECT_DISTINCT_PATTERN.matcher( sql ); + if ( selectDistinctMatcher.matches() ) { + return insertTop( selectDistinctMatcher, sql, top ); + } + + Matcher selectMatcher = SELECT_PATTERN.matcher( sql ); + if ( selectMatcher.matches() ) { + return insertTop( selectMatcher, sql, top ); + } + + return sql; + } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean supportsLimitOffset() { + return false; + } + + @Override + public boolean useMaxForLimit() { + return true; + } + + @Override + public boolean supportsVariableLimit() { + return false; + } + + private static String insertTop(Matcher matcher, String sql, int top) { + int end = matcher.end( 1 ); + + if ( TOP_PATTERN.matcher( sql.substring( end ) ).matches() ) { + return sql; + } + + StringBuilder sb = new StringBuilder( sql ); + sb.insert( end, "top " + top + " " ); + return sb.toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/TopLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/TopLimitHandler.java index 8b5f7db4e8f0..1ef9792184bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/TopLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/TopLimitHandler.java @@ -39,7 +39,13 @@ public boolean useMaxForLimit() { public boolean supportsLimitOffset() { return supportsVariableLimit; } - + + @Override + public boolean supportsVariableLimit() { + return supportsVariableLimit; + } + + @Override public boolean bindLimitParametersFirst() { return bindLimitParametersFirst; } @@ -56,14 +62,14 @@ public String processSql(String sql, RowSelection selection) { StringBuilder sb = new StringBuilder( sql.length() + 8 ) .append( sql ); - + if ( supportsVariableLimit ) { sb.insert( insertionPoint, " TOP ? " ); } else { sb.insert( insertionPoint, " TOP " + getMaxOrLimit( selection ) + " " ); } - + return sb.toString(); } } 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 585a74482b19..eec4fbd62f1f 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 @@ -53,7 +53,8 @@ public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata m ); final String constraintName = dialect.quote( uniqueKey.getName() ); - return "alter table " + tableName + " add constraint " + constraintName + " " + uniqueConstraintSql( uniqueKey ); + return dialect.getAlterTableString( tableName ) + + " add constraint " + constraintName + " " + uniqueConstraintSql( uniqueKey ); } protected String uniqueConstraintSql(UniqueKey uniqueKey) { @@ -83,8 +84,7 @@ public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata dialect ); - final StringBuilder buf = new StringBuilder( "alter table " ); - buf.append( tableName ); + final StringBuilder buf = new StringBuilder( dialect.getAlterTableString(tableName) ); buf.append( getDropUnique() ); if ( dialect.supportsIfExistsBeforeConstraintName() ) { buf.append( "if exists " ); 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 61626e50fddf..ae8fc4204cac 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 @@ -32,7 +32,8 @@ public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata m metadata.getDatabase().getJdbcEnvironment().getDialect() ); final String constraintName = dialect.quote( uniqueKey.getName() ); - return "alter table " + tableName + " add constraint " + uniqueConstraintSql( uniqueKey ) + " constraint " + constraintName; + return dialect.getAlterTableString( tableName ) + + " add constraint " + uniqueConstraintSql( uniqueKey ) + " constraint " + constraintName; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/HibernateIterator.java b/hibernate-core/src/main/java/org/hibernate/engine/HibernateIterator.java index 5fd5c79194d4..86c9d1126395 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/HibernateIterator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/HibernateIterator.java @@ -6,6 +6,7 @@ */ package org.hibernate.engine; +import java.io.Closeable; import java.util.Iterator; import org.hibernate.JDBCException; @@ -18,7 +19,7 @@ * * @author Gavin King */ -public interface HibernateIterator extends Iterator, AutoCloseable { +public interface HibernateIterator extends Iterator, AutoCloseable, Closeable { /** * Close the Hibernate query result iterator * 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 bb87bced99ba..714f2f932b15 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java @@ -16,12 +16,15 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.Session; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -345,6 +348,15 @@ private boolean isUnequivocallyNonDirty(Object entity) { return ! persister.hasCollections() && ! ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes(); } + if ( entity instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // we never have to check an uninitialized proxy + return true; + } + } + final CustomEntityDirtinessStrategy customEntityDirtinessStrategy = getPersistenceContext().getSession().getFactory().getCustomEntityDirtinessStrategy(); if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) getPersistenceContext().getSession() ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java new file mode 100644 index 000000000000..225bef5735f4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.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.engine.internal; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.engine.spi.BatchFetchQueue; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.persister.entity.EntityPersister; + +import org.jboss.logging.Logger; + +/** + * @author Gail Badner + */ +public class BatchFetchQueueHelper { + private static final CoreMessageLogger LOG = Logger.getMessageLogger( + CoreMessageLogger.class, + BatchFetchQueueHelper.class.getName() + ); + + private BatchFetchQueueHelper(){ + } + + /** + * Finds the IDs for entities that were not found when the batch was loaded, and removes + * the corresponding entity keys from the {@link BatchFetchQueue}. + * + * @param ids - the IDs for the entities that were batch loaded + * @param results - the results from loading the batch + * @param persister - the entity persister for the entities in batch + * @param session - the session + */ + public static void removeNotFoundBatchLoadableEntityKeys( + Serializable[] ids, + List results, + EntityPersister persister, + SharedSessionContractImplementor session) { + if ( !persister.isBatchLoadable() ) { + return; + } + if ( ids.length == results.size() ) { + return; + } + LOG.debug( "Not all entities were loaded." ); + Set idSet = new HashSet<>( Arrays.asList( ids ) ); + for ( Object result : results ) { + // All results should be in the PersistenceContext + idSet.remove( session.getContextEntityIdentifier( result ) ); + } + if ( LOG.isDebugEnabled() ) { + LOG.debug( "Entities of type [" + persister.getEntityName() + "] not found; IDs: " + idSet ); + } + for ( Serializable id : idSet ) { + removeBatchLoadableEntityKey( id, persister, session ); + } + } + + /** + * Remove the entity key with the specified {@code id} and {@code persister} from + * the batch loadable entities {@link BatchFetchQueue}. + * + * @param id - the ID for the entity to be removed + * @param persister - the entity persister + * @param session - the session + */ + public static void removeBatchLoadableEntityKey( + Serializable id, + EntityPersister persister, + SharedSessionContractImplementor session) { + final EntityKey entityKey = session.generateEntityKey( id, persister ); + final BatchFetchQueue batchFetchQueue = session.getPersistenceContext().getBatchFetchQueue(); + batchFetchQueue.removeBatchLoadableEntityKey( entityKey ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/CacheHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/CacheHelper.java index 02784ffe3286..39a7218b0a6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/CacheHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/CacheHelper.java @@ -8,7 +8,7 @@ import java.io.Serializable; -import org.hibernate.cache.spi.access.RegionAccessStrategy; +import org.hibernate.cache.spi.access.CachedDomainDataAccess; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -24,12 +24,12 @@ private CacheHelper() { public static Serializable fromSharedCache( SharedSessionContractImplementor session, Object cacheKey, - RegionAccessStrategy cacheAccessStrategy) { + CachedDomainDataAccess cacheAccess) { final SessionEventListenerManager eventListenerManager = session.getEventListenerManager(); Serializable cachedValue = null; eventListenerManager.cacheGetStart(); try { - cachedValue = (Serializable) cacheAccessStrategy.get( session, cacheKey, session.getTimestamp() ); + cachedValue = (Serializable) cacheAccess.get( session, cacheKey ); } finally { eventListenerManager.cacheGetEnd( cachedValue != null ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index f4ab76af3f27..7d9d97cecaa4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -25,6 +25,7 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; +import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; @@ -70,9 +71,12 @@ public static void cascade( * which is specific to each CascadingAction type */ public static void cascade( - final CascadingAction action, final CascadePoint cascadePoint, - final EventSource eventSource, final EntityPersister persister, final Object parent, final Object anything) - throws HibernateException { + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final EntityPersister persister, + final Object parent, + final Object anything) throws HibernateException { if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt final boolean traceEnabled = LOG.isTraceEnabled(); @@ -86,21 +90,62 @@ public static void cascade( final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); final int componentPathStackDepth = 0; for ( int i = 0; i < types.length; i++) { - final CascadeStyle style = cascadeStyles[i]; - final String propertyName = propertyNames[i]; + final CascadeStyle style = cascadeStyles[ i ]; + final String propertyName = propertyNames[ i ]; + final boolean isUninitializedProperty = + hasUninitializedLazyProperties && + !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); if ( style.doCascade( action ) ) { - Object child; + final Object child; + if ( isUninitializedProperty ) { + // parent is a bytecode enhanced entity. + // Cascade to an uninitialized, lazy value only if + // parent is managed in the PersistenceContext. + // If parent is a detached entity being merged, + // then parent will not be in the PersistencContext + // (so lazy attributes must not be initialized). + if ( eventSource.getPersistenceContext().getEntry( parent ) == null ) { + // parent was not in the PersistenceContext + continue; + } + if ( types[ i ].isCollectionType() ) { + // CollectionType#getCollection gets the PersistentCollection + // that corresponds to the uninitialized collection from the + // PersistenceContext. If not present, an uninitialized + // PersistentCollection will be added to the PersistenceContext. + // The action may initialize it later, if necessary. + // This needs to be done even when action.performOnLazyProperty() returns false. + final CollectionType collectionType = (CollectionType) types[i]; + child = collectionType.getCollection( + collectionType.getKeyOfOwner( parent, eventSource ), + eventSource, + parent, + null + ); + } + else if ( types[ i ].isComponentType() ) { + // Hibernate does not support lazy embeddables, so this shouldn't happen. + throw new UnsupportedOperationException( + "Lazy components are not supported." + ); + } + else if ( action.performOnLazyProperty() && types[ i ].isEntityType() ) { + // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() + // returns true. + LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() + .extractInterceptor( parent ); + child = interceptor.fetchAttribute( parent, propertyName ); - // For bytecode enhanced entities, need to fetch the attribute - if ( hasUninitializedLazyProperties && persister.getPropertyLaziness()[i] && action.performOnLazyProperty() ) { - LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata().extractInterceptor( parent ); - child = interceptor.fetchAttribute( parent, propertyName ); + } + else { + // Nothing to do, so just skip cascading to this lazy attribute. + continue; + } } else { child = persister.getPropertyValue( parent, i ); } - cascadeProperty( action, cascadePoint, @@ -108,21 +153,37 @@ public static void cascade( componentPathStackDepth, parent, child, - types[i], + types[ i ], style, propertyName, anything, false ); } - else if ( action.requiresNoCascadeChecking() ) { - action.noCascade( - eventSource, - parent, - persister, - types[i], - i - ); + else { + if ( action.requiresNoCascadeChecking() ) { + action.noCascade( + eventSource, + parent, + persister, + types[i], + i + ); + } + // If the property is uninitialized, then there cannot be any orphans. + if ( action.deleteOrphans() && !isUninitializedProperty ) { + cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + componentPathStackDepth, + parent, + persister.getPropertyValue( parent, i ), + types[ i ], + style, + propertyName, + false + ); + } } } @@ -179,7 +240,30 @@ else if ( type.isComponentType() ) { ); } } - + + cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + componentPathStackDepth, + parent, + child, + type, + style, + propertyName, + isCascadeDeleteEnabled ); + } + + private static void cascadeLogicalOneToOneOrphanRemoval( + final CascadingAction action, + final EventSource eventSource, + final int componentPathStackDepth, + final Object parent, + final Object child, + final Type type, + final CascadeStyle style, + final String propertyName, + final boolean isCascadeDeleteEnabled) throws HibernateException { + // potentially we need to handle orphan deletes for one-to-ones here... if ( isLogicalOneToOne( type ) ) { // We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require @@ -189,7 +273,7 @@ else if ( type.isComponentType() ) { // because it is currently null. final EntityEntry entry = eventSource.getPersistenceContext().getEntry( parent ); if ( entry != null && entry.getStatus() != Status.SAVING ) { - final Object loadedValue; + Object loadedValue; if ( componentPathStackDepth == 0 ) { // association defined on entity loadedValue = entry.getLoadedValue( propertyName ); @@ -211,15 +295,30 @@ else if ( type.isComponentType() ) { // final String getPropertyPath = composePropertyPath( entityType.getPropertyName() ); loadedValue = null; } - + // orphaned if the association was nulled (child == null) or receives a new value while the // entity is managed (without first nulling and manually flushing). if ( child == null || ( loadedValue != null && child != loadedValue ) ) { - final EntityEntry valueEntry = eventSource - .getPersistenceContext().getEntry( + EntityEntry valueEntry = eventSource + .getPersistenceContext().getEntry( loadedValue ); - // Need to check this in case the context has - // already been flushed. See HHH-7829. + + if ( valueEntry == null && loadedValue instanceof HibernateProxy ) { + // un-proxy and re-associate for cascade operation + // useful for @OneToOne defined as FetchType.LAZY + loadedValue = eventSource.getPersistenceContext().unproxyAndReassociate( loadedValue ); + valueEntry = eventSource.getPersistenceContext().getEntry( loadedValue ); + + // HHH-11965 + // Should the unwrapped proxy value be equal via reference to the entity's property value + // provided by the 'child' variable, we should not trigger the orphan removal of the + // associated one-to-one. + if ( child == loadedValue ) { + // do nothing + return; + } + } + if ( valueEntry != null ) { final String entityName = valueEntry.getPersister().getEntityName(); if ( LOG.isTraceEnabled() ) { @@ -227,17 +326,17 @@ else if ( type.isComponentType() ) { final String description = MessageHelper.infoString( entityName, id ); LOG.tracev( "Deleting orphaned entity instance: {0}", description ); } - - if (type.isAssociationType() && ((AssociationType)type).getForeignKeyDirection().equals( - ForeignKeyDirection.TO_PARENT - )) { - // If FK direction is to-parent, we must remove the orphan *beforeQuery* the queued update(s) + + if ( type.isAssociationType() && ( (AssociationType) type ).getForeignKeyDirection().equals( + ForeignKeyDirection.TO_PARENT + ) ) { + // If FK direction is to-parent, we must remove the orphan *before* the queued update(s) // occur. Otherwise, replacing the association on a managed entity, without manually // nulling and flushing, causes FK constraint violations. eventSource.removeOrphanBeforeUpdates( entityName, loadedValue ); } else { - // Else, we must delete afterQuery the updates. + // Else, we must delete after the updates. eventSource.delete( entityName, loadedValue, isCascadeDeleteEnabled, new HashSet() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/CascadePoint.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/CascadePoint.java index c5c8e8ddfb2e..f040764c983b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/CascadePoint.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/CascadePoint.java @@ -13,52 +13,52 @@ */ public enum CascadePoint { /** - * A cascade point that occurs just afterQuery the insertion of the parent entity and - * just beforeQuery deletion + * A cascade point that occurs just after the insertion of the parent entity and + * just before deletion */ AFTER_INSERT_BEFORE_DELETE, /** - * A cascade point that occurs just beforeQuery the insertion of the parent entity and - * just afterQuery deletion + * A cascade point that occurs just before the insertion of the parent entity and + * just after deletion */ BEFORE_INSERT_AFTER_DELETE, /** - * A cascade point that occurs just afterQuery the insertion of the parent entity and - * just beforeQuery deletion, inside a collection + * A cascade point that occurs just after the insertion of the parent entity and + * just before deletion, inside a collection */ AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION, /** - * A cascade point that occurs just afterQuery update of the parent entity + * A cascade point that occurs just after update of the parent entity */ AFTER_UPDATE, /** - * A cascade point that occurs just beforeQuery the session is flushed + * A cascade point that occurs just before the session is flushed */ BEFORE_FLUSH, /** - * A cascade point that occurs just afterQuery eviction of the parent entity from the + * A cascade point that occurs just after eviction of the parent entity from the * session cache */ AFTER_EVICT, /** - * A cascade point that occurs just afterQuery locking a transient parent entity into the + * A cascade point that occurs just after locking a transient parent entity into the * session cache */ BEFORE_REFRESH, /** - * A cascade point that occurs just afterQuery refreshing a parent entity + * A cascade point that occurs just after refreshing a parent entity */ AFTER_LOCK, /** - * A cascade point that occurs just beforeQuery merging from a transient parent entity into + * A cascade point that occurs just before merging from a transient parent entity into * the object in the session cache */ BEFORE_MERGE diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java old mode 100755 new mode 100644 index c79b038d421c..f4b4a61a961f --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java @@ -10,6 +10,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; @@ -163,66 +164,70 @@ public static void processReachableCollection( //TODO: better to pass the id in as an argument? ce.setCurrentKey( type.getKeyOfOwner( entity, session ) ); - final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getInstrumentationMetadata().isEnhancedForLazyLoading(); + final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); if ( isBytecodeEnhanced && !collection.wasInitialized() ) { - // skip it - LOG.debugf( - "Skipping uninitialized bytecode-lazy collection: %s", - MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ) - ); + // the class of the collection owner is enhanced for lazy loading and we found an un-initialized PersistentCollection + // - skip it + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Skipping uninitialized bytecode-lazy collection: %s", + MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session) + ); + } ce.setReached( true ); ce.setProcessed( true ); + return; } - else { - // The CollectionEntry.isReached() stuff is just to detect any silly users - // who set up circular or shared references between/to collections. - if ( ce.isReached() ) { - // We've been here beforeQuery - throw new HibernateException( - "Found shared references to a collection: " + type.getRole() + + // The CollectionEntry.isReached() stuff is just to detect any silly users + // who set up circular or shared references between/to collections. + if ( ce.isReached() ) { + // We've been here before + throw new HibernateException( + "Found shared references to a collection: " + type.getRole() + ); + } + + ce.setReached( true ); + + if ( LOG.isDebugEnabled() ) { + if ( collection.wasInitialized() ) { + LOG.debugf( + "Collection found: %s, was: %s (initialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) ); } - ce.setReached( true ); - - if ( LOG.isDebugEnabled() ) { - if ( collection.wasInitialized() ) { - LOG.debugf( - "Collection found: %s, was: %s (initialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } - else { - LOG.debugf( - "Collection found: %s, was: %s (uninitialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } + else { + LOG.debugf( + "Collection found: %s, was: %s (uninitialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) + ); } - - prepareCollectionForUpdate( collection, ce, factory ); } + + prepareCollectionForUpdate( collection, ce, factory ); } /** @@ -245,9 +250,15 @@ private static void prepareCollectionForUpdate( if ( loadedPersister != null || currentPersister != null ) { // it is or was referenced _somewhere_ + // check if the key changed + // excludes marking key changed when the loaded key is a DelayedPostInsertIdentifier. + final boolean keyChanged = currentPersister != null + && entry != null + && !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory ) + && !( entry.getLoadedKey() instanceof DelayedPostInsertIdentifier ); + // if either its role changed, or its key changed - final boolean ownerChanged = loadedPersister != currentPersister - || !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory ); + final boolean ownerChanged = loadedPersister != currentPersister || keyChanged; if ( ownerChanged ) { // do a check diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java old mode 100755 new mode 100644 index 9c55a1312ea4..879533baffb4 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -13,7 +13,9 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -36,6 +38,7 @@ public static class Nullifier { private final boolean isEarlyInsert; private final SharedSessionContractImplementor session; private final Object self; + private final EntityPersister persister; /** * Constructs a Nullifier @@ -44,11 +47,18 @@ public static class Nullifier { * @param isDelete Are we in the middle of a delete action? * @param isEarlyInsert Is this an early insert (INSERT generated id strategy)? * @param session The session + * @param persister The EntityPersister for {@code self} */ - public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSessionContractImplementor session) { + public Nullifier( + final Object self, + final boolean isDelete, + final boolean isEarlyInsert, + final SharedSessionContractImplementor session, + final EntityPersister persister) { this.isDelete = isDelete; this.isEarlyInsert = isEarlyInsert; this.session = session; + this.persister = persister; this.self = self; } @@ -57,11 +67,12 @@ public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSes * points toward that entity. * * @param values The entity attribute values - * @param types The entity attribute types */ - public void nullifyTransientReferences(final Object[] values, final Type[] types) { + public void nullifyTransientReferences(final Object[] values) { + final String[] propertyNames = persister.getPropertyNames(); + final Type[] types = persister.getPropertyTypes(); for ( int i = 0; i < types.length; i++ ) { - values[i] = nullifyTransientReferences( values[i], types[i] ); + values[i] = nullifyTransientReferences( values[i], propertyNames[i], types[i] ); } } @@ -70,34 +81,53 @@ public void nullifyTransientReferences(final Object[] values, final Type[] types * input argument otherwise. This is how Hibernate avoids foreign key constraint violations. * * @param value An entity attribute value + * @param propertyName An entity attribute name * @param type An entity attribute type * * @return {@code null} if the argument is an unsaved entity; otherwise return the argument. */ - private Object nullifyTransientReferences(final Object value, final Type type) { + private Object nullifyTransientReferences(final Object value, final String propertyName, final Type type) { + final Object returnedValue; if ( value == null ) { - return null; + returnedValue = null; } else if ( type.isEntityType() ) { final EntityType entityType = (EntityType) type; if ( entityType.isOneToOne() ) { - return value; + returnedValue = value; } else { - final String entityName = entityType.getAssociatedEntityName(); - return isNullifiable( entityName, value ) ? null : value; + // If value is lazy, it may need to be initialized to + // determine if the value is nullifiable. + final Object possiblyInitializedValue = initializeIfNecessary( value, propertyName, entityType ); + if ( possiblyInitializedValue == null ) { + // The uninitialized value was initialized to null + returnedValue = null; + } + else { + // If the value is not nullifiable, make sure that the + // possibly initialized value is returned. + returnedValue = isNullifiable( entityType.getAssociatedEntityName(), possiblyInitializedValue ) + ? null + : possiblyInitializedValue; + } } } else if ( type.isAnyType() ) { - return isNullifiable( null, value ) ? null : value; + returnedValue = isNullifiable( null, value ) ? null : value; } else if ( type.isComponentType() ) { final CompositeType actype = (CompositeType) type; final Object[] subvalues = actype.getPropertyValues( value, session ); final Type[] subtypes = actype.getSubtypes(); + final String[] subPropertyNames = actype.getPropertyNames(); boolean substitute = false; for ( int i = 0; i < subvalues.length; i++ ) { - final Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] ); + final Object replacement = nullifyTransientReferences( + subvalues[i], + StringHelper.qualify( propertyName, subPropertyNames[i] ), + subtypes[i] + ); if ( replacement != subvalues[i] ) { substitute = true; subvalues[i] = replacement; @@ -107,7 +137,47 @@ else if ( type.isComponentType() ) { // todo : need to account for entity mode on the CompositeType interface :( actype.setPropertyValues( value, subvalues, EntityMode.POJO ); } - return value; + returnedValue = value; + } + else { + returnedValue = value; + } + // value != returnedValue if either: + // 1) returnedValue was nullified (set to null); + // or 2) returnedValue was initialized, but not nullified. + // When bytecode-enhancement is used for dirty-checking, the change should + // only be tracked when returnedValue was nullified (1)). + if ( value != returnedValue && returnedValue == null && SelfDirtinessTracker.class.isInstance( self ) ) { + ( (SelfDirtinessTracker) self ).$$_hibernate_trackChange( propertyName ); + } + return returnedValue; + } + + private Object initializeIfNecessary( + final Object value, + final String propertyName, + final Type type) { + if ( isDelete && + value == LazyPropertyInitializer.UNFETCHED_PROPERTY && + type.isEntityType() && + !session.getPersistenceContext().getNullifiableEntityKeys().isEmpty() ) { + // IMPLEMENTATION NOTE: If cascade-remove was mapped for the attribute, + // then value should have been initialized previously, when the remove operation was + // cascaded to the property (because CascadingAction.DELETE.performOnLazyProperty() + // returns true). This particular situation can only arise when cascade-remove is not + // mapped for the association. + + // There is at least one nullifiable entity. We don't know if the lazy + // associated entity is one of the nullifiable entities. If it is, and + // the property is not nullified, then a constraint violation will result. + // The only way to find out if the associated entity is nullifiable is + // to initialize it. + // TODO: there may be ways to fine-tune when initialization is necessary + // (e.g., only initialize when the associated entity type is a + // superclass or the same as the entity type of a nullifiable entity). + // It is unclear if a more complicated check would impact performance + // more than just initializing the associated entity. + return ( (LazyPropertyInitializer) persister ).initializeLazyProperty( propertyName, self, session ); } else { return value; @@ -162,9 +232,7 @@ private boolean isNullifiable(final String entityName, Object object) else { return entityEntry.isNullifiable( isEarlyInsert, session ); } - } - } /** @@ -234,7 +302,7 @@ public static boolean isTransient(String entityName, Object entity, Boolean assu return assumed; } - // hit the database, afterQuery checking the session cache for a snapshot + // hit the database, after checking the session cache for a snapshot final Object[] snapshot = session.getPersistenceContext().getDatabaseSnapshot( persister.getIdentifier( entity, session ), persister @@ -275,7 +343,7 @@ public static Serializable getEntityIdentifierIfNotUnsaved( // deeper checks... if ( isTransient( entityName, object, Boolean.FALSE, session ) ) { throw new TransientObjectException( - "object references an unsaved transient instance - save the transient instance beforeQuery flushing: " + + "object references an unsaved transient instance - save the transient instance before flushing: " + (entityName == null ? session.guessEntityName( object ) : entityName) ); } @@ -307,8 +375,8 @@ public static NonNullableTransientDependencies findNonNullableTransientEntities( Object[] values, boolean isEarlyInsert, SharedSessionContractImplementor session) { - final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session ); final EntityPersister persister = session.getEntityPersister( entityName, entity ); + final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session, persister ); final String[] propertyNames = persister.getPropertyNames(); final Type[] types = persister.getPropertyTypes(); final boolean[] nullability = persister.getPropertyNullability(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java index c27af02ec644..bf2d699ea818 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java @@ -129,7 +129,28 @@ public JoinSequence addJoin( String alias, JoinType joinType, String[] referencingKey) throws MappingException { - joins.add( new Join( factory, associationType, alias, joinType, referencingKey ) ); + joins.add( new Join( factory, associationType, alias, joinType, new String[][] { referencingKey } ) ); + return this; + } + + /** + * Add a join to this sequence + * + * @param associationType The type of the association representing the join + * @param alias The RHS alias for the join + * @param joinType The type of join (INNER, etc) + * @param referencingKeys The LHS columns for the join condition + * + * @return The Join memento + * + * @throws MappingException Generally indicates a problem resolving the associationType to a {@link Joinable} + */ + public JoinSequence addJoin( + AssociationType associationType, + String alias, + JoinType joinType, + String[][] referencingKeys) throws MappingException { + joins.add( new Join( factory, associationType, alias, joinType, referencingKeys ) ); return this; } @@ -242,8 +263,7 @@ else if ( needsTableGroupJoin( joins, withClauseFragment ) ) { join.getAlias(), join.getLHSColumns(), JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ), - join.joinType, - "" + join.joinType ); } addSubclassJoins( @@ -263,18 +283,29 @@ else if ( needsTableGroupJoin( joins, withClauseFragment ) ) { joinFragment.addFromFragmentString( " on " ); final String rhsAlias = first.getAlias(); - final String[] lhsColumns = first.getLHSColumns(); + final String[][] lhsColumns = first.getLHSColumns(); final String[] rhsColumns = JoinHelper.getRHSColumnNames( first.getAssociationType(), factory ); - for ( int j=0; j < lhsColumns.length; j++) { - joinFragment.addFromFragmentString( lhsColumns[j] ); - joinFragment.addFromFragmentString( "=" ); - joinFragment.addFromFragmentString( rhsAlias ); - joinFragment.addFromFragmentString( "." ); - joinFragment.addFromFragmentString( rhsColumns[j] ); - if ( j < lhsColumns.length - 1 ) { - joinFragment.addFromFragmentString( " and " ); + if ( lhsColumns.length > 1 ) { + joinFragment.addFromFragmentString( "(" ); + } + for ( int i = 0; i < lhsColumns.length; i++ ) { + for ( int j = 0; j < lhsColumns[i].length; j++ ) { + joinFragment.addFromFragmentString( lhsColumns[i][j] ); + joinFragment.addFromFragmentString( "=" ); + joinFragment.addFromFragmentString( rhsAlias ); + joinFragment.addFromFragmentString( "." ); + joinFragment.addFromFragmentString( rhsColumns[j] ); + if ( j < lhsColumns[i].length - 1 ) { + joinFragment.addFromFragmentString( " and " ); + } + } + if ( i < lhsColumns.length - 1 ) { + joinFragment.addFromFragmentString( " or " ); } } + if ( lhsColumns.length > 1 ) { + joinFragment.addFromFragmentString( ")" ); + } joinFragment.addFromFragmentString( " and " ); joinFragment.addFromFragmentString( withClauseFragment ); @@ -568,14 +599,14 @@ public static final class Join { private final Joinable joinable; private final JoinType joinType; private final String alias; - private final String[] lhsColumns; + private final String[][] lhsColumns; Join( SessionFactoryImplementor factory, AssociationType associationType, String alias, JoinType joinType, - String[] lhsColumns) throws MappingException { + String[][] lhsColumns) throws MappingException { this.associationType = associationType; this.joinable = associationType.getAssociatedJoinable( factory ); this.alias = alias; @@ -599,7 +630,7 @@ public JoinType getJoinType() { return joinType; } - public String[] getLHSColumns() { + public String[][] getLHSColumns() { return lhsColumns; } @@ -609,6 +640,22 @@ public String toString() { } } + public JoinSequence copyForCollectionProperty() { + JoinSequence copy = this.copy(); + copy.joins.clear(); + Iterator joinIterator = this.joins.iterator(); + while ( joinIterator.hasNext() ) { + Join join = joinIterator.next(); + copy.addJoin( + join.getAssociationType(), + join.getAlias(), + JoinType.INNER_JOIN, + join.getLHSColumns() + ); + } + return copy; + } + @Override public String toString() { final StringBuilder buf = new StringBuilder(); 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 95f7dbe1f5dd..f000607c1123 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 @@ -15,11 +15,12 @@ import java.util.concurrent.ConcurrentHashMap; import org.hibernate.AssertionFailure; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.stat.internal.StatsHelper; import org.hibernate.type.Type; import org.jboss.logging.Logger; @@ -107,7 +108,7 @@ public Object[] removeNaturalIdCrossReference(EntityPersister persister, Seriali } if ( persister.hasNaturalIdCache() ) { - final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister + final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister .getNaturalIdCacheAccessStrategy(); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() ); naturalIdCacheAccessStrategy.evict( naturalIdCacheKey ); @@ -238,7 +239,7 @@ public Serializable findCachedNaturalIdResolution(EntityPersister persister, Obj } // Try resolution from second-level cache - final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); + final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() ); pk = CacheHelper.fromSharedCache( session(), naturalIdCacheKey, naturalIdCacheAccessStrategy ); @@ -247,7 +248,8 @@ public Serializable findCachedNaturalIdResolution(EntityPersister persister, Obj final SessionFactoryImplementor factory = session().getFactory(); if ( pk != null ) { if ( factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatisticsImplementor().naturalIdCacheHit( + factory.getStatistics().naturalIdCacheHit( + StatsHelper.INSTANCE.getRootEntityRole( persister ), naturalIdCacheAccessStrategy.getRegion().getName() ); } @@ -274,7 +276,10 @@ public Serializable findCachedNaturalIdResolution(EntityPersister persister, Obj entityNaturalIdResolutionCache.naturalIdToPkMap.put( cachedNaturalId, pk ); } else if ( factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatisticsImplementor().naturalIdCacheMiss( naturalIdCacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().naturalIdCacheMiss( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + naturalIdCacheAccessStrategy.getRegion().getName() + ); } return pk; @@ -310,7 +315,7 @@ public Collection getCachedPkResolutions(EntityPersister persister /** * As part of "load synchronization process", if a particular natural id is found to have changed we need to track - * its invalidity until afterQuery the next flush. This method lets the "load synchronization process" indicate + * its invalidity until after the next flush. This method lets the "load synchronization process" indicate * when it has encountered such changes. * * @param persister The persister representing the entity type. @@ -331,7 +336,7 @@ public void stashInvalidNaturalIdReference(EntityPersister persister, Object[] i /** * Again, as part of "load synchronization process" we need to also be able to clear references to these - * known-invalid natural-ids afterQuery flush. This method exposes that capability. + * known-invalid natural-ids after flush. This method exposes that capability. */ public void unStashInvalidNaturalIdReferences() { for ( NaturalIdResolutionCache naturalIdResolutionCache : naturalIdResolutionCacheMap.values() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java old mode 100755 new mode 100644 index 79d7a8d464fc..ad4b8cd76755 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java @@ -11,11 +11,14 @@ import org.hibernate.HibernateException; import org.hibernate.PropertyValueException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; +import org.hibernate.type.EntityType; import org.hibernate.type.Type; /** @@ -49,7 +52,21 @@ public Nullability(SharedSessionContractImplementor session) { public void checkNullability( final Object[] values, final EntityPersister persister, - final boolean isUpdate) throws HibernateException { + final boolean isUpdate) { + checkNullability( values, persister, isUpdate ? NullabilityCheckType.UPDATE : NullabilityCheckType.CREATE ); + } + + public enum NullabilityCheckType { + CREATE, + UPDATE, + DELETE + } + + public void checkNullability( + final Object[] values, + final EntityPersister persister, + final NullabilityCheckType checkType) { + /* * Typically when Bean Validation is on, we don't want to validate null values * at the Hibernate Core level. Hence the checkNullability setting. @@ -60,7 +77,7 @@ public void checkNullability( * Check for any level one nullability breaks * Look at non null components to * recursively check next level of nullability breaks - * Look at Collections contraining component to + * Look at Collections containing components to * recursively check next level of nullability breaks * * @@ -74,9 +91,9 @@ public void checkNullability( */ final boolean[] nullability = persister.getPropertyNullability(); - final boolean[] checkability = isUpdate ? - persister.getPropertyUpdateability() : - persister.getPropertyInsertability(); + final boolean[] checkability = checkType == NullabilityCheckType.CREATE + ? persister.getPropertyInsertability() + : persister.getPropertyUpdateability(); final Type[] propertyTypes = persister.getPropertyTypes(); for ( int i = 0; i < values.length; i++ ) { @@ -84,7 +101,6 @@ public void checkNullability( if ( checkability[i] && values[i]!= LazyPropertyInitializer.UNFETCHED_PROPERTY ) { final Object value = values[i]; if ( !nullability[i] && value == null ) { - //check basic level one nullablilty throw new PropertyValueException( "not-null property references a null or transient value", @@ -161,11 +177,11 @@ private String checkComponentNullability(Object value, CompositeType compositeTy // IMPL NOTE : we currently skip checking "any" and "many to any" mappings. // // This is not the best solution. But atm there is a mismatch between AnyType#getPropertyNullability - // and the fact that cascaded-saves for "many to any" mappings are not performed until afterQuery this nullability + // and the fact that cascaded-saves for "many to any" mappings are not performed until after this nullability // check. So the nullability check fails for transient entity elements with generated identifiers because // the identifier is not yet generated/assigned (is null) // - // The more correct fix would be to cascade saves of the many-to-any elements beforeQuery the Nullability checking + // The more correct fix would be to cascade saves of the many-to-any elements before the Nullability checking if ( compositeType.isAnyType() ) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ParameterBinder.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ParameterBinder.java index 4184a95cbf22..56b273c6974a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ParameterBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ParameterBinder.java @@ -60,7 +60,7 @@ private ParameterBinder() { * @param source The named parameter source, for resolving the locations of named parameters * @param session The session * - * @return The next bind position afterQuery the last position we bound here. + * @return The next bind position after the last position we bound here. * * @throws SQLException Indicates a problem calling JDBC bind methods * @throws HibernateException Indicates a problem access bind values. 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 8fcf2a94686d..1865e16dae06 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -31,8 +31,10 @@ import org.hibernate.PersistentObjectException; import org.hibernate.TransientObjectException; import org.hibernate.action.spi.AfterTransactionCompletionProcess; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.loading.internal.LoadContexts; @@ -61,6 +63,7 @@ import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; +import org.hibernate.stat.internal.StatsHelper; import org.hibernate.type.CollectionType; import org.jboss.logging.Logger; @@ -172,10 +175,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { nullifiableEntityKeys = new HashSet<>(); - initTransientState(); - } - - private void initTransientState() { nullAssociations = new HashSet<>( INIT_COLL_SIZE ); nonlazyCollections = new ArrayList<>( INIT_COLL_SIZE ); } @@ -230,7 +229,6 @@ public void clear() { } for ( Entry objectEntityEntryEntry : entityEntryContext.reentrantSafeEntityEntries() ) { - // todo : I dont think this need be reentrant safe if ( objectEntityEntryEntry.getKey() instanceof PersistentAttributeInterceptable ) { final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) objectEntityEntryEntry.getKey() ).$$_hibernate_getInterceptor(); if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { @@ -239,9 +237,8 @@ public void clear() { } } - for ( Map.Entry aCollectionEntryArray : IdentityMap.concurrentEntries( collectionEntries ) ) { - aCollectionEntryArray.getKey().unsetSession( getSession() ); - } + final SharedSessionContractImplementor session = getSession(); + IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) ); arrayHolders.clear(); entitiesByKey.clear(); @@ -494,6 +491,8 @@ public EntityEntry addEntry( final boolean existsInDatabase, final EntityPersister persister, final boolean disableVersionIncrement) { + assert lockMode != null; + final EntityEntry e; /* @@ -570,15 +569,29 @@ public boolean containsProxy(Object entity) { @Override public boolean reassociateIfUninitializedProxy(Object value) throws MappingException { - if ( !Hibernate.isInitialized( value ) ) { - final HibernateProxy proxy = (HibernateProxy) value; - final LazyInitializer li = proxy.getHibernateLazyInitializer(); - reassociateProxy( li, proxy ); - return true; - } - else { - return false; + if ( ! Hibernate.isInitialized( value ) ) { + + // could be a proxy.... + if ( value instanceof HibernateProxy ) { + final HibernateProxy proxy = (HibernateProxy) value; + final LazyInitializer li = proxy.getHibernateLazyInitializer(); + reassociateProxy( li, proxy ); + return true; + } + + // or an uninitialized enhanced entity ("bytecode proxy")... + if ( value instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable bytecodeProxy = (PersistentAttributeInterceptable) value; + final BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) bytecodeProxy.$$_hibernate_getInterceptor(); + if ( interceptor != null ) { + interceptor.setSession( getSession() ); + } + return true; + } + } + + return false; } @Override @@ -635,6 +648,14 @@ public Object unproxyAndReassociate(Object maybeProxy) throws HibernateException //initialize + unwrap the object and return it return li.getImplementation(); } + else if ( maybeProxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) maybeProxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( maybeProxy, null ); + } + return maybeProxy; + } else { return maybeProxy; } @@ -719,6 +740,11 @@ public Object proxyFor(Object impl) throws HibernateException { return proxyFor( e.getPersister(), e.getEntityKey(), impl ); } + @Override + public void addEnhancedProxy(EntityKey key, PersistentAttributeInterceptable entity) { + entitiesByKey.put( key, entity ); + } + @Override public Object getCollectionOwner(Serializable key, CollectionPersister collectionPersister) throws MappingException { // todo : we really just need to add a split in the notions of: @@ -844,7 +870,7 @@ public void addNewCollection(CollectionPersister persister, PersistentCollection } /** - * Add an collection to the cache, with a given collection entry. + * Add a collection to the cache, with a given collection entry. * * @param coll The collection for which we are adding an entry. * @param entry The entry representing the collection. @@ -1073,7 +1099,7 @@ public void beginRemoveOrphanBeforeUpdates() { if ( removeOrphanBeforeUpdatesCounter >= getCascadeLevel() ) { throw new IllegalStateException( String.format( - "Cascade level [%d] is out of sync with removeOrphanBeforeUpdatesCounter [%d] beforeQuery incrementing removeOrphanBeforeUpdatesCounter", + "Cascade level [%d] is out of sync with removeOrphanBeforeUpdatesCounter [%d] before incrementing removeOrphanBeforeUpdatesCounter", getCascadeLevel(), removeOrphanBeforeUpdatesCounter ) @@ -1089,7 +1115,7 @@ public void endRemoveOrphanBeforeUpdates() { if ( removeOrphanBeforeUpdatesCounter > getCascadeLevel() ) { throw new IllegalStateException( String.format( - "Cascade level [%d] is out of sync with removeOrphanBeforeUpdatesCounter [%d] beforeQuery decrementing removeOrphanBeforeUpdatesCounter", + "Cascade level [%d] is out of sync with removeOrphanBeforeUpdatesCounter [%d] before decrementing removeOrphanBeforeUpdatesCounter", getCascadeLevel(), removeOrphanBeforeUpdatesCounter ) @@ -1099,7 +1125,7 @@ public void endRemoveOrphanBeforeUpdates() { } /** - * Call this beforeQuery beginning a two-phase load + * Call this before beginning a two-phase load */ @Override public void beforeLoad() { @@ -1107,7 +1133,7 @@ public void beforeLoad() { } /** - * Call this afterQuery finishing a two-phase load + * Call this after finishing a two-phase load */ @Override public void afterLoad() { @@ -1437,7 +1463,7 @@ public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializabl public void serialize(ObjectOutputStream oos) throws IOException { final boolean tracing = LOG.isTraceEnabled(); if ( tracing ) { - LOG.trace( "Serializing persisatence-context" ); + LOG.trace( "Serializing persistence-context" ); } oos.writeBoolean( defaultReadOnly ); @@ -1533,7 +1559,7 @@ public static StatefulPersistenceContext deserialize( SessionImplementor session) throws IOException, ClassNotFoundException { final boolean tracing = LOG.isTraceEnabled(); if ( tracing ) { - LOG.trace( "Serializing persistent-context" ); + LOG.trace( "Deserializing persistence-context" ); } final StatefulPersistenceContext rtn = new StatefulPersistenceContext( session ); SessionFactoryImplementor sfi = session.getFactory(); @@ -1670,7 +1696,7 @@ public void removeChildParent(Object child) { @Override public void registerInsertedKey(EntityPersister persister, Serializable id) { // we only are worried about registering these if the persister defines caching - if ( persister.hasCache() ) { + if ( persister.canWriteToCache() ) { if ( insertedKeysMap == null ) { insertedKeysMap = new HashMap<>(); } @@ -1687,7 +1713,7 @@ public void registerInsertedKey(EntityPersister persister, Serializable id) { @Override public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id) { // again, we only really care if the entity is cached - if ( persister.hasCache() ) { + if ( persister.canWriteToCache() ) { if ( insertedKeysMap != null ) { final List insertedEntityIds = insertedKeysMap.get( persister.getRootEntityName() ); if ( insertedEntityIds != null ) { @@ -1783,7 +1809,7 @@ private void managedSharedCacheEntries( Object[] naturalIdValues, Object[] previousNaturalIdValues, CachedNaturalIdValueSource source) { - final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); + final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session ); final SessionFactoryImplementor factory = session.getFactory(); @@ -1798,12 +1824,14 @@ private void managedSharedCacheEntries( session, naturalIdCacheKey, id, - session.getTimestamp(), null ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().naturalIdCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + naturalIdCacheAccessStrategy.getRegion().getName() + ); } break; @@ -1811,7 +1839,10 @@ private void managedSharedCacheEntries( case INSERT: { final boolean put = naturalIdCacheAccessStrategy.insert( session, naturalIdCacheKey, id ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().naturalIdCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + naturalIdCacheAccessStrategy.getRegion().getName() + ); } ( (EventSource) session ).getActionQueue().registerProcess( @@ -1820,9 +1851,11 @@ private void managedSharedCacheEntries( public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) { if (success) { final boolean put = naturalIdCacheAccessStrategy.afterInsert( session, naturalIdCacheKey, id ); - if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().naturalIdCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + naturalIdCacheAccessStrategy.getRegion().getName() + ); } } else { @@ -1846,7 +1879,10 @@ public void doAfterTransactionCompletion(boolean success, SharedSessionContractI final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( session, naturalIdCacheKey, null ); final boolean put = naturalIdCacheAccessStrategy.update( session, naturalIdCacheKey, id ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().naturalIdCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + naturalIdCacheAccessStrategy.getRegion().getName() + ); } ( (EventSource) session ).getActionQueue().registerProcess( @@ -1863,7 +1899,10 @@ public void doAfterTransactionCompletion(boolean success, SharedSessionContractI ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().naturalIdCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + naturalIdCacheAccessStrategy.getRegion().getName() + ); } } else { @@ -1917,7 +1956,7 @@ public void removeSharedNaturalIdCrossReference(EntityPersister persister, Seria // 2) should prefer session-cached values if any (requires interaction from removeLocalNaturalIdCrossReference persister = locateProperPersister( persister ); - final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); + final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session ); naturalIdCacheAccessStrategy.evict( naturalIdCacheKey ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java old mode 100755 new mode 100644 index fa099e5a2f12..ef4472d93ea3 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -13,10 +13,13 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; +import org.hibernate.engine.profile.Fetch; +import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -34,6 +37,8 @@ import org.hibernate.pretty.MessageHelper; import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl; import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.internal.StatsHelper; +import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; @@ -55,7 +60,7 @@ private TwoPhaseLoad() { } /** - * Register the "hydrated" state of an entity instance, afterQuery the first step of 2-phase loading. + * Register the "hydrated" state of an entity instance, after the first step of 2-phase loading. * * Add the "hydrated state" (an array) of an uninitialized entity to the session. We don't try * to resolve any associations yet, because there might be other entities waiting to be @@ -99,6 +104,34 @@ public static void postHydrate( } } + /** + * @deprecated This method will be removed. Use {@link #initializeEntity(Object, boolean, SharedSessionContractImplementor, PreLoadEvent, Iterable)} instead. + * + * @param entity The entity being loaded + * @param readOnly Is the entity being loaded as read-only + * @param session The Session + * @param preLoadEvent The (re-used) pre-load event + */ + @Deprecated + public static void initializeEntity( + final Object entity, + final boolean readOnly, + final SharedSessionContractImplementor session, + final PreLoadEvent preLoadEvent) { + final PersistenceContext persistenceContext = session.getPersistenceContext(); + final EntityEntry entityEntry = persistenceContext.getEntry( entity ); + if ( entityEntry == null ) { + throw new AssertionFailure( "possible non-threadsafe access to the session" ); + } + final EventListenerGroup listenerGroup = session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.PRE_LOAD ); + final Iterable listeners = listenerGroup.listeners(); + doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, listeners ); + } + /** * Perform the second step of 2-phase load. Fully initialize the entity * instance. @@ -111,18 +144,20 @@ public static void postHydrate( * @param readOnly Is the entity being loaded as read-only * @param session The Session * @param preLoadEvent The (re-used) pre-load event + * @param preLoadEventListeners the pre-load event listeners */ public static void initializeEntity( final Object entity, final boolean readOnly, final SharedSessionContractImplementor session, - final PreLoadEvent preLoadEvent) { + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners) { final PersistenceContext persistenceContext = session.getPersistenceContext(); final EntityEntry entityEntry = persistenceContext.getEntry( entity ); if ( entityEntry == null ) { throw new AssertionFailure( "possible non-threadsafe access to the session" ); } - doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent ); + doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, preLoadEventListeners ); } private static void doInitializeEntity( @@ -130,7 +165,8 @@ private static void doInitializeEntity( final EntityEntry entityEntry, final boolean readOnly, final SharedSessionContractImplementor session, - final PreLoadEvent preLoadEvent) throws HibernateException { + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners) throws HibernateException { final PersistenceContext persistenceContext = session.getPersistenceContext(); final EntityPersister persister = entityEntry.getPersister(); final Serializable id = entityEntry.getId(); @@ -144,6 +180,8 @@ private static void doInitializeEntity( ); } + String entityName = persister.getEntityName(); + String[] propertyNames = persister.getPropertyNames(); final Type[] types = persister.getPropertyTypes(); for ( int i = 0; i < hydratedState.length; i++ ) { final Object value = hydratedState[i]; @@ -157,25 +195,21 @@ private static void doInitializeEntity( // HHH-10989: We need to resolve the collection so that a CollectionReference is added to StatefulPersistentContext. // As mentioned above, hydratedState[i] needs to remain LazyPropertyInitializer.UNFETCHED_PROPERTY // so do not assign the resolved, unitialized PersistentCollection back to hydratedState[i]. - types[i].resolve( value, session, entity ); + Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i], debugEnabled ); + types[i].resolve( value, session, entity, overridingEager ); } } - else if ( value!= PropertyAccessStrategyBackRefImpl.UNKNOWN ) { + else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) { // we know value != LazyPropertyInitializer.UNFETCHED_PROPERTY - hydratedState[i] = types[i].resolve( value, session, entity ); + Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i], debugEnabled ); + hydratedState[i] = types[i].resolve( value, session, entity, overridingEager ); } } - //Must occur afterQuery resolving identifiers! + //Must occur after resolving identifiers! if ( session.isEventSource() ) { preLoadEvent.setEntity( entity ).setState( hydratedState ).setId( id ).setPersister( persister ); - - final EventListenerGroup listenerGroup = session - .getFactory() - .getServiceRegistry() - .getService( EventListenerRegistry.class ) - .getEventListenerGroup( EventType.PRE_LOAD ); - for ( PreLoadEventListener listener : listenerGroup.listeners() ) { + for ( PreLoadEventListener listener : preLoadEventListeners ) { listener.onPreLoad( preLoadEvent ); } } @@ -183,7 +217,8 @@ else if ( value!= PropertyAccessStrategyBackRefImpl.UNKNOWN ) { persister.setPropertyValues( entity, hydratedState ); final SessionFactoryImplementor factory = session.getFactory(); - if ( persister.hasCache() && session.getCacheMode().isPutEnabled() ) { + final StatisticsImplementor statistics = factory.getStatistics(); + if ( persister.canWriteToCache() && session.getCacheMode().isPutEnabled() ) { if ( debugEnabled ) { LOG.debugf( @@ -194,7 +229,7 @@ else if ( value!= PropertyAccessStrategyBackRefImpl.UNKNOWN ) { final Object version = Versioning.getVersion( hydratedState, persister ); final CacheEntry entry = persister.buildCacheEntry( entity, hydratedState, version, session ); - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final EntityDataAccess cache = persister.getCacheAccessStrategy(); final Object cacheKey = cache.generateCacheKey( id, persister, factory, session.getTenantIdentifier() ); // explicit handling of caching for rows just inserted and then somehow forced to be read @@ -220,13 +255,15 @@ else if ( value!= PropertyAccessStrategyBackRefImpl.UNKNOWN ) { session, cacheKey, persister.getCacheEntryStructure().structure( entry ), - session.getTimestamp(), version, useMinimalPuts( session, entityEntry ) ); - if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().secondLevelCachePut( cache.getRegion().getName() ); + if ( put && statistics.isStatisticsEnabled() ) { + statistics.entityCachePut( + StatsHelper.INSTANCE.getRootEntityRole( persister ), + cache.getRegion().getName() + ); } } finally { @@ -268,7 +305,7 @@ else if ( value!= PropertyAccessStrategyBackRefImpl.UNKNOWN ) { hydratedState, persister.getPropertyTypes(), persister.getPropertyUpdateability(), - //afterQuery setting values to object + //after setting values to object hydratedState, session ); @@ -284,28 +321,86 @@ else if ( value!= PropertyAccessStrategyBackRefImpl.UNKNOWN ) { ); } - if ( factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().loadEntity( persister.getEntityName() ); + if ( statistics.isStatisticsEnabled() ) { + statistics.loadEntity( persister.getEntityName() ); } } - + /** - * PostLoad cannot occur during initializeEntity, as that call occurs *beforeQuery* + * Check if eager of the association is overriden by anything. + * + * @param session session + * @param entityName entity name + * @param associationName association name + * + * @return null if there is no overriding, true if it is overridden to eager and false if it is overridden to lazy + */ + private static Boolean getOverridingEager( + final SharedSessionContractImplementor session, + final String entityName, + final String associationName, + final Type type, + final boolean isDebugEnabled) { + // Performance: check type.isCollectionType() first, as type.isAssociationType() is megamorphic + if ( type.isCollectionType() || type.isAssociationType() ) { + final Boolean overridingEager = isEagerFetchProfile( session, entityName, associationName ); + + //This method is very hot, and private so let's piggy back on the fact that the caller already knows the debugging state. + if ( isDebugEnabled ) { + if ( overridingEager != null ) { + LOG.debugf( + "Overriding eager fetching using active fetch profile. EntityName: %s, associationName: %s, eager fetching: %s", + entityName, + associationName, + overridingEager + ); + } + } + + return overridingEager; + } + return null; + } + + private static Boolean isEagerFetchProfile(SharedSessionContractImplementor session, String entityName, String associationName) { + LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); + + // Performance: avoid concatenating entityName + "." + associationName when there is no need, + // as otherwise this section becomes an hot allocation point. + if ( loadQueryInfluencers.hasEnabledFetchProfiles() ) { + final String role = entityName + '.' + associationName; + final SessionFactoryImplementor factory = session.getFactory(); + for ( String fetchProfileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) { + FetchProfile fp = factory.getFetchProfile( fetchProfileName ); + Fetch fetch = fp.getFetchByRole( role ); + if ( fetch != null && Fetch.Style.JOIN == fetch.getStyle() ) { + return true; + } + } + } + + return null; + } + + /** + * PostLoad cannot occur during initializeEntity, as that call occurs *before* * the Set collections are added to the persistence context by Loader. * Without the split, LazyInitializationExceptions can occur in the Entity's * postLoad if it acts upon the collection. * * HHH-6043 - * + * * @param entity The entity * @param session The Session * @param postLoadEvent The (re-used) post-load event + * @param postLoadEventListeners thet post-load eventListeners */ public static void postLoad( final Object entity, final SharedSessionContractImplementor session, - final PostLoadEvent postLoadEvent) { - + final PostLoadEvent postLoadEvent, + final Iterable postLoadEventListeners) { + if ( session.isEventSource() ) { final PersistenceContext persistenceContext = session.getPersistenceContext(); @@ -313,16 +408,31 @@ public static void postLoad( postLoadEvent.setEntity( entity ).setId( entityEntry.getId() ).setPersister( entityEntry.getPersister() ); - final EventListenerGroup listenerGroup = session.getFactory() - .getServiceRegistry() - .getService( EventListenerRegistry.class ) - .getEventListenerGroup( EventType.POST_LOAD ); - for ( PostLoadEventListener listener : listenerGroup.listeners() ) { + for ( PostLoadEventListener listener : postLoadEventListeners ) { listener.onPostLoad( postLoadEvent ); } } } + /** + * This method will be removed. + * @deprecated Use {@link #postLoad(Object, SharedSessionContractImplementor, PostLoadEvent, Iterable)} + * instead. + */ + @Deprecated + public static void postLoad( + final Object entity, + final SharedSessionContractImplementor session, + final PostLoadEvent postLoadEvent) { + + final EventListenerGroup listenerGroup = session.getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.POST_LOAD ); + + postLoad( entity, session, postLoadEvent, listenerGroup.listeners() ); + } + private static boolean useMinimalPuts(SharedSessionContractImplementor session, EntityEntry entityEntry) { if ( session.getFactory().getSessionFactoryOptions().isMinimalPutsEnabled() ) { return session.getCacheMode() != CacheMode.REFRESH; @@ -335,7 +445,7 @@ private static boolean useMinimalPuts(SharedSessionContractImplementor session, /** * Add an uninitialized instance of an entity class, as a placeholder to ensure object - * identity. Must be called beforeQuery postHydrate(). + * identity. Must be called before postHydrate(). * * Create a "temporary" entry for a newly instantiated entity. The entity is uninitialized, * but we need the mapping from id to instance in order to guarantee uniqueness. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ContextualLobCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ContextualLobCreator.java index dc710bc6bbea..69efebebef58 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ContextualLobCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ContextualLobCreator.java @@ -52,7 +52,7 @@ public Blob createBlob(byte[] bytes) { return blob; } catch ( SQLException e ) { - throw new JDBCException( "Unable to set BLOB bytes afterQuery creation", e ); + throw new JDBCException( "Unable to set BLOB bytes after creation", e ); } } @@ -80,7 +80,7 @@ public Clob createClob(String string) { return clob; } catch ( SQLException e ) { - throw new JDBCException( "Unable to set CLOB string afterQuery creation", e ); + throw new JDBCException( "Unable to set CLOB string after creation", e ); } } @@ -108,7 +108,7 @@ public NClob createNClob(String string) { return nclob; } catch ( SQLException e ) { - throw new JDBCException( "Unable to set NCLOB string afterQuery creation", e ); + throw new JDBCException( "Unable to set NCLOB string after creation", e ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java index 75df36b5f5d3..d179c0af9c85 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java @@ -6,19 +6,23 @@ */ package org.hibernate.engine.jdbc; +import static org.hibernate.internal.CoreLogging.messageLogger; + import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.service.ServiceRegistry; -import static org.hibernate.internal.CoreLogging.messageLogger; - /** * A proxy for a ResultSet delegate, responsible for locally caching the columnName-to-columnIndex resolution that * has been found to be inefficient in a few vendor's drivers (i.e., Oracle and Postgres). @@ -31,9 +35,29 @@ public class ResultSetWrapperProxy implements InvocationHandler { private static final SqlExceptionHelper SQL_EXCEPTION_HELPER = new SqlExceptionHelper( false ); + private static final Map NAME_TO_INDEX_METHOD_MAPPING; + private final ResultSet rs; private final ColumnNameCache columnNameCache; + static { + Map nameToIndexMethodMapping = new HashMap<>(); + for ( Method method : ResultSet.class.getDeclaredMethods() ) { + if ( isFirstArgColumnLabel( method ) ) { + try { + nameToIndexMethodMapping.put( + new ResultSetMethodKey( method.getName(), method.getParameterTypes() ), + locateCorrespondingColumnIndexMethod( method ) + ); + } + catch (NoSuchMethodException e) { + LOG.unableToSwitchToMethodUsingColumnIndex( method ); + } + } + } + NAME_TO_INDEX_METHOD_MAPPING = Collections.unmodifiableMap( nameToIndexMethodMapping ); + } + private ResultSetWrapperProxy(ResultSet rs, ColumnNameCache columnNameCache) { this.rs = rs; this.columnNameCache = columnNameCache; @@ -64,24 +88,23 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return findColumn( (String) args[0] ); } - if ( isFirstArgColumnLabel( method, args ) ) { - try { - final Integer columnIndex = findColumn( (String) args[0] ); - return invokeMethod( - locateCorrespondingColumnIndexMethod( method ), - buildColumnIndexMethodArgs( args, columnIndex ) - ); - } - catch ( SQLException ex ) { - final String msg = "Exception getting column index for column: [" + args[0] + - "].\nReverting to using: [" + args[0] + - "] as first argument for method: [" + method + "]"; - SQL_EXCEPTION_HELPER.logExceptions( ex, msg ); - } - catch ( NoSuchMethodException ex ) { - LOG.unableToSwitchToMethodUsingColumnIndex( method ); + if ( isFirstArgColumnLabel( method ) ) { + Method columnIndexMethod = NAME_TO_INDEX_METHOD_MAPPING.get( new ResultSetMethodKey( method.getName(), method.getParameterTypes() ) ); + if ( columnIndexMethod != null ) { + try { + final Integer columnIndex = findColumn( (String) args[0] ); + + return invokeMethod( columnIndexMethod, buildColumnIndexMethodArgs( args, columnIndex ) ); + } + catch ( SQLException ex ) { + final String msg = "Exception getting column index for column: [" + args[0] + + "].\nReverting to using: [" + args[0] + + "] as first argument for method: [" + method + "]"; + SQL_EXCEPTION_HELPER.logExceptions( ex, msg ); + } } } + return invokeMethod( method, args ); } @@ -96,20 +119,19 @@ private Integer findColumn(String columnName) throws SQLException { return columnNameCache.getIndexForColumnName( columnName, rs ); } - private boolean isFirstArgColumnLabel(Method method, Object[] args) { + private static boolean isFirstArgColumnLabel(Method method) { // method name should start with either get or update if ( ! ( method.getName().startsWith( "get" ) || method.getName().startsWith( "update" ) ) ) { return false; } - // method should have arguments, and have same number as incoming arguments - if ( ! ( method.getParameterCount() > 0 && args.length == method.getParameterCount() ) ) { + // method should have at least one parameter + if ( ! ( method.getParameterCount() > 0 ) ) { return false; } - // The first argument should be a String (the column name) - //noinspection RedundantIfStatement - if ( ! ( String.class.isInstance( args[0] ) && method.getParameterTypes()[0].equals( String.class ) ) ) { + // The first parameter should be a String (the column name) + if ( ! method.getParameterTypes()[0].equals( String.class ) ) { return false; } @@ -124,8 +146,8 @@ private boolean isFirstArgColumnLabel(Method method, Object[] args) { * @return The corresponding method passed the column index. * @throws NoSuchMethodException Should never happen, but... */ - private Method locateCorrespondingColumnIndexMethod(Method columnNameMethod) throws NoSuchMethodException { - final Class[] actualParameterTypes = new Class[columnNameMethod.getParameterCount()]; + private static Method locateCorrespondingColumnIndexMethod(Method columnNameMethod) throws NoSuchMethodException { + final Class[] actualParameterTypes = new Class[columnNameMethod.getParameterCount()]; actualParameterTypes[0] = int.class; System.arraycopy( columnNameMethod.getParameterTypes(), @@ -152,4 +174,47 @@ private Object invokeMethod(Method method, Object[] args) throws Throwable { throw e.getTargetException(); } } + + private static class ResultSetMethodKey { + + private String methodName; + + private Class[] parameterTypes; + + public ResultSetMethodKey(String methodName, Class[] parameterTypes) { + this.methodName = methodName; + this.parameterTypes = parameterTypes; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + methodName.hashCode(); + result = prime * result + Arrays.hashCode( parameterTypes ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + + ResultSetMethodKey other = (ResultSetMethodKey) obj; + if ( !methodName.equals( other.methodName ) ) { + return false; + } + if ( !Arrays.equals( parameterTypes, other.parameterTypes ) ) { + return false; + } + return true; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java index 815481b36f8a..465a9ffa7833 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java @@ -44,7 +44,7 @@ private SerializableBlobProxy(Blob blob) { */ public Blob getWrappedBlob() { if ( blob == null ) { - throw new IllegalStateException( "Blobs may not be accessed afterQuery serialization" ); + throw new IllegalStateException( "Blobs may not be accessed after serialization" ); } else { return blob; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java index 565c13b49363..a52e6cc09546 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java @@ -44,7 +44,7 @@ protected SerializableClobProxy(Clob clob) { */ public Clob getWrappedClob() { if ( clob == null ) { - throw new IllegalStateException( "Clobs may not be accessed afterQuery serialization" ); + throw new IllegalStateException( "Clobs may not be accessed after serialization" ); } else { return clob; 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 b27f6be90a66..bef49a41eee7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java @@ -21,6 +21,7 @@ import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.resource.jdbc.ResourceRegistry; /** * Convenience base class for implementers of the Batch interface. @@ -40,8 +41,8 @@ public abstract class AbstractBatchImpl implements Batch { private final SqlStatementLogger sqlStatementLogger; private final SqlExceptionHelper sqlExceptionHelper; - private LinkedHashMap statements = new LinkedHashMap(); - private LinkedHashSet observers = new LinkedHashSet(); + private LinkedHashMap statements = new LinkedHashMap<>(); + private LinkedHashSet observers = new LinkedHashSet<>(); protected AbstractBatchImpl(BatchKey key, JdbcCoordinator jdbcCoordinator) { if ( key == null ) { @@ -152,17 +153,31 @@ public final void execute() { } protected void releaseStatements() { - for ( PreparedStatement statement : getStatements().values() ) { + final LinkedHashMap statements = getStatements(); + final ResourceRegistry resourceRegistry = jdbcCoordinator.getResourceRegistry(); + for ( PreparedStatement statement : statements.values() ) { clearBatch( statement ); - jdbcCoordinator.getResourceRegistry().release( statement ); - jdbcCoordinator.afterStatementExecution(); + resourceRegistry.release( statement ); } - getStatements().clear(); + // IMPL NOTE: If the statements are not cleared and JTA is being used, then + // jdbcCoordinator.afterStatementExecution() will abort the batch and a + // warning will be logged. To avoid the warning, clear statements first, + // before calling jdbcCoordinator.afterStatementExecution(). + statements.clear(); + jdbcCoordinator.afterStatementExecution(); } protected void clearBatch(PreparedStatement statement) { try { - statement.clearBatch(); + // This code can be called after the connection is released + // and the statement is closed. If the statement is closed, + // then SQLException will be thrown when PreparedStatement#clearBatch + // is called. + // Ensure the statement is not closed before + // calling PreparedStatement#clearBatch. + if ( !statement.isClosed() ) { + statement.clearBatch(); + } } catch ( SQLException e ) { LOG.unableToReleaseBatchStatement(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java index dbc82060fef5..bf5f7e4b0767 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 @@ -31,7 +31,8 @@ public class BatchingBatch extends AbstractBatchImpl { // IMPL NOTE : Until HHH-5797 is fixed, there will only be 1 statement in a batch - private final int batchSize; + private int batchSize; + private final int configuredBatchSize; private int batchPosition; private boolean batchExecuted; private int statementPosition; @@ -52,6 +53,7 @@ public BatchingBatch( throw new HibernateException( "attempting to batch an operation which cannot be batched" ); } this.batchSize = batchSize; + this.configuredBatchSize = batchSize; } private String currentStatementSql; @@ -60,7 +62,12 @@ public BatchingBatch( @Override public PreparedStatement getBatchStatement(String sql, boolean callable) { currentStatementSql = sql; + int previousBatchSize = getStatements().size(); currentStatement = super.getBatchStatement( sql, callable ); + int currentBatchSize = getStatements().size(); + if ( currentBatchSize > previousBatchSize ) { + this.batchSize = this.configuredBatchSize * currentBatchSize; + } return currentStatement; } @@ -70,6 +77,7 @@ public void addToBatch() { currentStatement.addBatch(); } catch ( SQLException e ) { + abortBatch(); LOG.debugf( "SQLException escaped proxy", e ); throw sqlExceptionHelper().convert( e, "could not perform addBatch", currentStatementSql ); } @@ -134,7 +142,7 @@ private void performExecution() { private void checkRowCounts(int[] rowCounts, PreparedStatement ps) throws SQLException, HibernateException { final int numberOfRowCounts = rowCounts.length; - if ( numberOfRowCounts != batchPosition ) { + if ( batchPosition != 0 && numberOfRowCounts != batchPosition / getStatements().size() ) { LOG.unexpectedRowCounts(); } for ( int i = 0; i < numberOfRowCounts; i++ ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java index 0098dc90bea4..6f7978cc83f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java @@ -10,6 +10,7 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.sql.Connection; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -56,10 +57,20 @@ public class ConnectionProviderInitiator implements StandardServiceInitiator singleRegisteredProvider = getSingleRegisteredProvider( strategySelector ); + if ( singleRegisteredProvider != null ) { + try { + connectionProvider = singleRegisteredProvider.newInstance(); + } + catch (IllegalAccessException | InstantiationException e) { + throw new HibernateException( "Could not instantiate singular-registered ConnectionProvider", e ); + } + } + if ( connectionProvider == null ) { if ( c3p0ConfigDefined( configurationValues ) ) { connectionProvider = instantiateC3p0Provider( strategySelector ); @@ -154,6 +186,18 @@ else if ( explicitSetting instanceof Class ) { } } + if ( connectionProvider == null ) { + if ( viburConfigDefined( configurationValues ) ) { + connectionProvider = instantiateViburProvider( strategySelector ); + } + } + + if ( connectionProvider == null ) { + if ( agroalConfigDefined( configurationValues ) ) { + connectionProvider = instantiateAgroalProvider( strategySelector ); + } + } + if ( connectionProvider == null ) { if ( configurationValues.get( AvailableSettings.URL ) != null ) { connectionProvider = new DriverManagerConnectionProviderImpl(); @@ -192,6 +236,15 @@ public void processBeanInfo(BeanInfo beanInfo) throws Exception { return connectionProvider; } + private Class getSingleRegisteredProvider(StrategySelector strategySelector) { + final Collection> implementors = strategySelector.getRegisteredStrategyImplementors( ConnectionProvider.class ); + if ( implementors != null && implementors.size() == 1 ) { + return implementors.iterator().next(); + } + + return null; + } + private ConnectionProvider instantiateExplicitConnectionProvider(Class providerClass) { try { return (ConnectionProvider) providerClass.newInstance(); @@ -264,6 +317,53 @@ private ConnectionProvider instantiateHikariProvider(StrategySelector strategySe } } + private boolean viburConfigDefined(Map configValues) { + for ( Object key : configValues.keySet() ) { + if ( !String.class.isInstance( key ) ) { + continue; + } + + if ( ( (String) key ).startsWith( "hibernate.vibur." ) ) { + return true; + } + } + return false; + } + + + private boolean agroalConfigDefined(Map configValues) { + for ( Object key : configValues.keySet() ) { + if ( !String.class.isInstance( key ) ) { + continue; + } + + if ( ( (String) key ).startsWith( "hibernate.agroal." ) ) { + return true; + } + } + return false; + } + + private ConnectionProvider instantiateViburProvider(StrategySelector strategySelector) { + try { + return strategySelector.selectStrategyImplementor( ConnectionProvider.class, VIBUR_STRATEGY ).newInstance(); + } + catch ( Exception e ) { + LOG.viburProviderClassNotFound(); + return null; + } + } + + private ConnectionProvider instantiateAgroalProvider(StrategySelector strategySelector) { + try { + return strategySelector.selectStrategyImplementor( ConnectionProvider.class, AGROAL_STRATEGY ).newInstance(); + } + catch ( Exception e ) { + LOG.agroalProviderClassNotFound(); + return null; + } + } + /** * Build the connection properties capable of being passed to the {@link java.sql.DriverManager#getConnection} * forms taking {@link Properties} argument. We seek out all keys in the passed map which start with 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 b7a86f5c4a67..64c6c3af2ce6 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 @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.Map; import java.util.Properties; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -19,6 +20,8 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.ConnectionPoolingLogger; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.UnknownUnwrapTypeException; @@ -254,4 +257,149 @@ protected void finalize() throws Throwable { } //CHECKSTYLE:END_ALLOW_FINALIZER + public static class PooledConnections { + + private final ConcurrentLinkedQueue allConnections = new ConcurrentLinkedQueue(); + private final ConcurrentLinkedQueue availableConnections = new ConcurrentLinkedQueue(); + + private static final CoreMessageLogger log = CoreLogging.messageLogger( DriverManagerConnectionProviderImpl.class ); + + private final ConnectionCreator connectionCreator; + private final boolean autoCommit; + private final int minSize; + private final int maxSize; + + private boolean primed; + + private PooledConnections( + Builder builder) { + log.debugf( "Initializing Connection pool with %s Connections", builder.initialSize ); + connectionCreator = builder.connectionCreator; + autoCommit = builder.autoCommit; + maxSize = builder.maxSize; + minSize = builder.minSize; + log.hibernateConnectionPoolSize( maxSize, minSize ); + addConnections( builder.initialSize ); + } + + public void validate() { + final int size = size(); + + if ( !primed && size >= minSize ) { + // IMPL NOTE : the purpose of primed is to allow the pool to lazily reach its + // defined min-size. + log.debug( "Connection pool now considered primed; min-size will be maintained" ); + primed = true; + } + + if ( size < minSize && primed ) { + int numberToBeAdded = minSize - size; + log.debugf( "Adding %s Connections to the pool", numberToBeAdded ); + addConnections( numberToBeAdded ); + } + else if ( size > maxSize ) { + int numberToBeRemoved = size - maxSize; + log.debugf( "Removing %s Connections from the pool", numberToBeRemoved ); + removeConnections( numberToBeRemoved ); + } + } + + public void add(Connection conn) throws SQLException { + conn.setAutoCommit( true ); + conn.clearWarnings(); + availableConnections.offer( conn ); + } + + public Connection poll() throws SQLException { + Connection conn = availableConnections.poll(); + if ( conn == null ) { + synchronized (allConnections) { + if(allConnections.size() < maxSize) { + addConnections( 1 ); + return poll(); + } + } + throw new HibernateException( "The internal connection pool has reached its maximum size and no connection is currently available!" ); + } + conn.setAutoCommit( autoCommit ); + return conn; + } + + public void close() throws SQLException { + try { + int allocationCount = allConnections.size() - availableConnections.size(); + if(allocationCount > 0) { + log.error( "Connection leak detected: there are " + allocationCount + " unclosed connections upon shutting down pool " + getUrl()); + } + } + finally { + for ( Connection connection : allConnections ) { + connection.close(); + } + } + } + + public int size() { + return availableConnections.size(); + } + + protected void removeConnections(int numberToBeRemoved) { + for ( int i = 0; i < numberToBeRemoved; i++ ) { + Connection connection = availableConnections.poll(); + try { + if ( connection != null ) { + connection.close(); + } + allConnections.remove( connection ); + } + catch (SQLException e) { + log.unableToCloseConnection( e ); + } + } + } + + protected void addConnections(int numberOfConnections) { + for ( int i = 0; i < numberOfConnections; i++ ) { + Connection connection = connectionCreator.createConnection(); + allConnections.add( connection ); + availableConnections.add( connection ); + } + } + + public String getUrl() { + return connectionCreator.getUrl(); + } + + public static class Builder { + private final ConnectionCreator connectionCreator; + private boolean autoCommit; + private int initialSize = 1; + private int minSize = 1; + private int maxSize = 20; + + public Builder(ConnectionCreator connectionCreator, boolean autoCommit) { + this.connectionCreator = connectionCreator; + this.autoCommit = autoCommit; + } + + public Builder initialSize(int initialSize) { + this.initialSize = initialSize; + return this; + } + + public Builder minSize(int minSize) { + this.minSize = minSize; + return this; + } + + public Builder maxSize(int maxSize) { + this.maxSize = maxSize; + return this; + } + + public PooledConnections build() { + return new PooledConnections( this ); + } + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java index fee59974e2e5..7a62dc9b27ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java @@ -42,7 +42,7 @@ public Class getServiceInitiated() { @SuppressWarnings( {"unchecked"}) public MultiTenantConnectionProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) { final MultiTenancyStrategy strategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configurationValues ); - if ( strategy == MultiTenancyStrategy.NONE || strategy == MultiTenancyStrategy.DISCRIMINATOR ) { + if ( !strategy.requiresMultiTenantConnectionProvider() ) { // nothing to do, but given the separate hierarchies have to handle this here. return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PooledConnections.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PooledConnections.java deleted file mode 100644 index 77b99da45dfe..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PooledConnections.java +++ /dev/null @@ -1,164 +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.Connection; -import java.sql.SQLException; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.hibernate.HibernateException; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; - -/** - * @author Andrea Boriero - */ -public class PooledConnections { - - private final ConcurrentLinkedQueue allConnections = new ConcurrentLinkedQueue(); - private final ConcurrentLinkedQueue availableConnections = new ConcurrentLinkedQueue(); - - private static final CoreMessageLogger log = CoreLogging.messageLogger( DriverManagerConnectionProviderImpl.class ); - - private final ConnectionCreator connectionCreator; - private final boolean autoCommit; - private final int minSize; - private final int maxSize; - - private boolean primed; - - private PooledConnections( - Builder builder) { - log.debugf( "Initializing Connection pool with %s Connections", builder.initialSize ); - connectionCreator = builder.connectionCreator; - autoCommit = builder.autoCommit; - maxSize = builder.maxSize; - minSize = builder.minSize; - log.hibernateConnectionPoolSize( maxSize, minSize ); - addConnections( builder.initialSize ); - } - - public void validate() { - final int size = size(); - - if ( !primed && size >= minSize ) { - // IMPL NOTE : the purpose of primed is to allow the pool to lazily reach its - // defined min-size. - log.debug( "Connection pool now considered primed; min-size will be maintained" ); - primed = true; - } - - if ( size < minSize && primed ) { - int numberToBeAdded = minSize - size; - log.debugf( "Adding %s Connections to the pool", numberToBeAdded ); - addConnections( numberToBeAdded ); - } - else if ( size > maxSize ) { - int numberToBeRemoved = size - maxSize; - log.debugf( "Removing %s Connections from the pool", numberToBeRemoved ); - removeConnections( numberToBeRemoved ); - } - } - - public void add(Connection conn) throws SQLException { - conn.setAutoCommit( true ); - conn.clearWarnings(); - availableConnections.offer( conn ); - } - - public Connection poll() throws SQLException { - Connection conn = availableConnections.poll(); - if ( conn == null ) { - synchronized (allConnections) { - if(allConnections.size() < maxSize) { - addConnections( 1 ); - return poll(); - } - } - throw new HibernateException( "The internal connection pool has reached its maximum size and no connection is currently available!" ); - } - conn.setAutoCommit( autoCommit ); - return conn; - } - - public void close() throws SQLException { - try { - int allocationCount = allConnections.size() - availableConnections.size(); - if(allocationCount > 0) { - log.error( "Collection leak detected: there are " + allocationCount + " unclosed connections upon shutting down pool " + getUrl()); - } - } - finally { - for ( Connection connection : allConnections ) { - connection.close(); - } - } - } - - public int size() { - return availableConnections.size(); - } - - protected void removeConnections(int numberToBeRemoved) { - for ( int i = 0; i < numberToBeRemoved; i++ ) { - Connection connection = availableConnections.poll(); - try { - if ( connection != null ) { - connection.close(); - } - allConnections.remove( connection ); - } - catch (SQLException e) { - log.unableToCloseConnection( e ); - } - } - } - - protected void addConnections(int numberOfConnections) { - for ( int i = 0; i < numberOfConnections; i++ ) { - Connection connection = connectionCreator.createConnection(); - allConnections.add( connection ); - availableConnections.add( connection ); - } - } - - public String getUrl() { - return connectionCreator.getUrl(); - } - - public static class Builder { - private final ConnectionCreator connectionCreator; - private boolean autoCommit; - private int initialSize = 1; - private int minSize = 1; - private int maxSize = 20; - - public Builder(ConnectionCreator connectionCreator, boolean autoCommit) { - this.connectionCreator = connectionCreator; - this.autoCommit = autoCommit; - } - - public Builder initialSize(int initialSize) { - this.initialSize = initialSize; - return this; - } - - public Builder minSize(int minSize) { - this.minSize = minSize; - return this; - } - - public Builder maxSize(int maxSize) { - this.maxSize = maxSize; - return this; - } - - public PooledConnections build() { - return new PooledConnections( this ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java index 3d1302b79b61..2b2d8c5827f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java @@ -47,16 +47,20 @@ public boolean supportsAggressiveRelease() { @Override public boolean isUnwrappableAs(Class unwrapType) { - return MultiTenantConnectionProvider.class.equals( unwrapType ) || - AbstractMultiTenantConnectionProvider.class.isAssignableFrom( unwrapType ); + return + DataSource.class.isAssignableFrom( unwrapType ) || + MultiTenantConnectionProvider.class.isAssignableFrom( unwrapType ); } @Override @SuppressWarnings( {"unchecked"}) public T unwrap(Class unwrapType) { - if ( isUnwrappableAs( unwrapType ) ) { + if ( MultiTenantConnectionProvider.class.isAssignableFrom( unwrapType ) ) { return (T) this; } + else if ( DataSource.class.isAssignableFrom( unwrapType ) ) { + return (T) selectAnyDataSource(); + } else { throw new UnknownUnwrapTypeException( unwrapType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java index c13b02e80fce..2e72e054c9df 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java @@ -48,17 +48,20 @@ public boolean supportsAggressiveRelease() { @Override public boolean isUnwrappableAs(Class unwrapType) { - return ConnectionProvider.class.equals( unwrapType ) || - MultiTenantConnectionProvider.class.equals( unwrapType ) || - AbstractMultiTenantConnectionProvider.class.isAssignableFrom( unwrapType ); + return + ConnectionProvider.class.isAssignableFrom( unwrapType ) || + MultiTenantConnectionProvider.class.isAssignableFrom( unwrapType ); } @Override - @SuppressWarnings( {"unchecked"}) + @SuppressWarnings({"unchecked"}) public T unwrap(Class unwrapType) { - if ( isUnwrappableAs( unwrapType ) ) { + if ( MultiTenantConnectionProvider.class.isAssignableFrom( unwrapType ) ) { return (T) this; } + else if ( ConnectionProvider.class.isAssignableFrom( unwrapType ) ) { + return (T) getAnyConnectionProvider(); + } else { throw new UnknownUnwrapTypeException( unwrapType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java index 2190dca45f88..7a837c052bb2 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java @@ -173,21 +173,8 @@ public static boolean supportsRefCursors(DatabaseMetaData meta) { } - private static Integer refCursorTypeCode; - private int refCursorTypeCode() { - if ( refCursorTypeCode == null ) { - try { - refCursorTypeCode = (Integer) Types.class.getField( "REF_CURSOR" ).get( null ); - } - catch (NoSuchFieldException e) { - throw new HibernateException( "java.sql.Types class does not define REF_CURSOR field..." ); - } - catch (IllegalAccessException e) { - throw new HibernateException( "Unexpected error trying to determine REF_CURSOR field value : " + e.getMessage() ); - } - } - return refCursorTypeCode; + return Types.REF_CURSOR; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolver.java index ae838142d277..6168fd5b518e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolver.java @@ -6,40 +6,8 @@ */ package org.hibernate.engine.jdbc.dialect.internal; -import org.hibernate.dialect.CUBRIDDialect; -import org.hibernate.dialect.DB2400Dialect; -import org.hibernate.dialect.DB2Dialect; -import org.hibernate.dialect.DerbyDialect; -import org.hibernate.dialect.DerbyTenFiveDialect; -import org.hibernate.dialect.DerbyTenSevenDialect; -import org.hibernate.dialect.DerbyTenSixDialect; +import org.hibernate.dialect.Database; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.FirebirdDialect; -import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.HANAColumnStoreDialect; -import org.hibernate.dialect.HSQLDialect; -import org.hibernate.dialect.InformixDialect; -import org.hibernate.dialect.Ingres10Dialect; -import org.hibernate.dialect.Ingres9Dialect; -import org.hibernate.dialect.IngresDialect; -import org.hibernate.dialect.MySQL5Dialect; -import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.Oracle10gDialect; -import org.hibernate.dialect.Oracle12cDialect; -import org.hibernate.dialect.Oracle8iDialect; -import org.hibernate.dialect.Oracle9iDialect; -import org.hibernate.dialect.PostgreSQL81Dialect; -import org.hibernate.dialect.PostgreSQL82Dialect; -import org.hibernate.dialect.PostgreSQL92Dialect; -import org.hibernate.dialect.PostgreSQL94Dialect; -import org.hibernate.dialect.PostgreSQL9Dialect; -import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SQLServer2005Dialect; -import org.hibernate.dialect.SQLServer2008Dialect; -import org.hibernate.dialect.SQLServer2012Dialect; -import org.hibernate.dialect.SQLServerDialect; -import org.hibernate.dialect.SybaseASE15Dialect; -import org.hibernate.dialect.SybaseAnywhereDialect; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; import org.hibernate.internal.CoreLogging; @@ -60,172 +28,14 @@ public class StandardDialectResolver implements DialectResolver { @Override public Dialect resolveDialect(DialectResolutionInfo info) { - final String databaseName = info.getDatabaseName(); - if ( "CUBRID".equalsIgnoreCase( databaseName ) ) { - return new CUBRIDDialect(); - } - - if ( "HSQL Database Engine".equals( databaseName ) ) { - return new HSQLDialect(); - } - - if ( "H2".equals( databaseName ) ) { - return new H2Dialect(); - } - - if ( "MySQL".equals( databaseName ) ) { - final int majorVersion = info.getDatabaseMajorVersion(); - - if (majorVersion >= 5 ) { - return new MySQL5Dialect(); - } - - return new MySQLDialect(); - } - - if ( "PostgreSQL".equals( databaseName ) ) { - final int majorVersion = info.getDatabaseMajorVersion(); - final int minorVersion = info.getDatabaseMinorVersion(); - - if ( majorVersion == 9 ) { - if ( minorVersion >= 4 ) { - return new PostgreSQL94Dialect(); - } - else if ( minorVersion >= 2 ) { - return new PostgreSQL92Dialect(); - } - return new PostgreSQL9Dialect(); - } - - if ( majorVersion == 8 && minorVersion >= 2 ) { - return new PostgreSQL82Dialect(); - } - - return new PostgreSQL81Dialect(); - } - - if ( "EnterpriseDB".equals( databaseName ) ) { - return new PostgresPlusDialect(); - } - - if ( "Apache Derby".equals( databaseName ) ) { - final int majorVersion = info.getDatabaseMajorVersion(); - final int minorVersion = info.getDatabaseMinorVersion(); - - if ( majorVersion > 10 || ( majorVersion == 10 && minorVersion >= 7 ) ) { - return new DerbyTenSevenDialect(); - } - else if ( majorVersion == 10 && minorVersion == 6 ) { - return new DerbyTenSixDialect(); - } - else if ( majorVersion == 10 && minorVersion == 5 ) { - return new DerbyTenFiveDialect(); - } - else { - return new DerbyDialect(); - } - } - - if ( "ingres".equalsIgnoreCase( databaseName ) ) { - final int majorVersion = info.getDatabaseMajorVersion(); - final int minorVersion = info.getDatabaseMinorVersion(); - - switch ( majorVersion ) { - case 9: - if (minorVersion > 2) { - return new Ingres9Dialect(); - } - return new IngresDialect(); - case 10: - return new Ingres10Dialect(); - default: - LOG.unknownIngresVersion( majorVersion ); - } - return new IngresDialect(); - } - - if ( databaseName.startsWith( "Microsoft SQL Server" ) ) { - final int majorVersion = info.getDatabaseMajorVersion(); - - switch ( majorVersion ) { - case 8: { - return new SQLServerDialect(); - } - case 9: { - return new SQLServer2005Dialect(); - } - case 10: { - return new SQLServer2008Dialect(); - } - case 11: - case 12: - case 13: { - return new SQLServer2012Dialect(); - } - default: { - if ( majorVersion < 8 ) { - LOG.unknownSqlServerVersion( majorVersion, SQLServerDialect.class ); - return new SQLServerDialect(); - } - else { - // assume `majorVersion > 13` - LOG.unknownSqlServerVersion( majorVersion, SQLServer2012Dialect.class ); - return new SQLServer2012Dialect(); - } - } + for ( Database database : Database.values() ) { + Dialect dialect = database.resolveDialect( info ); + if ( dialect != null ) { + return dialect; } } - if ( "Sybase SQL Server".equals( databaseName ) || "Adaptive Server Enterprise".equals( databaseName ) ) { - return new SybaseASE15Dialect(); - } - - if ( databaseName.startsWith( "Adaptive Server Anywhere" ) ) { - return new SybaseAnywhereDialect(); - } - - if ( "Informix Dynamic Server".equals( databaseName ) ) { - return new InformixDialect(); - } - - if ( "DB2 UDB for AS/400".equals( databaseName ) ) { - return new DB2400Dialect(); - } - - if ( databaseName.startsWith( "DB2/" ) ) { - return new DB2Dialect(); - } - - if ( "Oracle".equals( databaseName ) ) { - final int majorVersion = info.getDatabaseMajorVersion(); - - switch ( majorVersion ) { - case 12: - return new Oracle12cDialect(); - case 11: - // fall through - case 10: - return new Oracle10gDialect(); - case 9: - return new Oracle9iDialect(); - case 8: - return new Oracle8iDialect(); - default: - LOG.unknownOracleVersion( majorVersion ); - } - return new Oracle8iDialect(); - } - - if ( "HDB".equals( databaseName ) ) { - // SAP recommends defaulting to column store. - return new HANAColumnStoreDialect(); - } - - if ( databaseName.startsWith( "Firebird" ) ) { - return new FirebirdDialect(); - } - return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/DefaultSchemaNameResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/DefaultSchemaNameResolver.java index 4c2556a5c05a..2ad44fc217d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/DefaultSchemaNameResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/DefaultSchemaNameResolver.java @@ -28,52 +28,59 @@ public class DefaultSchemaNameResolver implements SchemaNameResolver { public static final DefaultSchemaNameResolver INSTANCE = new DefaultSchemaNameResolver(); - private final SchemaNameResolver delegate; + // NOTE: The actual delegate should not be cached in DefaultSchemaNameResolver because, + // in the case of multiple data sources, there may be a data source that + // requires a different delegate. See HHH-12392. - public DefaultSchemaNameResolver() { - this.delegate = determineAppropriateResolverDelegate(); + private DefaultSchemaNameResolver() { } - private static SchemaNameResolver determineAppropriateResolverDelegate() { + private SchemaNameResolver determineAppropriateResolverDelegate(Connection connection) { // unfortunately Connection#getSchema is only available in Java 1.7 and above // and Hibernate still baselines on 1.6. So for now, use reflection and // leverage the Connection#getSchema method if it is available. - final Class jdbcConnectionClass = Connection.class; try { + final Class jdbcConnectionClass = connection.getClass(); final Method getSchemaMethod = jdbcConnectionClass.getMethod( "getSchema" ); - if ( getSchemaMethod != null ) { - if ( getSchemaMethod.getReturnType().equals( String.class ) ) { - return new SchemaNameResolverJava17Delegate( getSchemaMethod ); + if ( getSchemaMethod != null && getSchemaMethod.getReturnType().equals( String.class ) ) { + try { + // If the JDBC driver does not implement the Java 7 spec, but the JRE is Java 7 + // then the getSchemaMethod is not null but the call to getSchema() throws an java.lang.AbstractMethodError + connection.getSchema(); + return new SchemaNameResolverJava17Delegate(); + } + catch (java.lang.AbstractMethodError e) { + log.debugf( "Unable to use Java 1.7 Connection#getSchema" ); + return SchemaNameResolverFallbackDelegate.INSTANCE; } } + else { + log.debugf( "Unable to use Java 1.7 Connection#getSchema" ); + return SchemaNameResolverFallbackDelegate.INSTANCE; + } } catch (Exception ignore) { + log.debugf( + "Unable to use Java 1.7 Connection#getSchema : An error occurred trying to resolve the connection default schema resolver: " + + ignore.getMessage() ); + return SchemaNameResolverFallbackDelegate.INSTANCE; } - - log.debugf( "Unable to use Java 1.7 Connection#getSchema" ); - return SchemaNameResolverFallbackDelegate.INSTANCE; } @Override public String resolveSchemaName(Connection connection, Dialect dialect) throws SQLException { + // NOTE: delegate should not be cached in DefaultSchemaNameResolver because, + // in the case of multiple data sources, there may be a data source that + // requires a different delegate. See HHH-12392. + final SchemaNameResolver delegate = determineAppropriateResolverDelegate( connection ); return delegate.resolveSchemaName( connection, dialect ); } public static class SchemaNameResolverJava17Delegate implements SchemaNameResolver { - private final Method getSchemaMethod; - - public SchemaNameResolverJava17Delegate(Method getSchemaMethod) { - this.getSchemaMethod = getSchemaMethod; - } @Override public String resolveSchemaName(Connection connection, Dialect dialect) throws SQLException { - try { - return (String) getSchemaMethod.invoke( connection ); - } - catch (Exception e) { - throw new HibernateException( "Unable to invoke Connection#getSchema method via reflection", e ); - } + return connection.getSchema(); } } @@ -95,29 +102,11 @@ public String resolveSchemaName(Connection connection, Dialect dialect) throws S ); } - final Statement statement = connection.createStatement(); - try { - final ResultSet resultSet = statement.executeQuery( dialect.getCurrentSchemaCommand() ); - try { - if ( !resultSet.next() ) { - return null; - } - return resultSet.getString( 1 ); - } - finally { - try { - resultSet.close(); - } - catch (SQLException ignore) { - } - } - } - finally { - try { - statement.close(); - } - catch (SQLException ignore) { - } + try ( + final Statement statement = connection.createStatement(); + final ResultSet resultSet = statement.executeQuery( dialect.getCurrentSchemaCommand() ) + ) { + return resultSet.next() ? resultSet.getString( 1 ) : null; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java index 9674322020b9..e983cb850f37 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java @@ -257,7 +257,7 @@ public JdbcEnvironmentImpl( } this.identifierHelper = identifierHelper; - // and that current-catalog and current-schema happen afterQuery it + // and that current-catalog and current-schema happen after it this.currentCatalog = identifierHelper.toIdentifier( extractedMetaDataSupport.getConnectionCatalogName() ); this.currentSchema = identifierHelper.toIdentifier( extractedMetaDataSupport.getConnectionSchemaName() ); @@ -298,7 +298,7 @@ private String determineCurrentSchemaName( return schemaNameResolver.resolveSchemaName( databaseMetaData.getConnection(), dialect ); } catch (Exception e) { - // for now, just ignore the exception. + log.debug( "Unable to resolve connection default schema", e ); return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java index d7a46942e601..70c44e310f6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java @@ -141,7 +141,7 @@ private JdbcConnectionAccess buildJdbcConnectionAccess(Map configValues, Service final MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configValues ); - if ( MultiTenancyStrategy.NONE == multiTenancyStrategy ) { + if ( !multiTenancyStrategy.requiresMultiTenantConnectionProvider() ) { ConnectionProvider connectionProvider = registry.getService( ConnectionProvider.class ); return new ConnectionProviderJdbcConnectionAccess( connectionProvider ); } @@ -154,7 +154,7 @@ private JdbcConnectionAccess buildJdbcConnectionAccess(Map configValues, Service public static JdbcConnectionAccess buildBootstrapJdbcConnectionAccess( MultiTenancyStrategy multiTenancyStrategy, ServiceRegistryImplementor registry) { - if ( MultiTenancyStrategy.NONE == multiTenancyStrategy ) { + if ( !multiTenancyStrategy.requiresMultiTenantConnectionProvider() ) { ConnectionProvider connectionProvider = registry.getService( ConnectionProvider.class ); return new ConnectionProviderJdbcConnectionAccess( connectionProvider ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java index 4e3d1808e3d8..46ba1c39efd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java @@ -93,7 +93,7 @@ public Identifier toIdentifier(String text, boolean quoted) { @Override public Identifier applyGlobalQuoting(String text) { - return Identifier.toIdentifier( text, globallyQuoteIdentifiers && globallyQuoteIdentifiersSkipColumnDefinitions ); + return Identifier.toIdentifier( text, globallyQuoteIdentifiers && !globallyQuoteIdentifiersSkipColumnDefinitions ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java index d9acbca4ae26..be95c6738fa7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java @@ -17,7 +17,7 @@ public interface IdentifierHelper { /** * Essentially quotes the identifier if it needs to be. Useful to apply global quoting, - * as well as reserved word quoting afterQuery calls to naming strategies. + * as well as reserved word quoting after calls to naming strategies. * * @param identifier The identifier for which to normalize quoting. * diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java index 9812f41efbf3..aed6fe3599b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java @@ -21,7 +21,7 @@ /** * Builder for IdentifierHelper instances. Mainly here to allow progressive - * building of the immutable (afterQuery instantiation) IdentifierHelper. + * building of the immutable (after instantiation) IdentifierHelper. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SchemaNameResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SchemaNameResolver.java index 747e7a02c874..d6e7c8dc3dd7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SchemaNameResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SchemaNameResolver.java @@ -25,5 +25,5 @@ public interface SchemaNameResolver { * * @return The name of the schema (may be null). */ - public String resolveSchemaName(Connection connection, Dialect dialect) throws SQLException; + String resolveSchemaName(Connection connection, Dialect dialect) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java index 29e8e56ad4d3..a7aed17f9231 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java @@ -127,7 +127,17 @@ else if ( "\"".equals( token ) ) { t = tokens.nextToken(); token += t; } - while ( !"\"".equals( t ) ); + while ( !"\"".equals( t ) && tokens.hasMoreTokens() ); + } + // SQL Server uses "[" and "]" to escape reserved words + // see SQLServerDialect.openQuote and SQLServerDialect.closeQuote + else if ( "[".equals( token ) ) { + String t; + do { + t = tokens.nextToken(); + token += t; + } + while ( !"]".equals( t ) && tokens.hasMoreTokens()); } if ( afterByOrSetOrFromOrSelect && ",".equals( token ) ) { @@ -356,6 +366,10 @@ private void openParen() { } private static boolean isFunctionName(String tok) { + if ( tok == null || tok.length() == 0 ) { + return false; + } + final char begin = tok.charAt( 0 ); final boolean isIdentifier = Character.isJavaIdentifierStart( begin ) || '"' == begin; return isIdentifier && diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java index f11cfa5ada9b..9a4ba49a3bf5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java @@ -38,8 +38,10 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jdbc.WorkExecutor; import org.hibernate.jdbc.WorkExecutorVisitable; +import org.hibernate.resource.jdbc.ResourceRegistry; import org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl; import org.hibernate.resource.jdbc.internal.LogicalConnectionProvidedImpl; +import org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl; import org.hibernate.resource.jdbc.spi.JdbcSessionOwner; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.resource.transaction.backend.jdbc.spi.JdbcResourceTransaction; @@ -59,26 +61,15 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { private transient LogicalConnectionImplementor logicalConnection; private transient JdbcSessionOwner owner; + private transient JdbcServices jdbcServices; + private transient Batch currentBatch; private transient long transactionTimeOutInstant = -1; - /** - * This is a marker value to insert instead of null values for when a Statement gets registered in xref - * but has no associated ResultSets registered. This is useful to efficiently check against duplicate - * registration but you'll have to check against instance equality rather than null beforeQuery attempting - * to add elements to this set. - */ - private static final Set EMPTY_RESULTSET = Collections.emptySet(); - - private final HashMap> xref = new HashMap<>(); - private final Set unassociatedResultSets = new HashSet<>(); - private transient SqlExceptionHelper exceptionHelper; - private Statement lastQuery; private final boolean isUserSuppliedConnection; - /** * If true, manually (and temporarily) circumvent aggressive release processing. */ @@ -94,20 +85,23 @@ public JdbcCoordinatorImpl( JdbcSessionOwner owner) { this.isUserSuppliedConnection = userSuppliedConnection != null; + final ResourceRegistry resourceRegistry = new ResourceRegistryStandardImpl( + owner.getJdbcSessionContext().getObserver() + ); if ( isUserSuppliedConnection ) { - this.logicalConnection = new LogicalConnectionProvidedImpl( userSuppliedConnection ); + this.logicalConnection = new LogicalConnectionProvidedImpl( userSuppliedConnection, resourceRegistry ); } else { this.logicalConnection = new LogicalConnectionManagedImpl( owner.getJdbcConnectionAccess(), - owner.getJdbcSessionContext() + owner.getJdbcSessionContext(), + resourceRegistry ); } this.owner = owner; - this.exceptionHelper = owner.getJdbcSessionContext() + this.jdbcServices = owner.getJdbcSessionContext() .getServiceRegistry() - .getService( JdbcServices.class ) - .getSqlExceptionHelper(); + .getService( JdbcServices.class ); } private JdbcCoordinatorImpl( @@ -117,10 +111,9 @@ private JdbcCoordinatorImpl( this.logicalConnection = logicalConnection; this.isUserSuppliedConnection = isUserSuppliedConnection; this.owner = owner; - this.exceptionHelper = owner.getJdbcSessionContext() + this.jdbcServices = owner.getJdbcSessionContext() .getServiceRegistry() - .getService( JdbcServices.class ) - .getSqlExceptionHelper(); + .getService( JdbcServices.class ); } @Override @@ -142,7 +135,7 @@ protected BatchBuilder batchBuilder() { * @return The SqlExceptionHelper */ public SqlExceptionHelper sqlExceptionHelper() { - return exceptionHelper; + return jdbcServices.getSqlExceptionHelper(); } private int flushDepth; @@ -177,7 +170,6 @@ public Connection close() { LOG.closingUnreleasedBatch(); currentBatch.release(); } - cleanup(); } finally { connection = logicalConnection.close(); @@ -221,7 +213,7 @@ public void abortBatch() { @Override public StatementPreparer getStatementPreparer() { if ( statementPreparer == null ) { - statementPreparer = new StatementPreparerImpl( this ); + statementPreparer = new StatementPreparerImpl( this, jdbcServices ); } return statementPreparer; } @@ -260,7 +252,7 @@ public int determineRemainingTransactionTimeOutPeriod() { @Override public void afterStatementExecution() { - LOG.tracev( "Starting afterQuery statement execution processing [{0}]", getConnectionReleaseMode() ); + LOG.tracev( "Starting after statement execution processing [{0}]", getConnectionReleaseMode() ); if ( getConnectionReleaseMode() == ConnectionReleaseMode.AFTER_STATEMENT ) { if ( ! releasesEnabled ) { LOG.debug( "Skipping aggressive release due to manual disabling" ); @@ -342,12 +334,17 @@ public void registerLastQuery(Statement statement) { @Override public void cancelLastQuery() { try { - if (lastQuery != null) { + if ( lastQuery != null ) { lastQuery.cancel(); } } catch (SQLException sqle) { - throw exceptionHelper.convert( sqle, "Cannot cancel query" ); + SqlExceptionHelper sqlExceptionHelper = jdbcServices.getSqlExceptionHelper(); + //Should always be non-null, but to make sure as the implementation is lazy: + if ( sqlExceptionHelper != null ) { + sqlExceptionHelper = new SqlExceptionHelper( false ); + } + throw sqlExceptionHelper.convert( sqle, "Cannot cancel query" ); } finally { lastQuery = null; @@ -364,23 +361,6 @@ public void disableReleases() { releasesEnabled = false; } - private void cleanup() { - for ( Map.Entry> entry : xref.entrySet() ) { - closeAll( entry.getValue() ); - close( entry.getKey() ); - } - xref.clear(); - - closeAll( unassociatedResultSets ); - } - - protected void closeAll(Set resultSets) { - for ( ResultSet resultSet : resultSets ) { - close( resultSet ); - } - resultSets.clear(); - } - @SuppressWarnings({ "unchecked" }) protected void close(Statement statement) { LOG.tracev( "Closing prepared statement [{0}]", statement ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java index 3f753f801f8d..47f3251ad937 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java @@ -31,8 +31,6 @@ public class ResultSetReturnImpl implements ResultSetReturn { private final SqlStatementLogger sqlStatementLogger; private final SqlExceptionHelper sqlExceptionHelper; - private boolean isJdbc4 = true; - /** * Constructs a ResultSetReturnImpl * @@ -56,15 +54,6 @@ public ResultSetReturnImpl(JdbcCoordinator jdbcCoordinator) { public ResultSet extract(PreparedStatement statement) { // IMPL NOTE : SQL logged by caller try { - if ( isTypeOf( statement, CallableStatement.class ) ) { - // We actually need to extract from Callable statement. Although - // this seems needless, Oracle can return an - // OracleCallableStatementWrapper that finds its way to this method, - // rather than extract(CallableStatement). See HHH-8022. - final CallableStatement callableStatement = statement.unwrap( CallableStatement.class ); - return extract( callableStatement ); - } - final ResultSet rs; try { jdbcExecuteStatementStart(); @@ -89,25 +78,6 @@ private void jdbcExecuteStatementStart() { jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcExecuteStatementStart(); } - private boolean isTypeOf(final Statement statement, final Class type) { - if ( isJdbc4 ) { - try { - // This is "more correct" than #isInstance, but not always supported. - return statement.isWrapperFor( type ); - } - catch (SQLException e) { - // No operation - } - catch (Throwable e) { - // No operation. Note that this catches more than just SQLException to - // cover edge cases where a driver might throw an UnsupportedOperationException, AbstractMethodError, - // etc. If so, skip permanently. - isJdbc4 = false; - } - } - return type.isInstance( statement ); - } - @Override public ResultSet extract(CallableStatement callableStatement) { // IMPL NOTE : SQL logged by caller diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java index a6b44fc50b7f..2bbbe6da0a4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java @@ -18,6 +18,7 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.StatementPreparer; +import org.hibernate.resource.jdbc.spi.JdbcObserver; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; /** @@ -28,15 +29,17 @@ * @author Brett Meyer */ class StatementPreparerImpl implements StatementPreparer { - private JdbcCoordinatorImpl jdbcCoordinator; + private final JdbcCoordinatorImpl jdbcCoordinator; + private final JdbcServices jdbcServices; /** * Construct a StatementPreparerImpl * * @param jdbcCoordinator The JdbcCoordinatorImpl */ - StatementPreparerImpl(JdbcCoordinatorImpl jdbcCoordinator) { + StatementPreparerImpl(JdbcCoordinatorImpl jdbcCoordinator, JdbcServices jdbcServices) { this.jdbcCoordinator = jdbcCoordinator; + this.jdbcServices = jdbcServices; } protected final SessionFactoryOptions settings() { @@ -52,7 +55,7 @@ protected final LogicalConnectionImplementor logicalConnection() { } protected final SqlExceptionHelper sqlExceptionHelper() { - return getJdbcService().getSqlExceptionHelper(); + return jdbcServices.getSqlExceptionHelper(); } @Override @@ -164,16 +167,17 @@ protected StatementPreparationTemplate(String incomingSql) { public PreparedStatement prepareStatement() { try { - getJdbcService().getSqlStatementLogger().logStatement( sql ); + jdbcServices.getSqlStatementLogger().logStatement( sql ); final PreparedStatement preparedStatement; + final JdbcObserver observer = jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver(); try { - jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcPrepareStatementStart(); + observer.jdbcPrepareStatementStart(); preparedStatement = doPrepare(); setStatementTimeout( preparedStatement ); } finally { - jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcPrepareStatementEnd(); + observer.jdbcPrepareStatementEnd(); } postProcess( preparedStatement ); return preparedStatement; @@ -198,14 +202,6 @@ private void setStatementTimeout(PreparedStatement preparedStatement) throws SQL } } - private JdbcServices getJdbcService() { - return jdbcCoordinator - .getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry() - .getService( JdbcServices.class ); - } - private abstract class QueryStatementPreparationTemplate extends StatementPreparationTemplate { protected QueryStatementPreparationTemplate(String sql) { super( sql ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java index 86982351796d..90af28a7a0a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java @@ -78,7 +78,7 @@ public interface JdbcCoordinator extends Serializable, TransactionCoordinatorOwn /** * Callback to let us know that a flush is beginning. We use this fact - * to temporarily circumvent aggressive connection releasing until afterQuery + * to temporarily circumvent aggressive connection releasing until after * the flush cycle is complete {@link #flushEnding()} */ void flushBeginning(); @@ -101,7 +101,7 @@ public interface JdbcCoordinator extends Serializable, TransactionCoordinatorOwn /** * Signals the end of transaction. *

    - * Intended for use from the transaction coordinator, afterQuery local transaction completion. Used to conditionally + * Intended for use from the transaction coordinator, after local transaction completion. Used to conditionally * release the JDBC connection aggressively if the configured release mode indicates. */ void afterTransaction(); @@ -128,7 +128,7 @@ public interface JdbcCoordinator extends Serializable, TransactionCoordinatorOwn void cancelLastQuery(); /** - * Calculate the amount of time, in seconds, still remaining beforeQuery transaction timeout occurs. + * Calculate the amount of time, in seconds, still remaining before transaction timeout occurs. * * @return The number of seconds remaining until until a transaction timeout occurs. A negative value indicates * no timeout was requested. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java index 94cea4310075..daac437a4a57 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java @@ -10,6 +10,8 @@ import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; import org.hibernate.JDBCException; import org.hibernate.exception.internal.SQLStateConverter; @@ -124,11 +126,22 @@ public void logExceptions(SQLException sqlException, String message) { LOG.debug( message, sqlException ); } final boolean warnEnabled = LOG.isEnabled( Level.WARN ); + + List previousWarnMessages = new ArrayList<>(); + List previousErrorMessages = new ArrayList<>(); + while ( sqlException != null ) { if ( warnEnabled ) { - LOG.warn( "SQL Error: " + sqlException.getErrorCode() + ", SQLState: " + sqlException.getSQLState() ); + String warnMessage = "SQL Error: " + sqlException.getErrorCode() + ", SQLState: " + sqlException.getSQLState(); + if ( !previousWarnMessages.contains( warnMessage ) ) { + LOG.warn( warnMessage ); + previousWarnMessages.add( warnMessage ); + } + } + if ( !previousErrorMessages.contains( sqlException.getMessage() ) ) { + LOG.error( sqlException.getMessage() ); + previousErrorMessages.add( sqlException.getMessage() ); } - LOG.error( sqlException.getMessage() ); sqlException = sqlException.getNextException(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java index d1b91462e854..3a2ddbde0a32 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java @@ -9,6 +9,7 @@ import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.internal.Formatter; import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.build.AllowSysOut; import org.jboss.logging.Logger; @@ -54,7 +55,11 @@ public boolean isLogToStdout() { * Enable (true) or disable (false) logging to stdout. * * @param logToStdout True to enable logging to stdout; false to disable. + * + * @deprecated Will likely be removed: + * Should either become immutable or threadsafe. */ + @Deprecated public void setLogToStdout(boolean logToStdout) { this.logToStdout = logToStdout; } @@ -63,6 +68,11 @@ public boolean isFormat() { return format; } + /** + * @deprecated Will likely be removed: + * Should either become immutable or threadsafe. + */ + @Deprecated public void setFormat(boolean format) { this.format = format; } @@ -83,6 +93,7 @@ public void logStatement(String statement) { * @param statement The SQL statement. * @param formatter The formatter to use. */ + @AllowSysOut public void logStatement(String statement, Formatter formatter) { if ( format ) { if ( logToStdout || LOG.isDebugEnabled() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java index dcf6ee605a4d..3aa6e60399b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java @@ -17,7 +17,8 @@ import org.hibernate.CacheMode; import org.hibernate.EntityMode; import org.hibernate.HibernateException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.entry.CollectionCacheEntry; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; @@ -27,6 +28,7 @@ import org.hibernate.engine.spi.Status; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.EntityPersister; @@ -91,8 +93,7 @@ public LoadContexts getLoadContext() { * @return The loading collection (see discussion above). */ public PersistentCollection getLoadingCollection(final CollectionPersister persister, final Serializable key) { - final EntityMode em = persister.getOwnerEntityPersister().getEntityMetamodel().getEntityMode(); - final CollectionKey collectionKey = new CollectionKey( persister, key, em ); + final CollectionKey collectionKey = new CollectionKey( persister, key ); if ( LOG.isTraceEnabled() ) { LOG.tracev( "Starting attempt to find loading collection [{0}]", MessageHelper.collectionInfoString( persister.getRole(), key ) ); @@ -177,8 +178,7 @@ else if ( lce.getResultSet() == resultSet && lce.getPersister() == persister ) { session.getPersistenceContext().addUnownedCollection( new CollectionKey( persister, - lce.getKey(), - persister.getOwnerEntityPersister().getEntityMetamodel().getEntityMode() + lce.getKey() ), lce.getCollection() ); @@ -248,6 +248,38 @@ private void endLoadingCollection(LoadingCollectionEntry lce, CollectionPersiste // } } + // The collection has been completely initialized and added to the PersistenceContext. + + if ( lce.getCollection().getOwner() != null ) { + // If the owner is bytecode-enhanced and the owner's collection value is uninitialized, + // then go ahead and set it to the newly initialized collection. + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = + persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata(); + if ( bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { + // Lazy properties in embeddables/composites are not currently supported for embeddables (HHH-10480), + // so check to make sure the collection is not in an embeddable before checking to see if + // the collection is lazy. + // TODO: More will probably need to be done here when HHH-10480 is fixed.. + if ( StringHelper.qualifier( persister.getRole() ).length() == + persister.getOwnerEntityPersister().getEntityName().length() ) { + // Assume the collection is not in an embeddable. + // Strip off to get the collection property name. + final String propertyName = persister.getRole().substring( + persister.getOwnerEntityPersister().getEntityName().length() + 1 + ); + if ( !bytecodeEnhancementMetadata.isAttributeLoaded( lce.getCollection().getOwner(), propertyName ) ) { + int propertyIndex = persister.getOwnerEntityPersister().getEntityMetamodel().getPropertyIndex( + propertyName + ); + persister.getOwnerEntityPersister().setPropertyValue( + lce.getCollection().getOwner(), + propertyIndex, + lce.getCollection() + ); + } + } + } + } // add to cache if: boolean addToCache = @@ -287,7 +319,7 @@ private void addCollectionToCache(LoadingCollectionEntry lce, CollectionPersiste LOG.debugf( "Caching collection: %s", MessageHelper.collectionInfoString( persister, lce.getCollection(), lce.getKey(), session ) ); } - if ( !session.getLoadQueryInfluencers().getEnabledFilters().isEmpty() && persister.isAffectedByEnabledFilters( session ) ) { + if ( session.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( session ) ) { // some filters affecting the collection are enabled on the session, so do not do the put into the cache. if ( debugEnabled ) { LOG.debug( "Refusing to add to cache due to enabled filters" ); @@ -330,8 +362,8 @@ private void addCollectionToCache(LoadingCollectionEntry lce, CollectionPersiste } final CollectionCacheEntry entry = new CollectionCacheEntry( lce.getCollection(), persister ); - final CollectionRegionAccessStrategy cache = persister.getCacheAccessStrategy(); - final Object cacheKey = cache.generateCacheKey( + final CollectionDataAccess cacheAccess = persister.getCacheAccessStrategy(); + final Object cacheKey = cacheAccess.generateCacheKey( lce.getKey(), persister, session.getFactory(), @@ -353,17 +385,19 @@ private void addCollectionToCache(LoadingCollectionEntry lce, CollectionPersiste if (isPutFromLoad) { try { session.getEventListenerManager().cachePutStart(); - final boolean put = cache.putFromLoad( + final boolean put = cacheAccess.putFromLoad( session, cacheKey, persister.getCacheEntryStructure().structure( entry ), - session.getTimestamp(), version, factory.getSessionFactoryOptions().isMinimalPutsEnabled() && session.getCacheMode()!= CacheMode.REFRESH ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().secondLevelCachePut( persister.getCacheAccessStrategy().getRegion().getName() ); + factory.getStatistics().collectionCachePut( + persister.getNavigableRole(), + persister.getCacheAccessStrategy().getRegion().getName() + ); } } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java index 4412dfbf799f..53e6452c2786 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java @@ -159,20 +159,20 @@ public CollectionLoadContext getCollectionLoadContext(ResultSet resultSet) { } /** - * Attempt to locate the loading collection given the owner's key. The lookup here + * Attempt to locate the loading collection given the CollectionKey obtained from the owner's key. The lookup here * occurs against all result-set contexts... * * @param persister The collection persister - * @param ownerKey The owner key + * @param key The collection key * @return The loading collection, or null if not found. */ - public PersistentCollection locateLoadingCollection(CollectionPersister persister, Serializable ownerKey) { - final LoadingCollectionEntry lce = locateLoadingCollectionEntry( new CollectionKey( persister, ownerKey ) ); + public PersistentCollection locateLoadingCollection(CollectionPersister persister, CollectionKey key) { + final LoadingCollectionEntry lce = locateLoadingCollectionEntry( key ) ; if ( lce != null ) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Returning loading collection: %s", - MessageHelper.collectionInfoString( persister, ownerKey, getSession().getFactory() ) + MessageHelper.collectionInfoString( persister, key.getKey(), getSession().getFactory() ) ); } return lce.getCollection(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/ParameterRecognitionException.java b/hibernate-core/src/main/java/org/hibernate/engine/query/ParameterRecognitionException.java new file mode 100644 index 000000000000..3dd434aa0621 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/ParameterRecognitionException.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.engine.query; + +import org.hibernate.HibernateException; +import org.hibernate.engine.query.spi.ParamLocationRecognizer; + +/** + * Indicates a problem during parameter recognition via + * {@link ParamLocationRecognizer} + * + * @author Steve Ebersole + */ +public class ParameterRecognitionException extends HibernateException { + public ParameterRecognitionException(String message) { + super( message ); + } + + public ParameterRecognitionException(String message, Throwable cause) { + super( message, cause ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/internal/NativeQueryInterpreterStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/query/internal/NativeQueryInterpreterStandardImpl.java index 382e87b867a2..444f04387161 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/internal/NativeQueryInterpreterStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/internal/NativeQueryInterpreterStandardImpl.java @@ -6,13 +6,8 @@ */ package org.hibernate.engine.query.internal; -import java.util.HashMap; -import java.util.Map; - -import org.hibernate.engine.query.spi.NamedParameterDescriptor; import org.hibernate.engine.query.spi.NativeQueryInterpreter; import org.hibernate.engine.query.spi.NativeSQLQueryPlan; -import org.hibernate.engine.query.spi.OrdinalParameterDescriptor; import org.hibernate.engine.query.spi.ParamLocationRecognizer; import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -24,46 +19,25 @@ * @author Steve Ebersole */ public class NativeQueryInterpreterStandardImpl implements NativeQueryInterpreter { - /** - * Singleton access - */ - public static final NativeQueryInterpreterStandardImpl INSTANCE = new NativeQueryInterpreterStandardImpl(); + private final SessionFactoryImplementor sessionFactory; + + public NativeQueryInterpreterStandardImpl(SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; + } @Override public ParameterMetadataImpl getParameterMetadata(String nativeQuery) { - final ParamLocationRecognizer recognizer = ParamLocationRecognizer.parseLocations( nativeQuery ); - - final int size = recognizer.getOrdinalParameterLocationList().size(); - final OrdinalParameterDescriptor[] ordinalDescriptors = new OrdinalParameterDescriptor[ size ]; - for ( int i = 0; i < size; i++ ) { - final Integer position = recognizer.getOrdinalParameterLocationList().get( i ); - ordinalDescriptors[i] = new OrdinalParameterDescriptor( i, null, position ); - } - - final Map namedParamDescriptorMap = new HashMap(); - final Map map = recognizer.getNamedParameterDescriptionMap(); - - for ( final String name : map.keySet() ) { - final ParamLocationRecognizer.NamedParameterDescription description = map.get( name ); - namedParamDescriptorMap.put( - name, - new NamedParameterDescriptor( - name, - null, - description.buildPositionsArray(), - description.isJpaStyle() - ) - ); - } - - return new ParameterMetadataImpl( ordinalDescriptors, namedParamDescriptorMap ); + final ParamLocationRecognizer recognizer = ParamLocationRecognizer.parseLocations( nativeQuery, sessionFactory ); + return new ParameterMetadataImpl( + recognizer.getOrdinalParameterDescriptionMap(), + recognizer.getNamedParameterDescriptionMap() + ); } @Override public NativeSQLQueryPlan createQueryPlan( NativeSQLQuerySpecification specification, SessionFactoryImplementor sessionFactory) { - CustomQuery customQuery = new SQLCustomQuery( specification.getQueryString(), specification.getQueryReturns(), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java new file mode 100644 index 000000000000..df24844621f0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.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.engine.query.spi; + +import org.hibernate.Incubating; +import org.hibernate.query.QueryParameter; +import org.hibernate.type.Type; + +/** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * + * @author Steve Ebersole + */ +@Incubating +public abstract class AbstractParameterDescriptor implements QueryParameter { + private final int[] sourceLocations; + + private Type expectedType; + + public AbstractParameterDescriptor(int[] sourceLocations, Type expectedType) { + this.sourceLocations = sourceLocations; + this.expectedType = expectedType; + } + + @Override + public String getName() { + return null; + } + + @Override + public Integer getPosition() { + return null; + } + + @Override + public Class getParameterType() { + return expectedType == null ? null : expectedType.getReturnedClass(); + } + + @Override + public Type getHibernateType() { + return getExpectedType(); + } + + @Override + public int[] getSourceLocations() { + return sourceLocations; + } + + public Type getExpectedType() { + return expectedType; + } + + public void resetExpectedType(Type expectedType) { + this.expectedType = expectedType; + } +} 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 4a9054b3dd68..704d8ff4b9dd 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 @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -25,13 +26,14 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.hql.internal.QuerySplitter; import org.hibernate.hql.spi.FilterTranslator; +import org.hibernate.hql.spi.NamedParameterInformation; import org.hibernate.hql.spi.ParameterTranslations; +import org.hibernate.hql.spi.PositionalParameterInformation; import org.hibernate.hql.spi.QueryTranslator; import org.hibernate.hql.spi.QueryTranslatorFactory; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.internal.util.collections.EmptyIterator; import org.hibernate.internal.util.collections.IdentitySet; import org.hibernate.internal.util.collections.JoinedIterator; import org.hibernate.query.internal.ParameterMetadataImpl; @@ -58,6 +60,7 @@ public class HQLQueryPlan implements Serializable { private final Set enabledFilterNames; private final boolean shallow; + private final SessionFactoryImplementor factory; /** * We'll check the trace level only once per instance @@ -92,8 +95,9 @@ protected HQLQueryPlan( EntityGraphQueryHint entityGraphQueryHint) { this.sourceQuery = hql; this.shallow = shallow; + this.factory = factory; - final Set copy = new HashSet(); + final Set copy = new HashSet<>(); copy.addAll( enabledFilters.keySet() ); this.enabledFilterNames = java.util.Collections.unmodifiableSet( copy ); @@ -101,8 +105,8 @@ protected HQLQueryPlan( final int length = concreteQueryStrings.length; this.translators = new QueryTranslator[length]; - final List sqlStringList = new ArrayList(); - final Set combinedQuerySpaces = new HashSet(); + final List sqlStringList = new ArrayList<>(); + final Set combinedQuerySpaces = new HashSet<>(); final Map querySubstitutions = factory.getSessionFactoryOptions().getQuerySubstitutions(); final QueryTranslatorFactory queryTranslatorFactory = factory.getServiceRegistry().getService( QueryTranslatorFactory.class ); @@ -151,7 +155,7 @@ public Set getQuerySpaces() { } public ParameterMetadataImpl getParameterMetadata() { - return parameterMetadata.getOrdinalParametersZeroBasedCopy(); + return parameterMetadata; } public ReturnMetadata getReturnMetadata() { @@ -303,7 +307,7 @@ public Iterator performIterate( queryParameters.traceParameters( session.getFactory() ); } if ( translators.length == 0 ) { - return EmptyIterator.INSTANCE; + return Collections.emptyIterator(); } final boolean many = translators.length > 1; @@ -377,46 +381,53 @@ public int performExecuteUpdate(QueryParameters queryParameters, SharedSessionCo } private ParameterMetadataImpl buildParameterMetadata(ParameterTranslations parameterTranslations, String hql) { - final long start = traceEnabled ? System.nanoTime() : 0; - final ParamLocationRecognizer recognizer = ParamLocationRecognizer.parseLocations( hql ); - - if ( traceEnabled ) { - final long end = System.nanoTime(); - LOG.tracev( "HQL param location recognition took {0} nanoseconds ({1})", ( end - start ), hql ); + final Map ordinalParamDescriptors; + if ( parameterTranslations.getPositionalParameterInformationMap().isEmpty() ) { + ordinalParamDescriptors = Collections.emptyMap(); } - - int ordinalParamCount = parameterTranslations.getOrdinalParameterCount(); - final int[] locations = ArrayHelper.toIntArray( recognizer.getOrdinalParameterLocationList() ); - if ( parameterTranslations.supportsOrdinalParameterMetadata() && locations.length != ordinalParamCount ) { - throw new HibernateException( "ordinal parameter mismatch" ); + else { + final Map temp = new HashMap<>(); + for ( Map.Entry entry : + parameterTranslations.getPositionalParameterInformationMap().entrySet() ) { + final int position = entry.getKey(); + temp.put( + position, + new OrdinalParameterDescriptor( + position, + position - 1, + entry.getValue().getExpectedType(), + entry.getValue().getSourceLocations() + ) + ); + } + ordinalParamDescriptors = Collections.unmodifiableMap( temp ); } - ordinalParamCount = locations.length; - - final OrdinalParameterDescriptor[] ordinalParamDescriptors = new OrdinalParameterDescriptor[ordinalParamCount]; - for ( int i = 0; i < ordinalParamCount; i++ ) { - ordinalParamDescriptors[ i ] = new OrdinalParameterDescriptor( - i, - parameterTranslations.supportsOrdinalParameterMetadata() - ? parameterTranslations.getOrdinalParameterExpectedType( i ) - : null, - locations[ i ] - ); + + + final Map namedParamDescriptorMap; + + if ( parameterTranslations.getNamedParameterInformationMap().isEmpty() ) { + namedParamDescriptorMap = Collections.emptyMap(); } + else { + final Map tmp = new HashMap<>(); + for ( Map.Entry namedEntry : + parameterTranslations.getNamedParameterInformationMap().entrySet() ) { + final String name = namedEntry.getKey(); + tmp.put( + name, + new NamedParameterDescriptor( + name, + parameterTranslations.getNamedParameterInformation( name ).getExpectedType(), + namedEntry.getValue().getSourceLocations() + ) + ); + } - final Map namedParamDescriptorMap = new HashMap(); - final Map map = recognizer.getNamedParameterDescriptionMap(); - for ( final String name : map.keySet() ) { - final ParamLocationRecognizer.NamedParameterDescription description = map.get( name ); - namedParamDescriptorMap.put( - name, - new NamedParameterDescriptor( - name, - parameterTranslations.getNamedParameterExpectedType( name ), - description.buildPositionsArray(), - description.isJpaStyle() - ) - ); + namedParamDescriptorMap = Collections.unmodifiableMap( tmp ); } + + return new ParameterMetadataImpl( ordinalParamDescriptors, namedParamDescriptorMap ); } @@ -438,4 +449,8 @@ public Class getDynamicInstantiationResultType() { public boolean isSelect() { return !translators[0].isManipulationStatement(); } + + public boolean isUpdate() { + return translators[0].isUpdateStatement(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java index 4b5cc863c8be..4edddeb7dbf8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java @@ -7,7 +7,6 @@ package org.hibernate.engine.query.spi; import org.hibernate.Incubating; -import org.hibernate.query.QueryParameter; import org.hibernate.type.Type; /** @@ -16,11 +15,8 @@ * @author Steve Ebersole */ @Incubating -public class NamedParameterDescriptor implements QueryParameter { +public class NamedParameterDescriptor extends AbstractParameterDescriptor { private final String name; - private Type expectedType; - private final int[] sourceLocations; - private final boolean jpaStyle; /** * Constructs a NamedParameterDescriptor @@ -28,60 +24,17 @@ public class NamedParameterDescriptor implements QueryParameter { * @param name The name of the parameter * @param expectedType The expected type of the parameter, according to the translator * @param sourceLocations The locations of the named parameters (aye aye aye) - * @param jpaStyle Was the parameter a JPA style "named parameter"? */ - public NamedParameterDescriptor(String name, Type expectedType, int[] sourceLocations, boolean jpaStyle) { + public NamedParameterDescriptor(String name, Type expectedType, int[] sourceLocations) { + super( sourceLocations, expectedType ); this.name = name; - this.expectedType = expectedType; - this.sourceLocations = sourceLocations; - this.jpaStyle = jpaStyle; } + @Override public String getName() { return name; } - @Override - public Integer getPosition() { - return null; - } - - @Override - public Class getParameterType() { - return expectedType == null ? null : expectedType.getReturnedClass(); - } - - @Override - public boolean isJpaPositionalParameter() { - return isJpaStyle(); - } - - public Type getExpectedType() { - return expectedType; - } - - public int[] getSourceLocations() { - return sourceLocations; - } - - public boolean isJpaStyle() { - return jpaStyle; - } - - /** - * Set the parameters expected type - * - * @param type The new expected type - */ - public void resetExpectedType(Type type) { - this.expectedType = type; - } - - @Override - public Type getType() { - return expectedType; - } - @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreter.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreter.java index d894e1bda1ab..0c001bcc6a7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreter.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreter.java @@ -8,6 +8,8 @@ import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.custom.CustomLoader; +import org.hibernate.loader.custom.CustomQuery; import org.hibernate.query.internal.ParameterMetadataImpl; import org.hibernate.service.Service; @@ -16,6 +18,7 @@ * * @author Steve Ebersole * @author Gunnar Morling + * @author Guillaume Smet */ public interface NativeQueryInterpreter extends Service { /** @@ -38,4 +41,17 @@ public interface NativeQueryInterpreter extends Service { * @return A query plan for the specified native query. */ NativeSQLQueryPlan createQueryPlan(NativeSQLQuerySpecification specification, SessionFactoryImplementor sessionFactory); + + /** + * Creates a {@link CustomLoader} for the given {@link CustomQuery}. + * + * @param customQuery The CustomQuery to create a loader for + * @param sessionFactory The current session factory + * + * @deprecated This method will be removed in 6. + */ + @Deprecated + default CustomLoader createCustomLoader(CustomQuery customQuery, SessionFactoryImplementor sessionFactory) { + return new CustomLoader( customQuery, sessionFactory ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreterInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreterInitiator.java index 1ff8eadfaa96..3a5476c7002c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreterInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeQueryInterpreterInitiator.java @@ -11,6 +11,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.SessionFactoryServiceInitiator; +import org.hibernate.service.spi.SessionFactoryServiceInitiatorContext; /** * @author Steve Ebersole @@ -26,7 +27,12 @@ public NativeQueryInterpreter initiateService( SessionFactoryImplementor sessionFactory, SessionFactoryOptions sessionFactoryOptions, ServiceRegistryImplementor registry) { - return NativeQueryInterpreterStandardImpl.INSTANCE; + return new NativeQueryInterpreterStandardImpl( sessionFactory ); + } + + @Override + public NativeQueryInterpreter initiateService(SessionFactoryServiceInitiatorContext context) { + return new NativeQueryInterpreterStandardImpl( context.getSessionFactory() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java index d9b4f945b0e3..50afb8a81f68 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java @@ -9,22 +9,17 @@ import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.Iterator; -import java.util.List; -import java.util.Map; import org.hibernate.HibernateException; -import org.hibernate.QueryException; import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.engine.spi.TypedValue; import org.hibernate.event.spi.EventSource; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.custom.CustomQuery; -import org.hibernate.type.Type; +import org.hibernate.param.ParameterBinder; /** * Defines a query execution plan for a native-SQL query. @@ -56,98 +51,6 @@ public CustomQuery getCustomQuery() { return customQuery; } - private int[] getNamedParameterLocs(String name) throws QueryException { - final Object loc = customQuery.getNamedParameterBindPoints().get( name ); - if ( loc == null ) { - throw new QueryException( - "Named parameter does not appear in Query: " + name, - customQuery.getSQL() ); - } - if ( loc instanceof Integer ) { - return new int[] { (Integer) loc }; - } - else { - return ArrayHelper.toIntArray( (List) loc ); - } - } - - /** - * Perform binding of all the JDBC bind parameter values based on the user-defined - * positional query parameters (these are the '?'-style hibernate query - * params) into the JDBC {@link PreparedStatement}. - * - * @param st The prepared statement to which to bind the parameter values. - * @param queryParameters The query parameters specified by the application. - * @param start JDBC paramer binds are positional, so this is the position - * from which to start binding. - * @param session The session from which the query originated. - * - * @return The number of JDBC bind positions accounted for during execution. - * - * @throws SQLException Some form of JDBC error binding the values. - * @throws HibernateException Generally indicates a mapping problem or type mismatch. - */ - private int bindPositionalParameters( - final PreparedStatement st, - final QueryParameters queryParameters, - final int start, - final SharedSessionContractImplementor session) throws SQLException { - final Object[] values = queryParameters.getFilteredPositionalParameterValues(); - final Type[] types = queryParameters.getFilteredPositionalParameterTypes(); - int span = 0; - for (int i = 0; i < values.length; i++) { - types[i].nullSafeSet( st, values[i], start + span, session ); - span += types[i].getColumnSpan( session.getFactory() ); - } - return span; - } - - /** - * Perform binding of all the JDBC bind parameter values based on the user-defined - * named query parameters into the JDBC {@link PreparedStatement}. - * - * @param ps The prepared statement to which to bind the parameter values. - * @param namedParams The named query parameters specified by the application. - * @param start JDBC paramer binds are positional, so this is the position - * from which to start binding. - * @param session The session from which the query originated. - * - * @return The number of JDBC bind positions accounted for during execution. - * - * @throws SQLException Some form of JDBC error binding the values. - * @throws HibernateException Generally indicates a mapping problem or type mismatch. - */ - private int bindNamedParameters( - final PreparedStatement ps, - final Map namedParams, - final int start, - final SharedSessionContractImplementor session) throws SQLException { - if ( namedParams != null ) { - // assumes that types are all of span 1 - final Iterator iter = namedParams.entrySet().iterator(); - int result = 0; - while ( iter.hasNext() ) { - final Map.Entry e = (Map.Entry) iter.next(); - final String name = (String) e.getKey(); - final TypedValue typedval = (TypedValue) e.getValue(); - final int[] locs = getNamedParameterLocs( name ); - for ( int loc : locs ) { - LOG.debugf( "bindNamedParameters() %s -> %s [%s]", typedval.getValue(), name, loc + start ); - typedval.getType().nullSafeSet( - ps, - typedval.getValue(), - loc + start, - session - ); - } - result += locs.length; - } - return result; - } - - return 0; - } - protected void coordinateSharedCacheCleanup(SharedSessionContractImplementor session) { final BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, getCustomQuery().getQuerySpaces() ); @@ -181,16 +84,26 @@ public int performExecuteUpdate( int result = 0; PreparedStatement ps; + RowSelection selection = queryParameters.getRowSelection(); try { queryParameters.processFilters( this.customQuery.getSQL(), session ); - final String sql = queryParameters.getFilteredSQL(); + final String sql = session.getJdbcServices().getDialect() + .addSqlHintOrComment( + queryParameters.getFilteredSQL(), + queryParameters, + session.getFactory().getSessionFactoryOptions().isCommentsEnabled() + ); ps = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false ); try { int col = 1; - col += bindPositionalParameters( ps, queryParameters, col, session ); - col += bindNamedParameters( ps, queryParameters.getNamedParameters(), col, session ); + for ( ParameterBinder binder : this.customQuery.getParameterValueBinders() ) { + col += binder.bind( ps, queryParameters, session, col ); + } + if ( selection != null && selection.getTimeout() != null ) { + ps.setQueryTimeout( selection.getTimeout() ); + } result = session.getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/OrdinalParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/OrdinalParameterDescriptor.java index a318a76e2780..c738db0f98b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/OrdinalParameterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/OrdinalParameterDescriptor.java @@ -7,7 +7,6 @@ package org.hibernate.engine.query.spi; import org.hibernate.Incubating; -import org.hibernate.query.QueryParameter; import org.hibernate.type.Type; /** @@ -16,58 +15,29 @@ * @author Steve Ebersole */ @Incubating -public class OrdinalParameterDescriptor implements QueryParameter { - private final int ordinalPosition; - private final Type expectedType; - private final int sourceLocation; +public class OrdinalParameterDescriptor extends AbstractParameterDescriptor { + private final int label; + private final int valuePosition; /** * Constructs an ordinal parameter descriptor. - * - * @param ordinalPosition The ordinal position - * @param expectedType The expected type of the parameter - * @param sourceLocation The location of the parameter */ - public OrdinalParameterDescriptor(int ordinalPosition, Type expectedType, int sourceLocation) { - this.ordinalPosition = ordinalPosition; - this.expectedType = expectedType; - this.sourceLocation = sourceLocation; - } - - public int getOrdinalPosition() { - return ordinalPosition; - } - - public Type getExpectedType() { - return expectedType; - } - - public int getSourceLocation() { - return sourceLocation; - } - - @Override - public Type getType() { - return expectedType; - } - - @Override - public String getName() { - return null; + public OrdinalParameterDescriptor( + int label, + int valuePosition, + Type expectedType, + int[] sourceLocations) { + super( sourceLocations, expectedType ); + this.label = label; + this.valuePosition = valuePosition; } @Override public Integer getPosition() { - return ordinalPosition; + return label; } - @Override - public Class getParameterType() { - return expectedType == null ? null : expectedType.getReturnedClass(); - } - - @Override - public boolean isJpaPositionalParameter() { - return false; + public int getValuePosition() { + return valuePosition; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParamLocationRecognizer.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParamLocationRecognizer.java index d4f6e383664c..7fc93494b8eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParamLocationRecognizer.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParamLocationRecognizer.java @@ -7,10 +7,13 @@ package org.hibernate.engine.query.spi; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.hibernate.engine.query.ParameterRecognitionException; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.collections.ArrayHelper; /** @@ -20,97 +23,234 @@ * @author Steve Ebersole */ public class ParamLocationRecognizer implements ParameterParser.Recognizer { - /** - * Internal representation of a recognized named parameter - */ - public static class NamedParameterDescription { - private final boolean jpaStyle; - private final List positions = new ArrayList(); - NamedParameterDescription(boolean jpaStyle) { - this.jpaStyle = jpaStyle; - } + private Map namedParameterDescriptors; + private Map ordinalParameterDescriptors; - public boolean isJpaStyle() { - return jpaStyle; - } + private Map inFlightNamedStateMap; + private Map inFlightOrdinalStateMap; + private Map inFlightJpaOrdinalStateMap; - private void add(int position) { - positions.add( position ); - } + private final int jdbcStyleOrdinalCountBase; + private int jdbcStyleOrdinalCount; - public int[] buildPositionsArray() { - return ArrayHelper.toIntArray( positions ); - } + public ParamLocationRecognizer(int jdbcStyleOrdinalCountBase) { + this.jdbcStyleOrdinalCountBase = jdbcStyleOrdinalCountBase; + this.jdbcStyleOrdinalCount = jdbcStyleOrdinalCountBase; } - private Map namedParameterDescriptions = new HashMap(); - private List ordinalParameterLocationList = new ArrayList(); - /** * Convenience method for creating a param location recognizer and * initiating the parse. * * @param query The query to be parsed for parameter locations. + * @param sessionFactory * @return The generated recognizer, with journaled location info. */ - public static ParamLocationRecognizer parseLocations(String query) { - final ParamLocationRecognizer recognizer = new ParamLocationRecognizer(); + public static ParamLocationRecognizer parseLocations( + String query, + SessionFactoryImplementor sessionFactory) { + final ParamLocationRecognizer recognizer = new ParamLocationRecognizer( + sessionFactory.getSessionFactoryOptions().jdbcStyleParamsZeroBased() ? 0 : 1 + ); ParameterParser.parse( query, recognizer ); return recognizer; } - /** - * Returns the map of named parameter locations. The map is keyed by - * parameter name; the corresponding value is a (@link NamedParameterDescription}. - * - * @return The map of named parameter locations. - */ - public Map getNamedParameterDescriptionMap() { - return namedParameterDescriptions; + @Override + public void complete() { + if ( inFlightNamedStateMap != null && ( inFlightOrdinalStateMap != null || inFlightJpaOrdinalStateMap != null ) ) { + throw mixedParamStrategy(); + } + + // we know `inFlightNamedStateMap` is null, so no need to check it again + + if ( inFlightOrdinalStateMap != null && inFlightJpaOrdinalStateMap != null ) { + throw mixedParamStrategy(); + } + + if ( inFlightNamedStateMap != null ) { + final Map tmp = new HashMap<>(); + for ( InFlightNamedParameterState inFlightState : inFlightNamedStateMap.values() ) { + tmp.put( inFlightState.name, inFlightState.complete() ); + } + namedParameterDescriptors = Collections.unmodifiableMap( tmp ); + } + else { + namedParameterDescriptors = Collections.emptyMap(); + } + + if ( inFlightOrdinalStateMap == null && inFlightJpaOrdinalStateMap == null ) { + ordinalParameterDescriptors = Collections.emptyMap(); + } + else { + final Map tmp = new HashMap<>(); + if ( inFlightOrdinalStateMap != null ) { + for ( InFlightOrdinalParameterState state : inFlightOrdinalStateMap.values() ) { + tmp.put( state.identifier, state.complete() ); + } + } + else { + for ( InFlightJpaOrdinalParameterState state : inFlightJpaOrdinalStateMap.values() ) { + tmp.put( state.identifier, state.complete() ); + } + } + ordinalParameterDescriptors = Collections.unmodifiableMap( tmp ); + } } - /** - * Returns the list of ordinal parameter locations. The list elements - * are Integers, representing the location for that given ordinal. Thus calling - * {@code getOrdinalParameterLocationList().elementAt(n)} represents the - * location for the nth parameter. - * - * @return The list of ordinal parameter locations. - */ - public List getOrdinalParameterLocationList() { - return ordinalParameterLocationList; + private ParameterRecognitionException mixedParamStrategy() { + throw new ParameterRecognitionException( "Mixed parameter strategies - use just one of named, positional or JPA-ordinal strategy" ); } + public Map getNamedParameterDescriptionMap() { + return namedParameterDescriptors; + } + + public Map getOrdinalParameterDescriptionMap() { + return ordinalParameterDescriptors; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Recognition code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + // NOTE : we keep track of `inFlightOrdinalStateMap` versus `inFlightJpaOrdinalStateMap` + // in order to perform better validations of mixed parameter strategies + @Override public void ordinalParameter(int position) { - ordinalParameterLocationList.add( position ); + if ( inFlightOrdinalStateMap == null ) { + inFlightOrdinalStateMap = new HashMap<>(); + } + + final int label = jdbcStyleOrdinalCount++; + inFlightOrdinalStateMap.put( + label, + new InFlightOrdinalParameterState( label, label - jdbcStyleOrdinalCountBase, position ) + ); } + @Override public void namedParameter(String name, int position) { - getOrBuildNamedParameterDescription( name, false ).add( position ); + getOrBuildNamedParameterDescription( name ).add( position ); + } + + private InFlightNamedParameterState getOrBuildNamedParameterDescription(String name) { + if ( inFlightNamedStateMap == null ) { + inFlightNamedStateMap = new HashMap<>(); + } + + InFlightNamedParameterState descriptor = inFlightNamedStateMap.get( name ); + if ( descriptor == null ) { + descriptor = new InFlightNamedParameterState( name ); + inFlightNamedStateMap.put( name, descriptor ); + } + return descriptor; } + @Override - public void jpaPositionalParameter(String name, int position) { - getOrBuildNamedParameterDescription( name, true ).add( position ); + public void jpaPositionalParameter(int name, int position) { + getOrBuildJpaOrdinalParameterDescription( name ).add( position ); } - private NamedParameterDescription getOrBuildNamedParameterDescription(String name, boolean jpa) { - NamedParameterDescription desc = namedParameterDescriptions.get( name ); - if ( desc == null ) { - desc = new NamedParameterDescription( jpa ); - namedParameterDescriptions.put( name, desc ); + private InFlightJpaOrdinalParameterState getOrBuildJpaOrdinalParameterDescription(int name) { + if ( inFlightJpaOrdinalStateMap == null ) { + inFlightJpaOrdinalStateMap = new HashMap<>(); } - return desc; + + InFlightJpaOrdinalParameterState descriptor = inFlightJpaOrdinalStateMap.get( name ); + if ( descriptor == null ) { + descriptor = new InFlightJpaOrdinalParameterState( name ); + inFlightJpaOrdinalStateMap.put( name, descriptor ); + } + return descriptor; } + @Override public void other(char character) { // don't care... } + @Override public void outParameter(int position) { // don't care... } + + + /** + * Internal in-flight representation of a recognized named parameter + */ + public static class InFlightNamedParameterState { + private final String name; + private final List sourcePositions = new ArrayList<>(); + + InFlightNamedParameterState(String name) { + this.name = name; + } + + private void add(int position) { + sourcePositions.add( position ); + } + + private NamedParameterDescriptor complete() { + return new NamedParameterDescriptor( + name, + null, + ArrayHelper.toIntArray( sourcePositions ) + ); + } + } + + + /** + * Internal in-flight representation of a recognized named parameter + */ + public static class InFlightOrdinalParameterState { + private final int identifier; + private final int valuePosition; + private final int sourcePosition; + + InFlightOrdinalParameterState(int label, int valuePosition, int sourcePosition) { + this.identifier = label; + this.valuePosition = valuePosition; + this.sourcePosition = sourcePosition; + } + + private OrdinalParameterDescriptor complete() { + return new OrdinalParameterDescriptor( + identifier, + valuePosition, + null, + new int[] { sourcePosition } + ); + } + } + + + /** + * Internal in-flight representation of a recognized named parameter + */ + public static class InFlightJpaOrdinalParameterState { + private final int identifier; + private final List sourcePositions = new ArrayList<>(); + + InFlightJpaOrdinalParameterState(int identifier) { + this.identifier = identifier; + } + + private void add(int position) { + sourcePositions.add( position ); + } + + private OrdinalParameterDescriptor complete() { + return new OrdinalParameterDescriptor( + identifier, + identifier - 1, + null, + ArrayHelper.toIntArray( sourcePositions ) + ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParameterParser.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParameterParser.java index 9c801832e3cd..a8d41766eb20 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParameterParser.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/ParameterParser.java @@ -22,20 +22,20 @@ public class ParameterParser { /** * Maybe better named a Journaler. Essentially provides a callback contract for things that recognize parameters */ - public static interface Recognizer { + public interface Recognizer { /** * Called when an output parameter is recognized * * @param position The position within the query */ - public void outParameter(int position); + void outParameter(int position); /** * Called when an ordinal parameter is recognized * * @param position The position within the query */ - public void ordinalParameter(int position); + void ordinalParameter(int position); /** * Called when a named parameter is recognized @@ -43,22 +43,24 @@ public static interface Recognizer { * @param name The recognized parameter name * @param position The position within the query */ - public void namedParameter(String name, int position); + void namedParameter(String name, int position); /** * Called when a JPA-style named parameter is recognized * - * @param name The name of the JPA-style parameter + * @param identifier The identifier (name) of the JPA-style parameter * @param position The position within the query */ - public void jpaPositionalParameter(String name, int position); + void jpaPositionalParameter(int identifier, int position); /** * Called when a character that is not a parameter (or part of a parameter dfinition) is recognized. * * @param character The recognized character */ - public void other(char character); + void other(char character); + + void complete(); } /** @@ -171,7 +173,7 @@ else if ( c == ':' ) { final String param = sqlString.substring( indx + 1, chopLocation ); if ( StringHelper.isEmpty( param ) ) { throw new QueryException( - "Space is not allowed afterQuery parameter prefix ':' [" + sqlString + "]" + "Space is not allowed after parameter prefix ':' [" + sqlString + "]" ); } recognizer.namedParameter( param, indx ); @@ -180,19 +182,18 @@ else if ( c == ':' ) { else if ( c == '?' ) { // could be either an ordinal or JPA-positional parameter if ( indx < stringLength - 1 && Character.isDigit( sqlString.charAt( indx + 1 ) ) ) { - // a peek ahead showed this as an JPA-positional parameter + // a peek ahead showed this as a JPA-positional parameter final int right = StringHelper.firstIndexOfChar( sqlString, ParserHelper.HQL_SEPARATORS, indx + 1 ); final int chopLocation = right < 0 ? sqlString.length() : right; final String param = sqlString.substring( indx + 1, chopLocation ); // make sure this "name" is an integral try { - Integer.valueOf( param ); + recognizer.jpaPositionalParameter( Integer.valueOf( param ), indx ); + indx = chopLocation - 1; } catch( NumberFormatException e ) { throw new QueryException( "JPA-style positional param was not an integral ordinal" ); } - recognizer.jpaPositionalParameter( param, indx ); - indx = chopLocation - 1; } else { if ( hasMainOutputParameter && !foundMainOutputParam ) { @@ -209,6 +210,8 @@ else if ( c == '?' ) { } } } + + recognizer.complete(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java index 1ab7b081fbe2..3abec770b27a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java @@ -67,7 +67,7 @@ public class QueryPlanCache implements Serializable { private final BoundedConcurrentHashMap parameterMetadataCache; - private NativeQueryInterpreter nativeQueryInterpreterService; + private NativeQueryInterpreter nativeQueryInterpreter; /** * Constructs the QueryPlanCache to be used by the given SessionFactory @@ -108,7 +108,7 @@ public QueryPlanCache(final SessionFactoryImplementor factory) { BoundedConcurrentHashMap.Eviction.LIRS ); - nativeQueryInterpreterService = factory.getServiceRegistry().getService( NativeQueryInterpreter.class ); + nativeQueryInterpreter = factory.getServiceRegistry().getService( NativeQueryInterpreter.class ); } /** @@ -125,7 +125,7 @@ public ParameterMetadata getSQLParameterMetadata(final String query, boolean isO final ParameterMetadataKey key = new ParameterMetadataKey( query, isOrdinalParameterZeroBased ); ParameterMetadataImpl value = parameterMetadataCache.get( key ); if ( value == null ) { - value = nativeQueryInterpreterService.getParameterMetadata( query ); + value = nativeQueryInterpreter.getParameterMetadata( query ); parameterMetadataCache.putIfAbsent( key, value ); } return value; @@ -210,7 +210,7 @@ public NativeSQLQueryPlan getNativeSQLQueryPlan(final NativeSQLQuerySpecificatio NativeSQLQueryPlan value = (NativeSQLQueryPlan) queryPlanCache.get( spec ); if ( value == null ) { LOG.tracev( "Unable to locate native-sql query plan in cache; generating ({0})", spec.getQueryString() ); - value = nativeQueryInterpreterService.createQueryPlan( spec, factory ); + value = nativeQueryInterpreter.createQueryPlan( spec, factory ); queryPlanCache.putIfAbsent( spec, value ); } else { @@ -220,7 +220,13 @@ public NativeSQLQueryPlan getNativeSQLQueryPlan(final NativeSQLQuerySpecificatio } /** - * clean up QueryPlanCache when SessionFactory is closed + * Clean up the caches when the SessionFactory is closed. + *

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

    + * Typically, when using LIRS, clearing the cache only invalidates the entries but the outdated entries are kept in + * memory until they are replaced by others. It is not considered a memory leak as the cache is bounded. */ public void cleanup() { LOG.trace( "Cleaning QueryPlan Cache" ); @@ -228,6 +234,10 @@ public void cleanup() { parameterMetadataCache.clear(); } + public NativeQueryInterpreter getNativeQueryInterpreter() { + return nativeQueryInterpreter; + } + private static class ParameterMetadataKey implements Serializable { private final String query; private final boolean isOrdinalParameterZeroBased; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilder.java index 7da44e0617be..ca8eb4f2ba99 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilder.java @@ -10,10 +10,12 @@ import java.util.TimeZone; import org.hibernate.ConnectionReleaseMode; +import org.hibernate.FlushMode; import org.hibernate.Interceptor; import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionEventListener; +import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; /** @@ -21,8 +23,9 @@ * while forwarding other method invocations to a delegate instance. * * @author Gunnar Morling + * @author Guillaume Smet */ -public abstract class AbstractDelegatingSessionBuilder implements SessionBuilder { +public abstract class AbstractDelegatingSessionBuilder implements SessionBuilder { private final SessionBuilder delegate; @@ -30,80 +33,115 @@ public AbstractDelegatingSessionBuilder(SessionBuilder delegate) { this.delegate = delegate; } + @SuppressWarnings("unchecked") + protected T getThis() { + return (T) this; + } + + protected SessionBuilder delegate() { + return delegate; + } + @Override public Session openSession() { return delegate.openSession(); } @Override - public SessionBuilder interceptor(Interceptor interceptor) { + public T interceptor(Interceptor interceptor) { delegate.interceptor( interceptor ); - return this; + return getThis(); } @Override - public SessionBuilder noInterceptor() { + public T noInterceptor() { delegate.noInterceptor(); - return this; + return getThis(); } @Override - public SessionBuilder statementInspector(StatementInspector statementInspector) { + public T statementInspector(StatementInspector statementInspector) { delegate.statementInspector( statementInspector ); - return this; + return getThis(); } @Override - public SessionBuilder connection(Connection connection) { + public T connection(Connection connection) { delegate.connection( connection ); - return this; + return getThis(); } + @SuppressWarnings("deprecation") @Override - public SessionBuilder connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) { + public T connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) { delegate.connectionReleaseMode( connectionReleaseMode ); - return this; + return getThis(); } @Override - public SessionBuilder autoJoinTransactions(boolean autoJoinTransactions) { + public T autoJoinTransactions(boolean autoJoinTransactions) { delegate.autoJoinTransactions( autoJoinTransactions ); - return this; + return getThis(); } @Override - public SessionBuilder autoClose(boolean autoClose) { + public T autoClose(boolean autoClose) { delegate.autoClose( autoClose ); - return this; + return getThis(); } + @SuppressWarnings("deprecation") @Override - public SessionBuilder flushBeforeCompletion(boolean flushBeforeCompletion) { + public T flushBeforeCompletion(boolean flushBeforeCompletion) { delegate.flushBeforeCompletion( flushBeforeCompletion ); - return this; + return getThis(); } @Override - public SessionBuilder tenantIdentifier(String tenantIdentifier) { + public T tenantIdentifier(String tenantIdentifier) { delegate.tenantIdentifier( tenantIdentifier ); - return this; + return getThis(); } @Override - public SessionBuilder eventListeners(SessionEventListener... listeners) { + public T eventListeners(SessionEventListener... listeners) { delegate.eventListeners( listeners ); - return this; + return getThis(); } @Override - public SessionBuilder clearEventListeners() { + public T clearEventListeners() { delegate.clearEventListeners(); - return this; + return getThis(); } @Override - public SessionBuilder jdbcTimeZone(TimeZone timeZone) { + public T jdbcTimeZone(TimeZone timeZone) { delegate.jdbcTimeZone(timeZone); - return this; + return getThis(); + } + + @Override + public T setQueryParameterValidation(boolean enabled) { + delegate.setQueryParameterValidation( enabled ); + return getThis(); + } + + @Override + public T connectionHandlingMode(PhysicalConnectionHandlingMode mode) { + delegate.connectionHandlingMode( mode ); + return getThis(); + } + + @Override + public T autoClear(boolean autoClear) { + delegate.autoClear( autoClear ); + return getThis(); + } + + @Override + public T flushMode(FlushMode flushMode) { + delegate.flushMode( flushMode ); + return getThis(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilderImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilderImplementor.java index 4b2fba99606d..689d7ee166ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilderImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSessionBuilderImplementor.java @@ -6,29 +6,28 @@ */ package org.hibernate.engine.spi; -import org.hibernate.SessionBuilder; - /** * Base class for {@link SessionBuilderImplementor} implementations that wish to implement only parts of that contract * themselves while forwarding other method invocations to a delegate instance. * * @author Gunnar Morling */ -@SuppressWarnings("unused") -public abstract class AbstractDelegatingSessionBuilderImplementor - extends AbstractDelegatingSessionBuilder - implements SessionBuilderImplementor { - - private final SessionBuilderImplementor delegate; +public abstract class AbstractDelegatingSessionBuilderImplementor + extends AbstractDelegatingSessionBuilder + implements SessionBuilderImplementor { public AbstractDelegatingSessionBuilderImplementor(SessionBuilderImplementor delegate) { super( delegate ); - this.delegate = delegate; } + protected SessionBuilderImplementor delegate() { + return (SessionBuilderImplementor) super.delegate(); + } + + @SuppressWarnings({ "unchecked", "deprecation" }) @Override - public SessionBuilder owner(SessionOwner sessionOwner) { - delegate.owner( sessionOwner ); - return this; + public T owner(SessionOwner sessionOwner) { + delegate().owner( sessionOwner ); + return (T) this; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSharedSessionBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSharedSessionBuilder.java index 60b9320c29c3..e39b0632e761 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSharedSessionBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/AbstractDelegatingSharedSessionBuilder.java @@ -7,13 +7,16 @@ package org.hibernate.engine.spi; import java.sql.Connection; +import java.util.TimeZone; import org.hibernate.ConnectionReleaseMode; +import org.hibernate.FlushMode; import org.hibernate.Interceptor; import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionEventListener; import org.hibernate.SharedSessionBuilder; +import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; /** @@ -21,9 +24,10 @@ * themselves while forwarding other method invocations to a delegate instance. * * @author Gunnar Morling + * @author Guillaume Smet */ @SuppressWarnings("unused") -public abstract class AbstractDelegatingSharedSessionBuilder implements SharedSessionBuilder { +public abstract class AbstractDelegatingSharedSessionBuilder implements SharedSessionBuilder { private final SharedSessionBuilder delegate; @@ -31,116 +35,159 @@ public AbstractDelegatingSharedSessionBuilder(SharedSessionBuilder delegate) { this.delegate = delegate; } + @SuppressWarnings("unchecked") + protected T getThis() { + return (T) this; + } + + public SharedSessionBuilder delegate() { + return delegate; + } + @Override public Session openSession() { return delegate.openSession(); } @Override - public SharedSessionBuilder interceptor() { + public T interceptor() { delegate.interceptor(); - return this; + return getThis(); } @Override - public SharedSessionBuilder connection() { + public T connection() { delegate.connection(); - return this; + return getThis(); } + @SuppressWarnings("deprecation") @Override - public SharedSessionBuilder connectionReleaseMode() { + public T connectionReleaseMode() { delegate.connectionReleaseMode(); - return this; + return getThis(); } @Override - public SharedSessionBuilder connectionHandlingMode() { + public T connectionHandlingMode() { delegate.connectionHandlingMode(); - return this; + return getThis(); } @Override - public SharedSessionBuilder autoJoinTransactions() { + public T autoJoinTransactions() { delegate.autoJoinTransactions(); - return this; + return getThis(); } @Override - public SharedSessionBuilder autoClose() { + public T autoClose() { delegate.autoClose(); - return this; + return getThis(); } + @SuppressWarnings("deprecation") @Override - public SharedSessionBuilder flushBeforeCompletion() { + public T flushBeforeCompletion() { delegate.flushBeforeCompletion(); - return this; + return getThis(); } @Override - public SharedSessionBuilder interceptor(Interceptor interceptor) { + public T interceptor(Interceptor interceptor) { delegate.interceptor( interceptor ); - return this; + return getThis(); } @Override - public SharedSessionBuilder noInterceptor() { + public T noInterceptor() { delegate.noInterceptor(); - return this; + return getThis(); } @Override - public SessionBuilder statementInspector(StatementInspector statementInspector) { + public T statementInspector(StatementInspector statementInspector) { delegate.statementInspector( statementInspector ); - return this; + return getThis(); } @Override - public SharedSessionBuilder connection(Connection connection) { + public T connection(Connection connection) { delegate.connection( connection ); - return this; + return getThis(); } + @SuppressWarnings("deprecation") @Override - public SharedSessionBuilder connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) { + public T connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) { delegate.connectionReleaseMode( connectionReleaseMode ); - return this; + return getThis(); } @Override - public SharedSessionBuilder autoJoinTransactions(boolean autoJoinTransactions) { + public T autoJoinTransactions(boolean autoJoinTransactions) { delegate.autoJoinTransactions( autoJoinTransactions ); - return this; + return getThis(); } @Override - public SharedSessionBuilder autoClose(boolean autoClose) { + public T autoClose(boolean autoClose) { delegate.autoClose( autoClose ); - return this; + return getThis(); } + @SuppressWarnings("deprecation") @Override - public SharedSessionBuilder flushBeforeCompletion(boolean flushBeforeCompletion) { + public T flushBeforeCompletion(boolean flushBeforeCompletion) { delegate.flushBeforeCompletion( flushBeforeCompletion ); - return this; + return getThis(); } @Override - public SessionBuilder tenantIdentifier(String tenantIdentifier) { + public T tenantIdentifier(String tenantIdentifier) { delegate.tenantIdentifier( tenantIdentifier ); - return this; + return getThis(); } @Override - public SessionBuilder eventListeners(SessionEventListener... listeners) { + public T eventListeners(SessionEventListener... listeners) { delegate.eventListeners( listeners ); - return this; + return getThis(); } @Override - public SessionBuilder clearEventListeners() { + public T clearEventListeners() { delegate.clearEventListeners(); - return this; + return getThis(); + } + + @Override + public T connectionHandlingMode(PhysicalConnectionHandlingMode mode) { + delegate.connectionHandlingMode( mode ); + return getThis(); + } + + @Override + public T autoClear(boolean autoClear) { + delegate.autoClear( autoClear ); + return getThis(); + } + + @Override + public T flushMode(FlushMode flushMode) { + delegate.flushMode( flushMode ); + return getThis(); + } + + @Override + public T flushMode() { + delegate.flushMode(); + return getThis(); + } + + @Override + public T jdbcTimeZone(TimeZone timeZone) { + delegate.jdbcTimeZone( timeZone ); + return getThis(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java index 4c058625817d..253cbdce79b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java @@ -30,6 +30,7 @@ import org.hibernate.action.internal.CollectionRecreateAction; import org.hibernate.action.internal.CollectionRemoveAction; import org.hibernate.action.internal.CollectionUpdateAction; +import org.hibernate.action.internal.EntityActionVetoException; import org.hibernate.action.internal.EntityDeleteAction; import org.hibernate.action.internal.EntityIdentityInsertAction; import org.hibernate.action.internal.EntityInsertAction; @@ -44,10 +45,14 @@ import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.metadata.ClassMetadata; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.type.CollectionType; +import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; +import org.hibernate.type.ForeignKeyDirection; +import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; /** @@ -55,7 +60,7 @@ * * The ActionQueue holds the DML operations queued as part of a session's transactional-write-behind semantics. The * DML operations are queued here until a flush forces them to be executed against the database. - * + * * @author Steve Ebersole * @author Gail Badner * @author Anton Marsden @@ -85,7 +90,7 @@ public class ActionQueue { private ExecutableList collectionUpdates; private ExecutableList collectionQueuedOps; private ExecutableList collectionRemovals; - + // TODO: The removeOrphan concept is a temporary "hack" for HHH-6484. This should be removed once action/task // ordering is improved. private ExecutableList orphanRemovals; @@ -96,7 +101,7 @@ public class ActionQueue { private BeforeTransactionCompletionProcessQueue beforeTransactionProcesses; /** - * An LinkedHashMap containing providers for all the ExecutableLists, inserted in execution order + * A LinkedHashMap containing providers for all the ExecutableLists, inserted in execution order */ private static final LinkedHashMap,ListProvider> EXECUTABLE_LISTS_MAP; static { @@ -215,7 +220,7 @@ ExecutableList init(ActionQueue instance) { /** * Constructs an action queue bound to the given session. - * + * * @param session The session "owning" this queue. */ public ActionQueue(SessionImplementor session) { @@ -247,9 +252,9 @@ public void addAction(EntityInsertAction action) { private void addInsertAction(AbstractEntityInsertAction insert) { if ( insert.isEarlyInsert() ) { - // For early inserts, must execute inserts beforeQuery finding non-nullable transient entities. + // For early inserts, must execute inserts before finding non-nullable transient entities. // TODO: find out why this is necessary - LOG.tracev( "Executing inserts beforeQuery finding non-nullable transient entities for early insert: [{0}]", insert ); + LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert ); executeInserts(); } NonNullableTransientDependencies nonNullableTransientDependencies = insert.findNonNullableTransientEntities(); @@ -271,7 +276,7 @@ private void addInsertAction(AbstractEntityInsertAction insert) { private void addResolvedEntityInsertAction(AbstractEntityInsertAction insert) { if ( insert.isEarlyInsert() ) { - LOG.trace( "Executing insertions beforeQuery resolved early-insert" ); + LOG.trace( "Executing insertions before resolved early-insert" ); executeInserts(); LOG.debug( "Executing identity-insert immediately" ); execute( insert ); @@ -280,12 +285,21 @@ private void addResolvedEntityInsertAction(AbstractEntityInsertAction insert) { LOG.trace( "Adding resolved non-early insert action." ); addAction( AbstractEntityInsertAction.class, insert ); } - insert.makeEntityManaged(); - if( unresolvedInsertions != null ) { - for (AbstractEntityInsertAction resolvedAction : unresolvedInsertions.resolveDependentActions(insert.getInstance(), session)) { - addResolvedEntityInsertAction(resolvedAction); + if ( !insert.isVeto() ) { + insert.makeEntityManaged(); + + if( unresolvedInsertions != null ) { + for ( AbstractEntityInsertAction resolvedAction : unresolvedInsertions.resolveDependentActions( insert.getInstance(), session ) ) { + addResolvedEntityInsertAction( resolvedAction ); + } } } + else { + throw new EntityActionVetoException( + "The EntityInsertAction was vetoed.", + insert + ); + } } @SuppressWarnings("unchecked") @@ -380,22 +394,26 @@ private void registerCleanupActions(Executable executable) { if( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } - beforeTransactionProcesses.register(executable.getBeforeTransactionCompletionProcess()); + beforeTransactionProcesses.register( executable.getBeforeTransactionCompletionProcess() ); } if ( session.getFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { - invalidateSpaces( executable.getPropertySpaces() ); + invalidateSpaces( convertTimestampSpaces( executable.getPropertySpaces() ) ); } if( executable.getAfterTransactionCompletionProcess() != null ) { if( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } - afterTransactionProcesses.register(executable.getAfterTransactionCompletionProcess()); + afterTransactionProcesses.register( executable.getAfterTransactionCompletionProcess() ); } } + private static String[] convertTimestampSpaces(Serializable[] spaces) { + return (String[]) spaces; + } + /** * Are there unresolved entity insert actions that depend on non-nullable associations with a transient entity? - * + * * @return true, if there are unresolved entity insert actions that depend on non-nullable associations with a * transient entity; false, otherwise */ @@ -406,8 +424,8 @@ public boolean hasUnresolvedEntityInsertActions() { /** * Throws {@link org.hibernate.PropertyValueException} if there are any unresolved entity insert actions that depend * on non-nullable associations with a transient entity. This method should be called on completion of an operation - * (afterQuery all cascades are completed) that saves an entity. - * + * (after all cascades are completed) that saves an entity. + * * @throws org.hibernate.PropertyValueException if there are any unresolved entity insert actions; * {@link org.hibernate.PropertyValueException#getEntityName()} and * {@link org.hibernate.PropertyValueException#getPropertyName()} will return the entity name and property value for @@ -435,7 +453,7 @@ public void registerProcess(BeforeTransactionCompletionProcess process) { /** * Perform all currently queued entity-insertion actions. - * + * * @throws HibernateException error executing queued insertion actions. */ public void executeInserts() throws HibernateException { @@ -446,7 +464,7 @@ public void executeInserts() throws HibernateException { /** * Perform all currently queued actions. - * + * * @throws HibernateException error executing queued actions. */ public void executeActions() throws HibernateException { @@ -464,7 +482,7 @@ public void executeActions() throws HibernateException { /** * Prepares the internal action queues for execution. - * + * * @throws HibernateException error preparing actions. */ public void prepareActions() throws HibernateException { @@ -485,14 +503,14 @@ private void prepareActions(ExecutableList queue) throws HibernateException { /** * Performs cleanup of any held cache softlocks. - * + * * @param success Was the transaction successful. */ public void afterTransactionCompletion(boolean success) { if ( !isTransactionCoordinatorShared ) { // Execute completion actions only in transaction owner (aka parent session). if( afterTransactionProcesses != null ) { - afterTransactionProcesses.afterTransactionCompletion(success); + afterTransactionProcesses.afterTransactionCompletion( success ); } } } @@ -520,7 +538,7 @@ public boolean areInsertionsOrDeletionsQueued() { /** * Check whether the given tables/query-spaces are to be executed against given the currently queued actions. - * + * * @param tables The table/query-spaces to check. * * @return {@code true} if we contain pending actions against any of the given tables; {@code false} otherwise. @@ -571,7 +589,7 @@ private static boolean areTablesToBeUpdated(UnresolvedEntityInsertActions action /** * Perform {@link org.hibernate.action.spi.Executable#execute()} on each element of the list - * + * * @param list The list of Executable elements to be performed * * @throws HibernateException @@ -590,13 +608,13 @@ private & Serializable> void executeAction if( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } - beforeTransactionProcesses.register(e.getBeforeTransactionCompletionProcess()); + beforeTransactionProcesses.register( e.getBeforeTransactionCompletionProcess() ); } if( e.getAfterTransactionCompletionProcess() != null ) { if( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } - afterTransactionProcesses.register(e.getAfterTransactionCompletionProcess()); + afterTransactionProcesses.register( e.getAfterTransactionCompletionProcess() ); } } } @@ -604,10 +622,10 @@ private & Serializable> void executeAction finally { if ( session.getFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { // Strictly speaking, only a subset of the list may have been processed if a RuntimeException occurs. - // We still invalidate all spaces. I don't see this as a big deal - afterQuery all, RuntimeExceptions are + // We still invalidate all spaces. I don't see this as a big deal - after all, RuntimeExceptions are // unexpected. - Set propertySpaces = list.getQuerySpaces(); - invalidateSpaces( propertySpaces.toArray( new Serializable[propertySpaces.size()] ) ); + Set propertySpaces = list.getQuerySpaces(); + invalidateSpaces( convertTimestampSpaces( propertySpaces ) ); } } @@ -615,6 +633,10 @@ private & Serializable> void executeAction session.getJdbcCoordinator().executeBatch(); } + private static String[] convertTimestampSpaces(Set spaces) { + return (String[]) spaces.toArray( new String[ spaces.size() ] ); + } + /** * @param executable The action to execute */ @@ -629,10 +651,10 @@ public > void execute(E executable) { /** * This method is now called once per execution of an ExecutableList or once for execution of an Execution. - * + * * @param spaces The spaces to invalidate */ - private void invalidateSpaces(Serializable... spaces) { + private void invalidateSpaces(String... spaces) { if ( spaces != null && spaces.length > 0 ) { for ( Serializable s : spaces ) { if( afterTransactionProcesses == null ) { @@ -641,25 +663,25 @@ private void invalidateSpaces(Serializable... spaces) { afterTransactionProcesses.addSpaceToInvalidate( (String) s ); } // Performance win: If we are processing an ExecutableList, this will only be called once - session.getFactory().getUpdateTimestampsCache().preInvalidate( spaces, session ); + session.getFactory().getCache().getTimestampsCache().preInvalidate( spaces, session ); } } /** * Returns a string representation of the object. - * + * * @return a string representation of the object. */ @Override public String toString() { - return "ActionQueue[insertions=" + toString(insertions) - + " updates=" + toString(updates) - + " deletions=" + toString(deletions) - + " orphanRemovals=" + toString(orphanRemovals) - + " collectionCreations=" + toString(collectionCreations) - + " collectionRemovals=" + toString(collectionRemovals) - + " collectionUpdates=" + toString(collectionUpdates) - + " collectionQueuedOps=" + toString(collectionQueuedOps) + return "ActionQueue[insertions=" + toString( insertions ) + + " updates=" + toString( updates ) + + " deletions=" + toString( deletions ) + + " orphanRemovals=" + toString( orphanRemovals ) + + " collectionCreations=" + toString( collectionCreations ) + + " collectionRemovals=" + toString( collectionRemovals ) + + " collectionUpdates=" + toString( collectionUpdates ) + + " collectionQueuedOps=" + toString( collectionQueuedOps ) + " unresolvedInsertDependencies=" + unresolvedInsertions + "]"; } @@ -808,25 +830,25 @@ public boolean hasAnyQueuedActions() { public void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) { if ( rescuedEntity instanceof HibernateProxy ) { - LazyInitializer initializer = ( ( HibernateProxy ) rescuedEntity ).getHibernateLazyInitializer(); + LazyInitializer initializer = ( (HibernateProxy) rescuedEntity ).getHibernateLazyInitializer(); if ( !initializer.isUninitialized() ) { rescuedEntity = initializer.getImplementation( session ); } } if( deletions != null ) { for ( int i = 0; i < deletions.size(); i++ ) { - EntityDeleteAction action = deletions.get(i); + EntityDeleteAction action = deletions.get( i ); if (action.getInstance() == rescuedEntity) { - deletions.remove(i); + deletions.remove( i ); return; } } } if( orphanRemovals != null ) { for ( int i = 0; i < orphanRemovals.size(); i++ ) { - EntityDeleteAction action = orphanRemovals.get(i); + EntityDeleteAction action = orphanRemovals.get( i ); if (action.getInstance() == rescuedEntity) { - orphanRemovals.remove(i); + orphanRemovals.remove( i ); return; } } @@ -836,7 +858,7 @@ public void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) { /** * Used by the owning session to explicitly control serialization of the action queue - * + * * @param oos The stream to which the action queue should get written * @throws IOException Indicates an error writing to the stream */ @@ -848,9 +870,9 @@ public void serialize(ObjectOutputStream oos) throws IOException { unresolvedInsertions.serialize( oos ); for ( ListProvider p : EXECUTABLE_LISTS_MAP.values() ) { - ExecutableList l = p.get(this); + ExecutableList l = p.get( this ); if( l == null ) { - oos.writeBoolean(false); + oos.writeBoolean( false ); } else { oos.writeBoolean( true ); @@ -861,7 +883,7 @@ public void serialize(ObjectOutputStream oos) throws IOException { /** * Used by the owning session to explicitly control deserialization of the action queue. - * + * * @param ois The stream from which to read the action queue * @param session The session to which the action queue belongs * @return The deserialized action queue @@ -871,18 +893,18 @@ public void serialize(ObjectOutputStream oos) throws IOException { public static ActionQueue deserialize(ObjectInputStream ois, SessionImplementor session) throws IOException, ClassNotFoundException { final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { - LOG.trace("Deserializing action-queue"); + LOG.trace( "Deserializing action-queue" ); } ActionQueue rtn = new ActionQueue( session ); rtn.unresolvedInsertions = UnresolvedEntityInsertActions.deserialize( ois, session ); for ( ListProvider provider : EXECUTABLE_LISTS_MAP.values() ) { - ExecutableList l = provider.get(rtn); + ExecutableList l = provider.get( rtn ); boolean notNull = ois.readBoolean(); if( notNull ) { if(l == null) { - l = provider.init(rtn); + l = provider.init( rtn ); } l.readExternal( ois ); @@ -896,7 +918,7 @@ public static ActionQueue deserialize(ObjectInputStream ois, SessionImplementor return rtn; } - private static abstract class AbstractTransactionCompletionProcessQueue { + private abstract static class AbstractTransactionCompletionProcessQueue { protected SessionImplementor session; // Concurrency handling required when transaction completion process is dynamically registered // inside event listener (HHH-7478). @@ -919,7 +941,7 @@ public boolean hasActions() { } /** - * Encapsulates behavior needed for beforeQuery transaction processing + * Encapsulates behavior needed for before transaction processing */ private static class BeforeTransactionCompletionProcessQueue extends AbstractTransactionCompletionProcessQueue { private BeforeTransactionCompletionProcessQueue(SessionImplementor session) { @@ -942,7 +964,7 @@ public void beforeTransactionCompletion() { } /** - * Encapsulates behavior needed for afterQuery transaction processing + * Encapsulates behavior needed for after transaction processing */ private static class AfterTransactionCompletionProcessQueue extends AbstractTransactionCompletionProcessQueue { private Set querySpacesToInvalidate = new HashSet(); @@ -970,7 +992,7 @@ public void afterTransactionCompletion(boolean success) { } if ( session.getFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { - session.getFactory().getUpdateTimestampsCache().invalidate( + session.getFactory().getCache().getTimestampsCache().invalidate( querySpacesToInvalidate.toArray( new String[querySpacesToInvalidate.size()] ), session ); @@ -1003,7 +1025,7 @@ private TransactionCompletionProcesses( * Sorts the insert actions using more hashes. *

    * NOTE: this class is not thread-safe. - * + * * @author Jay Erb */ private static class InsertActionSorter implements ExecutableList.Sorter { @@ -1015,14 +1037,25 @@ private static class InsertActionSorter implements ExecutableList.Sorter parentEntityNames = new HashSet<>( ); private Set childEntityNames = new HashSet<>( ); - public BatchIdentifier( - String entityName) { + private BatchIdentifier parent; + + BatchIdentifier(String entityName, String rootEntityName) { this.entityName = entityName; + this.rootEntityName = rootEntityName; + } + + public BatchIdentifier getParent() { + return parent; + } + + public void setParent(BatchIdentifier parent) { + this.parent = parent; } @Override @@ -1042,24 +1075,62 @@ public int hashCode() { return Objects.hash( entityName ); } - public String getEntityName() { + String getEntityName() { return entityName; } - public Set getParentEntityNames() { + String getRootEntityName() { + return rootEntityName; + } + + Set getParentEntityNames() { return parentEntityNames; } - public Set getChildEntityNames() { + Set getChildEntityNames() { return childEntityNames; } + + boolean hasAnyParentEntityNames(BatchIdentifier batchIdentifier) { + return parentEntityNames.contains( batchIdentifier.getEntityName() ) || + parentEntityNames.contains( batchIdentifier.getRootEntityName() ); + } + + boolean hasAnyChildEntityNames(BatchIdentifier batchIdentifier) { + return childEntityNames.contains( batchIdentifier.getEntityName() ); + } + + /** + * Check if the this {@link BatchIdentifier} has a parent or grand parent + * matching the given {@link BatchIdentifier reference. + * + * @param batchIdentifier {@link BatchIdentifier} reference + * + * @return This {@link BatchIdentifier} has a parent matching the given {@link BatchIdentifier reference + */ + boolean hasParent(BatchIdentifier batchIdentifier) { + return ( + parent == batchIdentifier + || ( parentEntityNames.contains( batchIdentifier.getEntityName() ) ) + || parent != null && parent.hasParent( batchIdentifier, new ArrayList<>() ) + ); + } + + private boolean hasParent(BatchIdentifier batchIdentifier, List stack) { + if ( !stack.contains( this ) && parent != null ) { + stack.add( this ); + return parent.hasParent( batchIdentifier, stack ); + } + return ( + parent == batchIdentifier + || parentEntityNames.contains( batchIdentifier.getEntityName() ) + ); + } } // the mapping of entity names to their latest batch numbers. private List latestBatches; - private Map entityBatchIdentifier; - // the map of batch numbers to EntityInsertAction lists private Map> actionBatches; @@ -1072,11 +1143,17 @@ public InsertActionSorter() { public void sort(List insertions) { // optimize the hash size to eliminate a rehash. this.latestBatches = new ArrayList<>( ); - this.entityBatchIdentifier = new HashMap<>( insertions.size() + 1, 1.0f ); this.actionBatches = new HashMap<>(); for ( AbstractEntityInsertAction action : insertions ) { - BatchIdentifier batchIdentifier = new BatchIdentifier( action.getEntityName() ); + BatchIdentifier batchIdentifier = new BatchIdentifier( + action.getEntityName(), + action.getSession() + .getFactory() + .getMetamodel() + .entityPersister( action.getEntityName() ) + .getRootEntityName() + ); // the entity associated with the current action. Object currentEntity = action.getInstance(); @@ -1089,37 +1166,75 @@ public void sort(List insertions) { latestBatches.add( batchIdentifier ); } addParentChildEntityNames( action, batchIdentifier ); - entityBatchIdentifier.put( currentEntity, batchIdentifier ); - addToBatch(batchIdentifier, action); + addToBatch( batchIdentifier, action ); } insertions.clear(); + // Examine each entry in the batch list, and build the dependency graph. for ( int i = 0; i < latestBatches.size(); i++ ) { BatchIdentifier batchIdentifier = latestBatches.get( i ); - String entityName = batchIdentifier.getEntityName(); - //Make sure that child entries are not before parents for ( int j = i - 1; j >= 0; j-- ) { BatchIdentifier prevBatchIdentifier = latestBatches.get( j ); - if(prevBatchIdentifier.getParentEntityNames().contains( entityName )) { - latestBatches.remove( i ); - latestBatches.add( j, batchIdentifier ); + if ( prevBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) { + prevBatchIdentifier.parent = batchIdentifier; + } + if ( batchIdentifier.hasAnyChildEntityNames( prevBatchIdentifier ) ) { + prevBatchIdentifier.parent = batchIdentifier; } } - //Make sure that parent entries are not after children for ( int j = i + 1; j < latestBatches.size(); j++ ) { BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); - //Take care of unidirectional @OneToOne associations but exclude bidirectional @ManyToMany - if(nextBatchIdentifier.getChildEntityNames().contains( entityName ) && - !batchIdentifier.getChildEntityNames().contains( nextBatchIdentifier.getEntityName() )) { - latestBatches.remove( i ); - latestBatches.add( j, batchIdentifier ); + + if ( nextBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) { + nextBatchIdentifier.parent = batchIdentifier; + } + if ( batchIdentifier.hasAnyChildEntityNames( nextBatchIdentifier ) ) { + nextBatchIdentifier.parent = batchIdentifier; + } + } + } + + boolean sorted = false; + + long maxIterations = latestBatches.size() * latestBatches.size(); + long iterations = 0; + + sort: + do { + // Examine each entry in the batch list, sorting them based on parent/child association + // as depicted by the dependency graph. + iterations++; + + for ( int i = 0; i < latestBatches.size(); i++ ) { + BatchIdentifier batchIdentifier = latestBatches.get( i ); + + // Iterate next batches and make sure that children types are after parents. + // Since the outer loop looks at each batch entry individually and the prior loop will reorder + // entries as well, we need to look and verify if the current batch is a child of the next + // batch or if the current batch is seen as a parent or child of the next batch. + for ( int j = i + 1; j < latestBatches.size(); j++ ) { + BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); + + if ( batchIdentifier.hasParent( nextBatchIdentifier ) && !nextBatchIdentifier.hasParent( batchIdentifier ) ) { + latestBatches.remove( batchIdentifier ); + latestBatches.add( j, batchIdentifier ); + + continue sort; + } } } + sorted = true; } + while ( !sorted && iterations <= maxIterations); - // now rebuild the insertions list. There is a batch for each entry in the name list. + if ( iterations > maxIterations ) { + LOG.warn( "The batch containing " + latestBatches.size() + " statements could not be sorted after " + maxIterations + " iterations. " + + "This might indicate a circular entity relationship." ); + } + + // Now, rebuild the insertions list. There is a batch for each entry in the name list. for ( BatchIdentifier rootIdentifier : latestBatches ) { List batch = actionBatches.get( rootIdentifier ); insertions.addAll( batch ); @@ -1128,30 +1243,67 @@ public void sort(List insertions) { /** * Add parent and child entity names so that we know how to rearrange dependencies - * + * * @param action The action being sorted * @param batchIdentifier The batch identifier of the entity affected by the action */ private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier) { Object[] propertyValues = action.getState(); - Type[] propertyTypes = action.getPersister().getClassMetadata().getPropertyTypes(); - - for ( int i = 0; i < propertyValues.length; i++ ) { - Object value = propertyValues[i]; - Type type = propertyTypes[i]; - if ( type.isEntityType() && value != null ) { - EntityType entityType = (EntityType) type; - String entityName = entityType.getName(); - batchIdentifier.getParentEntityNames().add( entityName ); + ClassMetadata classMetadata = action.getPersister().getClassMetadata(); + if ( classMetadata != null ) { + Type[] propertyTypes = classMetadata.getPropertyTypes(); + + for ( int i = 0; i < propertyValues.length; i++ ) { + Object value = propertyValues[i]; + Type type = propertyTypes[i]; + addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value ); } - else if ( type.isCollectionType() && value != null ) { - CollectionType collectionType = (CollectionType) type; - final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() ) - .getSessionFactory(); - if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { - String entityName = collectionType.getAssociatedEntityName( sessionFactory ); + } + } + + private void addParentChildEntityNameByPropertyAndValue(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier, Type type, Object value) { + if ( type.isEntityType() && value != null ) { + final EntityType entityType = (EntityType) type; + final String entityName = entityType.getName(); + final String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); + + if ( entityType.isOneToOne() && OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) { + if ( !entityType.isReferenceToPrimaryKey() ) { batchIdentifier.getChildEntityNames().add( entityName ); } + if ( !rootEntityName.equals( entityName ) ) { + batchIdentifier.getChildEntityNames().add( rootEntityName ); + } + } + else { + batchIdentifier.getParentEntityNames().add( entityName ); + if ( !rootEntityName.equals( entityName ) ) { + batchIdentifier.getParentEntityNames().add( rootEntityName ); + } + } + } + else if ( type.isCollectionType() && value != null ) { + CollectionType collectionType = (CollectionType) type; + final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() ) + .getSessionFactory(); + if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { + String entityName = collectionType.getAssociatedEntityName( sessionFactory ); + String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); + batchIdentifier.getChildEntityNames().add( entityName ); + if ( !rootEntityName.equals( entityName ) ) { + batchIdentifier.getChildEntityNames().add( rootEntityName ); + } + } + } + else if ( type.isComponentType() && value != null ) { + // Support recursive checks of composite type properties for associations and collections. + CompositeType compositeType = (CompositeType) type; + final SharedSessionContractImplementor session = action.getSession(); + Object[] componentValues = compositeType.getPropertyValues( value, session ); + for ( int j = 0; j < componentValues.length; ++j ) { + Type componentValueType = compositeType.getSubtypes()[j]; + Object componentValue = componentValues[j]; + addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, componentValueType, componentValue ); } } } @@ -1168,7 +1320,7 @@ private void addToBatch(BatchIdentifier batchIdentifier, AbstractEntityInsertAct } - private static abstract class ListProvider { + private abstract static class ListProvider { abstract ExecutableList get(ActionQueue instance); abstract ExecutableList init(ActionQueue instance); ExecutableList getOrInit( ActionQueue instance ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java old mode 100755 new mode 100644 index d6ff1d0439ea..5b4e2876179d --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java @@ -14,8 +14,8 @@ import java.util.Map.Entry; import org.hibernate.EntityMode; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.CacheHelper; import org.hibernate.internal.CoreLogging; @@ -42,7 +42,7 @@ public class BatchFetchQueue { * A map of {@link SubselectFetch subselect-fetch descriptors} keyed by the * {@link EntityKey) against which the descriptor is registered. */ - private final Map subselectsByEntityKey = new HashMap<>( 8 ); + private Map subselectsByEntityKey; /** * Used to hold information about the entities that are currently eligible for batch-fetching. Ultimately @@ -51,13 +51,13 @@ public class BatchFetchQueue { * A Map structure is used to segment the keys by entity type since loading can only be done for a particular entity * type at a time. */ - private final Map > batchLoadableEntityKeys = new HashMap<>( 8 ); + private Map > batchLoadableEntityKeys; /** * Used to hold information about the collections that are currently eligible for batch-fetching. Ultimately * used by {@link #getCollectionBatch} to build collection load batches. */ - private final Map> batchLoadableCollections = new HashMap<>( 8 ); + private Map> batchLoadableCollections; /** * Constructs a queue for the given context. @@ -71,12 +71,12 @@ public BatchFetchQueue(PersistenceContext context) { /** * Clears all entries from this fetch queue. *

    - * Called afterQuery flushing or clearing the session. + * Called after flushing or clearing the session. */ public void clear() { - batchLoadableEntityKeys.clear(); - batchLoadableCollections.clear(); - subselectsByEntityKey.clear(); + batchLoadableEntityKeys = null; + batchLoadableCollections = null; + subselectsByEntityKey = null; } @@ -90,6 +90,9 @@ public void clear() { * this entity key. */ public SubselectFetch getSubselect(EntityKey key) { + if ( subselectsByEntityKey == null ) { + return null; + } return subselectsByEntityKey.get( key ); } @@ -100,17 +103,22 @@ public SubselectFetch getSubselect(EntityKey key) { * @param subquery The fetch descriptor. */ public void addSubselect(EntityKey key, SubselectFetch subquery) { + if ( subselectsByEntityKey == null ) { + subselectsByEntityKey = new HashMap<>( 12 ); + } subselectsByEntityKey.put( key, subquery ); } /** * After evicting or deleting an entity, we don't need to * know the query that was used to load it anymore (don't - * call this afterQuery loading the entity, since we might still + * call this after loading the entity, since we might still * need to load its collections) */ public void removeSubselect(EntityKey key) { - subselectsByEntityKey.remove( key ); + if ( subselectsByEntityKey != null ) { + subselectsByEntityKey.remove( key ); + } } // entity batch support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -127,12 +135,15 @@ public void removeSubselect(EntityKey key) { */ public void addBatchLoadableEntityKey(EntityKey key) { if ( key.isBatchLoadable() ) { - LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName()); - if (set == null) { - set = new LinkedHashSet<>( 8 ); - batchLoadableEntityKeys.put( key.getEntityName(), set); + if ( batchLoadableEntityKeys == null ) { + batchLoadableEntityKeys = new HashMap<>( 12 ); } - set.add(key); + final LinkedHashSet keysForEntity = batchLoadableEntityKeys.computeIfAbsent( + key.getEntityName(), + k -> new LinkedHashSet<>( 8 ) + ); + + keysForEntity.add( key ); } } @@ -143,9 +154,9 @@ public void addBatchLoadableEntityKey(EntityKey key) { * if necessary */ public void removeBatchLoadableEntityKey(EntityKey key) { - if ( key.isBatchLoadable() ) { - LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName()); - if (set != null) { + if ( batchLoadableEntityKeys != null && key.isBatchLoadable() ) { + LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName() ); + if ( set != null ) { set.remove(key); } } @@ -155,8 +166,8 @@ public void removeBatchLoadableEntityKey(EntityKey key) { * Intended for test usage. Really has no use-case in Hibernate proper. */ public boolean containsEntityKey(EntityKey key) { - if ( key.isBatchLoadable() ) { - LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName()); + if ( batchLoadableEntityKeys != null && key.isBatchLoadable() ) { + LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName() ); if ( set != null ) { return set.contains( key ); } @@ -166,7 +177,7 @@ public boolean containsEntityKey(EntityKey key) { /** * Get a batch of unloaded identifiers for this class, using a slightly - * complex algorithm that tries to grab keys registered immediately afterQuery + * complex algorithm that tries to grab keys registered immediately after * the given key. * * @param persister The persister for the entities being loaded. @@ -179,8 +190,14 @@ public Serializable[] getEntityBatch( final Serializable id, final int batchSize, final EntityMode entityMode) { - Serializable[] ids = new Serializable[batchSize]; + + final Serializable[] ids = new Serializable[batchSize]; ids[0] = id; //first element of array is reserved for the actual instance we are loading! + + if ( batchLoadableEntityKeys == null ) { + return ids; + } + int i = 1; int end = -1; boolean checkForEnd = false; @@ -191,7 +208,7 @@ public Serializable[] getEntityBatch( if ( set != null ) { for ( EntityKey key : set ) { if ( checkForEnd && i == end ) { - //the first id found afterQuery the given id + //the first id found after the given id return ids; } if ( persister.getIdentifierType().isEqual( id, key.getIdentifier() ) ) { @@ -215,8 +232,8 @@ public Serializable[] getEntityBatch( private boolean isCached(EntityKey entityKey, EntityPersister persister) { final SharedSessionContractImplementor session = context.getSession(); - if ( context.getSession().getCacheMode().isGetEnabled() && persister.hasCache() ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + if ( context.getSession().getCacheMode().isGetEnabled() && persister.canReadFromCache() ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); final Object key = cache.generateCacheKey( entityKey.getIdentifier(), persister, @@ -232,26 +249,33 @@ private boolean isCached(EntityKey entityKey, EntityPersister persister) { // collection batch support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** - * If an CollectionEntry represents a batch loadable collection, add + * If a CollectionEntry represents a batch loadable collection, add * it to the queue. */ public void addBatchLoadableCollection(PersistentCollection collection, CollectionEntry ce) { final CollectionPersister persister = ce.getLoadedPersister(); - LinkedHashMap map = batchLoadableCollections.get( persister.getRole() ); - if ( map == null ) { - map = new LinkedHashMap<>( 16 ); - batchLoadableCollections.put( persister.getRole(), map ); + if ( batchLoadableCollections == null ) { + batchLoadableCollections = new HashMap<>( 12 ); } + + final LinkedHashMap map = batchLoadableCollections.computeIfAbsent( + persister.getRole(), + k -> new LinkedHashMap<>( 16 ) + ); + map.put( ce, collection ); } - + /** * After a collection was initialized or evicted, we don't * need to batch fetch it anymore, remove it from the queue * if necessary */ public void removeBatchLoadableCollection(CollectionEntry ce) { + if ( batchLoadableCollections == null ) { + return; + } LinkedHashMap map = batchLoadableCollections.get( ce.getLoadedPersister().getRole() ); if ( map != null ) { map.remove( ce ); @@ -271,9 +295,13 @@ public Serializable[] getCollectionBatch( final Serializable id, final int batchSize) { - Serializable[] keys = new Serializable[batchSize]; + final Serializable[] keys = new Serializable[batchSize]; keys[0] = id; + if ( batchLoadableCollections == null ) { + return keys; + } + int i = 1; int end = -1; boolean checkForEnd = false; @@ -300,7 +328,7 @@ public Serializable[] getCollectionBatch( } if ( checkForEnd && i == end ) { - return keys; //the first key found afterQuery the given key + return keys; //the first key found after the given key } final boolean isEqual = collectionPersister.getKeyType().isEqual( @@ -332,7 +360,7 @@ else if ( !isCached( ce.getLoadedKey(), collectionPersister ) ) { private boolean isCached(Serializable collectionKey, CollectionPersister persister) { SharedSessionContractImplementor session = context.getSession(); if ( session.getCacheMode().isGetEnabled() && persister.hasCache() ) { - CollectionRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + CollectionDataAccess cache = persister.getCacheAccessStrategy(); Object cacheKey = cache.generateCacheKey( collectionKey, persister, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheImplementor.java index 47eaa450c236..672c62e31640 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheImplementor.java @@ -1,128 +1,266 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.engine.spi; import java.io.Serializable; +import java.util.Locale; +import java.util.Set; import org.hibernate.Cache; import org.hibernate.HibernateException; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; import org.hibernate.cache.spi.QueryCache; +import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.cache.spi.Region; import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cache.spi.TimestampsCache; import org.hibernate.cache.spi.UpdateTimestampsCache; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; -import org.hibernate.mapping.Collection; -import org.hibernate.mapping.PersistentClass; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.service.Service; /** - * Define internal contact of Cache API + * SPI contract for Hibernate's second-level cache engine + * + * @since 4.1 * * @author Strong Liu + * @author Steve Ebersole + * + * @deprecated Moved to {@link org.hibernate.cache.spi.CacheImplementor} */ +@Deprecated +@SuppressWarnings("unused") public interface CacheImplementor extends Service, Cache, Serializable { + @Override + SessionFactoryImplementor getSessionFactory(); /** - * Close all cache regions. + * The underlying RegionFactory in use. + * + * @apiNote CacheImplementor acts partially as a wrapper for details + * of interacting with the configured RegionFactory. Care should + * be taken when accessing the RegionFactory directly. */ - void close(); + RegionFactory getRegionFactory(); /** - * Get query cache by region name or create a new one if none exist. - *

    - * If the region name is null, then default query cache region will be returned. + * An initialization phase allowing the caching provider to prime itself + * from the passed configs * - * @param regionName Query cache region name. - * @return The {@code QueryCache} associated with the region name, or default query cache if the region name is null. - * @throws HibernateException {@code HibernateException} maybe thrown when the creation of new QueryCache instance. + * @since 5.3 */ - QueryCache getQueryCache(String regionName) throws HibernateException; + void prime(Set cacheRegionConfigs); /** - * Get the default {@code QueryCache}. + * Get a cache Region by name * - * @deprecated Use {@link #getDefaultQueryCache} instead. + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed + * + * @since 5.3 */ - @Deprecated - default QueryCache getQueryCache() { - return getDefaultQueryCache(); - } + Region getRegion(String regionName); /** - * Get the default {@code QueryCache}. + * The unqualified name of all regions. Intended for use with {@link #getRegion} + * + * @since 5.3 */ - QueryCache getDefaultQueryCache(); + Set getCacheRegionNames(); /** - * Get {@code UpdateTimestampsCache} instance managed by the {@code SessionFactory}. + * Find the cache data access strategy for Hibernate's timestamps cache. + * Will return {@code null} if Hibernate is not configured for query result caching + * + * @since 5.3 */ - UpdateTimestampsCache getUpdateTimestampsCache(); + TimestampsCache getTimestampsCache(); /** - * Clean up the default {@code QueryCache}. - * - * @throws HibernateException + * Access to the "default" region used to store query results when caching + * was requested but no region was explicitly named. Will return {@code null} + * if Hibernate is not configured for query result caching */ - void evictQueries() throws HibernateException; + QueryResultsCache getDefaultQueryResultsCache(); /** - * The underlying RegionFactory in use. + * Get query cache by region name or create a new one if none exist. + * + * If the region name is null, then default query cache region will be returned. * - * @return The {@code RegionFactory} + * Will return {@code null} if Hibernate is not configured for query result caching */ - RegionFactory getRegionFactory(); + QueryResultsCache getQueryResultsCache(String regionName); /** - * Applies any defined prefix, handling all {@code null} checks. + * Get the named QueryResultRegionAccess but not creating one if it + * does not already exist. This is intended for use by statistics. * - * @param regionName The region name to qualify + * Will return {@code null} if Hibernate is not configured for query result + * caching or if no such region (yet) exists * - * @return The qualified name + * @since 5.3 + */ + QueryResultsCache getQueryResultsCacheStrictly(String regionName); + + /** + * Clean up the default query cache */ - String qualifyRegionName(String regionName); + default void evictQueries() throws HibernateException { + QueryResultsCache cache = getDefaultQueryResultsCache(); + if ( cache != null ) { + cache.clear(); + } + } /** - * Get the names of all cache regions, including entity, collection, natural-id and query caches. + * Close this "cache", releasing all underlying resources. + */ + void close(); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Deprecations (5.3) + + /** + * Get the *qualified* names of all regions caching entity and collection data. * * @return All cache region names + * + * @deprecated (since 5.3) Use {@link CacheImplementor#getCacheRegionNames()} instead */ + @Deprecated String[] getSecondLevelCacheRegionNames(); /** - * Find the "access strategy" for the named entity cache region. + * Find the cache data access strategy for an entity. Will + * return {@code null} when the entity is not configured for caching. * - * @param regionName The name of the region + * @param rootEntityName The NavigableRole representation of the root entity * - * @return That region's "access strategy" + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed + * + * @deprecated Use {@link EntityPersister#getCacheAccessStrategy()} instead */ - EntityRegionAccessStrategy getEntityRegionAccess(String regionName); + @Deprecated + EntityDataAccess getEntityRegionAccess(NavigableRole rootEntityName); /** - * Find the "access strategy" for the named collection cache region. + * Find the cache data access strategy for the given entity's natural-id cache. + * Will return {@code null} when the entity does not define a natural-id, or its + * natural-id is not configured for caching. * - * @param regionName The name of the region + * @param rootEntityName The NavigableRole representation of the root entity * - * @return That region's "access strategy" + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed + * + * @deprecated Use {@link EntityPersister#getNaturalIdCacheAccessStrategy()} ()} instead */ - CollectionRegionAccessStrategy getCollectionRegionAccess(String regionName); + @Deprecated + NaturalIdDataAccess getNaturalIdCacheRegionAccessStrategy(NavigableRole rootEntityName); /** - * Find the "access strategy" for the named natrual-id cache region. + * Find the cache data access strategy for the given collection. Will + * return {@code null} when the collection is not configured for caching. * - * @param regionName The name of the region + * @apiNote It is only valid to call this method after {@link #prime} has + * been performed * - * @return That region's "access strategy" + * @deprecated Use {@link EntityPersister#getNaturalIdCacheAccessStrategy()} ()} instead */ - NaturalIdRegionAccessStrategy getNaturalIdCacheRegionAccessStrategy(String regionName); + @Deprecated + CollectionDataAccess getCollectionRegionAccess(NavigableRole collectionRole); - EntityRegionAccessStrategy determineEntityRegionAccessStrategy(PersistentClass model); - NaturalIdRegionAccessStrategy determineNaturalIdRegionAccessStrategy(PersistentClass model); + /** + * Get {@code UpdateTimestampsCache} instance managed by the {@code SessionFactory}. + * + * @deprecated Use {@link #getTimestampsCache} instead + */ + @Deprecated + default UpdateTimestampsCache getUpdateTimestampsCache() { + return getTimestampsCache(); + } - CollectionRegionAccessStrategy determineCollectionRegionAccessStrategy(Collection model); + /** + * Get the default {@code QueryCache}. + * + * @deprecated Use {@link #getDefaultQueryResultsCache} instead. + */ + @Deprecated + default QueryCache getQueryCache() { + return getDefaultQueryResultsCache(); + } + + /** + * Get the default {@code QueryCache}. + * + * @deprecated Use {@link #getDefaultQueryResultsCache} instead. + */ + @Deprecated + default QueryCache getDefaultQueryCache() { + return getDefaultQueryResultsCache(); + } + + /** + * @deprecated Use {@link #getQueryResultsCache(String)} instead, but using unqualified name + */ + @Deprecated + default QueryCache getQueryCache(String regionName) throws HibernateException { + return getQueryResultsCache( unqualifyRegionName( regionName ) ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Some new (default) support methods for the above deprecations + // - themselves deprecated + + /** + * @deprecated (since 5.3) No replacement - added just to continue some backwards compatibility + * in supporting the newly deprecated methods expecting a qualified (prefix +) region name + */ + @Deprecated + default String unqualifyRegionName(String name) { + if ( getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix() == null ) { + return name; + } + + if ( !name.startsWith( getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix() ) ) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Legacy methods for accessing cache information expect a qualified (prefix) region name - " + + "but passed name [%s] was not qualified by the configured prefix [%s]", + name, + getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix() + ) + ); + } + + return name.substring( getSessionFactory().getSessionFactoryOptions().getCacheRegionPrefix().length() + 1 ); + } + + /** + * @deprecated No replacement - added just for support of the newly deprecated methods expecting a qualified region name + */ + @Deprecated + default Region getRegionByLegacyName(String legacyName) { + return getRegion( unqualifyRegionName( legacyName ) ); + } + + /** + * @deprecated No replacement - added just for support of the newly deprecated methods expecting a qualified region name + */ + @Deprecated + Set getNaturalIdAccessesInRegion(String legacyQualifiedRegionName); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheInitiator.java index 814ae3b4b2bd..5f9c5633656f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CacheInitiator.java @@ -7,7 +7,11 @@ package org.hibernate.engine.spi; import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.internal.CacheImpl; +import org.hibernate.cache.internal.DisabledCaching; +import org.hibernate.cache.internal.EnabledCaching; +import org.hibernate.cache.internal.NoCachingRegionFactory; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cache.spi.RegionFactory; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.SessionFactoryServiceInitiator; @@ -25,7 +29,10 @@ public CacheImplementor initiateService( SessionFactoryImplementor sessionFactory, SessionFactoryOptions sessionFactoryOptions, ServiceRegistryImplementor registry) { - return new CacheImpl( sessionFactory ); + final RegionFactory regionFactory = registry.getService( RegionFactory.class ); + return ( ! NoCachingRegionFactory.class.isInstance( regionFactory ) ) + ? new EnabledCaching( sessionFactory ) + : new DisabledCaching( sessionFactory ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java index ea3acd9c49d0..679be575aed9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java @@ -17,7 +17,8 @@ /** * @author Steve Ebersole */ -public class CascadeStyles { +public final class CascadeStyles { + private static final Logger log = Logger.getLogger( CascadeStyles.class ); /** @@ -266,7 +267,7 @@ public static void registerCascadeStyle(String name, BaseCascadeStyle cascadeSty final CascadeStyle old = STYLES.put( name, cascadeStyle ); if ( old != null ) { log.debugf( - "External cascade style regsitration [%s : %s] overrode base registration [%s]", + "External cascade style registration [%s : %s] overrode base registration [%s]", name, cascadeStyle, old diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java index d343a8fe0288..aa332bf96bf3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java @@ -294,7 +294,7 @@ public void cascade( Object anything, boolean isCascadeDeleteEnabled) throws HibernateException { - LOG.tracev( "Cascading to persist: {0}" + entityName ); + LOG.tracev( "Cascading to persist: {0}", entityName ); session.persist( entityName, child, (Map) anything ); } @@ -304,7 +304,7 @@ public Iterator getCascadableChildrenIterator( CollectionType collectionType, Object collection) { // persists don't cascade to uninitialized collections - return getAllElementsIterator( session, collectionType, collection ); + return getLoadedElementsIterator( session, collectionType, collection ); } @Override @@ -369,21 +369,20 @@ public void noCascade( int propertyIndex) { if ( propertyType.isEntityType() ) { Object child = persister.getPropertyValue( parent, propertyIndex ); - String childEntityName = ((EntityType) propertyType).getAssociatedEntityName( session.getFactory() ); - if ( child != null && !isInManagedState( child, session ) - && !(child instanceof HibernateProxy) //a proxy cannot be transient and it breaks ForeignKeys.isTransient - && ForeignKeys.isTransient( childEntityName, child, null, session ) ) { - String parentEntiytName = persister.getEntityName(); - String propertyName = persister.getPropertyNames()[propertyIndex]; - throw new TransientPropertyValueException( - "object references an unsaved transient instance - save the transient instance beforeQuery flushing", + && !(child instanceof HibernateProxy) ) { //a proxy cannot be transient and it breaks ForeignKeys.isTransient + final String childEntityName = ((EntityType) propertyType).getAssociatedEntityName(session.getFactory()); + if (ForeignKeys.isTransient(childEntityName, child, null, session)) { + String parentEntityName = persister.getEntityName(); + String propertyName = persister.getPropertyNames()[propertyIndex]; + throw new TransientPropertyValueException( + "object references an unsaved transient instance - save the transient instance before flushing", childEntityName, - parentEntiytName, + parentEntityName, propertyName - ); - + ); + } } } } @@ -471,7 +470,7 @@ public boolean performOnLazyProperty() { * * @return The children iterator. */ - private static Iterator getAllElementsIterator( + public static Iterator getAllElementsIterator( EventSource session, CollectionType collectionType, Object collection) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java index 64cb7ba77b16..7b0fdd893b46 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java @@ -94,7 +94,7 @@ public CollectionEntry( collection.setSnapshot(loadedKey, role, null); - //postInitialize() will be called afterQuery initialization + //postInitialize() will be called after initialization } /** @@ -209,7 +209,7 @@ public void postInitialize(PersistentCollection collection) throws HibernateExce } /** - * Called afterQuery a successful flush + * Called after a successful flush */ public void postFlush(PersistentCollection collection) throws HibernateException { if ( isIgnore() ) { @@ -222,7 +222,7 @@ else if ( !isProcessed() ) { } /** - * Called afterQuery execution of an action + * Called after execution of an action */ public void afterAction(PersistentCollection collection) { loadedKey = getCurrentKey(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java index 091539351eaa..5db19a612df7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java @@ -27,32 +27,33 @@ public final class CollectionKey implements Serializable { private final Type keyType; private final SessionFactoryImplementor factory; private final int hashCode; - private EntityMode entityMode; public CollectionKey(CollectionPersister persister, Serializable key) { this( persister.getRole(), key, persister.getKeyType(), - persister.getOwnerEntityPersister().getEntityMetamodel().getEntityMode(), persister.getFactory() ); } + /** + * The EntityMode parameter is now ignored. Use the other constructor. + * @deprecated Use {@link #CollectionKey(CollectionPersister, Serializable)} + */ + @Deprecated public CollectionKey(CollectionPersister persister, Serializable key, EntityMode em) { - this( persister.getRole(), key, persister.getKeyType(), em, persister.getFactory() ); + this( persister.getRole(), key, persister.getKeyType(), persister.getFactory() ); } private CollectionKey( String role, Serializable key, Type keyType, - EntityMode entityMode, SessionFactoryImplementor factory) { this.role = role; this.key = key; this.keyType = keyType; - this.entityMode = entityMode; this.factory = factory; //cache the hash-code this.hashCode = generateHashCode(); @@ -65,7 +66,6 @@ private int generateHashCode() { return result; } - public String getRole() { return role; } @@ -112,7 +112,6 @@ public void serialize(ObjectOutputStream oos) throws IOException { oos.writeObject( role ); oos.writeObject( key ); oos.writeObject( keyType ); - oos.writeObject( entityMode.toString() ); } /** @@ -134,7 +133,6 @@ public static CollectionKey deserialize( (String) ois.readObject(), (Serializable) ois.readObject(), (Type) ois.readObject(), - EntityMode.parse( (String) ois.readObject() ), (session == null ? null : session.getFactory()) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java index 39ca1444e152..c6e373a50d53 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java @@ -66,12 +66,12 @@ public interface EntityEntry { Object getRowId(); /** - * Handle updating the internal state of the entry afterQuery actually performing + * Handle updating the internal state of the entry after actually performing * the database update. Specifically we update the snapshot information and * escalate the lock mode * * @param entity The entity instance - * @param updatedState The state calculated afterQuery the update (becomes the + * @param updatedState The state calculated after the update (becomes the * new {@link #getLoadedState() loaded state}. * @param nextVersion The new version. */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java index d646ad204323..52a9cd870671 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java @@ -10,9 +10,9 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.Objects; import org.hibernate.AssertionFailure; -import org.hibernate.internal.util.compare.EqualsHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; @@ -74,6 +74,10 @@ public String getEntityName() { return persister.getEntityName(); } + public EntityPersister getPersister() { + return persister; + } + @Override public boolean equals(Object other) { if ( this == other ) { @@ -98,7 +102,7 @@ private boolean samePersistentType(final EntityKey otherKey) { return true; } else { - return EqualsHelper.equals( otherKey.persister.getRootEntityName(), persister.getRootEntityName() ); + return Objects.equals( otherKey.persister.getRootEntityName(), persister.getRootEntityName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ExecutableList.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ExecutableList.java index 66fb51b66926..af6e2db340a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/ExecutableList.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ExecutableList.java @@ -297,7 +297,7 @@ public void writeExternal(ObjectOutput oos) throws IOException { oos.writeObject( e ); } - // if the spaces are initialized, write them out for usage afterQuery deserialization + // if the spaces are initialized, write them out for usage after deserialization if ( querySpaces == null ) { oos.writeInt( -1 ); } @@ -342,7 +342,7 @@ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundExcept } /** - * Allow the Executables to re-associate themselves with the Session afterQuery deserialization. + * Allow the Executables to re-associate themselves with the Session after deserialization. * * @param session The session to which to associate the Executables */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java index 3822eb870a0d..317038ba5516 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java @@ -7,6 +7,7 @@ package org.hibernate.engine.spi; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -38,23 +39,21 @@ public class LoadQueryInfluencers implements Serializable { private final SessionFactoryImplementor sessionFactory; private String internalFetchProfile; - private final Map enabledFilters; - private final Set enabledFetchProfileNames; private EntityGraph fetchGraph; private EntityGraph loadGraph; + //Lazily initialized! + private HashSet enabledFetchProfileNames; + + //Lazily initialized! + private HashMap enabledFilters; + public LoadQueryInfluencers() { this( null ); } public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory) { - this( sessionFactory, new HashMap(), new HashSet() ); - } - - private LoadQueryInfluencers(SessionFactoryImplementor sessionFactory, Map enabledFilters, Set enabledFetchProfileNames) { this.sessionFactory = sessionFactory; - this.enabledFilters = enabledFilters; - this.enabledFetchProfileNames = enabledFetchProfileNames; } public SessionFactoryImplementor getSessionFactory() { @@ -81,16 +80,21 @@ public void setInternalFetchProfile(String internalFetchProfile) { // filter support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean hasEnabledFilters() { - return !enabledFilters.isEmpty(); + return enabledFilters != null && !enabledFilters.isEmpty(); } public Map getEnabledFilters() { - // First, validate all the enabled filters... - //TODO: this implementation has bad performance - for ( Filter filter : enabledFilters.values() ) { - filter.validate(); + if ( enabledFilters == null ) { + return Collections.EMPTY_MAP; + } + else { + // First, validate all the enabled filters... + for ( Filter filter : enabledFilters.values() ) { + //TODO: this implementation has bad performance + filter.validate(); + } + return enabledFilters; } - return enabledFilters; } /** @@ -98,25 +102,43 @@ public Map getEnabledFilters() { * @return an unmodifiable Set of enabled filter names. */ public Set getEnabledFilterNames() { - return java.util.Collections.unmodifiableSet( enabledFilters.keySet() ); + if ( enabledFilters == null ) { + return Collections.EMPTY_SET; + } + else { + return java.util.Collections.unmodifiableSet( enabledFilters.keySet() ); + } } public Filter getEnabledFilter(String filterName) { - return enabledFilters.get( filterName ); + if ( enabledFilters == null ) { + return null; + } + else { + return enabledFilters.get( filterName ); + } } public Filter enableFilter(String filterName) { FilterImpl filter = new FilterImpl( sessionFactory.getFilterDefinition( filterName ) ); + if ( enabledFilters == null ) { + this.enabledFilters = new HashMap<>(); + } enabledFilters.put( filterName, filter ); return filter; } public void disableFilter(String filterName) { - enabledFilters.remove( filterName ); + if ( enabledFilters != null ) { + enabledFilters.remove( filterName ); + } } public Object getFilterParameterValue(String filterParameterName) { final String[] parsed = parseFilterParameterName( filterParameterName ); + if ( enabledFilters == null ) { + throw new IllegalArgumentException( "Filter [" + parsed[0] + "] currently not enabled" ); + } final FilterImpl filter = (FilterImpl) enabledFilters.get( parsed[0] ); if ( filter == null ) { throw new IllegalArgumentException( "Filter [" + parsed[0] + "] currently not enabled" ); @@ -154,11 +176,16 @@ public static String[] parseFilterParameterName(String filterParameterName) { // fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean hasEnabledFetchProfiles() { - return !enabledFetchProfileNames.isEmpty(); + return enabledFetchProfileNames != null && !enabledFetchProfileNames.isEmpty(); } public Set getEnabledFetchProfileNames() { - return enabledFetchProfileNames; + if ( enabledFetchProfileNames == null ) { + return Collections.EMPTY_SET; + } + else { + return enabledFetchProfileNames; + } } private void checkFetchProfileName(String name) { @@ -169,17 +196,22 @@ private void checkFetchProfileName(String name) { public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { checkFetchProfileName( name ); - return enabledFetchProfileNames.contains( name ); + return enabledFetchProfileNames != null && enabledFetchProfileNames.contains( name ); } public void enableFetchProfile(String name) throws UnknownProfileException { checkFetchProfileName( name ); + if ( enabledFetchProfileNames == null ) { + this.enabledFetchProfileNames = new HashSet<>(); + } enabledFetchProfileNames.add( name ); } public void disableFetchProfile(String name) throws UnknownProfileException { checkFetchProfileName( name ); - enabledFetchProfileNames.remove( name ); + if ( enabledFetchProfileNames != null ) { + enabledFetchProfileNames.remove( name ); + } } public EntityGraph getFetchGraph() { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java old mode 100755 new mode 100644 index 848ceeadc39a..42ea03bb1e06 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java @@ -164,7 +164,7 @@ public NamedSQLQueryDefinition( NativeSQLQueryReturn[] queryReturns) { super( name, - query.trim(), /* trim done to workaround stupid oracle bug that cant handle whitespaces beforeQuery a { in a sp */ + query.trim(), /* trim done to workaround stupid oracle bug that cant handle whitespaces before a { in a sp */ cacheable, cacheRegion, timeout, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index ed6a19a04aea..b3a4589aa179 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -39,24 +39,24 @@ public interface PersistenceContext { /** * Marker object used to indicate (via reference checking) that no row was returned. */ - public static final Object NO_ROW = new MarkerObject( "NO_ROW" ); + Object NO_ROW = new MarkerObject( "NO_ROW" ); @SuppressWarnings( {"UnusedDeclaration"}) - public boolean isStateless(); + boolean isStateless(); /** * Get the session to which this persistence context is bound. * * @return The session. */ - public SharedSessionContractImplementor getSession(); + SharedSessionContractImplementor getSession(); /** * Retrieve this persistence context's managed load context. * * @return The load context */ - public LoadContexts getLoadContexts(); + LoadContexts getLoadContexts(); /** * Add a collection which has no owner loaded @@ -64,7 +64,7 @@ public interface PersistenceContext { * @param key The collection key under which to add the collection * @param collection The collection to add */ - public void addUnownedCollection(CollectionKey key, PersistentCollection collection); + void addUnownedCollection(CollectionKey key, PersistentCollection collection); /** * Take ownership of a previously unowned collection, if one. This method returns {@code null} if no such @@ -76,25 +76,25 @@ public interface PersistenceContext { * * @return The unowned collection, or {@code null} */ - public PersistentCollection useUnownedCollection(CollectionKey key); + PersistentCollection useUnownedCollection(CollectionKey key); /** * Get the {@link BatchFetchQueue}, instantiating one if necessary. * * @return The batch fetch queue in effect for this persistence context */ - public BatchFetchQueue getBatchFetchQueue(); + BatchFetchQueue getBatchFetchQueue(); /** * Clear the state of the persistence context */ - public void clear(); + void clear(); /** * @return false if we know for certain that all the entities are read-only */ @SuppressWarnings( {"UnusedDeclaration"}) - public boolean hasNonReadOnlyEntities(); + boolean hasNonReadOnlyEntities(); /** * Set the status of an entry @@ -102,12 +102,12 @@ public interface PersistenceContext { * @param entry The entry for which to set the status * @param status The new status */ - public void setEntryStatus(EntityEntry entry, Status status); + void setEntryStatus(EntityEntry entry, Status status); /** - * Called afterQuery transactions end + * Called after transactions end */ - public void afterTransactionCompletion(); + void afterTransactionCompletion(); /** * Get the current state of the entity as known to the underlying database, or null if there is no @@ -120,7 +120,7 @@ public interface PersistenceContext { * * @see #getCachedDatabaseSnapshot */ - public Object[] getDatabaseSnapshot(Serializable id, EntityPersister persister); + Object[] getDatabaseSnapshot(Serializable id, EntityPersister persister); /** * Retrieve the cached database snapshot for the requested entity key. @@ -133,7 +133,7 @@ public interface PersistenceContext { * @return The cached snapshot * @throws IllegalStateException if the cached snapshot was == {@link #NO_ROW}. */ - public Object[] getCachedDatabaseSnapshot(EntityKey key); + Object[] getCachedDatabaseSnapshot(EntityKey key); /** * Get the values of the natural id fields as known to the underlying database, or null if the entity has no @@ -144,7 +144,7 @@ public interface PersistenceContext { * * @return The current (non-cached) snapshot of the entity's natural id state. */ - public Object[] getNaturalIdSnapshot(Serializable id, EntityPersister persister); + Object[] getNaturalIdSnapshot(Serializable id, EntityPersister persister); /** * Add a canonical mapping from entity key to entity instance @@ -152,7 +152,7 @@ public interface PersistenceContext { * @param key The key under which to add an entity * @param entity The entity instance to add */ - public void addEntity(EntityKey key, Object entity); + void addEntity(EntityKey key, Object entity); /** * Get the entity instance associated with the given key @@ -161,7 +161,7 @@ public interface PersistenceContext { * * @return The matching entity, or {@code null} */ - public Object getEntity(EntityKey key); + Object getEntity(EntityKey key); /** * Is there an entity with the given key in the persistence context @@ -170,7 +170,7 @@ public interface PersistenceContext { * * @return {@code true} indicates an entity was found; otherwise {@code false} */ - public boolean containsEntity(EntityKey key); + boolean containsEntity(EntityKey key); /** * Remove an entity. Also clears up all other state associated with the entity aside from the {@link EntityEntry} @@ -179,7 +179,7 @@ public interface PersistenceContext { * * @return The matching entity */ - public Object removeEntity(EntityKey key); + Object removeEntity(EntityKey key); /** * Add an entity to the cache by unique key @@ -187,7 +187,7 @@ public interface PersistenceContext { * @param euk The unique (non-primary) key under which to add an entity * @param entity The entity instance */ - public void addEntity(EntityUniqueKey euk, Object entity); + void addEntity(EntityUniqueKey euk, Object entity); /** * Get an entity cached by unique key @@ -196,7 +196,7 @@ public interface PersistenceContext { * * @return The located entity */ - public Object getEntity(EntityUniqueKey euk); + Object getEntity(EntityUniqueKey euk); /** * Retrieve the {@link EntityEntry} representation of the given entity. @@ -204,7 +204,7 @@ public interface PersistenceContext { * @param entity The entity instance for which to locate the corresponding entry * @return The entry */ - public EntityEntry getEntry(Object entity); + EntityEntry getEntry(Object entity); /** * Remove an entity entry from the session cache @@ -212,7 +212,7 @@ public interface PersistenceContext { * @param entity The entity instance for which to remove the corresponding entry * @return The matching entry */ - public EntityEntry removeEntry(Object entity); + EntityEntry removeEntry(Object entity); /** * Is there an {@link EntityEntry} registration for this entity instance? @@ -221,7 +221,7 @@ public interface PersistenceContext { * * @return {@code true} indicates a matching entry was found. */ - public boolean isEntryFor(Object entity); + boolean isEntryFor(Object entity); /** * Get the collection entry for a persistent collection @@ -230,7 +230,7 @@ public interface PersistenceContext { * * @return The matching collection entry */ - public CollectionEntry getCollectionEntry(PersistentCollection coll); + CollectionEntry getCollectionEntry(PersistentCollection coll); /** * Adds an entity to the internal caches. @@ -250,7 +250,7 @@ EntityEntry addEntity( * Generates an appropriate EntityEntry instance and adds it * to the event source's internal caches. */ - public EntityEntry addEntry( + EntityEntry addEntry( final Object entity, final Status status, final Object[] loadedState, @@ -265,12 +265,12 @@ public EntityEntry addEntry( /** * Is the given collection associated with this persistence context? */ - public boolean containsCollection(PersistentCollection collection); + boolean containsCollection(PersistentCollection collection); /** * Is the given proxy associated with this persistence context? */ - public boolean containsProxy(Object proxy); + boolean containsProxy(Object proxy); /** * Takes the given object and, if it represents a proxy, reassociates it with this event source. @@ -279,20 +279,20 @@ public EntityEntry addEntry( * @return Whether the passed value represented an actual proxy which got initialized. * @throws MappingException */ - public boolean reassociateIfUninitializedProxy(Object value) throws MappingException; + boolean reassociateIfUninitializedProxy(Object value) throws MappingException; /** * If a deleted entity instance is re-saved, and it has a proxy, we need to * reset the identifier of the proxy */ - public void reassociateProxy(Object value, Serializable id) throws MappingException; + void reassociateProxy(Object value, Serializable id) throws MappingException; /** * Get the entity instance underlying the given proxy, throwing * an exception if the proxy is uninitialized. If the given object * is not a proxy, simply return the argument. */ - public Object unproxy(Object maybeProxy) throws HibernateException; + Object unproxy(Object maybeProxy) throws HibernateException; /** * Possibly unproxy the given reference and reassociate it with the current session. @@ -301,7 +301,7 @@ public EntityEntry addEntry( * @return The unproxied instance. * @throws HibernateException */ - public Object unproxyAndReassociate(Object maybeProxy) throws HibernateException; + Object unproxyAndReassociate(Object maybeProxy) throws HibernateException; /** * Attempts to check whether the given key represents an entity already loaded within the @@ -311,7 +311,7 @@ public EntityEntry addEntry( * * @throws HibernateException */ - public void checkUniqueness(EntityKey key, Object object) throws HibernateException; + void checkUniqueness(EntityKey key, Object object) throws HibernateException; /** * If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy @@ -326,7 +326,7 @@ public EntityEntry addEntry( * @return An appropriately narrowed instance. * @throws HibernateException */ - public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key, Object object) + Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key, Object object) throws HibernateException; /** @@ -334,7 +334,7 @@ public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key * third argument (the entity associated with the key) if no proxy exists. Init * the proxy to the target implementation, if necessary. */ - public Object proxyFor(EntityPersister persister, EntityKey key, Object impl) + Object proxyFor(EntityPersister persister, EntityKey key, Object impl) throws HibernateException; /** @@ -342,12 +342,18 @@ public Object proxyFor(EntityPersister persister, EntityKey key, Object impl) * argument (the entity associated with the key) if no proxy exists. * (slower than the form above) */ - public Object proxyFor(Object impl) throws HibernateException; + Object proxyFor(Object impl) throws HibernateException; + + /** + * Cross between {@link #addEntity(EntityKey, Object)} and {@link #addProxy(EntityKey, Object)} + * for use with enhancement-as-proxy + */ + void addEnhancedProxy(EntityKey key, PersistentAttributeInterceptable entity); /** * Get the entity that owns this persistent collection */ - public Object getCollectionOwner(Serializable key, CollectionPersister collectionPersister) + Object getCollectionOwner(Serializable key, CollectionPersister collectionPersister) throws MappingException; /** @@ -365,18 +371,18 @@ public Object getCollectionOwner(Serializable key, CollectionPersister collectio * @param collection The persistent collection * @return the owner ID if available from the collection's loaded key; otherwise, returns null */ - public Serializable getLoadedCollectionOwnerIdOrNull(PersistentCollection collection); + Serializable getLoadedCollectionOwnerIdOrNull(PersistentCollection collection); /** * add a collection we just loaded up (still needs initializing) */ - public void addUninitializedCollection(CollectionPersister persister, + void addUninitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id); /** * add a detached uninitialized collection */ - public void addUninitializedDetachedCollection(CollectionPersister persister, + void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection collection); /** @@ -384,79 +390,79 @@ public void addUninitializedDetachedCollection(CollectionPersister persister, * application, with no database state or snapshot) * @param collection The collection to be associated with the persistence context */ - public void addNewCollection(CollectionPersister persister, PersistentCollection collection) + void addNewCollection(CollectionPersister persister, PersistentCollection collection) throws HibernateException; /** * add an (initialized) collection that was created by another session and passed * into update() (ie. one with a snapshot and existing state on the database) */ - public void addInitializedDetachedCollection(CollectionPersister collectionPersister, + void addInitializedDetachedCollection(CollectionPersister collectionPersister, PersistentCollection collection) throws HibernateException; /** * add a collection we just pulled out of the cache (does not need initializing) */ - public CollectionEntry addInitializedCollection(CollectionPersister persister, + CollectionEntry addInitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id) throws HibernateException; /** * Get the collection instance associated with the CollectionKey */ - public PersistentCollection getCollection(CollectionKey collectionKey); + PersistentCollection getCollection(CollectionKey collectionKey); /** * Register a collection for non-lazy loading at the end of the * two-phase load */ - public void addNonLazyCollection(PersistentCollection collection); + void addNonLazyCollection(PersistentCollection collection); /** * Force initialization of all non-lazy collections encountered during * the current two-phase load (actually, this is a no-op, unless this * is the "outermost" load) */ - public void initializeNonLazyCollections() throws HibernateException; + void initializeNonLazyCollections() throws HibernateException; /** * Get the PersistentCollection object for an array */ - public PersistentCollection getCollectionHolder(Object array); + PersistentCollection getCollectionHolder(Object array); /** * Register a PersistentCollection object for an array. - * Associates a holder with an array - MUST be called afterQuery loading + * Associates a holder with an array - MUST be called after loading * array, since the array instance is not created until endLoad(). */ - public void addCollectionHolder(PersistentCollection holder); + void addCollectionHolder(PersistentCollection holder); /** * Remove the mapping of collection to holder during eviction * of the owning entity */ - public PersistentCollection removeCollectionHolder(Object array); + PersistentCollection removeCollectionHolder(Object array); /** * Get the snapshot of the pre-flush collection state */ - public Serializable getSnapshot(PersistentCollection coll); + Serializable getSnapshot(PersistentCollection coll); /** * Get the collection entry for a collection passed to filter, * which might be a collection wrapper, an array, or an unwrapped * collection. Return null if there is no entry. */ - public CollectionEntry getCollectionEntryOrNull(Object collection); + CollectionEntry getCollectionEntryOrNull(Object collection); /** * Get an existing proxy by key */ - public Object getProxy(EntityKey key); + Object getProxy(EntityKey key); /** * Add a proxy to the session cache */ - public void addProxy(EntityKey key, Object proxy); + void addProxy(EntityKey key, Object proxy); /** * Remove a proxy from the session cache. @@ -467,17 +473,17 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * @param key The key of the entity proxy to be removed * @return The proxy reference. */ - public Object removeProxy(EntityKey key); + Object removeProxy(EntityKey key); /** * Retrieve the set of EntityKeys representing nullifiable references */ - public HashSet getNullifiableEntityKeys(); + HashSet getNullifiableEntityKeys(); /** * Get the mapping from key value to entity instance */ - public Map getEntitiesByKey(); + Map getEntitiesByKey(); /** * Provides access to the entity/EntityEntry combos associated with the persistence context in a manner that @@ -485,7 +491,7 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * * @return */ - public Map.Entry[] reentrantSafeEntityEntries(); + Map.Entry[] reentrantSafeEntityEntries(); /** * Get the mapping from entity instance to entity entry @@ -495,66 +501,66 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * {@link #reentrantSafeEntityEntries} */ @Deprecated - public Map getEntityEntries(); + Map getEntityEntries(); - public int getNumberOfManagedEntities(); + int getNumberOfManagedEntities(); /** * Get the mapping from collection instance to collection entry */ - public Map getCollectionEntries(); + Map getCollectionEntries(); /** * Get the mapping from collection key to collection instance */ - public Map getCollectionsByKey(); + Map getCollectionsByKey(); /** * How deep are we cascaded? */ - public int getCascadeLevel(); + int getCascadeLevel(); /** - * Called beforeQuery cascading + * Called before cascading */ - public int incrementCascadeLevel(); + int incrementCascadeLevel(); /** - * Called afterQuery cascading + * Called after cascading */ - public int decrementCascadeLevel(); + int decrementCascadeLevel(); /** * Is a flush cycle currently in process? */ @SuppressWarnings( {"UnusedDeclaration"}) - public boolean isFlushing(); + boolean isFlushing(); /** - * Called beforeQuery and afterQuery the flushcycle + * Called before and after the flushcycle */ - public void setFlushing(boolean flushing); + void setFlushing(boolean flushing); /** - * Call this beforeQuery begining a two-phase load + * Call this before begining a two-phase load */ - public void beforeLoad(); + void beforeLoad(); /** - * Call this afterQuery finishing a two-phase load + * Call this after finishing a two-phase load */ - public void afterLoad(); + void afterLoad(); /** * Is in a two-phase load? */ - public boolean isLoadFinished(); + boolean isLoadFinished(); /** * Returns a string representation of the object. * * @return a string representation of the object. */ - public String toString(); + String toString(); /** * Search this persistence context for an associated entity instance which is considered the "owner" of @@ -577,24 +583,24 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * @return The id of the entityName instance which is said to own the child; null if an appropriate owner not * located. */ - public Serializable getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap); + Serializable getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap); /** * Search the persistence context for an index of the child object, * given a collection role */ - public Object getIndexInOwner(String entity, String property, Object childObject, Map mergeMap); + Object getIndexInOwner(String entity, String property, Object childObject, Map mergeMap); /** * Record the fact that the association belonging to the keyed * entity is null. */ - public void addNullProperty(EntityKey ownerKey, String propertyName); + void addNullProperty(EntityKey ownerKey, String propertyName); /** * Is the association property belonging to the keyed entity null? */ - public boolean isPropertyNull(EntityKey ownerKey, String propertyName); + boolean isPropertyNull(EntityKey ownerKey, String propertyName); /** * Will entities and proxies that are loaded into this persistence @@ -610,7 +616,7 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * * @see org.hibernate.Session#isDefaultReadOnly() */ - public boolean isDefaultReadOnly(); + boolean isDefaultReadOnly(); /** * Change the default for entities and proxies loaded into this persistence @@ -639,7 +645,7 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * * @see org.hibernate.Session#setDefaultReadOnly(boolean) */ - public void setDefaultReadOnly(boolean readOnly); + void setDefaultReadOnly(boolean readOnly); /** * Is the entity or proxy read-only? @@ -652,7 +658,7 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * @return {@code true} if the object is read-only; otherwise {@code false} to indicate that the object is * modifiable. */ - public boolean isReadOnly(Object entityOrProxy); + boolean isReadOnly(Object entityOrProxy); /** * Set an unmodified persistent object to read-only mode, or a read-only @@ -677,7 +683,7 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * @see org.hibernate.Session#setReadOnly * @see org.hibernate.Query#setReadOnly */ - public void setReadOnly(Object entityOrProxy, boolean readOnly); + void setReadOnly(Object entityOrProxy, boolean readOnly); void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId); @@ -687,14 +693,14 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * @param child The child of the relationship * @param parent The parent of the relationship */ - public void addChildParent(Object child, Object parent); + void addChildParent(Object child, Object parent); /** * Remove child/parent relation from cache * * @param child The child to be removed. */ - public void removeChildParent(Object child); + void removeChildParent(Object child); /** * Register keys inserted during the current transaction @@ -702,7 +708,7 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * @param persister The entity persister * @param id The id */ - public void registerInsertedKey(EntityPersister persister, Serializable id); + void registerInsertedKey(EntityPersister persister, Serializable id); /** * Allows callers to check to see if the identified entity was inserted during the current transaction. @@ -712,13 +718,13 @@ public CollectionEntry addInitializedCollection(CollectionPersister persister, * * @return True if inserted during this transaction, false otherwise. */ - public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id); + boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id); /** * Provides centralized access to natural-id-related functionality. */ - public static interface NaturalIdHelper { - public static final Serializable INVALID_NATURAL_ID_REFERENCE = new Serializable() {}; + interface NaturalIdHelper { + Serializable INVALID_NATURAL_ID_REFERENCE = new Serializable() {}; /** * Given an array of "full entity state", extract the portions that represent the natural id @@ -728,7 +734,7 @@ public static interface NaturalIdHelper { * * @return The extracted natural id values */ - public Object[] extractNaturalIdValues(Object[] state, EntityPersister persister); + Object[] extractNaturalIdValues(Object[] state, EntityPersister persister); /** * Given an entity instance, extract the values that represent the natural id @@ -738,7 +744,7 @@ public static interface NaturalIdHelper { * * @return The extracted natural id values */ - public Object[] extractNaturalIdValues(Object entity, EntityPersister persister); + Object[] extractNaturalIdValues(Object entity, EntityPersister persister); /** * Performs processing related to creating natural-id cross-reference entries on load. @@ -748,9 +754,9 @@ public static interface NaturalIdHelper { * @param id The primary key value * @param naturalIdValues The natural id values */ - public void cacheNaturalIdCrossReferenceFromLoad( - EntityPersister persister, - Serializable id, + void cacheNaturalIdCrossReferenceFromLoad( + EntityPersister persister, + Serializable id, Object[] naturalIdValues); /** @@ -763,7 +769,7 @@ public void cacheNaturalIdCrossReferenceFromLoad( * Specifically represents the previous values on update, and so is only used with {@link CachedNaturalIdValueSource#UPDATE} * @param source Enumeration representing how these values are coming into cache. */ - public void manageLocalNaturalIdCrossReference( + void manageLocalNaturalIdCrossReference( EntityPersister persister, Serializable id, Object[] state, @@ -779,7 +785,7 @@ public void manageLocalNaturalIdCrossReference( * * @return The local cached natural id values (could be different from given values). */ - public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] state); + Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] state); /** * Creates necessary shared (second level cache) cross-reference entries. @@ -791,7 +797,7 @@ public void manageLocalNaturalIdCrossReference( * Specifically represents the previous values on update, and so is only used with {@link CachedNaturalIdValueSource#UPDATE} * @param source Enumeration representing how these values are coming into cache. */ - public void manageSharedNaturalIdCrossReference( + void manageSharedNaturalIdCrossReference( EntityPersister persister, Serializable id, Object[] state, @@ -805,7 +811,7 @@ public void manageSharedNaturalIdCrossReference( * @param id The primary key value * @param naturalIdValues The natural id values array */ - public void removeSharedNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] naturalIdValues); + void removeSharedNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] naturalIdValues); /** * Given a persister and primary key, find the corresponding cross-referenced natural id values. @@ -815,7 +821,7 @@ public void manageSharedNaturalIdCrossReference( * * @return The cross-referenced natural-id values, or {@code null} */ - public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk); + Object[] findCachedNaturalId(EntityPersister persister, Serializable pk); /** * Given a persister and natural-id values, find the corresponding cross-referenced primary key. Will return @@ -829,7 +835,7 @@ public void manageSharedNaturalIdCrossReference( * {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE}, * or {@code null}. */ - public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues); + Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues); /** * Find all the locally cached primary key cross-reference entries for the given persister. @@ -838,14 +844,14 @@ public void manageSharedNaturalIdCrossReference( * * @return The primary keys */ - public Collection getCachedPkResolutions(EntityPersister persister); + Collection getCachedPkResolutions(EntityPersister persister); /** * Part of the "load synchronization process". Responsible for maintaining cross-reference entries * when natural-id values were found to have changed. Also responsible for tracking the old values * as no longer valid until the next flush because otherwise going to the database would just re-pull * the old values as valid. In this last responsibility, {@link #cleanupFromSynchronizations} is - * the inverse process called afterQuery flush to clean up those entries. + * the inverse process called after flush to clean up those entries. * * @param persister The persister representing the entity type. * @param pk The primary key @@ -853,13 +859,13 @@ public void manageSharedNaturalIdCrossReference( * * @see #cleanupFromSynchronizations */ - public void handleSynchronization(EntityPersister persister, Serializable pk, Object entity); + void handleSynchronization(EntityPersister persister, Serializable pk, Object entity); /** * The clean up process of {@link #handleSynchronization}. Responsible for cleaning up the tracking * of old values as no longer valid. */ - public void cleanupFromSynchronizations(); + void cleanupFromSynchronizations(); /** * Called on {@link org.hibernate.Session#evict} to give a chance to clean up natural-id cross refs. @@ -868,7 +874,7 @@ public void manageSharedNaturalIdCrossReference( * @param persister The entity persister * @param identifier The entity identifier */ - public void handleEviction(Object object, EntityPersister persister, Serializable identifier); + void handleEviction(Object object, EntityPersister persister, Serializable identifier); } /** @@ -876,5 +882,5 @@ public void manageSharedNaturalIdCrossReference( * * @return This persistence context's natural-id helper */ - public NaturalIdHelper getNaturalIdHelper(); + NaturalIdHelper getNaturalIdHelper(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java index 38bf8090beac..61e80eca6da2 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java @@ -6,47 +6,76 @@ */ package org.hibernate.engine.spi; +import java.util.Collections; +import java.util.Set; + +import org.hibernate.Incubating; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; /** + * The base contract for interceptors that can be injected into + * enhanced entities for the purpose of intercepting attribute access + * * @author Steve Ebersole + * + * @see PersistentAttributeInterceptable */ +@Incubating +@SuppressWarnings("unused") public interface PersistentAttributeInterceptor extends InterceptorImplementor { + boolean readBoolean(Object obj, String name, boolean oldValue); - public boolean readBoolean(Object obj, String name, boolean oldValue); + boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); + byte readByte(Object obj, String name, byte oldValue); - public byte readByte(Object obj, String name, byte oldValue); + byte writeByte(Object obj, String name, byte oldValue, byte newValue); - public byte writeByte(Object obj, String name, byte oldValue, byte newValue); + char readChar(Object obj, String name, char oldValue); - public char readChar(Object obj, String name, char oldValue); + char writeChar(Object obj, String name, char oldValue, char newValue); - public char writeChar(Object obj, String name, char oldValue, char newValue); + short readShort(Object obj, String name, short oldValue); - public short readShort(Object obj, String name, short oldValue); + short writeShort(Object obj, String name, short oldValue, short newValue); - public short writeShort(Object obj, String name, short oldValue, short newValue); + int readInt(Object obj, String name, int oldValue); - public int readInt(Object obj, String name, int oldValue); + int writeInt(Object obj, String name, int oldValue, int newValue); - public int writeInt(Object obj, String name, int oldValue, int newValue); + float readFloat(Object obj, String name, float oldValue); - public float readFloat(Object obj, String name, float oldValue); + float writeFloat(Object obj, String name, float oldValue, float newValue); - public float writeFloat(Object obj, String name, float oldValue, float newValue); + double readDouble(Object obj, String name, double oldValue); - public double readDouble(Object obj, String name, double oldValue); + double writeDouble(Object obj, String name, double oldValue, double newValue); - public double writeDouble(Object obj, String name, double oldValue, double newValue); + long readLong(Object obj, String name, long oldValue); - public long readLong(Object obj, String name, long oldValue); + long writeLong(Object obj, String name, long oldValue, long newValue); - public long writeLong(Object obj, String name, long oldValue, long newValue); + Object readObject(Object obj, String name, Object oldValue); - public Object readObject(Object obj, String name, Object oldValue); + Object writeObject(Object obj, String name, Object oldValue, Object newValue); - public Object writeObject(Object obj, String name, Object oldValue, Object newValue); + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Deprecated + @Override + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Override + @Deprecated + default void attributeInitialized(String name) { + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java index 8edfacbbd971..a942be7c1fb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java @@ -25,6 +25,7 @@ import org.hibernate.internal.util.EntityPrinter; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.internal.QueryParameterBindingsImpl; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.ComponentType; import org.hibernate.type.Type; @@ -45,6 +46,7 @@ public final class QueryParameters { private Type[] positionalParameterTypes; private Object[] positionalParameterValues; private Map namedParameters; + private LockOptions lockOptions; private RowSelection rowSelection; private boolean cacheable; @@ -68,7 +70,7 @@ public final class QueryParameters { private String processedSQL; private Type[] processedPositionalParameterTypes; private Object[] processedPositionalParameterValues; - + private HQLQueryPlan queryPlan; public QueryParameters() { @@ -89,7 +91,6 @@ public QueryParameters( this.optionalObject = optionalObject; this.optionalId = optionalObjectId; this.optionalEntityName = optionalEntityName; - } public QueryParameters( @@ -229,7 +230,7 @@ public QueryParameters( } public QueryParameters( - QueryParameterBindingsImpl queryParameterBindings, + QueryParameterBindings queryParameterBindings, LockOptions lockOptions, RowSelection selection, final boolean isReadOnlyInitialized, @@ -424,14 +425,14 @@ public boolean isReadOnlyInitialized() { /** * Should entities and proxies loaded by the Query be put in read-only mode? The * read-only/modifiable setting must be initialized via QueryParameters#setReadOnly(boolean) - * beforeQuery calling this method. + * before calling this method. * * @see QueryParameters#isReadOnlyInitialized() * @see QueryParameters#isReadOnly(SharedSessionContractImplementor) * @see QueryParameters#setReadOnly(boolean) * * The read-only/modifiable setting has no impact on entities/proxies returned by the - * query that existed in the session beforeQuery the query was executed. + * query that existed in the session before the query was executed. * * @return true, entities and proxies loaded by the Query will be put in read-only mode * false, entities and proxies loaded by the Query will be put in modifiable mode @@ -451,7 +452,7 @@ public boolean isReadOnly() { * then the default read-only/modifiable setting for the persistence context is returned instead. *

    * The read-only/modifiable setting has no impact on entities/proxies returned by the - * query that existed in the session beforeQuery the query was executed. + * query that existed in the session before the query was executed. * * @param session The originating session * @@ -463,7 +464,7 @@ public boolean isReadOnly() { * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() * * The read-only/modifiable setting has no impact on entities/proxies returned by the - * query that existed in the session beforeQuery the query was executed. + * query that existed in the session before the query was executed. * */ public boolean isReadOnly(SharedSessionContractImplementor session) { @@ -476,7 +477,7 @@ public boolean isReadOnly(SharedSessionContractImplementor session) { * Set the read-only/modifiable mode for entities and proxies loaded by the query. *

    * The read-only/modifiable setting has no impact on entities/proxies returned by the - * query that existed in the session beforeQuery the query was executed. + * query that existed in the session before the query was executed. * * @param readOnly if {@code true}, entities and proxies loaded by the query will be put in read-only mode; if * {@code false}, entities and proxies loaded by the query will be put in modifiable mode @@ -652,6 +653,7 @@ public QueryParameters createCopyUsing(RowSelection selection) { copy.processedSQL = this.processedSQL; copy.processedPositionalParameterTypes = this.processedPositionalParameterTypes; copy.processedPositionalParameterValues = this.processedPositionalParameterValues; + copy.passDistinctThrough = this.passDistinctThrough; return copy; } @@ -662,4 +664,20 @@ public HQLQueryPlan getQueryPlan() { public void setQueryPlan(HQLQueryPlan queryPlan) { this.queryPlan = queryPlan; } + + public void bindDynamicParameter(Type paramType, Object paramValue) { + if(processedPositionalParameterTypes != null) { + int length = processedPositionalParameterTypes.length; + Type[] types = new Type[length + 1]; + Object[] values = new Object[length + 1]; + for ( int i = 0; i < length; i++ ) { + types[i] = processedPositionalParameterTypes[i]; + values[i] = processedPositionalParameterValues[i]; + } + types[length] = paramType; + values[length] = paramValue; + processedPositionalParameterTypes = types; + processedPositionalParameterValues = values; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java index 75a62c50ae5c..225c116f93fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java @@ -18,6 +18,9 @@ public final class RowSelection { private Integer fetchSize; public void setFirstRow(Integer firstRow) { + if ( firstRow != null && firstRow < 0 ) { + throw new IllegalArgumentException( "first-row value cannot be negative : " + firstRow ); + } this.firstRow = firstRow; } 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 1ae03910a680..7fa97934ebe0 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 @@ -38,6 +38,7 @@ import org.hibernate.LockOptions; import org.hibernate.MultiIdentifierLoadAccess; import org.hibernate.NaturalIdLoadAccess; +import org.hibernate.Query; import org.hibernate.ReplicationMode; import org.hibernate.ScrollMode; import org.hibernate.Session; @@ -47,6 +48,7 @@ import org.hibernate.Transaction; import org.hibernate.TypeHelper; import org.hibernate.UnknownProfileException; +import org.hibernate.cache.spi.CacheTransactionSynchronization; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -68,12 +70,12 @@ /** * This class is meant to be extended. - * + * * Wraps and delegates all methods to a {@link SessionImplementor} and * a {@link Session}. This is useful for custom implementations of this * API so that only some methods need to be overridden * (Used by Hibernate Search). - * + * * @author Sanne Grinovero (C) 2012 Red Hat Inc. */ @SuppressWarnings("deprecation") @@ -82,7 +84,7 @@ public class SessionDelegatorBaseImpl implements SessionImplementor { protected final SessionImplementor delegate; /** - * @deprecated (snce 6.0) SessionDelegatorBaseImpl should take just one argument, the SessionImplementor. + * @deprecated (since 5.3) SessionDelegatorBaseImpl should take just one argument, the SessionImplementor. * Use the {@link #SessionDelegatorBaseImpl(SessionImplementor)} form instead */ @Deprecated @@ -104,6 +106,16 @@ public SessionDelegatorBaseImpl(SessionImplementor delegate) { this( delegate, delegate ); } + /** + * Returns the underlying delegate. Be careful that is has a different behavior from the {@link #getDelegate()} + * method coming from the EntityManager interface which returns the current session. + * + * @see SessionDelegatorBaseImpl#getDelegate() + */ + protected SessionImplementor delegate() { + return delegate; + } + @Override public T execute(Callback callback) { return delegate.execute( callback ); @@ -144,6 +156,11 @@ public boolean isTransactionInProgress() { return delegate.isTransactionInProgress(); } + @Override + public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + delegate.checkTransactionNeededForUpdateOperation( exceptionMessage ); + } + @Override public LockOptions getLockRequest(LockModeType lockModeType, Map properties) { return delegate.getLockRequest( lockModeType, properties ); @@ -323,6 +340,11 @@ public void markForRollbackOnly() { delegate.markForRollbackOnly(); } + @Override + public long getTransactionStartTimestamp() { + return delegate.getTransactionStartTimestamp(); + } + @Override public FlushModeType getFlushMode() { return delegate.getFlushMode(); @@ -403,6 +425,16 @@ public boolean isClosed() { return delegate.isClosed(); } + @Override + public void checkOpen() { + delegate.checkOpen(); + } + + @Override + public boolean isOpenOrWaitingForAutoClose() { + return delegate.isOpenOrWaitingForAutoClose(); + } + @Override public boolean shouldAutoClose() { return delegate.shouldAutoClose(); @@ -413,6 +445,11 @@ public boolean isAutoCloseSessionEnabled() { return delegate.isAutoCloseSessionEnabled(); } + @Override + public boolean isQueryParametersValidationEnabled() { + return delegate.isQueryParametersValidationEnabled(); + } + @Override public boolean shouldAutoJoinTransaction() { return delegate.shouldAutoJoinTransaction(); @@ -448,6 +485,16 @@ public Transaction getTransaction() { return delegate.getTransaction(); } + @Override + public void startTransactionBoundary() { + delegate.startTransactionBoundary(); + } + + @Override + public CacheTransactionSynchronization getCacheTransactionSynchronization() { + return delegate.getCacheTransactionSynchronization(); + } + @Override public void afterTransactionBegin() { delegate.afterTransactionBegin(); @@ -575,7 +622,7 @@ public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { @Override public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { - return delegate.createNamedStoredProcedureQuery( procedureName ); + return delegate.createStoredProcedureQuery( procedureName ); } @Override @@ -603,9 +650,18 @@ public T unwrap(Class cls) { return delegate.unwrap( cls ); } + /** + * This is an implementation of EntityManager#getDelegate(). It returns the current session and not the delegate + * session as it is what we want. The name of the method is misleading here but, as it is part of JPA, we cannot do + * anything about it. + *

    + * To get the underlying delegate, use {@link #delegate()} instead. + * + * @see SessionDelegatorBaseImpl#delegate() + */ @Override public Object getDelegate() { - return delegate; + return this; } @Override @@ -919,7 +975,7 @@ public LockMode getCurrentLockMode(Object object) { } @Override - public org.hibernate.query.Query createFilter(Object collection, String queryString) { + public Query createFilter(Object collection, String queryString) { return delegate.createFilter( collection, queryString ); } 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 8a5499ab5cb7..93930a79a01a 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 @@ -31,10 +31,7 @@ import org.hibernate.StatelessSessionBuilder; import org.hibernate.TypeHelper; import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.spi.QueryCache; -import org.hibernate.cache.spi.Region; -import org.hibernate.cache.spi.UpdateTimestampsCache; -import org.hibernate.cache.spi.access.RegionAccessStrategy; +import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.cfg.Settings; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.Dialect; @@ -73,6 +70,10 @@ public SessionFactoryDelegatingImpl(SessionFactoryImplementor delegate) { this.delegate = delegate; } + protected SessionFactoryImplementor delegate() { + return delegate; + } + @Override public SessionFactoryOptions getSessionFactoryOptions() { return delegate.getSessionFactoryOptions(); @@ -155,22 +156,22 @@ public CacheImplementor getCache() { @Override public PersistenceUnitUtil getPersistenceUnitUtil() { - return null; + return delegate.getPersistenceUnitUtil(); } @Override public void addNamedQuery(String name, Query query) { - + delegate.addNamedQuery( name, query ); } @Override public T unwrap(Class cls) { - return null; + return delegate.unwrap( cls ); } @Override public void addNamedEntityGraph(String graphName, EntityGraph entityGraph) { - + delegate.addNamedEntityGraph( graphName, entityGraph ); } @Override @@ -193,7 +194,14 @@ public TypeHelper getTypeHelper() { return delegate.getTypeHelper(); } - @Override + /** + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated public TypeResolver getTypeResolver() { return delegate.getTypeResolver(); } @@ -268,24 +276,9 @@ public EntityGraph findEntityGraphByName(String name) { return delegate.findEntityGraphByName( name ); } - @Override - public QueryCache getQueryCache() { - return delegate.getQueryCache(); - } - - @Override - public QueryCache getQueryCache(String regionName) throws HibernateException { - return delegate.getQueryCache( regionName ); - } - - @Override - public UpdateTimestampsCache getUpdateTimestampsCache() { - return delegate.getUpdateTimestampsCache(); - } - @Override public StatisticsImplementor getStatisticsImplementor() { - return delegate.getStatisticsImplementor(); + return delegate.getStatistics(); } @Override @@ -318,31 +311,6 @@ public IdentifierGenerator getIdentifierGenerator(String rootEntityName) { return delegate.getIdentifierGenerator( rootEntityName ); } - @Override - public Region getSecondLevelCacheRegion(String regionName) { - return delegate.getSecondLevelCacheRegion( regionName ); - } - - @Override - public RegionAccessStrategy getSecondLevelCacheRegionAccessStrategy(String regionName) { - return delegate.getSecondLevelCacheRegionAccessStrategy(regionName); - } - - @Override - public Region getNaturalIdCacheRegion(String regionName) { - return delegate.getNaturalIdCacheRegion( regionName ); - } - - @Override - public RegionAccessStrategy getNaturalIdCacheRegionAccessStrategy(String regionName) { - return delegate.getNaturalIdCacheRegionAccessStrategy(regionName); - } - - @Override - public Map getAllSecondLevelCacheRegions() { - return delegate.getAllSecondLevelCacheRegions(); - } - @Override public SQLExceptionConverter getSQLExceptionConverter() { return delegate.getSQLExceptionConverter(); 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 58784f70433b..b3f6593d89b0 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,13 +21,7 @@ import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.spi.QueryCache; -import org.hibernate.cache.spi.Region; -import org.hibernate.cache.spi.UpdateTimestampsCache; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; -import org.hibernate.cache.spi.access.RegionAccessStrategy; +import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.cfg.Settings; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.Dialect; @@ -115,14 +109,20 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory, Quer * Access to the cachres of HQL/JPQL and native query plans. * * @return The query plan cache + * + * @deprecated (since 5.2) it will be replaced with the new QueryEngine concept introduced in 6.0 */ + @Deprecated QueryPlanCache getQueryPlanCache(); /** * Provides access to the named query repository * * @return The repository for named query definitions + * + * @deprecated (since 5.2) it will be replaced with the new QueryEngine concept introduced in 6.0 */ + @Deprecated NamedQueryRepository getNamedQueryRepository(); /** @@ -137,7 +137,10 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory, Quer * Retrieve the {@link Type} resolver associated with this factory. * * @return The type resolver + * + * @deprecated (since 5.2) No replacement, access to and handling of Types will be much different in 6.0 */ + @Deprecated TypeResolver getTypeResolver(); /** @@ -208,7 +211,7 @@ default String[] getReturnAliases(String queryString) { /** - * @deprecated Just use {@link #getStatistics} (with covariant return here as {@link StatisticsImplementor}). + * @deprecated (since 5.2) Just use {@link #getStatistics} (with covariant return here as {@link StatisticsImplementor}). */ @Deprecated default StatisticsImplementor getStatisticsImplementor() { @@ -274,16 +277,11 @@ default ResultSetMappingDefinition getResultSetMapping(String name) { * * @return The dialect * - * @deprecated (since 5.2) instead, use this factory's {{@link #getServiceRegistry()}} -> - * {@link JdbcServices#getDialect()} + * @deprecated (since 5.2) instead, use {@link JdbcServices#getDialect()} */ @Deprecated default Dialect getDialect() { - if ( getServiceRegistry() == null ) { - throw new IllegalStateException( "Cannot determine dialect because serviceRegistry is null." ); - } - - return getServiceRegistry().getService( JdbcServices.class ).getDialect(); + return getJdbcServices().getDialect(); } /** @@ -296,7 +294,7 @@ default Dialect getDialect() { */ @Deprecated default SQLExceptionConverter getSQLExceptionConverter() { - return getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().getSqlExceptionConverter(); + return getJdbcServices().getSqlExceptionHelper().getSqlExceptionConverter(); } /** @@ -309,7 +307,7 @@ default SQLExceptionConverter getSQLExceptionConverter() { */ @Deprecated default SqlExceptionHelper getSQLExceptionHelper() { - return getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper(); + return getJdbcServices().getSqlExceptionHelper(); } /** @@ -411,138 +409,4 @@ default String getImportedClassName(String name) { EntityGraph findEntityGraphByName(String name); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Move to CacheImplementor calls - - /** - * Get a named second-level cache region - * - * @param regionName The name of the region to retrieve. - * - * @return The name of the region - * - * @deprecated (since 5.2) Use this factory's {@link #getCache()} reference - * to access Region via {@link CacheImplementor#determineEntityRegionAccessStrategy} or - * {@link CacheImplementor#determineCollectionRegionAccessStrategy} instead. - */ - @Deprecated - default Region getSecondLevelCacheRegion(String regionName) { - final EntityRegionAccessStrategy entityRegionAccess = getCache().getEntityRegionAccess( regionName ); - if ( entityRegionAccess != null ) { - return entityRegionAccess.getRegion(); - } - - final CollectionRegionAccessStrategy collectionRegionAccess = getCache().getCollectionRegionAccess( regionName ); - if ( collectionRegionAccess != null ) { - return collectionRegionAccess.getRegion(); - } - - return null; - } - - /** - * Find the "access strategy" for the named cache region. - * - * @param regionName The name of the region - * - * @return That region's "access strategy" - * - * - * @deprecated (since 5.2) Use this factory's {@link #getCache()} reference - * to access {@link CacheImplementor#determineEntityRegionAccessStrategy} or - * {@link CacheImplementor#determineCollectionRegionAccessStrategy} instead. - */ - @Deprecated - default RegionAccessStrategy getSecondLevelCacheRegionAccessStrategy(String regionName) { - final EntityRegionAccessStrategy entityRegionAccess = getCache().getEntityRegionAccess( regionName ); - if ( entityRegionAccess != null ) { - return entityRegionAccess; - } - - final CollectionRegionAccessStrategy collectionRegionAccess = getCache().getCollectionRegionAccess( regionName ); - if ( collectionRegionAccess != null ) { - return collectionRegionAccess; - } - - return null; - } - - /** - * Get a named natural-id cache region - * - * @param regionName The name of the region to retrieve. - * - * @return The region - * - * @deprecated (since 5.2) Use this factory's {@link #getCache()} -> - * {@link CacheImplementor#getNaturalIdCacheRegionAccessStrategy(String)} -> - * {@link NaturalIdRegionAccessStrategy#getRegion()} instead. - */ - @Deprecated - default Region getNaturalIdCacheRegion(String regionName) { - return getCache().getNaturalIdCacheRegionAccessStrategy( regionName ).getRegion(); - } - - /** - * Find the "access strategy" for the named naturalId cache region. - * - * @param regionName The region name - * - * @return That region's "access strategy" - * - * @deprecated (since 5.2) Use this factory's {@link #getCache()} -> - * {@link CacheImplementor#getNaturalIdCacheRegionAccessStrategy(String)} instead. - */ - @Deprecated - default RegionAccessStrategy getNaturalIdCacheRegionAccessStrategy(String regionName) { - return getCache().getNaturalIdCacheRegionAccessStrategy( regionName ); - } - - /** - * Get a map of all the second level cache regions currently maintained in - * this session factory. The map is structured with the region name as the - * key and the {@link Region} instances as the values. - * - * @return The map of regions - * - * @deprecated (since 5.2) with no direct replacement; use this factory's {@link #getCache()} reference - * to access cache objects as needed. - */ - @Deprecated - Map getAllSecondLevelCacheRegions(); - - /** - * Get the default query cache. - * - * @deprecated Use {@link CacheImplementor#getDefaultQueryCache()} instead - */ - @Deprecated - default QueryCache getQueryCache() { - return getCache().getDefaultQueryCache(); - } - - /** - * Get a particular named query cache, or the default cache - * - * @param regionName the name of the cache region, or null for the default query cache - * - * @return the existing cache, or a newly created cache if none by that region name - * - * @deprecated Use {@link CacheImplementor#getQueryCache(String)} instead - */ - @Deprecated - default QueryCache getQueryCache(String regionName) { - return getCache().getQueryCache( regionName ); - } - - /** - * Get the cache of table update timestamps - * - * @deprecated Use {@link CacheImplementor#getUpdateTimestampsCache()} instead - */ - @Deprecated - default UpdateTimestampsCache getUpdateTimestampsCache() { - return getCache().getUpdateTimestampsCache(); - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java index 685fc834e884..95e9776fc561 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java @@ -91,6 +91,9 @@ public interface SessionImplementor @Override NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMapping); + @Override + NativeQueryImplementor createSQLQuery(String sqlString); + @Override NativeQueryImplementor getNamedNativeQuery(String name); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionOwner.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionOwner.java index 903a3ee6a808..036181691e94 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionOwner.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionOwner.java @@ -23,7 +23,7 @@ @Deprecated public interface SessionOwner { /** - * Should session automatically be closed afterQuery transaction completion? + * Should session automatically be closed after transaction completion? * * @return {@literal true}/{@literal false} appropriately. */ 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 b6d3756bb179..809c5888fbb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.UUID; import javax.persistence.FlushModeType; +import javax.persistence.TransactionRequiredException; import org.hibernate.CacheMode; import org.hibernate.Criteria; @@ -21,11 +22,14 @@ import org.hibernate.ScrollMode; import org.hibernate.SharedSessionContract; import org.hibernate.Transaction; +import org.hibernate.cache.spi.CacheTransactionSynchronization; +import org.hibernate.cfg.Environment; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.loader.custom.CustomQuery; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.QueryProducerImplementor; @@ -116,6 +120,15 @@ public interface SharedSessionContractImplementor */ boolean isClosed(); + /** + * Checks whether the session is open or is waiting for auto-close + * + * @return {@code true} if the session is closed or if it's waiting for auto-close; {@code false} otherwise. + */ + default boolean isOpenOrWaitingForAutoClose() { + return !isClosed(); + } + /** * Performs a check whether the Session is open, and if not:

      *
    • marks current transaction (if one) for rollback only
    • @@ -140,9 +153,27 @@ default void checkOpen() { void markForRollbackOnly(); /** - * System time beforeQuery the start of the transaction + * A "timestamp" at or before the start of the current transaction. + * + * @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()} */ - long getTimestamp(); + long getTransactionStartTimestamp(); + + /** + * @deprecated (since 5.3) Use {@link #getTransactionStartTimestamp()} instead. + */ + @Deprecated + default long getTimestamp() { + return getTransactionStartTimestamp(); + } + + /** + * The current CacheTransactionContext associated with the Session. This may + * return {@code null} when the Session is not currently part of a transaction. + */ + CacheTransactionSynchronization getCacheTransactionSynchronization(); /** * Does this Session have an active Hibernate transaction @@ -150,6 +181,18 @@ default void checkOpen() { */ boolean isTransactionInProgress(); + /** + * Check if an active Transaction is necessary for the update operation to be executed. + * If an active Transaction is necessary but it is not then a TransactionRequiredException is raised. + * + * @param exceptionMessage, the message to use for the TransactionRequiredException + */ + default void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + if ( !isTransactionInProgress() ) { + throw new TransactionRequiredException( exceptionMessage ); + } + } + /** * Provides access to the underlying transaction or creates a new transaction if * one does not already exist or is active. This is primarily for internal or @@ -177,7 +220,7 @@ default void checkOpen() { Interceptor getInterceptor(); /** - * Enable/disable automatic cache clearing from afterQuery transaction + * Enable/disable automatic cache clearing from after transaction * completion (for EJB3) */ void setAutoClear(boolean enabled); @@ -209,6 +252,7 @@ Object internalLoad(String entityName, Serializable id, boolean eager, boolean n */ Object immediateLoad(String entityName, Serializable id) throws HibernateException; + /** * Execute a find() query */ @@ -399,6 +443,10 @@ int executeNativeUpdate(NativeSQLQuerySpecification specification, QueryParamete boolean isAutoCloseSessionEnabled(); + default boolean isQueryParametersValidationEnabled(){ + return getFactory().getSessionFactoryOptions().isQueryParametersValidationEnabled(); + } + /** * Get the load query influencers associated with this session. * @@ -408,4 +456,28 @@ int executeNativeUpdate(NativeSQLQuerySpecification specification, QueryParamete LoadQueryInfluencers getLoadQueryInfluencers(); ExceptionConverter getExceptionConverter(); + + /** + * Get the currently configured JDBC batch size either at the Session-level or SessionFactory-level. + * + * If the Session-level JDBC batch size was not configured, return the SessionFactory-level one. + * + * @return Session-level or or SessionFactory-level JDBC batch size. + * + * @since 5.2 + * + * @see org.hibernate.boot.spi.SessionFactoryOptions#getJdbcBatchSize + * @see org.hibernate.boot.SessionFactoryBuilder#applyJdbcBatchSize + */ + default Integer getConfiguredJdbcBatchSize() { + final Integer sessionJdbcBatchSize = getJdbcBatchSize(); + + return sessionJdbcBatchSize == null ? + ConfigurationHelper.getInt( + Environment.STATEMENT_BATCH_SIZE, + getFactory().getProperties(), + 1 + ) : + sessionJdbcBatchSize; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java index a0622a8fd49b..626539c9c236 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java @@ -177,7 +177,7 @@ public String toSubselectString(String ukname) { ? StringHelper.qualify( alias, loadable.getIdentifierColumnNames() ) : ( (PropertyMapping) loadable ).toColumns( alias, ukname ); - return "select " + StringHelper.join( ", ", joinColumns ) + queryString; + return "select " + String.join( ", ", joinColumns ) + queryString; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/VersionValue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/VersionValue.java index 597fb5243141..0a719b7cacec 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/VersionValue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/VersionValue.java @@ -13,7 +13,7 @@ import org.jboss.logging.Logger; /** - * A strategy for determining if a version value is an version of + * A strategy for determining if a version value is a version of * a new transient instance or a previously persistent transient instance. * The strategy is determined by the unsaved-value attribute in * the mapping file. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java index 3270406a4565..42879d569e52 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java @@ -12,7 +12,9 @@ import org.hibernate.TransactionException; import org.hibernate.engine.spi.ExceptionConverter; import org.hibernate.engine.transaction.spi.TransactionImplementor; +import org.hibernate.internal.AbstractSharedSessionContract; import org.hibernate.internal.CoreLogging; +import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionStatus; @@ -29,18 +31,39 @@ public class TransactionImpl implements TransactionImplementor { private final TransactionCoordinator transactionCoordinator; private final ExceptionConverter exceptionConverter; + private final JpaCompliance jpaCompliance; + private final AbstractSharedSessionContract session; + private TransactionDriver transactionDriverControl; - public TransactionImpl(TransactionCoordinator transactionCoordinator, ExceptionConverter exceptionConverter) { + public TransactionImpl( + TransactionCoordinator transactionCoordinator, + ExceptionConverter exceptionConverter, + AbstractSharedSessionContract session) { this.transactionCoordinator = transactionCoordinator; this.exceptionConverter = exceptionConverter; - transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + this.jpaCompliance = session.getFactory().getSessionFactoryOptions().getJpaCompliance(); + this.session = session; + + if ( session.isOpen() && transactionCoordinator.isActive() ) { + this.transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + } + else { + LOG.debug( "TransactionImpl created on closed Session/EntityManager" ); + } + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == %s", + jpaCompliance.isJpaTransactionComplianceEnabled() + ); + } } @Override public void begin() { - if ( !transactionCoordinator.isActive() ) { - throw new TransactionException( "Cannot begin Transaction on closed Session/EntityManager" ); + if ( !session.isOpen() ) { + throw new IllegalStateException( "Cannot begin Transaction on closed Session/EntityManager" ); } if ( transactionDriverControl == null ) { @@ -49,21 +72,34 @@ public void begin() { // per-JPA if ( isActive() ) { - throw new IllegalStateException( "Transaction already active" ); + if ( jpaCompliance.isJpaTransactionComplianceEnabled() + || !transactionCoordinator.getTransactionCoordinatorBuilder().isJta() ) { + throw new IllegalStateException( "Transaction already active" ); + } + else { + return; + } } LOG.debug( "begin" ); + this.transactionDriverControl.begin(); } @Override public void commit() { - if ( !isActive() ) { + if ( !isActive( true ) ) { // allow MARKED_ROLLBACK to propagate through to transactionDriverControl + // the boolean passed to isActive indicates whether MARKED_ROLLBACK should be + // considered active + // + // essentially here we have a transaction that is not active and + // has not been marked for rollback only throw new IllegalStateException( "Transaction not successfully started" ); } LOG.debug( "committing" ); + try { internalGetTransactionDriverControl().commit(); } @@ -82,7 +118,15 @@ public TransactionDriver internalGetTransactionDriverControl() { @Override public void rollback() { - // todo : may need a "JPA compliant" flag here + if ( !isActive() ) { + if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) { + + throw new IllegalStateException( + "JPA compliance dictates throwing IllegalStateException when #rollback " + + "is called on non-active transaction" + ); + } + } TransactionStatus status = getStatus(); if ( status == TransactionStatus.ROLLED_BACK || status == TransactionStatus.NOT_ACTIVE ) { @@ -96,6 +140,7 @@ public void rollback() { } LOG.debug( "rolling back" ); + if ( status != TransactionStatus.FAILED_COMMIT || allowFailedCommitToPhysicallyRollback() ) { internalGetTransactionDriverControl().rollback(); } @@ -104,13 +149,19 @@ public void rollback() { @Override public boolean isActive() { // old behavior considered TransactionStatus#MARKED_ROLLBACK as active +// return isActive( jpaCompliance.isJpaTransactionComplianceEnabled() ? false : true ); return isActive( true ); } @Override public boolean isActive(boolean isMarkedForRollbackConsideredActive) { if ( transactionDriverControl == null ) { - transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + if ( session.isOpen() ) { + transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + } + else { + return false; + } } return transactionDriverControl.isActive( isMarkedForRollbackConsideredActive ); } @@ -118,7 +169,12 @@ public boolean isActive(boolean isMarkedForRollbackConsideredActive) { @Override public TransactionStatus getStatus() { if ( transactionDriverControl == null ) { - transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + if ( session.isOpen() ) { + transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + } + else { + return TransactionStatus.NOT_ACTIVE; + } } return transactionDriverControl.getStatus(); } @@ -138,13 +194,51 @@ public int getTimeout() { return this.transactionCoordinator.getTimeOut(); } + @Override + public void markRollbackOnly() { + // this is the Hibernate-specific API, whereas #setRollbackOnly is the + // JPA-defined API. In our opinion it is much more user-friendly to + // always allow user/integration to indicate that the transaction + // should not be allowed to commit. + // + // However.. should only "do something" on an active transaction + if ( isActive() ) { + internalGetTransactionDriverControl().markRollbackOnly(); + } + } + @Override public void setRollbackOnly() { - internalGetTransactionDriverControl().markRollbackOnly(); + if ( !isActive() ) { + // Since this is the JPA-defined one, we make sure the txn is active first + // so long as compliance (JpaCompliance) has not been defined to disable + // that check - making this active more like Hibernate's #markRollbackOnly + if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) { + throw new IllegalStateException( + "JPA compliance dictates throwing IllegalStateException when #setRollbackOnly " + + "is called on non-active transaction" + ); + } + else { + LOG.debug( "#setRollbackOnly called on a not-active transaction" ); + } + } + else { + markRollbackOnly(); + } } @Override public boolean getRollbackOnly() { + if ( !isActive() ) { + if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) { + throw new IllegalStateException( + "JPA compliance dictates throwing IllegalStateException when #getRollbackOnly " + + "is called on non-active transaction" + ); + } + } + return getStatus() == TransactionStatus.MARKED_ROLLBACK; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java index 61a87856c2e2..1e46283c4d92 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java @@ -13,18 +13,29 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformException; /** - * Return a standalone JTA transaction manager for JBoss Transactions - * Known to work for org.jboss.jbossts:jbossjta:4.9.0.GA + * Return a standalone JTA transaction manager for JBoss (Arjuna) Transactions or WildFly transaction client + * Known to work for org.jboss.jbossts:jbossjta:4.9.0.GA as well as WildFly 11+ * * @author Emmanuel Bernard * @author Steve Ebersole */ public class JBossStandAloneJtaPlatform extends AbstractJtaPlatform { + public static final String JBOSS_TM_CLASS_NAME = "com.arjuna.ats.jta.TransactionManager"; public static final String JBOSS_UT_CLASS_NAME = "com.arjuna.ats.jta.UserTransaction"; + private static final WildFlyStandAloneJtaPlatform wildflyBasedAlternative = new WildFlyStandAloneJtaPlatform(); + @Override protected TransactionManager locateTransactionManager() { + //Try WildFly first as it's the "new generation": + try { + return wildflyBasedAlternative.locateTransactionManager(); + } + catch ( Exception ignore) { + // ignore and look for Arjuna class + } + try { final Class jbossTmClass = serviceRegistry() .getService( ClassLoaderService.class ) @@ -38,6 +49,14 @@ protected TransactionManager locateTransactionManager() { @Override protected UserTransaction locateUserTransaction() { + //Try WildFly first as it's the "new generation": + try { + return wildflyBasedAlternative.locateUserTransaction(); + } + catch ( Exception ignore) { + // ignore and look for Arjuna class + } + try { final Class jbossUtClass = serviceRegistry() .getService( ClassLoaderService.class ) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java index 56e81d68f3f5..f570c888615b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java @@ -37,11 +37,23 @@ public Class getServiceInitiated() { @SuppressWarnings( {"unchecked"}) public JtaPlatform initiateService(Map configurationValues, ServiceRegistryImplementor registry) { final Object setting = configurationValues.get( AvailableSettings.JTA_PLATFORM ); - final JtaPlatform platform = registry.getService( StrategySelector.class ).resolveStrategy( JtaPlatform.class, setting ); + JtaPlatform platform = registry.getService( StrategySelector.class ).resolveStrategy( JtaPlatform.class, setting ); + + if ( platform == null ) { + LOG.debugf( "No JtaPlatform was specified, checking resolver" ); + platform = registry.getService( JtaPlatformResolver.class ).resolveJtaPlatform( configurationValues, registry ); + } + if ( platform == null ) { LOG.debugf( "No JtaPlatform was specified, checking resolver" ); - return registry.getService( JtaPlatformResolver.class ).resolveJtaPlatform( configurationValues, registry ); + platform = getFallbackProvider( configurationValues, registry ); } + return platform; } + + @SuppressWarnings({"WeakerAccess", "unused"}) + protected JtaPlatform getFallbackProvider(Map configurationValues, ServiceRegistryImplementor registry) { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/SapNetWeaverJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/SapNetWeaverJtaPlatform.java new file mode 100644 index 000000000000..1fe80f804bfa --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/SapNetWeaverJtaPlatform.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.engine.transaction.jta.platform.internal; + +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +/** + * {@link org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform} implementation for SAP NetWeaver + * + * @author Lukas Pradel + */ +public class SapNetWeaverJtaPlatform extends AbstractJtaPlatform { + public static final String TM_NAME = "TransactionManager"; + public static final String UT_NAME = "UserTransaction"; + + @Override + protected TransactionManager locateTransactionManager() { + return (TransactionManager) jndiService().locate( TM_NAME ); + } + + @Override + protected UserTransaction locateUserTransaction() { + return (UserTransaction) jndiService().locate( UT_NAME ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/StandardJtaPlatformResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/StandardJtaPlatformResolver.java index bd8db67e0586..a04af918a0b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/StandardJtaPlatformResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/StandardJtaPlatformResolver.java @@ -45,6 +45,25 @@ public JtaPlatform resolveJtaPlatform(Map configurationValues, ServiceRegistryIm // IMPL NOTE : essentially we attempt Class lookups and use the exceptions from the class(es) not // being found as the indicator + // first try loading WildFly Transaction Client + try { + classLoaderService.classForName( WildFlyStandAloneJtaPlatform.WILDFLY_TM_CLASS_NAME ); + classLoaderService.classForName( WildFlyStandAloneJtaPlatform.WILDFLY_UT_CLASS_NAME ); + + // we know that the WildFly Transaction Client TM classes are available + // if neither of these look-ups resulted in an error (no such class), then WildFly Transaction Client TM is available on + // the classpath. + // + // todo : we cannot really distinguish between the need for JBossStandAloneJtaPlatform versus JBossApServerJtaPlatform + // but discussions with David led to the JtaPlatformProvider solution above, so inside JBoss AS we + // should be relying on that. + // Note that on WF13+, we can expect org.jboss.as.jpa.hibernate5.service.WildFlyCustomJtaPlatformInitiator to choose + // the WildFlyCustomJtaPlatform, unless the application has disabled WildFlyCustomJtaPlatformInitiator. + return new WildFlyStandAloneJtaPlatform(); + } + catch (ClassLoadingException ignore) { + } + // JBoss ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ try { @@ -86,8 +105,16 @@ public JtaPlatform resolveJtaPlatform(Map configurationValues, ServiceRegistryIm } catch (ClassLoadingException ignore) { } - - // WebSphere ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // WebSphere Liberty ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + try { + classLoaderService.classForName(WebSphereLibertyJtaPlatform.TMF_CLASS_NAME); + return new WebSphereLibertyJtaPlatform(); + } + catch (ClassLoadingException ignore) { + } + + // WebSphere traditional ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for ( WebSphereJtaPlatform.WebSphereEnvironment webSphereEnvironment : WebSphereJtaPlatform.WebSphereEnvironment.values() ) { try { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereExtendedJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereExtendedJtaPlatform.java index 4c436214762b..9b99d03a41f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereExtendedJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereExtendedJtaPlatform.java @@ -32,7 +32,7 @@ *
    • the WAS container in which Hibernate will be utilized
    • *
    *

    - * This class is reported to work on WAS version 6 in any of the standard J2EE/JEE component containers. + * This class is reported to work on WAS version 6 in any of the standard J2EE/Java EE component containers. * * @author Gavin King * @author TransactionManagerFactory = serviceRegistry() + .getService( ClassLoaderService.class ) + .classForName( TMF_CLASS_NAME ); + return (TransactionManager) TransactionManagerFactory.getMethod("getTransactionManager").invoke(null); + } + catch ( Exception e ) { + throw new JtaPlatformException( "Could not obtain WebSphere Liberty transaction manager instance", e ); + } + } + + @Override + protected UserTransaction locateUserTransaction() { + return (UserTransaction) jndiService().locate( UT_NAME ); + } + + public boolean canRegisterSynchronization() { + try { + return getCurrentStatus() == Status.STATUS_ACTIVE; + } + catch (SystemException x) { + throw new RuntimeException(x); + } + } + + public int getCurrentStatus() throws SystemException { + return retrieveTransactionManager().getStatus(); + } + + public Object getTransactionIdentifier(Transaction transaction) { + return transaction; + } + + public void registerSynchronization(Synchronization synchronization) { + try { + retrieveTransactionManager().getTransaction().registerSynchronization(synchronization); + } + catch ( RollbackException | SystemException x ) { + throw new RuntimeException(x); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WildFlyStandAloneJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WildFlyStandAloneJtaPlatform.java new file mode 100644 index 000000000000..64e2a80c708f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WildFlyStandAloneJtaPlatform.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.engine.transaction.jta.platform.internal; + +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformException; + +/** + * Return a standalone JTA transaction manager for WildFly transaction client + * Known to work for WildFly 13+ + * + * @author Scott Marlow + */ +public class WildFlyStandAloneJtaPlatform extends AbstractJtaPlatform { + public static final String WILDFLY_TM_CLASS_NAME = "org.wildfly.transaction.client.ContextTransactionManager"; + public static final String WILDFLY_UT_CLASS_NAME = "org.wildfly.transaction.client.LocalUserTransaction"; + + @Override + protected TransactionManager locateTransactionManager() { + try { + final Class wildflyTmClass = serviceRegistry() + .getService( ClassLoaderService.class ) + .classForName( WILDFLY_TM_CLASS_NAME ); + return (TransactionManager) wildflyTmClass.getMethod( "getInstance" ).invoke( null ); + } + catch (Exception e) { + throw new JtaPlatformException( + "Could not obtain WildFly Transaction Client transaction manager instance", + e + ); + } + } + + @Override + protected UserTransaction locateUserTransaction() { + try { + final Class jbossUtClass = serviceRegistry() + .getService( ClassLoaderService.class ) + .classForName( WILDFLY_UT_CLASS_NAME ); + return (UserTransaction) jbossUtClass.getMethod( "getInstance" ).invoke( null ); + } + catch (Exception e) { + throw new JtaPlatformException( + "Could not obtain WildFly Transaction Client user transaction instance", + e + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatform.java index 23c734ad3c22..c795555ecb16 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatform.java @@ -39,8 +39,7 @@ public interface JtaPlatform extends Service { * Determine an identifier for the given transaction appropriate for use in caching/lookup usages. *

    * Generally speaking the transaction itself will be returned here. This method was added specifically - * for use in WebSphere and other unfriendly JEE containers (although WebSphere is still the only known - * such brain-dead, sales-driven impl). + * for use in WebSphere and other unfriendly Java EE containers. * * @param transaction The transaction to be identified. * @return An appropriate identifier diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index 732ebf68f8f8..6fd35ae0fb0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -7,6 +7,7 @@ package org.hibernate.event.internal; import java.io.Serializable; +import java.util.IdentityHashMap; import java.util.Map; import org.hibernate.HibernateException; @@ -28,6 +29,7 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.service.spi.JpaBootstrapSensitive; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.FlushEntityEvent; @@ -46,10 +48,17 @@ * * @author Steve Ebersole */ -public abstract class AbstractFlushingEventListener implements Serializable { +public abstract class AbstractFlushingEventListener implements JpaBootstrapSensitive, Serializable { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, AbstractFlushingEventListener.class.getName() ); + private boolean jpaBootstrap; + + @Override + public void wasJpaBootstrap(boolean wasJpaBootstrap) { + this.jpaBootstrap = wasJpaBootstrap; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Pre-flushing section // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -155,11 +164,21 @@ private void cascadeOnFlush(EventSource session, EntityPersister persister, Obje } protected Object getAnything() { - return null; + if ( jpaBootstrap ) { + return new IdentityHashMap( 10 ); + } + else { + return null; + } } protected CascadingAction getCascadingAction() { - return CascadingActions.SAVE_UPDATE; + if ( jpaBootstrap ) { + return CascadingActions.PERSIST_ON_FLUSH; + } + else { + return CascadingActions.SAVE_UPDATE; + } } /** @@ -174,7 +193,7 @@ private void prepareCollectionFlushes(PersistenceContext persistenceContext) thr LOG.debug( "Dirty checking collections" ); for ( Map.Entry entry : - IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() )) { + IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() ) ) { entry.getValue().preFlush( entry.getKey() ); } } @@ -250,7 +269,7 @@ private int flushCollections(final EventSource session, final PersistenceContext ActionQueue actionQueue = session.getActionQueue(); for ( Map.Entry me : - IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() )) { + IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() ) ) { PersistentCollection coll = me.getKey(); CollectionEntry ce = me.getValue(); @@ -331,7 +350,7 @@ protected void performExecutions(EventSource session) { try { session.getJdbcCoordinator().flushBeginning(); session.getPersistenceContext().setFlushing( true ); - // we need to lock the collection caches beforeQuery executing entity inserts/updates in order to + // we need to lock the collection caches before executing entity inserts/updates in order to // account for bi-directional associations session.getActionQueue().prepareActions(); session.getActionQueue().executeActions(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractLockUpgradeEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractLockUpgradeEventListener.java index 44c197019e10..fcffbcd4cd4d 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractLockUpgradeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractLockUpgradeEventListener.java @@ -9,7 +9,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.ObjectDeletedException; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.Status; @@ -61,12 +61,12 @@ protected void upgradeLock(Object object, EntityEntry entry, LockOptions lockOpt ); } - final boolean cachingEnabled = persister.hasCache(); + final boolean cachingEnabled = persister.canWriteToCache(); SoftLock lock = null; Object ck = null; try { if ( cachingEnabled ) { - EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + EntityDataAccess cache = persister.getCacheAccessStrategy(); ck = cache.generateCacheKey( entry.getId(), persister, source.getFactory(), source.getTenantIdentifier() ); lock = cache.lockItem( source, ck, entry.getVersion() ); } 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 8d60ec715293..76ce2d161164 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 @@ -31,23 +31,33 @@ import org.hibernate.id.IdentifierGeneratorHelper; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; /** - * A convenience bas class for listeners responding to save events. + * A convenience base class for listeners responding to save events. * * @author Steve Ebersole. */ -public abstract class AbstractSaveEventListener extends AbstractReassociateEventListener { +public abstract class AbstractSaveEventListener + extends AbstractReassociateEventListener + implements CallbackRegistryConsumer { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractSaveEventListener.class ); - public static enum EntityState { + public enum EntityState { PERSISTENT, TRANSIENT, DETACHED, DELETED } + private CallbackRegistry callbackRegistry; + + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + /** * Prepares the save call using the given requested id. * @@ -65,6 +75,8 @@ protected Serializable saveWithRequestedId( String entityName, Object anything, EventSource source) { + callbackRegistry.preCreate( entity ); + return performSave( entity, requestedId, @@ -84,7 +96,7 @@ protected Serializable saveWithRequestedId( * @param anything Generally cascade-specific information. * @param source The session which is the source of this save event. * @param requiresImmediateIdAccess does the event context require - * access to the identifier immediately afterQuery execution of this method (if + * access to the identifier immediately after execution of this method (if * not, post-insert style id generators may be postponed if we are outside * a transaction). * @@ -97,6 +109,8 @@ protected Serializable saveWithGeneratedId( Object anything, EventSource source, boolean requiresImmediateIdAccess) { + callbackRegistry.preCreate( entity ); + if ( entity instanceof SelfDirtinessTracker ) { ( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes(); } @@ -137,7 +151,7 @@ else if ( generatedId == IdentifierGeneratorHelper.POST_INSERT_INDICATOR ) { * @param anything Generally cascade-specific information. * @param source The session from which the event originated. * @param requiresImmediateIdAccess does the event context require - * access to the identifier immediately afterQuery execution of this method (if + * access to the identifier immediately after execution of this method (if * not, post-insert style id generators may be postponed if we are outside * a transaction). * @@ -191,7 +205,7 @@ protected Serializable performSave( } protected boolean invokeSaveLifecycle(Object entity, EntityPersister persister, EventSource source) { - // Sub-insertions should occur beforeQuery containing insertion so + // Sub-insertions should occur before containing insertion so // Try to do the callback now if ( persister.implementsLifecycle() ) { LOG.debug( "Calling onSave()" ); @@ -214,7 +228,7 @@ protected boolean invokeSaveLifecycle(Object entity, EntityPersister persister, * @param anything Generally cascade-specific information. * @param source The session which is the source of the current event. * @param requiresImmediateIdAccess Is access to the identifier required immediately - * afterQuery the completion of the save? persist(), for example, does not require this... + * after the completion of the save? persist(), for example, does not require this... * * @return The id used to save the entity; may be null depending on the * type of id generator used and the requiresImmediateIdAccess value @@ -230,12 +244,12 @@ protected Serializable performSaveOrReplicate( Serializable id = key == null ? null : key.getIdentifier(); - boolean inTxn = source.isTransactionInProgress(); - boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess; + boolean inTrx = source.isTransactionInProgress(); + boolean shouldDelayIdentityInserts = !inTrx && !requiresImmediateIdAccess; // Put a placeholder in entries, so we don't recurse back and try to save() the - // same object again. QUESTION: should this be done beforeQuery onSave() is called? - // likewise, should it be done beforeQuery onUpdate()? + // same object again. QUESTION: should this be done before onSave() is called? + // likewise, should it be done before onUpdate()? EntityEntry original = source.getPersistenceContext().addEntry( entity, Status.SAVING, @@ -336,7 +350,7 @@ protected Map getMergeMap(Object anything) { * After the save, will te version number be incremented * if the instance is modified? * - * @return True if the version will be incremented on an entity change afterQuery save; + * @return True if the version will be incremented on an entity change after save; * false otherwise. */ protected boolean isVersionIncrementDisabled() { @@ -349,7 +363,7 @@ protected boolean visitCollectionsBeforeSave( Object[] values, Type[] types, EventSource source) { - WrapVisitor visitor = new WrapVisitor( source ); + WrapVisitor visitor = new WrapVisitor( entity, id, source ); // substitutes into values by side-effect visitor.processEntityPropertyValues( values, types ); return visitor.isSubstitutionRequired(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java index ff0d77e54f02..04e492cd3b14 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java @@ -8,6 +8,7 @@ import org.hibernate.FlushMode; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.ActionQueue; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.AutoFlushEventListener; import org.hibernate.event.spi.EventSource; @@ -38,12 +39,14 @@ public void onAutoFlush(AutoFlushEvent event) throws HibernateException { source.getEventListenerManager().partialFlushStart(); if ( flushMightBeNeeded(source) ) { - // Need to get the number of collection removals beforeQuery flushing to executions + // Need to get the number of collection removals before flushing to executions // (because flushing to executions can add collection removal actions to the action queue). - final int oldSize = source.getActionQueue().numberOfCollectionRemovals(); - flushEverythingToExecutions(event); + final ActionQueue actionQueue = source.getActionQueue(); + final int oldSize = actionQueue.numberOfCollectionRemovals(); + flushEverythingToExecutions( event ); if ( flushIsReallyNeeded(event, source) ) { LOG.trace( "Need to execute flush" ); + event.setFlushRequired( true ); // note: performExecutions() clears all collectionXxxxtion // collections (the collection actions) in the session @@ -58,10 +61,9 @@ public void onAutoFlush(AutoFlushEvent event) throws HibernateException { } else { LOG.trace( "Don't need to execute flush" ); - source.getActionQueue().clearFromFlushNeededCheck( oldSize ); + event.setFlushRequired( false ); + actionQueue.clearFromFlushNeededCheck( oldSize ); } - - event.setFlushRequired( flushIsReallyNeeded( event, source ) ); } } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java index 3d6b5202efbb..f04b7504b010 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java @@ -25,12 +25,15 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.Status; +import org.hibernate.event.service.spi.JpaBootstrapSensitive; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.DeleteEventListener; import org.hibernate.event.spi.EventSource; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.IdentitySet; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.Type; @@ -42,9 +45,22 @@ * * @author Steve Ebersole */ -public class DefaultDeleteEventListener implements DeleteEventListener { +public class DefaultDeleteEventListener implements DeleteEventListener, CallbackRegistryConsumer, JpaBootstrapSensitive { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultDeleteEventListener.class ); + private CallbackRegistry callbackRegistry; + private boolean jpaBootstrap; + + @Override + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + + @Override + public void wasJpaBootstrap(boolean wasJpaBootstrap) { + this.jpaBootstrap = wasJpaBootstrap; + } + /** * Handle the given delete event. * @@ -115,6 +131,7 @@ public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateE persister, false ); + persister.afterReassociate( entity, source ); } else { LOG.trace( "Deleting a persistent instance" ); @@ -163,10 +180,22 @@ public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateE * @param event The event. */ protected void performDetachedEntityDeletionCheck(DeleteEvent event) { + if ( jpaBootstrap ) { + disallowDeletionOfDetached( event ); + } // ok in normal Hibernate usage to delete a detached entity; JPA however // forbids it, thus this is a hook for HEM to affect this behavior } + private void disallowDeletionOfDetached(DeleteEvent event) { + EventSource source = event.getSession(); + String entityName = event.getEntityName(); + EntityPersister persister = source.getEntityPersister( entityName, event.getObject() ); + Serializable id = persister.getIdentifier( event.getObject(), source ); + entityName = entityName == null ? source.guessEntityName( event.getObject() ) : entityName; + throw new IllegalArgumentException("Removing a detached instance "+ entityName + "#" + id); + } + /** * We encountered a delete request on a transient instance. *

    @@ -250,15 +279,14 @@ protected final void deleteEntity( propTypes ); - // beforeQuery any callbacks, etc, so subdeletions see that this deletion happened first + // before any callbacks, etc, so subdeletions see that this deletion happened first persistenceContext.setEntryStatus( entityEntry, Status.DELETED ); final EntityKey key = session.generateEntityKey( entityEntry.getId(), persister ); cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities ); - new ForeignKeys.Nullifier( entity, true, false, session ) - .nullifyTransientReferences( entityEntry.getDeletedState(), propTypes ); - new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, true ); + new ForeignKeys.Nullifier( entity, true, false, session, persister ).nullifyTransientReferences( entityEntry.getDeletedState() ); + new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, Nullability.NullabilityCheckType.DELETE ); persistenceContext.getNullifiableEntityKeys().add( key ); if ( isOrphanRemovalBeforeUpdates ) { @@ -277,7 +305,7 @@ protected final void deleteEntity( ); } else { - // Ensures that containing deletions happen beforeQuery sub-deletions + // Ensures that containing deletions happen before sub-deletions session.getActionQueue().addAction( new EntityDeleteAction( entityEntry.getId(), @@ -293,7 +321,7 @@ protected final void deleteEntity( cascadeAfterDelete( session, persister, entity, transientEntities ); - // the entry will be removed afterQuery the flush, and will no longer + // the entry will be removed after the flush, and will no longer // override the stale snapshot // This is now handled by removeEntity() in EntityDeleteAction //persistenceContext.removeDatabaseSnapshot(key); @@ -310,6 +338,8 @@ private Object[] createDeletedState(EntityPersister persister, Object[] currentS } protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) { + callbackRegistry.preRemove( entity ); + if ( persister.implementsLifecycle() ) { LOG.debug( "Calling onDelete()" ); if ( ( (Lifecycle) entity ).onDelete( session ) ) { 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 7d1f08136209..1abf516c460a 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 @@ -73,9 +73,8 @@ public void onEvict(EvictEvent event) throws HibernateException { li.unsetSession(); } else { - EntityEntry e = persistenceContext.removeEntry( object ); + EntityEntry e = persistenceContext.getEntry( object ); if ( e != null ) { - persistenceContext.removeEntity( e.getEntityKey() ); doEvict( object, e.getEntityKey(), e.getPersister(), source ); } else { @@ -119,7 +118,7 @@ protected void doEvict( // remove all collections for the entity from the session-level cache if ( persister.hasCollections() ) { - new EvictVisitor( session ).process( object, persister ); + new EvictVisitor( session, object ).process( object, persister ); } // remove any snapshot, not really for memory management purposes, but @@ -127,6 +126,9 @@ protected void doEvict( // EntityEntry to take precedence // This is now handled by removeEntity() //session.getPersistenceContext().removeDatabaseSnapshot(key); + + session.getPersistenceContext().removeEntity( key ); + session.getPersistenceContext().removeEntry( object ); Cascade.cascade( CascadingActions.EVICT, CascadePoint.AFTER_EVICT, session, persister, object ); } 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 old mode 100755 new mode 100644 index 01e7faf534ab..0ac4059860ee --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -12,14 +12,18 @@ import org.hibernate.AssertionFailure; import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.HibernateException; -import org.hibernate.Session; +import org.hibernate.SessionFactory; import org.hibernate.StaleObjectStateException; import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.action.internal.EntityUpdateAction; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; @@ -29,6 +33,9 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; +import org.hibernate.metadata.ClassMetadata; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.Type; @@ -38,9 +45,16 @@ * * @author Gavin King */ -public class DefaultFlushEntityEventListener implements FlushEntityEventListener { +public class DefaultFlushEntityEventListener implements FlushEntityEventListener, CallbackRegistryConsumer { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultFlushEntityEventListener.class ); + private CallbackRegistry callbackRegistry; + + @Override + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + /** * make sure user didn't mangle the id */ @@ -57,7 +71,7 @@ public void checkId(Object object, EntityPersister persister, Serializable id, S Serializable oid = persister.getIdentifier( object, session ); if ( id == null ) { - throw new AssertionFailure( "null id in " + persister.getEntityName() + " entry (don't flush the Session afterQuery an exception occurs)" ); + throw new AssertionFailure( "null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)" ); } if ( !persister.getIdentifierType().isEqual( id, oid, session.getFactory() ) ) { throw new HibernateException( @@ -71,14 +85,24 @@ public void checkId(Object object, EntityPersister persister, Serializable id, S private void checkNaturalId( EntityPersister persister, + Object entity, EntityEntry entry, Object[] current, Object[] loaded, SessionImplementor session) { + if ( entity instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // EARLY EXIT!!! + // nothing to check - the entity is an un-initialized enhancement-as-proxy reference + return; + } + } + if ( persister.hasNaturalIdentifier() && entry.getStatus() != Status.READ_ONLY ) { if ( !persister.getEntityMetamodel().hasImmutableNaturalId() ) { - // SHORT-CUT: if the natural id is mutable (!immutable), no need to do the below checks // EARLY EXIT!!! + // the natural id is mutable (!immutable), no need to do the below checks return; } @@ -101,7 +125,7 @@ private void checkNaturalId( if ( !propertyType.isEqual( current[naturalIdentifierPropertyIndex], snapshot[i] ) ) { throw new HibernateException( String.format( - "An immutable natural identifier of entity %s was altered from %s to %s", + "An immutable natural identifier of entity %s was altered from `%s` to `%s`", persister.getEntityName(), propertyTypes[naturalIdentifierPropertyIndex].toLoggableString( snapshot[i], @@ -177,7 +201,7 @@ else if ( !mightBeDirty && loadedState != null ) { // grab its current state values = persister.getPropertyValues( entity ); - checkNaturalId( persister, entry, values, loadedState, session ); + checkNaturalId( persister, entity, entry, values, loadedState, session ); } return values; } @@ -196,7 +220,7 @@ private boolean wrapCollections( // NOTE: we need to do the wrap here even if its not "dirty", // because collections need wrapping but changes to _them_ // don't dirty the container. Also, for versioned data, we - // need to wrap beforeQuery calling searchForDirtyCollections + // need to wrap before calling searchForDirtyCollections WrapVisitor visitor = new WrapVisitor( session ); // substitutes into values by side-effect @@ -321,15 +345,8 @@ protected boolean handleInterception(FlushEntityEvent event) { final boolean intercepted = invokeInterceptor( session, entity, entry, values, persister ); //now we might need to recalculate the dirtyProperties array - if ( intercepted && event.isDirtyCheckPossible() && !event.isDirtyCheckHandledByInterceptor() ) { - int[] dirtyProperties; - if ( event.hasDatabaseSnapshot() ) { - dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session ); - } - else { - dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session ); - } - event.setDirtyProperties( dirtyProperties ); + if ( intercepted && event.isDirtyCheckPossible() ) { + dirtyCheck( event ); } return intercepted; @@ -341,7 +358,14 @@ protected boolean invokeInterceptor( EntityEntry entry, final Object[] values, EntityPersister persister) { - return session.getInterceptor().onFlushDirty( + boolean isDirty = false; + if ( entry.getStatus() != Status.DELETED ) { + if ( callbackRegistry.preUpdate( entity ) ) { + isDirty = copyState( entity, persister.getPropertyTypes(), values, session.getFactory() ); + } + } + + final boolean answerFromInterceptor = session.getInterceptor().onFlushDirty( entity, entry.getId(), values, @@ -349,6 +373,25 @@ protected boolean invokeInterceptor( persister.getPropertyNames(), persister.getPropertyTypes() ); + + return answerFromInterceptor || isDirty; + } + + private boolean copyState(Object entity, Type[] types, Object[] state, SessionFactory sf) { + // copy the entity state into the state array and return true if the state has changed + ClassMetadata metadata = sf.getClassMetadata( entity.getClass() ); + Object[] newState = metadata.getPropertyValues( entity ); + int size = newState.length; + boolean isDirty = false; + for ( int index = 0; index < size; index++ ) { + if ( ( state[index] == LazyPropertyInitializer.UNFETCHED_PROPERTY && + newState[index] != LazyPropertyInitializer.UNFETCHED_PROPERTY ) || + ( state[index] != newState[index] && !types[index].isEqual( state[index], newState[index] ) ) ) { + isDirty = true; + state[index] = newState[index]; + } + } + return isDirty; } /** @@ -480,16 +523,26 @@ protected void dirtyCheck(final FlushEntityEvent event) throws HibernateExceptio if ( dirtyProperties == null ) { if ( entity instanceof SelfDirtinessTracker ) { if ( ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes() ) { - dirtyProperties = persister.resolveAttributeIndexes( ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() ); + int[] dirty = persister.resolveAttributeIndexes( ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() ); + + // HHH-12051 - filter non-updatable attributes + // TODO: add Updateability to EnhancementContext and skip dirty tracking of those attributes + int count = 0; + for ( int i : dirty ) { + if ( persister.getPropertyUpdateability()[i] ) { + dirty[count++] = i; + } + } + dirtyProperties = count == 0 ? ArrayHelper.EMPTY_INT_ARRAY : count == dirty.length ? dirty : Arrays.copyOf( dirty, count ); } else { - dirtyProperties = new int[0]; + dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY; } } else { // see if the custom dirtiness strategy can tell us... class DirtyCheckContextImpl implements CustomEntityDirtinessStrategy.DirtyCheckContext { - int[] found; + private int[] found; @Override public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) { @@ -503,7 +556,7 @@ public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attri session.getFactory().getCustomEntityDirtinessStrategy().findDirty( entity, persister, - (Session) session, + session, context ); dirtyProperties = context.found; @@ -513,7 +566,8 @@ public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attri event.setDatabaseSnapshot( null ); final boolean interceptorHandledDirtyCheck; - boolean cannotDirtyCheck; + //The dirty check is considered possible unless proven otherwise (see below) + boolean dirtyCheckPossible = true; if ( dirtyProperties == null ) { // Interceptor returned null, so do the dirtycheck ourself, if possible @@ -522,14 +576,14 @@ public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attri interceptorHandledDirtyCheck = false; // object loaded by update() - cannotDirtyCheck = loadedState == null; - if ( !cannotDirtyCheck ) { + dirtyCheckPossible = loadedState != null; + if ( dirtyCheckPossible ) { // dirty check against the usual snapshot of the entity dirtyProperties = persister.findDirty( values, loadedState, entity, session ); } else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModifiableEntity() ) { // A non-modifiable (e.g., read-only or immutable) entity needs to be have - // references to transient entities set to null beforeQuery being deleted. No other + // references to transient entities set to null before being deleted. No other // fields should be updated. if ( values != entry.getDeletedState() ) { throw new IllegalStateException( @@ -545,14 +599,14 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif // - dirtyProperties will only contain properties that refer to transient entities final Object[] currentState = persister.getPropertyValues( event.getEntity() ); dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session ); - cannotDirtyCheck = false; + dirtyCheckPossible = true; } else { // dirty check against the database snapshot, if possible/necessary final Object[] databaseSnapshot = getDatabaseSnapshot( session, persister, id ); if ( databaseSnapshot != null ) { dirtyProperties = persister.findModified( databaseSnapshot, values, entity, session ); - cannotDirtyCheck = false; + dirtyCheckPossible = true; event.setDatabaseSnapshot( databaseSnapshot ); } } @@ -562,8 +616,7 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif } } else { - // the Interceptor handled the dirty checking - cannotDirtyCheck = false; + // either the Interceptor, the bytecode enhancement or a custom dirtiness strategy handled the dirty checking interceptorHandledDirtyCheck = true; } @@ -571,7 +624,7 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif event.setDirtyProperties( dirtyProperties ); event.setDirtyCheckHandledByInterceptor( interceptorHandledDirtyCheck ); - event.setDirtyCheckPossible( !cannotDirtyCheck ); + event.setDirtyCheckPossible( dirtyCheckPossible ); } @@ -659,7 +712,7 @@ private Object[] getDatabaseSnapshot(SessionImplementor session, EntityPersister if ( snapshot == null ) { //do we even really need this? the update will fail anyway.... if ( session.getFactory().getStatistics().isStatisticsEnabled() ) { - session.getFactory().getStatisticsImplementor() + session.getFactory().getStatistics() .optimisticFailure( persister.getEntityName() ); } throw new StaleObjectStateException( persister.getEntityName(), id ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java index 2118ecd20bc6..17d067633844 100755 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; import org.hibernate.HibernateException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.entry.CollectionCacheEntry; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.CacheHelper; @@ -78,7 +78,7 @@ public void onInitializeCollection(InitializeCollectionEvent event) throws Hiber } if ( source.getFactory().getStatistics().isStatisticsEnabled() ) { - source.getFactory().getStatisticsImplementor().fetchCollection( + source.getFactory().getStatistics().fetchCollection( ce.getLoadedPersister().getRole() ); } @@ -103,7 +103,7 @@ private boolean initializeCollectionFromCache( PersistentCollection collection, SessionImplementor source) { - if ( !source.getLoadQueryInfluencers().getEnabledFilters().isEmpty() + if ( source.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( source ) ) { LOG.trace( "Disregarding cached version (if any) of collection due to enabled filters" ); return false; @@ -116,18 +116,22 @@ private boolean initializeCollectionFromCache( } final SessionFactoryImplementor factory = source.getFactory(); - final CollectionRegionAccessStrategy cacheAccessStrategy = persister.getCacheAccessStrategy(); + final CollectionDataAccess cacheAccessStrategy = persister.getCacheAccessStrategy(); final Object ck = cacheAccessStrategy.generateCacheKey( id, persister, factory, source.getTenantIdentifier() ); final Object ce = CacheHelper.fromSharedCache( source, ck, persister.getCacheAccessStrategy() ); if ( factory.getStatistics().isStatisticsEnabled() ) { if ( ce == null ) { - factory.getStatisticsImplementor() - .secondLevelCacheMiss( cacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().collectionCacheMiss( + persister.getNavigableRole(), + cacheAccessStrategy.getRegion().getName() + ); } else { - factory.getStatisticsImplementor() - .secondLevelCacheHit( cacheAccessStrategy.getRegion().getName() ); + factory.getStatistics().collectionCacheHit( + persister.getNavigableRole(), + cacheAccessStrategy.getRegion().getName() + ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index c9d4e268d6c1..4c9474f985dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -14,7 +14,8 @@ import org.hibernate.PersistentObjectException; import org.hibernate.TypeMismatchException; import org.hibernate.WrongClassException; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.action.internal.DelayedPostInsertIdentifier; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl; @@ -30,7 +31,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; @@ -44,6 +44,10 @@ import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; +import org.hibernate.stat.internal.StatsHelper; +import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.tuple.IdentifierProperty; +import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -68,8 +72,6 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i * Handle the given load event. * * @param event The load event to be handled. - * - * @throws HibernateException */ public void onLoad( final LoadEvent event, @@ -82,24 +84,27 @@ public void onLoad( } final Class idClass = persister.getIdentifierType().getReturnedClass(); - if ( idClass != null && !idClass.isInstance( event.getEntityId() ) ) { + if ( idClass != null && + !idClass.isInstance( event.getEntityId() ) && + !DelayedPostInsertIdentifier.class.isInstance( event.getEntityId() ) ) { checkIdClass( persister, event, loadType, idClass ); } doOnLoad( persister, event, loadType ); } - private EntityPersister getPersister( final LoadEvent event ) { - if ( event.getInstanceToLoad() != null ) { + protected EntityPersister getPersister(final LoadEvent event) { + final Object instanceToLoad = event.getInstanceToLoad(); + if ( instanceToLoad != null ) { //the load() which takes an entity does not pass an entityName - event.setEntityClassName( event.getInstanceToLoad().getClass().getName() ); + event.setEntityClassName( instanceToLoad.getClass().getName() ); return event.getSession().getEntityPersister( null, - event.getInstanceToLoad() + instanceToLoad ); } else { - return event.getSession().getFactory().getEntityPersister( event.getEntityClassName() ); + return event.getSession().getFactory().getMetamodel().entityPersister( event.getEntityClassName() ); } } @@ -109,7 +114,8 @@ private void doOnLoad( final LoadEventListener.LoadType loadType) { try { - final EntityKey keyToLoad = event.getSession().generateEntityKey( event.getEntityId(), persister ); + final EventSource session = event.getSession(); + final EntityKey keyToLoad = session.generateEntityKey( event.getEntityId(), persister ); if ( loadType.isNakedEntityReturned() ) { //do not return a proxy! //(this option indicates we are initializing a proxy) @@ -121,7 +127,7 @@ private void doOnLoad( event.setResult( proxyOrLoad( event, persister, keyToLoad, loadType ) ); } else { - event.setResult( lockAndLoad( event, persister, keyToLoad, loadType, event.getSession() ) ); + event.setResult( lockAndLoad( event, persister, keyToLoad, loadType, session ) ); } } } @@ -139,14 +145,16 @@ private void checkIdClass( // we may have the kooky jpa requirement of allowing find-by-id where // "id" is the "simple pk value" of a dependent objects parent. This // is part of its generally goofy "derived identity" "feature" - if ( persister.getEntityMetamodel().getIdentifierProperty().isEmbedded() ) { + final IdentifierProperty identifierProperty = persister.getEntityMetamodel().getIdentifierProperty(); + if ( identifierProperty.isEmbedded() ) { final EmbeddedComponentType dependentIdType = - (EmbeddedComponentType) persister.getEntityMetamodel().getIdentifierProperty().getType(); + (EmbeddedComponentType) identifierProperty.getType(); if ( dependentIdType.getSubtypes().length == 1 ) { final Type singleSubType = dependentIdType.getSubtypes()[0]; if ( singleSubType.isEntityType() ) { final EntityType dependentParentType = (EntityType) singleSubType; - final Type dependentParentIdType = dependentParentType.getIdentifierOrUniqueKeyType( event.getSession().getFactory() ); + final SessionFactoryImplementor factory = event.getSession().getFactory(); + final Type dependentParentIdType = dependentParentType.getIdentifierOrUniqueKeyType( factory ); if ( dependentParentIdType.getReturnedClass().isInstance( event.getEntityId() ) ) { // yep that's what we have... loadByDerivedIdentitySimplePkValue( @@ -154,7 +162,7 @@ private void checkIdClass( loadType, persister, dependentIdType, - event.getSession().getFactory().getEntityPersister( dependentParentType.getAssociatedEntityName() ) + factory.getMetamodel().entityPersister( dependentParentType.getAssociatedEntityName() ) ); return; } @@ -173,12 +181,13 @@ private void loadByDerivedIdentitySimplePkValue( EntityPersister dependentPersister, EmbeddedComponentType dependentIdType, EntityPersister parentPersister) { - final EntityKey parentEntityKey = event.getSession().generateEntityKey( event.getEntityId(), parentPersister ); + final EventSource session = event.getSession(); + final EntityKey parentEntityKey = session.generateEntityKey( event.getEntityId(), parentPersister ); final Object parent = doLoad( event, parentPersister, parentEntityKey, options ); - final Serializable dependent = (Serializable) dependentIdType.instantiate( parent, event.getSession() ); + final Serializable dependent = (Serializable) dependentIdType.instantiate( parent, session ); dependentIdType.setPropertyValues( dependent, new Object[] {parent}, dependentPersister.getEntityMode() ); - final EntityKey dependentEntityKey = event.getSession().generateEntityKey( dependent, dependentPersister ); + final EntityKey dependentEntityKey = session.generateEntityKey( dependent, dependentPersister ); event.setEntityId( dependent ); event.setResult( doLoad( event, dependentPersister, dependentEntityKey, options ) ); @@ -193,8 +202,6 @@ private void loadByDerivedIdentitySimplePkValue( * @param options The defined load options * * @return The loaded entity. - * - * @throws HibernateException */ private Object load( final LoadEvent event, @@ -250,34 +257,100 @@ private Object proxyOrLoad( final EntityKey keyToLoad, final LoadEventListener.LoadType options) { + final EventSource session = event.getSession(); + final SessionFactoryImplementor factory = session.getFactory(); + final boolean traceEnabled = LOG.isTraceEnabled(); + if ( traceEnabled ) { LOG.tracev( "Loading entity: {0}", - MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() ) + MessageHelper.infoString( persister, event.getEntityId(), factory ) ); } - // this class has no proxies (so do a shortcut) - if ( !persister.hasProxy() ) { - return load( event, persister, keyToLoad, options ); - } + final PersistenceContext persistenceContext = session.getPersistenceContext(); + + final boolean allowBytecodeProxy = factory + .getSessionFactoryOptions() + .isEnhancementAsProxyEnabled(); + + final EntityMetamodel entityMetamodel = persister.getEntityMetamodel(); + final boolean entityHasHibernateProxyFactory = persister.getEntityMetamodel() + .getTuplizer() + .getProxyFactory() != null; + + // Check for the case where we can use the entity itself as a proxy + if ( options.isAllowProxyCreation() + && allowBytecodeProxy + && persister.getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + // if there is already a managed entity instance associated with the PC, return it + final Object managed = persistenceContext.getEntity( keyToLoad ); + if ( managed != null ) { + if ( options.isCheckDeleted() ) { + final EntityEntry entry = persistenceContext.getEntry( managed ); + final Status status = entry.getStatus(); + if ( status == Status.DELETED || status == Status.GONE ) { + return null; + } + } + return managed; + } - final PersistenceContext persistenceContext = event.getSession().getPersistenceContext(); + // if the entity defines a HibernateProxy factory, see if there is an + // existing proxy associated with the PC - and if so, use it + if ( entityHasHibernateProxyFactory ) { + final Object proxy = persistenceContext.getProxy( keyToLoad ); - // look for a proxy - Object proxy = persistenceContext.getProxy( keyToLoad ); - if ( proxy != null ) { - return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); + if ( proxy != null ) { + if( traceEnabled ) { + LOG.trace( "Entity proxy found in session cache" ); + } + + if ( LOG.isDebugEnabled() && ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUnwrap() ) { + LOG.debug( "Ignoring NO_PROXY to honor laziness" ); + } + + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null ); + } + + // specialized handling for entities with subclasses with a HibernateProxy factory + if ( entityMetamodel.hasSubclasses() ) { + // entities with subclasses that define a ProxyFactory can create a HibernateProxy + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + } + if ( !entityMetamodel.hasSubclasses() ) { + if ( keyToLoad.isBatchLoadable() ) { + // Add a batch-fetch entry into the queue for this entity + persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); + } + + // This is the crux of HHH-11147 + // create the (uninitialized) entity instance - has only id set + return persister.getBytecodeEnhancementMetadata().createEnhancedProxy( keyToLoad, true, session ); + } + // If we get here, then the entity class has subclasses and there is no HibernateProxy factory. + // The entity will get loaded below. } + else { + if ( persister.hasProxy() ) { + // look for a proxy + Object proxy = persistenceContext.getProxy( keyToLoad ); + if ( proxy != null ) { + return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); + } - if ( options.isAllowProxyCreation() ) { - return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + if ( options.isAllowProxyCreation() ) { + return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + } + } } // return a newly loaded object return load( event, persister, keyToLoad, options ); } + /** * Given a proxy, initialize it and/or narrow it provided either * is necessary. @@ -301,10 +374,13 @@ private Object returnNarrowedProxy( if ( traceEnabled ) { LOG.trace( "Entity proxy found in session cache" ); } + LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer(); + if ( li.isUnwrap() ) { return li.getImplementation(); } + Object impl = null; if ( !options.isAllowProxyCreation() ) { impl = load( event, persister, keyToLoad, options ); @@ -315,6 +391,7 @@ private Object returnNarrowedProxy( .handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() ); } } + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl ); } @@ -338,6 +415,7 @@ private Object createProxyIfNecessary( final LoadEventListener.LoadType options, final PersistenceContext persistenceContext) { Object existing = persistenceContext.getEntity( keyToLoad ); + final boolean traceEnabled = LOG.isTraceEnabled(); if ( existing != null ) { // return existing object or initialized proxy (unless deleted) if ( traceEnabled ) { @@ -355,6 +433,14 @@ private Object createProxyIfNecessary( if ( traceEnabled ) { LOG.trace( "Creating new proxy for entity" ); } + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + + private Object createProxy( + LoadEvent event, + EntityPersister persister, + EntityKey keyToLoad, + PersistenceContext persistenceContext) { // return new uninitialized proxy Object proxy = persister.createProxy( event.getEntityId(), event.getSession() ); persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); @@ -373,8 +459,6 @@ private Object createProxyIfNecessary( * @param source The originating session * * @return The loaded entity - * - * @throws HibernateException */ private Object lockAndLoad( final LoadEvent event, @@ -384,15 +468,16 @@ private Object lockAndLoad( final SessionImplementor source) { SoftLock lock = null; final Object ck; - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); - if ( persister.hasCache() ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + final boolean canWriteToCache = persister.canWriteToCache(); + if ( canWriteToCache ) { ck = cache.generateCacheKey( event.getEntityId(), persister, source.getFactory(), source.getTenantIdentifier() ); - lock = persister.getCacheAccessStrategy().lockItem( source, ck, null ); + lock = cache.lockItem( source, ck, null ); } else { ck = null; @@ -403,7 +488,7 @@ private Object lockAndLoad( entity = load( event, persister, keyToLoad, options ); } finally { - if ( persister.hasCache() ) { + if ( canWriteToCache ) { cache.unlockItem( source, ck, lock ); } } @@ -431,6 +516,8 @@ private Object doLoad( final EntityKey keyToLoad, final LoadEventListener.LoadType options) { + final EventSource session = event.getSession(); + final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( "Attempting to resolve: {0}", @@ -479,17 +566,18 @@ private Object doLoad( } if ( entity != null && persister.hasNaturalIdentifier() ) { - event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad( + final PersistenceContext persistenceContext = session.getPersistenceContext(); + final PersistenceContext.NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper(); + naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad( persister, event.getEntityId(), - event.getSession().getPersistenceContext().getNaturalIdHelper().extractNaturalIdValues( + naturalIdHelper.extractNaturalIdValues( entity, persister ) ); } - return entity; } @@ -502,7 +590,8 @@ private Object doLoad( * * @return The object loaded from the datasource, or null if not found. */ - private Object loadFromDatasource( + @SuppressWarnings("WeakerAccess") + protected Object loadFromDatasource( final LoadEvent event, final EntityPersister persister) { Object entity = persister.load( @@ -512,8 +601,9 @@ private Object loadFromDatasource( event.getSession() ); - if ( event.isAssociationFetch() && event.getSession().getFactory().getStatistics().isStatisticsEnabled() ) { - event.getSession().getFactory().getStatistics().fetchEntity( event.getEntityClassName() ); + final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics(); + if ( event.isAssociationFetch() && statistics.isStatisticsEnabled() ) { + statistics.fetchEntity( event.getEntityClassName() ); } return entity; @@ -583,7 +673,7 @@ private Object loadFromSecondLevelCache( final EntityKey entityKey) { final SessionImplementor source = event.getSession(); - final boolean useCache = persister.hasCache() + final boolean useCache = persister.canReadFromCache() && source.getCacheMode().isGetEnabled() && event.getLockMode().lessThan( LockMode.READ ); @@ -640,23 +730,27 @@ private Object getFromSharedCache( final LoadEvent event, final EntityPersister persister, SessionImplementor source ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + final SessionFactoryImplementor factory = source.getFactory(); final Object ck = cache.generateCacheKey( event.getEntityId(), persister, - source.getFactory(), + factory, source.getTenantIdentifier() ); final Object ce = CacheHelper.fromSharedCache( source, ck, persister.getCacheAccessStrategy() ); - if ( source.getFactory().getStatistics().isStatisticsEnabled() ) { + final StatisticsImplementor statistics = factory.getStatistics(); + if ( statistics.isStatisticsEnabled() ) { if ( ce == null ) { - source.getFactory().getStatisticsImplementor().secondLevelCacheMiss( + statistics.entityCacheMiss( + StatsHelper.INSTANCE.getRootEntityRole( persister ), cache.getRegion().getName() ); } else { - source.getFactory().getStatisticsImplementor().secondLevelCacheHit( + statistics.entityCacheHit( + StatsHelper.INSTANCE.getRootEntityRole( persister ), cache.getRegion().getName() ); } @@ -804,100 +898,7 @@ private Object convertCacheEntryToEntity( return entity; } - private Object assembleCacheEntry( - final StandardCacheEntryImpl entry, - final Serializable id, - final EntityPersister persister, - final LoadEvent event) throws HibernateException { - - final Object optionalObject = event.getInstanceToLoad(); - final EventSource session = event.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - - if ( traceEnabled ) { - LOG.tracev( - "Assembling entity from second-level cache: {0}", - MessageHelper.infoString( persister, id, factory ) - ); - } - - EntityPersister subclassPersister = factory.getEntityPersister( entry.getSubclass() ); - Object result = optionalObject == null ? - session.instantiate( subclassPersister, id ) : optionalObject; - - // make it circular-reference safe - final EntityKey entityKey = session.generateEntityKey( id, subclassPersister ); - TwoPhaseLoad.addUninitializedCachedEntity( - entityKey, - result, - subclassPersister, - LockMode.NONE, - entry.getVersion(), - session - ); - - Type[] types = subclassPersister.getPropertyTypes(); - Object[] values = entry.assemble( - result, - id, - subclassPersister, - session.getInterceptor(), - session - ); // intializes result by side-effect - TypeHelper.deepCopy( - values, - types, - subclassPersister.getPropertyUpdateability(), - values, - session - ); - - Object version = Versioning.getVersion( values, subclassPersister ); - LOG.tracev( "Cached Version: {0}", version ); - - final PersistenceContext persistenceContext = session.getPersistenceContext(); - boolean isReadOnly = session.isDefaultReadOnly(); - if ( persister.isMutable() ) { - Object proxy = persistenceContext.getProxy( entityKey ); - if ( proxy != null ) { - // there is already a proxy for this impl - // only set the status to read-only if the proxy is read-only - isReadOnly = ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isReadOnly(); - } - } - else { - isReadOnly = true; - } - persistenceContext.addEntry( - result, - ( isReadOnly ? Status.READ_ONLY : Status.MANAGED ), - values, - null, - id, - version, - LockMode.NONE, - true, - subclassPersister, - false - ); - subclassPersister.afterInitialize( result, session ); - persistenceContext.initializeNonLazyCollections(); - // upgrade the lock if necessary: - //lock(result, lockMode); - - //PostLoad is needed for EJB3 - //TODO: reuse the PostLoadEvent... - PostLoadEvent postLoadEvent = event.getPostLoadEvent() - .setEntity( result ) - .setId( id ) - .setPersister( persister ); - - for ( PostLoadEventListener listener : postLoadEventListeners( session ) ) { - listener.onPostLoad( postLoadEvent ); - } - - return result; - } + private Iterable postLoadEventListeners(EventSource session) { return session @@ -908,13 +909,4 @@ private Iterable postLoadEventListeners(EventSource sessi .listeners(); } - private EventListenerGroup getEvenListenerGroup(EventSource session) { - return session - .getFactory() - .getServiceRegistry() - .getService( EventListenerRegistry.class) - .getEventListenerGroup( EventType.POST_LOAD); - - } - } 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 old mode 100755 new mode 100644 index 687956a4ad73..9ba306d96e16 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java @@ -17,12 +17,15 @@ import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -109,26 +112,41 @@ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateExcepti final EventSource source = event.getSession(); final Object original = event.getOriginal(); - if ( original != null ) { + // NOTE : `original` is the value being merged + if ( original != null ) { final Object entity; if ( original instanceof HibernateProxy ) { LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer(); if ( li.isUninitialized() ) { LOG.trace( "Ignoring uninitialized proxy" ); event.setResult( source.load( li.getEntityName(), li.getIdentifier() ) ); - return; //EARLY EXIT! + //EARLY EXIT! + return; } else { entity = li.getImplementation(); } } + else if ( original instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) original; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; + LOG.trace( "Ignoring uninitialized enhanced-proxy" ); + event.setResult( source.load( proxyInterceptor.getEntityName(), (Serializable) proxyInterceptor.getIdentifier() ) ); + //EARLY EXIT! + return; + } + else { + entity = original; + } + } else { entity = original; } - if ( copyCache.containsKey( entity ) && - ( copyCache.isOperatedOn( entity ) ) ) { + if ( copyCache.containsKey( entity ) && ( copyCache.isOperatedOn( entity ) ) ) { LOG.trace( "Already in merge process" ); event.setResult( entity ); } @@ -197,7 +215,7 @@ protected void entityIsPersistent(MergeEvent event, Map copyCache) { final EventSource source = event.getSession(); final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - ( (MergeContext) copyCache ).put( entity, entity, true ); //beforeQuery cascade! + ( (MergeContext) copyCache ).put( entity, entity, true ); //before cascade! cascadeOnMerge( source, persister, entity, copyCache ); copyValues( persister, entity, entity, source, copyCache ); @@ -210,36 +228,50 @@ protected void entityIsTransient(MergeEvent event, Map copyCache) { LOG.trace( "Merging transient instance" ); final Object entity = event.getEntity(); - final EventSource source = event.getSession(); + final EventSource session = event.getSession(); final String entityName = event.getEntityName(); - final EntityPersister persister = source.getEntityPersister( entityName, entity ); + final EntityPersister persister = session.getEntityPersister( entityName, entity ); - final Serializable id = persister.hasIdentifierProperty() ? - persister.getIdentifier( entity, source ) : - null; - if ( copyCache.containsKey( entity ) ) { - persister.setIdentifier( copyCache.get( entity ), id, source ); + final Serializable id = persister.hasIdentifierProperty() + ? persister.getIdentifier( entity, session ) + : null; + + final Object copy; + final Object existingCopy = copyCache.get( entity ); + if ( existingCopy != null ) { + persister.setIdentifier( copyCache.get( entity ), id, session ); + copy = existingCopy; } else { - ( (MergeContext) copyCache ).put( entity, source.instantiate( persister, id ), true ); //beforeQuery cascade! + copy = session.instantiate( persister, id ); + + //before cascade! + ( (MergeContext) copyCache ).put( entity, copy, true ); } - final Object copy = copyCache.get( entity ); // cascade first, so that all unsaved objects get their - // copy created beforeQuery we actually copy + // copy created before we actually copy //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); - super.cascadeBeforeSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.FROM_PARENT ); + super.cascadeBeforeSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.FROM_PARENT ); - saveTransientEntity( copy, entityName, event.getRequestedId(), source, copyCache ); + saveTransientEntity( copy, entityName, event.getRequestedId(), session, copyCache ); // cascade first, so that all unsaved objects get their - // copy created beforeQuery we actually copy - super.cascadeAfterSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.TO_PARENT ); + // copy created before we actually copy + super.cascadeAfterSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.TO_PARENT ); event.setResult( copy ); + + if ( copy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) copy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor == null ) { + persister.getBytecodeEnhancementMetadata().injectInterceptor( copy, id, session ); + } + } } private void saveTransientEntity( @@ -283,11 +315,12 @@ protected void entityIsDetached(MergeEvent event, Map copyCache) { String previousFetchProfile = source.getLoadQueryInfluencers().getInternalFetchProfile(); source.getLoadQueryInfluencers().setInternalFetchProfile( "merge" ); + //we must clone embedded composite identifiers, or //we will get back the same instance that we pass in - final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType() - .deepCopy( id, source.getFactory() ); + final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType().deepCopy( id, source.getFactory() ); final Object result = source.get( entityName, clonedIdentifier ); + source.getLoadQueryInfluencers().setInternalFetchProfile( previousFetchProfile ); if ( result == null ) { @@ -301,9 +334,11 @@ protected void entityIsDetached(MergeEvent event, Map copyCache) { entityIsTransient( event, copyCache ); } else { - ( (MergeContext) copyCache ).put( entity, result, true ); //beforeQuery cascade! + // before cascade! + ( (MergeContext) copyCache ).put( entity, result, true ); + + final Object target = unproxyManagedForDetachedMerging( entity, result, persister, source ); - final Object target = source.getPersistenceContext().unproxy( result ); if ( target == entity ) { throw new AssertionFailure( "entity was not detached" ); } @@ -316,14 +351,13 @@ else if ( !source.getEntityName( target ).equals( entityName ) ) { } else if ( isVersionChanged( entity, source, persister, target ) ) { if ( source.getFactory().getStatistics().isStatisticsEnabled() ) { - source.getFactory().getStatisticsImplementor() - .optimisticFailure( entityName ); + source.getFactory().getStatistics().optimisticFailure( entityName ); } throw new StaleObjectStateException( entityName, id ); } // cascade first, so that all unsaved objects get their - // copy created beforeQuery we actually copy + // copy created before we actually copy cascadeOnMerge( source, persister, entity, copyCache ); copyValues( persister, entity, target, source, copyCache ); @@ -335,6 +369,43 @@ else if ( isVersionChanged( entity, source, persister, target ) ) { } + private Object unproxyManagedForDetachedMerging( + Object incoming, + Object managed, + EntityPersister persister, + EventSource source) { + if ( managed instanceof HibernateProxy ) { + return source.getPersistenceContext().unproxy( managed ); + } + + if ( incoming instanceof PersistentAttributeInterceptable + && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() + && source.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() ) { + + final PersistentAttributeInterceptor incomingInterceptor = ( (PersistentAttributeInterceptable) incoming ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor managedInterceptor = ( (PersistentAttributeInterceptable) managed ).$$_hibernate_getInterceptor(); + + // todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but + // with different attributes initialized? + // - for now, assume we do not... + + // if the managed entity is not a proxy, we can just return it + if ( ! ( managedInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) ) { + return managed; + } + + // if the incoming entity is still a proxy there is no need to force initialization of the managed one + if ( incomingInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return managed; + } + + // otherwise, force initialization + persister.initializeEnhancedEntityUsedAsProxy( managed, null, source ); + } + + return managed; + } + private void markInterceptorDirty(final Object entity, final Object target, EntityPersister persister) { // for enhanced entities, copy over the dirty attributes if ( entity instanceof SelfDirtinessTracker && target instanceof SelfDirtinessTracker ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java index 57963199cf39..fedbc270fc3e 100755 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java @@ -6,6 +6,7 @@ */ package org.hibernate.event.internal; +import java.io.Serializable; import java.util.IdentityHashMap; import java.util.Map; @@ -23,6 +24,8 @@ import org.hibernate.id.ForeignGenerator; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; @@ -34,7 +37,9 @@ * * @author Gavin King */ -public class DefaultPersistEventListener extends AbstractSaveEventListener implements PersistEventListener { +public class DefaultPersistEventListener + extends AbstractSaveEventListener + implements PersistEventListener, CallbackRegistryConsumer { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultPersistEventListener.class ); @Override @@ -52,7 +57,6 @@ protected Boolean getAssumedUnsaved() { * * @param event The create event to be handled. * - * @throws HibernateException */ public void onPersist(PersistEvent event) throws HibernateException { onPersist( event, new IdentityHashMap( 10 ) ); @@ -63,7 +67,6 @@ public void onPersist(PersistEvent event) throws HibernateException { * * @param event The create event to be handled. * - * @throws HibernateException */ public void onPersist(PersistEvent event, Map createCache) throws HibernateException { final SessionImplementor source = event.getSession(); @@ -197,14 +200,16 @@ private void entityIsDeleted(PersistEvent event, Map createCache) { final Object entity = source.getPersistenceContext().unproxy( event.getObject() ); final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - LOG.tracef( + if ( LOG.isTraceEnabled() ) { + LOG.tracef( "un-scheduling entity deletion [%s]", MessageHelper.infoString( - persister, - persister.getIdentifier( entity, source ), - source.getFactory() + persister, + persister.getIdentifier( entity, source ), + source.getFactory() ) - ); + ); + } if ( createCache.put( entity, entity ) == null ) { justCascade( createCache, source, entity, persister ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java index 0f975e321ff3..cbd3449a22bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java @@ -14,6 +14,8 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PostLoadEventListener; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.persister.entity.EntityPersister; /** @@ -25,10 +27,20 @@ * @author Gavin King * @author Steve Ebersole */ -public class DefaultPostLoadEventListener implements PostLoadEventListener { +public class DefaultPostLoadEventListener implements PostLoadEventListener, CallbackRegistryConsumer { + private CallbackRegistry callbackRegistry; + + @Override + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + @Override public void onPostLoad(PostLoadEvent event) { final Object entity = event.getEntity(); + + callbackRegistry.postLoad( entity ); + final EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity ); if ( entry == null ) { throw new AssertionFailure( "possible non-threadsafe access to the session" ); @@ -45,11 +57,11 @@ public void onPostLoad(PostLoadEvent event) { entry.forceLocked( entity, nextVersion ); } else if ( LockMode.OPTIMISTIC_FORCE_INCREMENT.equals( lockMode ) ) { - final EntityIncrementVersionProcess incrementVersion = new EntityIncrementVersionProcess( entity, entry ); + final EntityIncrementVersionProcess incrementVersion = new EntityIncrementVersionProcess( entity ); event.getSession().getActionQueue().registerProcess( incrementVersion ); } else if ( LockMode.OPTIMISTIC.equals( lockMode ) ) { - final EntityVerifyVersionProcess verifyVersion = new EntityVerifyVersionProcess( entity, entry ); + final EntityVerifyVersionProcess verifyVersion = new EntityVerifyVersionProcess( entity ); event.getSession().getActionQueue().registerProcess( verifyVersion ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPreLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPreLoadEventListener.java old mode 100755 new mode 100644 index 3a08f2ab4721..10c3b9f89819 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPreLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPreLoadEventListener.java @@ -11,7 +11,7 @@ import org.hibernate.persister.entity.EntityPersister; /** - * Called beforeQuery injecting property values into a newly + * Called before injecting property values into a newly * loaded entity instance. * * @author Gavin King diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java index 46d95cd8c248..fbbb88689ea8 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java @@ -11,18 +11,18 @@ import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.PersistentObjectException; import org.hibernate.UnresolvableObjectException; -import org.hibernate.action.spi.AfterTransactionCompletionProcess; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.RefreshEvent; import org.hibernate.event.spi.RefreshEventListener; @@ -140,11 +140,11 @@ public void onRefresh(RefreshEvent event, Map refreshedAlready) { final EntityKey key = source.generateEntityKey( id, persister ); source.getPersistenceContext().removeEntity( key ); if ( persister.hasCollections() ) { - new EvictVisitor( source ).process( object, persister ); + new EvictVisitor( source, object ).process( object, persister ); } } - if ( persister.hasCache() ) { + if ( persister.canWriteToCache() ) { Object previousVersion = null; if ( persister.isVersionPropertyGenerated() ) { // we need to grab the version value from the entity, otherwise @@ -152,7 +152,7 @@ public void onRefresh(RefreshEvent event, Map refreshedAlready) { // multiple actions queued during the same flush previousVersion = persister.getVersion( object ); } - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final EntityDataAccess cache = persister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( id, persister, @@ -160,23 +160,60 @@ public void onRefresh(RefreshEvent event, Map refreshedAlready) { source.getTenantIdentifier() ); final SoftLock lock = cache.lockItem( source, ck, previousVersion ); - source.getActionQueue().registerProcess( new AfterTransactionCompletionProcess() { - @Override - public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) { - cache.unlockItem( session, ck, lock ); - } - } ); cache.remove( source, ck ); + source.getActionQueue().registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); } evictCachedCollections( persister, id, source ); String previousFetchProfile = source.getLoadQueryInfluencers().getInternalFetchProfile(); source.getLoadQueryInfluencers().setInternalFetchProfile( "refresh" ); - Object result = persister.load( id, object, event.getLockOptions(), source ); - // Keep the same read-only/modifiable setting for the entity that it had beforeQuery refreshing; - // If it was transient, then set it to the default for the source. + + + // Handle the requested lock-mode (if one) in relation to the entry's (if one) current lock-mode + + LockOptions lockOptionsToUse = event.getLockOptions(); + + final LockMode requestedLockMode = lockOptionsToUse.getLockMode(); + LockMode postRefreshLockMode = null; + + if ( e != null ) { + final LockMode currentLockMode = e.getLockMode(); + if ( currentLockMode.greaterThan( requestedLockMode ) ) { + // the requested lock-mode is less restrictive than the current one + // - pass along the current lock-mode (after accounting for WRITE) + lockOptionsToUse = LockOptions.copy( event.getLockOptions(), new LockOptions() ); + if ( currentLockMode == LockMode.WRITE ) { + // our transaction should already hold the exclusive lock on + // the underlying row - so READ should be sufficient. + // + // in fact, this really holds true for any current lock-mode that indicates we + // hold an exclusive lock on the underlying row - but we *need* to handle + // WRITE specially because the Loader/Locker mechanism does not allow for WRITE + // locks + lockOptionsToUse.setLockMode( LockMode.READ ); + + // and prepare to reset the entry lock-mode to WRITE after the refresh completes + postRefreshLockMode = LockMode.WRITE; + } + else { + lockOptionsToUse.setLockMode( currentLockMode ); + } + } + } + + final Object result = persister.load( id, object, lockOptionsToUse, source ); + if ( result != null ) { + // apply `postRefreshLockMode`, if needed + if ( postRefreshLockMode != null ) { + // if we get here, there was a previous entry and we need to re-set its lock-mode + // - however, the refresh operation actually creates a new entry, so get it + source.getPersistenceContext().getEntry( result ).setLockMode( postRefreshLockMode ); + } + + // Keep the same read-only/modifiable setting for the entity that it had before refreshing; + // If it was transient, then set it to the default for the source. if ( !persister.isMutable() ) { // this is probably redundant; it should already be read-only source.setReadOnly( result, true ); @@ -201,7 +238,7 @@ private void evictCachedCollections(Type[] types, Serializable id, EventSource s if ( type.isCollectionType() ) { CollectionPersister collectionPersister = source.getFactory().getMetamodel().collectionPersister( ( (CollectionType) type ).getRole() ); if ( collectionPersister.hasCache() ) { - final CollectionRegionAccessStrategy cache = collectionPersister.getCacheAccessStrategy(); + final CollectionDataAccess cache = collectionPersister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( id, collectionPersister, @@ -209,13 +246,8 @@ private void evictCachedCollections(Type[] types, Serializable id, EventSource s source.getTenantIdentifier() ); final SoftLock lock = cache.lockItem( source, ck, null ); - source.getActionQueue().registerProcess( new AfterTransactionCompletionProcess() { - @Override - public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) { - cache.unlockItem( session, ck, lock ); - } - } ); cache.remove( source, ck ); + source.getActionQueue().registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); } } else if ( type.isComponentType() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java index c5e2e8b7ea24..cde0ada983d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java @@ -10,7 +10,7 @@ import java.util.concurrent.TimeUnit; import org.hibernate.HibernateException; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.spi.ResolveNaturalIdEvent; import org.hibernate.event.spi.ResolveNaturalIdEventListener; @@ -22,7 +22,7 @@ /** * Defines the default load event listeners used by hibernate for loading entities * in response to generated load events. - * + * * @author Eric Dalquist * @author Steve Ebersole */ @@ -46,7 +46,7 @@ public void onResolveNaturalId(ResolveNaturalIdEvent event) throws HibernateExce * made to load the entity from the session-level cache. If not found there, * an attempt is made to locate it in second-level cache. Lastly, an * attempt is made to load it directly from the datasource. - * + * * @param event The load event * * @return The loaded entity, or null. @@ -57,8 +57,9 @@ protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) { final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( - "Attempting to resolve: {0}", - MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) + "Attempting to resolve: {0}#{1}", + MessageHelper.infoString( persister ), + event.getNaturalIdValues() ); } @@ -66,8 +67,9 @@ protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) { if ( entityId != null ) { if ( traceEnabled ) { LOG.tracev( - "Resolved object in cache: {0}", - MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) + "Resolved object in cache: {0}#{1}", + MessageHelper.infoString( persister ), + event.getNaturalIdValues() ); } return entityId; @@ -75,8 +77,9 @@ protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) { if ( traceEnabled ) { LOG.tracev( - "Object not resolved in any cache: {0}", - MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) + "Object not resolved in any cache: {0}#{1}", + MessageHelper.infoString( persister ), + event.getNaturalIdValues() ); } @@ -85,7 +88,7 @@ protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) { /** * Attempts to resolve the entity id corresponding to the event's natural id values from the session - * + * * @param event The load event * * @return The entity from the cache, or null. @@ -100,7 +103,7 @@ protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) { /** * Performs the process of loading an entity from the configured * underlying datasource. - * + * * @param event The load event * * @return The object loaded from the datasource, or null if not found. @@ -112,23 +115,22 @@ protected Serializable loadFromDatasource(final ResolveNaturalIdEvent event) { if ( stats ) { startTime = System.nanoTime(); } - + final Serializable pk = event.getEntityPersister().loadEntityIdByNaturalId( event.getOrderedNaturalIdValues(), event.getLockOptions(), event.getSession() ); - + if ( stats ) { - final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = event.getEntityPersister().getNaturalIdCacheAccessStrategy(); - final String regionName = naturalIdCacheAccessStrategy == null ? null : naturalIdCacheAccessStrategy.getRegion().getName(); final long endTime = System.nanoTime(); final long milliseconds = TimeUnit.MILLISECONDS.convert( endTime - startTime, TimeUnit.NANOSECONDS ); - factory.getStatisticsImplementor().naturalIdQueryExecuted( - regionName, - milliseconds ); + factory.getStatistics().naturalIdQueryExecuted( + event.getEntityPersister().getRootEntityName(), + milliseconds + ); } - + //PK can be null if the entity doesn't exist if (pk != null) { event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad( @@ -137,7 +139,7 @@ protected Serializable loadFromDatasource(final ResolveNaturalIdEvent event) { event.getOrderedNaturalIdValues() ); } - + return pk; } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java old mode 100755 new mode 100644 index 5fa45d1e9a57..e37e10b099f9 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java @@ -53,7 +53,7 @@ public void onSaveOrUpdate(SaveOrUpdateEvent event) { final Serializable requestedId = event.getRequestedId(); if ( requestedId != null ) { - //assign the requested id to the proxy, *beforeQuery* + //assign the requested id to the proxy, *before* //reassociating the proxy if ( object instanceof HibernateProxy ) { ( (HibernateProxy) object ).getHibernateLazyInitializer().setIdentifier( requestedId ); @@ -156,7 +156,7 @@ protected Serializable entityIsPersistent(SaveOrUpdateEvent event) throws Hibern * * @param event The save event to be handled. * - * @return The entity's identifier afterQuery saving. + * @return The entity's identifier after saving. */ protected Serializable entityIsTransient(SaveOrUpdateEvent event) { @@ -186,7 +186,7 @@ protected Serializable entityIsTransient(SaveOrUpdateEvent event) { * * @param event The initiating event. * - * @return The entity's identifier value afterQuery saving. + * @return The entity's identifier value after saving. */ protected Serializable saveWithGeneratedOrRequestedId(SaveOrUpdateEvent event) { return saveWithGeneratedId( 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 eaa0b907c5d7..2a31ad70f574 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 @@ -41,12 +41,12 @@ Object processCollection(Object collection, CollectionType type) throws Hibernat if ( type.isArrayType() ) { persistentCollection = session.getPersistenceContext().getCollectionHolder( collection ); // if no array holder we found an unwrappered array (this can't occur, - // because we now always call wrap() beforeQuery getting to here) + // because we now always call wrap() before getting to here) // return (ah==null) ? true : searchForDirtyCollections(ah, type); } else { // if not wrappered yet, its dirty (this can't occur, because - // we now always call wrap() beforeQuery getting to here) + // we now always call wrap() before getting to here) // return ( ! (obj instanceof PersistentCollection) ) ? //true : searchForDirtyCollections( (PersistentCollection) obj, type ); persistentCollection = (PersistentCollection) collection; diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java index 68919486732a..d440adab6b66 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java @@ -7,6 +7,7 @@ package org.hibernate.event.internal; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.CollectionKey; @@ -25,9 +26,12 @@ */ public class EvictVisitor extends AbstractVisitor { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( EvictVisitor.class ); + + private Object owner; - EvictVisitor(EventSource session) { + EvictVisitor(EventSource session, Object owner) { super(session); + this.owner = owner; } @Override @@ -38,20 +42,23 @@ Object processCollection(Object collection, CollectionType type) throws Hibernat return null; } + public void evictCollection(Object value, CollectionType type) { - final Object pc; + final PersistentCollection collection; if ( type.hasHolder() ) { - pc = getSession().getPersistenceContext().removeCollectionHolder(value); + collection = getSession().getPersistenceContext().removeCollectionHolder(value); } else if ( value instanceof PersistentCollection ) { - pc = value; + collection = (PersistentCollection) value; + } + else if ( value == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + collection = (PersistentCollection) type.resolve( value, getSession(), this.owner ); } else { return; //EARLY EXIT! } - PersistentCollection collection = (PersistentCollection) pc; - if ( collection.unsetSession( getSession() ) ) { + if ( collection != null && collection.unsetSession( getSession() ) ) { evictCollection(collection); } } @@ -76,4 +83,9 @@ private void evictCollection(PersistentCollection collection) { ); } } + + @Override + boolean includeEntityProperty(Object[] values, int i) { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java index 63a8b4a7b629..c5cb4caada6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java @@ -7,6 +7,7 @@ package org.hibernate.event.internal; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.Collections; import org.hibernate.event.spi.EventSource; @@ -20,21 +21,27 @@ * @author Gavin King */ public class FlushVisitor extends AbstractVisitor { - private Object owner; - Object processCollection(Object collection, CollectionType type) - throws HibernateException { + FlushVisitor(EventSource session, Object owner) { + super(session); + this.owner = owner; + } + + Object processCollection(Object collection, CollectionType type) throws HibernateException { - if (collection==CollectionType.UNFETCHED_COLLECTION) { + if ( collection == CollectionType.UNFETCHED_COLLECTION ) { return null; } - if (collection!=null) { + if ( collection != null ) { final PersistentCollection coll; if ( type.hasHolder() ) { coll = getSession().getPersistenceContext().getCollectionHolder(collection); } + else if ( collection == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + coll = (PersistentCollection) type.resolve( collection, getSession(), owner ); + } else { coll = (PersistentCollection) collection; } @@ -46,9 +53,9 @@ Object processCollection(Object collection, CollectionType type) } - FlushVisitor(EventSource session, Object owner) { - super(session); - this.owner = owner; + @Override + boolean includeEntityProperty(Object[] values, int i) { + return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java b/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java index 6dcd79a8acf2..a63cb7807f95 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java @@ -21,7 +21,7 @@ /** * MergeContext is a Map implementation that is intended to be used by a merge * event listener to keep track of each entity being merged and their corresponding - * managed result. Entities to be merged may to be added to the MergeContext beforeQuery + * managed result. Entities to be merged may to be added to the MergeContext before * the merge operation has cascaded to that entity. * * "Merge entity" and "mergeEntity" method parameter refer to an entity that is (or will be) @@ -231,17 +231,6 @@ public Object put(Object mergeEntity, Object managedEntity) { throw new NullPointerException( "null merge and managed entities are not supported by " + getClass().getName() ); } - // Detect invalid 'managed entity' -> 'managed entity' mappings where key != value - if ( managedToMergeEntityXref.containsKey( mergeEntity ) ) { - if ( managedToMergeEntityXref.get( mergeEntity ) != mergeEntity ) { - throw new IllegalStateException( - "MergeContext#attempt to create managed -> managed mapping with different entities: " - + printEntity( mergeEntity ) + "; " + printEntity( - managedEntity ) - ); - } - } - Object oldManagedEntity = mergeToManagedEntityXref.put( mergeEntity, managedEntity ); Boolean oldOperatedOn = mergeEntityToOperatedOnFlagMap.put( mergeEntity, isOperatedOn ); // If managedEntity already corresponds with a different merge entity, that means @@ -262,7 +251,7 @@ public Object put(Object mergeEntity, Object managedEntity) { } if ( oldOperatedOn != null ) { throw new IllegalStateException( - "MergeContext#mergeEntityToOperatedOnFlagMap contains an merge entity " + printEntity( mergeEntity ) + "MergeContext#mergeEntityToOperatedOnFlagMap contains a merge entity " + printEntity( mergeEntity ) + ", but MergeContext#mergeToManagedEntityXref does not." ); } @@ -278,7 +267,7 @@ public Object put(Object mergeEntity, Object managedEntity) { } if ( oldOperatedOn == null ) { throw new IllegalStateException( - "MergeContext#mergeToManagedEntityXref contained an mergeEntity " + printEntity( mergeEntity ) + "MergeContext#mergeToManagedEntityXref contained a merge entity " + printEntity( mergeEntity ) + ", but MergeContext#mergeEntityToOperatedOnFlagMap did not." ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/PostDeleteEventListenerStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/event/internal/PostDeleteEventListenerStandardImpl.java new file mode 100644 index 000000000000..f12e344f9244 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/PostDeleteEventListenerStandardImpl.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.event.internal; + +import org.hibernate.event.spi.PostDeleteEvent; +import org.hibernate.event.spi.PostDeleteEventListener; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.persister.entity.EntityPersister; + +/** + * The standard PostDeleteEventListener implementation + * + * @author Kabir Khan + * @author Steve Ebersole + */ +public class PostDeleteEventListenerStandardImpl implements PostDeleteEventListener, CallbackRegistryConsumer { + private CallbackRegistry callbackRegistry; + + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + + public void onPostDelete(PostDeleteEvent event) { + Object entity = event.getEntity(); + callbackRegistry.postRemove( entity ); + } + + @Override + public boolean requiresPostCommitHanding(EntityPersister persister) { + return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_REMOVE ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/PostInsertEventListenerStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/event/internal/PostInsertEventListenerStandardImpl.java new file mode 100644 index 000000000000..eda6fb03c482 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/PostInsertEventListenerStandardImpl.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.event.internal; + +import org.hibernate.event.spi.PostInsertEvent; +import org.hibernate.event.spi.PostInsertEventListener; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.persister.entity.EntityPersister; + +/** + * @author Kabir Khan + * @author Steve Ebersole + */ +public class PostInsertEventListenerStandardImpl implements PostInsertEventListener, CallbackRegistryConsumer { + private CallbackRegistry callbackRegistry; + + @Override + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + + @Override + public void onPostInsert(PostInsertEvent event) { + Object entity = event.getEntity(); + callbackRegistry.postCreate( entity ); + } + + @Override + public boolean requiresPostCommitHanding(EntityPersister persister) { + return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_PERSIST ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpdateEventListenerStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpdateEventListenerStandardImpl.java new file mode 100644 index 000000000000..4e68dd10d9da --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpdateEventListenerStandardImpl.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 . + */ +package org.hibernate.event.internal; + +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.Status; +import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.PostUpdateEvent; +import org.hibernate.event.spi.PostUpdateEventListener; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.persister.entity.EntityPersister; + +/** + * @author Steve Ebersole + */ +public class PostUpdateEventListenerStandardImpl implements PostUpdateEventListener, CallbackRegistryConsumer { + private CallbackRegistry callbackRegistry; + + @Override + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + + @Override + public void onPostUpdate(PostUpdateEvent event) { + Object entity = event.getEntity(); + EventSource eventSource = event.getSession(); + handlePostUpdate(entity, eventSource); + } + + private void handlePostUpdate(Object entity, EventSource source) { + EntityEntry entry = source.getPersistenceContext().getEntry( entity ); + // mimic the preUpdate filter + if ( Status.DELETED != entry.getStatus()) { + callbackRegistry.postUpdate(entity); + } + } + + @Override + public boolean requiresPostCommitHanding(EntityPersister persister) { + return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_UPDATE ); + } +} 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 923d9389d1c1..0ddbc19b8bdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java @@ -6,8 +6,11 @@ */ package org.hibernate.event.internal; +import java.io.Serializable; + import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; @@ -25,10 +28,19 @@ * * @author Gavin King */ +@SuppressWarnings("WeakerAccess") public class WrapVisitor extends ProxyVisitor { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( WrapVisitor.class ); + private Object entity; + private Serializable id; + + private boolean substitute; - boolean substitute; + public WrapVisitor(Object entity, Serializable id, EventSource session) { + super( session ); + this.entity = entity; + this.id = id; + } boolean isSubstitutionRequired() { return substitute; @@ -42,20 +54,26 @@ boolean isSubstitutionRequired() { Object processCollection(Object collection, CollectionType collectionType) throws HibernateException { - if ( collection != null && ( collection instanceof PersistentCollection ) ) { + if ( collection == null ) { + return null; + } + if ( collection == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + return null; + } + + if ( collection instanceof PersistentCollection ) { + final PersistentCollection coll = (PersistentCollection) collection; final SessionImplementor session = getSession(); - PersistentCollection coll = (PersistentCollection) collection; + if ( coll.setCurrentSession( session ) ) { reattachCollection( coll, collectionType ); } - return null; - } - else { - return processArrayOrNewCollection( collection, collectionType ); + return null; } + return processArrayOrNewCollection( collection, collectionType ); } final Object processArrayOrNewCollection(Object collection, CollectionType collectionType) diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java index fddaf167ec99..21ab4cd9f388 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java @@ -16,19 +16,24 @@ import org.hibernate.event.service.spi.DuplicationStrategy; import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistrationException; +import org.hibernate.event.service.spi.JpaBootstrapSensitive; import org.hibernate.event.spi.EventType; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; /** * @author Steve Ebersole */ -public class EventListenerGroupImpl implements EventListenerGroup { +class EventListenerGroupImpl implements EventListenerGroup { private EventType eventType; + private final EventListenerRegistryImpl listenerRegistry; - private final Set duplicationStrategies = new LinkedHashSet(); + private final Set duplicationStrategies = new LinkedHashSet<>(); private List listeners; - public EventListenerGroupImpl(EventType eventType) { + public EventListenerGroupImpl(EventType eventType, EventListenerRegistryImpl listenerRegistry) { this.eventType = eventType; + this.listenerRegistry = listenerRegistry; + duplicationStrategies.add( // At minimum make sure we do not register the same exact listener class multiple times. new DuplicationStrategy() { @@ -75,12 +80,17 @@ public void addDuplicationStrategy(DuplicationStrategy strategy) { duplicationStrategies.add( strategy ); } - public Iterable listeners() { - return listeners == null ? Collections.emptyList() : listeners; + /** + * Implementation note: should be final for performance reasons. + */ + @Override + public final Iterable listeners() { + return listeners == null ? Collections.EMPTY_LIST : listeners; } @Override - public void appendListeners(T... listeners) { + @SafeVarargs + public final void appendListeners(T... listeners) { for ( T listener : listeners ) { appendListener( listener ); } @@ -94,7 +104,8 @@ public void appendListener(T listener) { } @Override - public void prependListeners(T... listeners) { + @SafeVarargs + public final void prependListeners(T... listeners) { for ( T listener : listeners ) { prependListener( listener ); } @@ -109,7 +120,7 @@ public void prependListener(T listener) { private boolean listenerShouldGetAdded(T listener) { if ( listeners == null ) { - listeners = new ArrayList(); + listeners = new ArrayList<>(); return true; // no need to do de-dup checks } @@ -143,9 +154,22 @@ private boolean listenerShouldGetAdded(T listener) { private void internalPrepend(T listener) { checkAgainstBaseInterface( listener ); + performInjections( listener ); listeners.add( 0, listener ); } + private void performInjections(T listener) { + if ( CallbackRegistryConsumer.class.isInstance( listener ) ) { + ( (CallbackRegistryConsumer) listener ).injectCallbackRegistry( listenerRegistry.getCallbackRegistry() ); + } + + if ( JpaBootstrapSensitive.class.isInstance( listener ) ) { + ( (JpaBootstrapSensitive) listener ).wasJpaBootstrap( + listenerRegistry.getSessionFactory().getSessionFactoryOptions().isJpaBootstrap() + ); + } + } + private void checkAgainstBaseInterface(T listener) { if ( !eventType.baseListenerInterface().isInstance( listener ) ) { throw new EventListenerRegistrationException( @@ -156,6 +180,7 @@ private void checkAgainstBaseInterface(T listener) { private void internalAppend(T listener) { checkAgainstBaseInterface( listener ); + performInjections( listener ); listeners.add( listener ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java index ff167509f92a..a3b9751f0443 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java @@ -8,9 +8,14 @@ import java.lang.reflect.Array; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.internal.DefaultAutoFlushEventListener; import org.hibernate.event.internal.DefaultDeleteEventListener; import org.hibernate.event.internal.DefaultDirtyCheckEventListener; @@ -31,10 +36,21 @@ import org.hibernate.event.internal.DefaultSaveEventListener; import org.hibernate.event.internal.DefaultSaveOrUpdateEventListener; import org.hibernate.event.internal.DefaultUpdateEventListener; +import org.hibernate.event.internal.PostDeleteEventListenerStandardImpl; +import org.hibernate.event.internal.PostInsertEventListenerStandardImpl; +import org.hibernate.event.internal.PostUpdateEventListenerStandardImpl; import org.hibernate.event.service.spi.DuplicationStrategy; import org.hibernate.event.service.spi.EventListenerRegistrationException; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; +import org.hibernate.jpa.event.internal.CallbackBuilderLegacyImpl; +import org.hibernate.jpa.event.internal.CallbackRegistryImpl; +import org.hibernate.jpa.event.spi.CallbackBuilder; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.service.spi.Stoppable; import static org.hibernate.event.spi.EventType.AUTO_FLUSH; import static org.hibernate.event.spi.EventType.CLEAR; @@ -76,10 +92,79 @@ /** * @author Steve Ebersole */ -public class EventListenerRegistryImpl implements EventListenerRegistry { - private Map listenerClassToInstanceMap = new HashMap(); +public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppable { + private Map listenerClassToInstanceMap = new HashMap<>(); + + private final SessionFactoryImplementor sessionFactory; + private final CallbackRegistryImpl callbackRegistry; + private final EventListenerGroupImpl[] registeredEventListeners; + private CallbackBuilder callbackBuilder; + + /** + * @deprecated Use {@link EventListenerRegistryImpl#EventListenerRegistryImpl(BootstrapContext, SessionFactoryImplementor)} instead + */ + @Deprecated + EventListenerRegistryImpl( + SessionFactoryImplementor sessionFactory, + SessionFactoryOptions sessionFactoryOptions, + ServiceRegistryImplementor registry) { + this.sessionFactory = sessionFactory; + + this.callbackRegistry = new CallbackRegistryImpl(); + + this.registeredEventListeners = buildListenerGroups(); + } + + EventListenerRegistryImpl(BootstrapContext bootstrapContext, SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; + + this.callbackRegistry = new CallbackRegistryImpl(); + this.callbackBuilder = new CallbackBuilderLegacyImpl( + bootstrapContext.getServiceRegistry().getService( ManagedBeanRegistry.class ), + bootstrapContext.getReflectionManager() + ); - private EventListenerGroupImpl[] registeredEventListeners = prepareListenerAssociation(); + this.registeredEventListeners = buildListenerGroups(); + } + + SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + CallbackRegistryImpl getCallbackRegistry() { + return callbackRegistry; + } + + @Override + public void prepare(MetadataImplementor metadata) { + if ( callbackBuilder == null ) { + // TODO : not needed anymore when the deprecate constructor will be removed + this.callbackBuilder = new CallbackBuilderLegacyImpl( + sessionFactory.getServiceRegistry().getService( ManagedBeanRegistry.class ), + metadata.getMetadataBuildingOptions().getReflectionManager() + ); + } + for ( PersistentClass persistentClass : metadata.getEntityBindings() ) { + if ( persistentClass.getClassName() == null ) { + // we can have non java class persisted by hibernate + continue; + } + callbackBuilder.buildCallbacksForEntity( persistentClass.getClassName(), callbackRegistry ); + + for ( Iterator propertyIterator = persistentClass.getDeclaredPropertyIterator(); + propertyIterator.hasNext(); ) { + Property property = (Property) propertyIterator.next(); + + if ( property.getType().isComponentType() ) { + callbackBuilder.buildCallbacksForEmbeddable( + property, + persistentClass.getClassName(), + callbackRegistry + ); + } + } + } + } @SuppressWarnings({ "unchecked" }) public EventListenerGroupImpl getEventListenerGroup(EventType eventType) { @@ -100,7 +185,8 @@ public void addDuplicationStrategy(DuplicationStrategy strategy) { } @Override - public void setListeners(EventType type, Class... listenerClasses) { + @SafeVarargs + public final void setListeners(EventType type, Class... listenerClasses) { setListeners( type, resolveListenerInstances( type, listenerClasses ) ); } @@ -136,37 +222,42 @@ private T instantiateListener(Class listenerClass) { } @Override - public void setListeners(EventType type, T... listeners) { + @SafeVarargs + public final void setListeners(EventType type, T... listeners) { EventListenerGroupImpl registeredListeners = getEventListenerGroup( type ); registeredListeners.clear(); if ( listeners != null ) { - for ( int i = 0, max = listeners.length; i < max; i++ ) { - registeredListeners.appendListener( listeners[i] ); + for ( T listener : listeners ) { + registeredListeners.appendListener( listener ); } } } @Override - public void appendListeners(EventType type, Class... listenerClasses) { + @SafeVarargs + public final void appendListeners(EventType type, Class... listenerClasses) { appendListeners( type, resolveListenerInstances( type, listenerClasses ) ); } @Override - public void appendListeners(EventType type, T... listeners) { + @SafeVarargs + public final void appendListeners(EventType type, T... listeners) { getEventListenerGroup( type ).appendListeners( listeners ); } @Override - public void prependListeners(EventType type, Class... listenerClasses) { + @SafeVarargs + public final void prependListeners(EventType type, Class... listenerClasses) { prependListeners( type, resolveListenerInstances( type, listenerClasses ) ); } @Override - public void prependListeners(EventType type, T... listeners) { + @SafeVarargs + public final void prependListeners(EventType type, T... listeners) { getEventListenerGroup( type ).prependListeners( listeners ); } - private static EventListenerGroupImpl[] prepareListenerAssociation() { + private EventListenerGroupImpl[] buildListenerGroups() { EventListenerGroupImpl[] listenerArray = new EventListenerGroupImpl[ EventType.values().size() ]; // auto-flush listeners @@ -347,12 +438,14 @@ private static EventListenerGroupImpl[] prepareListenerAssociation() { // post-delete listeners prepareListeners( POST_DELETE, + new PostDeleteEventListenerStandardImpl(), listenerArray ); // post-insert listeners prepareListeners( POST_INSERT, + new PostInsertEventListenerStandardImpl(), listenerArray ); @@ -366,6 +459,7 @@ private static EventListenerGroupImpl[] prepareListenerAssociation() { // post-update listeners prepareListeners( POST_UPDATE, + new PostUpdateEventListenerStandardImpl(), listenerArray ); @@ -407,19 +501,19 @@ private static EventListenerGroupImpl[] prepareListenerAssociation() { return listenerArray; } - private static void prepareListeners(EventType type, EventListenerGroupImpl[] listenerArray) { + private void prepareListeners(EventType type, EventListenerGroupImpl[] listenerArray) { prepareListeners( type, null, listenerArray ); } - private static void prepareListeners(EventType type, T defaultListener, EventListenerGroupImpl[] listenerArray) { + private void prepareListeners(EventType type, T defaultListener, EventListenerGroupImpl[] listenerArray) { final EventListenerGroupImpl listenerGroup; if ( type == EventType.POST_COMMIT_DELETE || type == EventType.POST_COMMIT_INSERT || type == EventType.POST_COMMIT_UPDATE ) { - listenerGroup = new PostCommitEventListenerGroupImpl( type ); + listenerGroup = new PostCommitEventListenerGroupImpl( type, this ); } else { - listenerGroup = new EventListenerGroupImpl( type ); + listenerGroup = new EventListenerGroupImpl( type, this ); } if ( defaultListener != null ) { @@ -428,4 +522,13 @@ private static void prepareListeners(EventType type, T defaultListener, E listenerArray[ type.ordinal() ] = listenerGroup; } + @Override + public void stop() { + if ( callbackRegistry != null ) { + callbackRegistry.release(); + } + if ( callbackBuilder != null ) { + callbackBuilder.release(); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerServiceInitiator.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerServiceInitiator.java index 2e249bf63b4d..2cedca3eb520 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerServiceInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerServiceInitiator.java @@ -11,6 +11,7 @@ import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.SessionFactoryServiceInitiator; +import org.hibernate.service.spi.SessionFactoryServiceInitiatorContext; /** * Service initiator for {@link EventListenerRegistry} @@ -30,6 +31,11 @@ public EventListenerRegistry initiateService( SessionFactoryImplementor sessionFactory, SessionFactoryOptions sessionFactoryOptions, ServiceRegistryImplementor registry) { - return new EventListenerRegistryImpl(); + return new EventListenerRegistryImpl( sessionFactory, sessionFactoryOptions, registry ); + } + + @Override + public EventListenerRegistry initiateService(SessionFactoryServiceInitiatorContext context) { + return new EventListenerRegistryImpl( context.getSessionFactory(), context.getSessionFactoryOptions(), context.getServiceRegistry()); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/PostCommitEventListenerGroupImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/PostCommitEventListenerGroupImpl.java index 73c7d7b5cddd..b7105a0ff5d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/PostCommitEventListenerGroupImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/PostCommitEventListenerGroupImpl.java @@ -22,13 +22,13 @@ * * @author Steve Ebersole */ -public class PostCommitEventListenerGroupImpl extends EventListenerGroupImpl { +class PostCommitEventListenerGroupImpl extends EventListenerGroupImpl { private static final CoreMessageLogger log = CoreLogging.messageLogger( PostCommitEventListenerGroupImpl.class ); private final Class extendedListenerContract; - public PostCommitEventListenerGroupImpl(EventType eventType) { - super( eventType ); + public PostCommitEventListenerGroupImpl(EventType eventType, EventListenerRegistryImpl listenerRegistry) { + super( eventType, listenerRegistry ); if ( eventType == EventType.POST_COMMIT_DELETE ) { this.extendedListenerContract = PostCommitDeleteEventListener.class; diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerRegistry.java b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerRegistry.java index deac5ef7efb9..44afb85b6087 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerRegistry.java @@ -8,6 +8,7 @@ import java.io.Serializable; +import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.event.spi.EventType; import org.hibernate.service.Service; @@ -18,16 +19,18 @@ * @author Steve Ebersole */ public interface EventListenerRegistry extends Service, Serializable { - public EventListenerGroup getEventListenerGroup(EventType eventType); + void prepare(MetadataImplementor metadata); - public void addDuplicationStrategy(DuplicationStrategy strategy); + EventListenerGroup getEventListenerGroup(EventType eventType); - public void setListeners(EventType type, Class... listeners); - public void setListeners(EventType type, T... listeners); + void addDuplicationStrategy(DuplicationStrategy strategy); - public void appendListeners(EventType type, Class... listeners); - public void appendListeners(EventType type, T... listeners); + void setListeners(EventType type, Class... listeners); + void setListeners(EventType type, T... listeners); - public void prependListeners(EventType type, Class... listeners); - public void prependListeners(EventType type, T... listeners); + void appendListeners(EventType type, Class... listeners); + void appendListeners(EventType type, T... listeners); + + void prependListeners(EventType type, Class... listeners); + void prependListeners(EventType type, T... listeners); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/spi/JpaBootstrapSensitive.java b/hibernate-core/src/main/java/org/hibernate/event/service/spi/JpaBootstrapSensitive.java new file mode 100644 index 000000000000..b9b9e682e74a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/service/spi/JpaBootstrapSensitive.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.event.service.spi; + +/** + * Defines an event listener that is sensitive to whether a native or jpa bootstrap was performed + * + * @author Steve Ebersole + */ +public interface JpaBootstrapSensitive { + void wasJpaBootstrap(boolean wasJpaBootstrap); +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java index ed186ab39527..bd33e6082477 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java @@ -87,10 +87,10 @@ private LoadEvent( boolean isAssociationFetch, EventSource source) { - super(source); + super( source ); if ( entityId == null ) { - throw new IllegalArgumentException("id to load is required for loading"); + throw new IllegalArgumentException( "id to load is required for loading" ); } if ( lockOptions.getLockMode() == LockMode.WRITE ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PersistEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PersistEvent.java index d794e11d61b8..208115344bcc 100755 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PersistEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PersistEvent.java @@ -25,7 +25,7 @@ public PersistEvent(Object object, EventSource source) { super(source); if ( object == null ) { throw new IllegalArgumentException( - "attempt to create create event with null entity" + "attempt to create event with null entity" ); } this.object = object; diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostActionEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostActionEventListener.java new file mode 100644 index 000000000000..5e9959cbbe89 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostActionEventListener.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.event.spi; + +import org.hibernate.persister.entity.EntityPersister; + +/** + * @author Andrea Boriero + */ +interface PostActionEventListener { + + /** + * Does this listener require that after transaction hooks be registered? + * + * @param persister The persister for the entity in question. + * + * @return {@code true} if after transaction callbacks should be added. + * + * @deprecated use {@link #requiresPostCommitHandling(EntityPersister)} + */ + @Deprecated + boolean requiresPostCommitHanding(EntityPersister persister); + + /** + * Does this listener require that after transaction hooks be registered? + * + * @param persister The persister for the entity in question. + * + * @return {@code true} if after transaction callbacks should be added. + */ + default boolean requiresPostCommitHandling(EntityPersister persister) { + return requiresPostCommitHanding( persister ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEvent.java index d9fd7b9d9d6e..f2a60eb9a7fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEvent.java @@ -10,7 +10,7 @@ import org.hibernate.persister.collection.CollectionPersister; /** - * An event that occurs afterQuery a collection is recreated + * An event that occurs after a collection is recreated * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEventListener.java index f0dc2d4366d5..7e98512b3183 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRecreateEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called afterQuery recreating a collection + * Called after recreating a collection * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEvent.java index 5d4ef1ac8851..ada9036e452c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEvent.java @@ -10,7 +10,7 @@ import org.hibernate.persister.collection.CollectionPersister; /** - * An event that occurs afterQuery a collection is removed + * An event that occurs after a collection is removed * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEventListener.java index ddc0a9c36a9e..ba83d04fd913 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionRemoveEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called afterQuery removing a collection + * Called after removing a collection * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEvent.java index e64c5a101632..8df470ed56ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEvent.java @@ -10,7 +10,7 @@ import org.hibernate.persister.collection.CollectionPersister; /** - * An event that occurs afterQuery a collection is updated + * An event that occurs after a collection is updated * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEventListener.java index 58ec0d93ef12..1417fdafa087 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCollectionUpdateEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called afterQuery updating a collection + * Called after updating a collection * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitDeleteEventListener.java index 9f96f9b29921..de8ef9ebebf9 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitDeleteEventListener.java @@ -7,7 +7,7 @@ package org.hibernate.event.spi; /** - * Called afterQuery an entity delete is committed to the datastore. + * Called after an entity delete is committed to the datastore. * * @author Shawn Clowater */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitInsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitInsertEventListener.java index 9bf0cb233025..4fc2551efb72 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitInsertEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitInsertEventListener.java @@ -7,7 +7,7 @@ package org.hibernate.event.spi; /** - * Called afterQuery an entity insert is committed to the datastore. + * Called after an entity insert is committed to the datastore. * * @author Shawn Clowater */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitUpdateEventListener.java index ceba995c70e4..eed3b38de861 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostCommitUpdateEventListener.java @@ -7,7 +7,7 @@ package org.hibernate.event.spi; /** - * Called afterQuery an entity update is committed to the datastore. + * Called after an entity update is committed to the datastore. * * @author Shawn Clowater */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java index 870f12d9cbda..d4057028a79d 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java @@ -11,7 +11,7 @@ import org.hibernate.persister.entity.EntityPersister; /** - * Occurs afterQuery deleting an item from the datastore + * Occurs after deleting an item from the datastore * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEventListener.java index 1173ff865d41..c028342a5823 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEventListener.java @@ -8,15 +8,11 @@ import java.io.Serializable; -import org.hibernate.persister.entity.EntityPersister; - /** - * Called afterQuery deleting an item from the datastore + * Called after deleting an item from the datastore * * @author Gavin King */ -public interface PostDeleteEventListener extends Serializable { - public void onPostDelete(PostDeleteEvent event); - - public boolean requiresPostCommitHanding(EntityPersister persister); +public interface PostDeleteEventListener extends Serializable, PostActionEventListener { + void onPostDelete(PostDeleteEvent event); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java old mode 100755 new mode 100644 index 2a90233a8277..c6b500244c56 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java @@ -11,7 +11,7 @@ import org.hibernate.persister.entity.EntityPersister; /** - * Occurs afterQuery inserting an item in the datastore + * Occurs after inserting an item in the datastore * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEventListener.java old mode 100755 new mode 100644 index 67e77bbda965..768bd0d0daae --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEventListener.java @@ -8,25 +8,12 @@ import java.io.Serializable; -import org.hibernate.persister.entity.EntityPersister; - /** - * Called afterQuery insterting an item in the datastore + * Called after insterting an item in the datastore * * @author Gavin King * @author Steve Ebersole */ -public interface PostInsertEventListener extends Serializable { - public void onPostInsert(PostInsertEvent event); - - /** - * Does this listener require that afterQuery transaction hooks be registered? Typically this is {@code true} - * for post-insert event listeners, but may not be, for example, in JPA cases where there are no callbacks defined - * for the particular entity. - * - * @param persister The persister for the entity in question. - * - * @return {@code true} if afterQuery transaction callbacks should be added. - */ - public boolean requiresPostCommitHanding(EntityPersister persister); +public interface PostInsertEventListener extends Serializable, PostActionEventListener { + void onPostInsert(PostInsertEvent event); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEvent.java index c09481ff1c7d..88f8e1bb3074 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEvent.java @@ -11,7 +11,7 @@ import org.hibernate.persister.entity.EntityPersister; /** - * Occurs afterQuery an an entity instance is fully loaded. + * Occurs after an an entity instance is fully loaded. * * @author Kabir Khan, Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEventListener.java index b379fe660519..7881d026d8c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostLoadEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Occurs afterQuery an an entity instance is fully loaded. + * Occurs after an an entity instance is fully loaded. * * @author Kabir Khan */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java old mode 100755 new mode 100644 index bffd0c567154..f51d97e0ef9e --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java @@ -11,7 +11,7 @@ import org.hibernate.persister.entity.EntityPersister; /** - * Occurs afterQuery the datastore is updated + * Occurs after the datastore is updated * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEventListener.java old mode 100755 new mode 100644 index 9654d16c1ca2..1324f1cabc55 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEventListener.java @@ -8,15 +8,11 @@ import java.io.Serializable; -import org.hibernate.persister.entity.EntityPersister; - /** - * Called afterQuery updating the datastore + * Called after updating the datastore * * @author Gavin King */ -public interface PostUpdateEventListener extends Serializable { - public void onPostUpdate(PostUpdateEvent event); - - public boolean requiresPostCommitHanding(EntityPersister persister); +public interface PostUpdateEventListener extends Serializable, PostActionEventListener { + void onPostUpdate(PostUpdateEvent event); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEvent.java index 5d4527aa7b7d..744d03cadbbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEvent.java @@ -10,7 +10,7 @@ import org.hibernate.persister.collection.CollectionPersister; /** - * An event that occurs beforeQuery a collection is recreated + * An event that occurs before a collection is recreated * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEventListener.java index f2190b76a9b6..a201d0120e40 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRecreateEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called beforeQuery recreating a collection + * Called before recreating a collection * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEvent.java index 812b460879ee..db097564fe14 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEvent.java @@ -10,7 +10,7 @@ import org.hibernate.persister.collection.CollectionPersister; /** - * An event that occurs beforeQuery a collection is removed + * An event that occurs before a collection is removed * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEventListener.java index 03fa0b03473d..dc2da1d1f4e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionRemoveEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called beforeQuery removing a collection + * Called before removing a collection * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEvent.java index 66c615cc925b..86819c670127 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEvent.java @@ -10,7 +10,7 @@ import org.hibernate.persister.collection.CollectionPersister; /** - * An event that occurs beforeQuery a collection is updated + * An event that occurs before a collection is updated * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEventListener.java index ab6b2d9518dd..8c73a5290518 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreCollectionUpdateEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called beforeQuery updating a collection + * Called before updating a collection * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEventListener.java old mode 100755 new mode 100644 index 2be9bd780cd8..46e36d0a9afa --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called beforeQuery deleting an item from the datastore + * Called before deleting an item from the datastore * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEventListener.java old mode 100755 new mode 100644 index b00ca67e6ab4..15a92ad085af --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called beforeQuery inserting an item in the datastore + * Called before inserting an item in the datastore * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEvent.java old mode 100755 new mode 100644 index 94f5807b1e64..8673d8b375f0 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEvent.java @@ -12,7 +12,7 @@ import org.hibernate.secure.spi.PermissionCheckEntityInformation; /** - * Called beforeQuery injecting property values into a newly loaded entity instance. + * Called before injecting property values into a newly loaded entity instance. * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEventListener.java old mode 100755 new mode 100644 index 7c5be1b4c8e4..99616fa6e397 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreLoadEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called beforeQuery injecting property values into a newly + * Called before injecting property values into a newly * loaded entity instance. * * @author Gavin King diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEventListener.java old mode 100755 new mode 100644 index d423a3971396..6b21f9d2ee40 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEventListener.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Called beforeQuery updating the datastore + * Called before updating the datastore * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/CollectionSubqueryFactory.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/CollectionSubqueryFactory.java index 1d5e2288fc45..bbfb1d1103b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/CollectionSubqueryFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/CollectionSubqueryFactory.java @@ -11,7 +11,6 @@ import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; -import org.hibernate.internal.util.StringHelper; import org.hibernate.sql.JoinFragment; /** @@ -31,7 +30,7 @@ public static String createCollectionSubquery( String[] columns) { try { JoinFragment join = joinSequence.toJoinFragment( enabledFilters, true ); - return "select " + StringHelper.join( ", ", columns ) + return "select " + String.join( ", ", columns ) + " from " + join.toFromFragmentString().substring( 2 ) + " where " + join.toWhereFragmentString().substring( 5 ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/HolderInstantiator.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/HolderInstantiator.java index 78918cc575b6..95e11244559a 100755 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/HolderInstantiator.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/HolderInstantiator.java @@ -6,6 +6,7 @@ */ package org.hibernate.hql.internal; import java.lang.reflect.Constructor; +import java.util.function.Supplier; import org.hibernate.transform.AliasToBeanConstructorResultTransformer; import org.hibernate.transform.ResultTransformer; @@ -16,10 +17,10 @@ */ public final class HolderInstantiator { - public static final HolderInstantiator NOOP_INSTANTIATOR = new HolderInstantiator(null,null); + public static final HolderInstantiator NOOP_INSTANTIATOR = new HolderInstantiator(null); private final ResultTransformer transformer; - private final String[] queryReturnAliases; + private Supplier queryReturnAliasesSupplier = () -> null; public static HolderInstantiator getHolderInstantiator(ResultTransformer selectNewTransformer, ResultTransformer customTransformer, String[] queryReturnAliases) { return new HolderInstantiator( @@ -47,20 +48,29 @@ else if ( returnLists ) { } } - static public HolderInstantiator createClassicHolderInstantiator(Constructor constructor, + static public HolderInstantiator createClassicHolderInstantiator(Constructor constructor, ResultTransformer transformer) { - return new HolderInstantiator( resolveClassicResultTransformer( constructor, transformer ), null ); + return new HolderInstantiator( resolveClassicResultTransformer( constructor, transformer ) ); } static public ResultTransformer resolveClassicResultTransformer( Constructor constructor, ResultTransformer transformer) { return constructor != null ? new AliasToBeanConstructorResultTransformer( constructor ) : transformer; - } + } + + public HolderInstantiator(ResultTransformer transformer) { + this.transformer = transformer; + } public HolderInstantiator(ResultTransformer transformer, String[] queryReturnAliases) { this.transformer = transformer; - this.queryReturnAliases = queryReturnAliases; + this.queryReturnAliasesSupplier = () -> queryReturnAliases; + } + + public HolderInstantiator(ResultTransformer transformer, Supplier queryReturnAliasesSupplier) { + this.transformer = transformer; + this.queryReturnAliasesSupplier = queryReturnAliasesSupplier; } public boolean isRequired() { @@ -72,12 +82,12 @@ public Object instantiate(Object[] row) { return row; } else { - return transformer.transformTuple(row, queryReturnAliases); + return transformer.transformTuple(row, getQueryReturnAliases()); } } public String[] getQueryReturnAliases() { - return queryReturnAliases; + return queryReturnAliasesSupplier.get(); } public ResultTransformer getResultTransformer() { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/DetailedSemanticException.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/DetailedSemanticException.java index 09da55cdcdc1..89e92863dcf3 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/DetailedSemanticException.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/DetailedSemanticException.java @@ -8,6 +8,8 @@ import java.io.PrintStream; import java.io.PrintWriter; +import org.hibernate.internal.build.AllowPrintStacktrace; + import antlr.SemanticException; /** @@ -45,6 +47,7 @@ public String toString() { /** * Prints a stack trace. */ + @AllowPrintStacktrace public void printStackTrace() { super.printStackTrace(); if ( cause != null ) { @@ -57,6 +60,7 @@ public void printStackTrace() { * * @param s the print stream. */ + @AllowPrintStacktrace public void printStackTrace(PrintStream s) { super.printStackTrace( s ); if ( cause != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ErrorCounter.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ErrorCounter.java deleted file mode 100644 index d2de0fe27b92..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ErrorCounter.java +++ /dev/null @@ -1,97 +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.hql.internal.ast; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.hibernate.QueryException; -import org.hibernate.internal.CoreMessageLogger; - -import org.jboss.logging.Logger; - -import antlr.RecognitionException; - -/** - * An error handler that counts parsing errors and warnings. - */ -public class ErrorCounter implements ParseErrorHandler { - private static final CoreMessageLogger LOG = Logger.getMessageLogger( - CoreMessageLogger.class, - ErrorCounter.class.getName() - ); - - private final String hql; - - private List errorList = new ArrayList(); - private List recognitionExceptions = new ArrayList(); - - /** - * Constructs an ErrorCounter without knowledge of the HQL, meaning that generated QueryException - * instances *will not* contain the HQL (and will need to be wrapped at a higher level in another - * QueryException). - */ - public ErrorCounter() { - this( null ); - } - - /** - * Constructs an ErrorCounter with knowledge of the HQL, meaning that generated QueryException - * instances *will* contain the HQL. - */ - public ErrorCounter(String hql) { - this.hql = hql; - } - - @Override - public void reportError(RecognitionException e) { - reportError( e.toString() ); - recognitionExceptions.add( e ); - LOG.error( e.toString(), e ); - } - - @Override - public void reportError(String message) { - LOG.error( message ); - errorList.add( message ); - } - - @Override - public int getErrorCount() { - return errorList.size(); - } - - @Override - public void reportWarning(String message) { - LOG.debug( message ); - } - - private String getErrorString() { - final StringBuilder buf = new StringBuilder(); - final Iterator iterator = errorList.iterator(); - while ( iterator.hasNext() ) { - buf.append( iterator.next() ); - if ( iterator.hasNext() ) { - buf.append( "\n" ); - } - - } - return buf.toString(); - } - - @Override - public void throwQueryException() throws QueryException { - if ( getErrorCount() > 0 ) { - if ( recognitionExceptions.size() > 0 ) { - throw QuerySyntaxException.convert( recognitionExceptions.get( 0 ), hql ); - } - throw new QueryException( getErrorString(), hql ); - } - LOG.debug( "throwQueryException() : no errors" ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ErrorTracker.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ErrorTracker.java new file mode 100644 index 000000000000..c413cf981f63 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ErrorTracker.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 . + */ +package org.hibernate.hql.internal.ast; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.hibernate.QueryException; +import org.hibernate.internal.CoreMessageLogger; + +import org.jboss.logging.Logger; + +import antlr.RecognitionException; + +/** + * An error handler that counts parsing errors and warnings. + */ +public class ErrorTracker implements ParseErrorHandler { + private static final CoreMessageLogger LOG = Logger.getMessageLogger( + CoreMessageLogger.class, + ErrorTracker.class.getName() + ); + + private final String hql; + + private List errorList = new ArrayList<>(); + private List recognitionExceptions = new ArrayList<>(); + + /** + * Constructs an ErrorCounter without knowledge of the HQL, meaning that generated QueryException + * instances *will not* contain the HQL (and will need to be wrapped at a higher level in another + * QueryException). + */ + @SuppressWarnings("WeakerAccess") + public ErrorTracker() { + this( null ); + } + + /** + * Constructs an ErrorCounter with knowledge of the HQL, meaning that generated QueryException + * instances *will* contain the HQL. + */ + @SuppressWarnings("WeakerAccess") + public ErrorTracker(String hql) { + this.hql = hql; + } + + @Override + public void reportError(RecognitionException e) { + reportError( e.toString() ); + recognitionExceptions.add( e ); + LOG.error( e.toString(), e ); + } + + @Override + public void reportError(String message) { + LOG.error( message ); + errorList.add( message ); + } + + @Override + public int getErrorCount() { + return errorList.size(); + } + + @Override + public void reportWarning(String message) { + LOG.debug( message ); + } + + private String getErrorString() { + final StringBuilder buf = new StringBuilder(); + final Iterator iterator = errorList.iterator(); + while ( iterator.hasNext() ) { + buf.append( iterator.next() ); + if ( iterator.hasNext() ) { + buf.append( "\n" ); + } + + } + return buf.toString(); + } + + @Override + public void throwQueryException() throws QueryException { + if ( getErrorCount() > 0 ) { + if ( recognitionExceptions.size() > 0 ) { + throw QuerySyntaxException.convert( recognitionExceptions.get( 0 ), hql ); + } + throw new QueryException( getErrorString(), hql ); + } + LOG.debug( "throwQueryException() : no errors" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java index 932a1367442f..12dc091503f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlParser.java @@ -20,6 +20,7 @@ import org.hibernate.hql.internal.antlr.HqlTokenTypes; import org.hibernate.hql.internal.ast.util.ASTPrinter; import org.hibernate.hql.internal.ast.util.ASTUtil; +import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; @@ -41,11 +42,6 @@ public final class HqlParser extends HqlBaseParser { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( HqlParser.class ); private final ParseErrorHandler parseErrorHandler; - private final ASTPrinter printer = getASTPrinter(); - - private static ASTPrinter getASTPrinter() { - return new ASTPrinter( org.hibernate.hql.internal.antlr.HqlTokenTypes.class ); - } /** * Get a HqlParser instance for the given HQL string. @@ -61,7 +57,7 @@ public static HqlParser getInstance(String hql) { private HqlParser(String hql) { // The fix for HHH-558... super( new HqlLexer( new StringReader( hql ) ) ); - parseErrorHandler = new ErrorCounter( hql ); + parseErrorHandler = new ErrorTracker( hql ); // Create nodes that track line and column number. setASTFactory( new HqlASTFactory() ); } @@ -362,7 +358,7 @@ public void showAst(AST ast, PrintStream out) { } private void showAst(AST ast, PrintWriter pw) { - printer.showAst( ast, pw ); + TokenPrinters.HQL_TOKEN_PRINTER.showAst( ast, pw ); } @Override @@ -389,6 +385,21 @@ public void firstPathTokenWeakKeywords() throws TokenStreamException { } } + @Override + public void handlePrimaryExpressionDotIdent() throws TokenStreamException { + if ( LA( 2 ) == DOT && LA( 3 ) != IDENT ) { + // See if the second lookahead token can be an identifier. + HqlToken t = (HqlToken) LT( 3 ); + if ( t.isPossibleID() ) { + // Set it! + t.setType( IDENT ); + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "handleDotIdent() : new LT(3) token - %s", LT( 1 ) ); + } + } + } + } + @Override public void weakKeywords() throws TokenStreamException { @@ -406,7 +417,7 @@ public void weakKeywords() throws TokenStreamException { } break; default: - // Case 2: The current token is afterQuery FROM and beforeQuery '.'. + // Case 2: The current token is after FROM and before '.'. if ( LA( 0 ) == FROM && t != IDENT && LA( 2 ) == DOT ) { HqlToken hqlToken = (HqlToken) LT( 1 ); if ( hqlToken.isPossibleID() ) { 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 f4fe6dabc359..f493f9c97418 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 @@ -15,12 +15,15 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import org.hibernate.QueryException; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.internal.JoinSequence; import org.hibernate.engine.internal.ParameterBinder; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.CollectionProperties; import org.hibernate.hql.internal.antlr.HqlSqlBaseWalker; @@ -62,6 +65,7 @@ import org.hibernate.hql.internal.ast.util.NodeTraverser; import org.hibernate.hql.internal.ast.util.SessionFactoryHelper; import org.hibernate.hql.internal.ast.util.SyntheticAndFactory; +import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.hibernate.hql.spi.QueryTranslator; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.IdentifierGenerator; @@ -92,6 +96,8 @@ import antlr.SemanticException; import antlr.collections.AST; +import static org.hibernate.hql.spi.QueryTranslator.ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED; + /** * Implements methods used by the HQL->SQL tree transform grammar (a.k.a. the second phase). *

      @@ -113,7 +119,6 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par private final AliasGenerator aliasGenerator = new AliasGenerator(); private final LiteralProcessor literalProcessor; private final ParseErrorHandler parseErrorHandler; - private final ASTPrinter printer; private final String collectionFilterRole; private FromClause currentFromClause; @@ -123,15 +128,16 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par * Maps each top-level result variable to its SelectExpression; * (excludes result variables defined in subqueries) */ - private Map selectExpressionsByResultVariable = new HashMap(); + private Map selectExpressionsByResultVariable = new HashMap<>(); - private Set querySpaces = new HashSet(); + private Set querySpaces = new HashSet<>(); private int parameterCount; - private Map namedParameters = new HashMap(); - private ArrayList parameters = new ArrayList(); + private Map namedParameters; + private Map positionalParameters; + + private ArrayList parameterSpecs = new ArrayList<>(); private int numberOfParametersInSetClause; - private int positionalParameterCount; private ArrayList assignmentSpecifications = new ArrayList(); @@ -158,14 +164,13 @@ public HqlSqlWalker( String collectionRole) { setASTFactory( new SqlASTFactory( this ) ); // Initialize the error handling delegate. - this.parseErrorHandler = new ErrorCounter( qti.getQueryString() ); + this.parseErrorHandler = new ErrorTracker( qti.getQueryString() ); this.queryTranslatorImpl = qti; this.sessionFactoryHelper = new SessionFactoryHelper( sfi ); this.literalProcessor = new LiteralProcessor( this ); this.tokenReplacements = tokenReplacements; this.collectionFilterRole = collectionRole; this.hqlParser = parser; - this.printer = new ASTPrinter( SqlTokenTypes.class ); } // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -188,7 +193,7 @@ public void traceIn(String ruleName, AST tree) { private String buildTraceNodeName(AST tree) { return tree == null ? "???" - : tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; + : tree.getText() + " [" + TokenPrinters.SQL_TOKEN_PRINTER.getTokenTypeName( tree.getType() ) + "]"; } @Override @@ -225,7 +230,7 @@ protected void prepareFromClauseInputTree(AST fromClauseInput) { // positionalParameterCount++ // ); // collectionFilterKeyParameter.setHqlParameterSpecification( paramSpec ); -// parameters.add( paramSpec ); +// parameterSpecs.add( paramSpec ); // } // } // } @@ -250,14 +255,16 @@ protected void prepareFromClauseInputTree(AST fromClauseInput) { queryTranslatorImpl.showHqlAst( hqlParser.getAST() ); // Create a parameter specification for the collection filter... - Type collectionFilterKeyType = sessionFactoryHelper.requireQueryableCollection( collectionFilterRole ) + final Type collectionFilterKeyType = sessionFactoryHelper.requireQueryableCollection( collectionFilterRole ) .getKeyType(); - ParameterNode collectionFilterKeyParameter = (ParameterNode) astFactory.create( PARAM, "?" ); - CollectionFilterKeyParameterSpecification collectionFilterKeyParameterSpec = new CollectionFilterKeyParameterSpecification( - collectionFilterRole, collectionFilterKeyType, positionalParameterCount++ + final ParameterNode collectionFilterKeyParameter = (ParameterNode) astFactory.create( PARAM, "?" ); + final CollectionFilterKeyParameterSpecification collectionFilterKeyParameterSpec = new CollectionFilterKeyParameterSpecification( + collectionFilterRole, + collectionFilterKeyType ); + parameterCount++; collectionFilterKeyParameter.setHqlParameterSpecification( collectionFilterKeyParameterSpec ); - parameters.add( collectionFilterKeyParameterSpec ); + parameterSpecs.add( collectionFilterKeyParameterSpec ); } } } @@ -934,7 +941,7 @@ protected void postProcessInsert(AST insert) throws SemanticException, QueryExce versionValueNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" ); ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType ); ( (ParameterNode) versionValueNode ).setHqlParameterSpecification( paramSpec ); - parameters.add( 0, paramSpec ); + parameterSpecs.add( 0, paramSpec ); if ( sessionFactoryHelper.getFactory().getDialect().requiresCastingOfParametersInSelectClause() ) { // we need to wrtap the param in a cast() @@ -975,7 +982,7 @@ else if ( isDatabaseGeneratedTimestamp( versionType ) ) { versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, functionName ); } else { - throw new QueryException( "cannot handle version type [" + versionType + "] on bulk inserts with dialects not supporting parameters in insert-select statements" ); + throw new QueryException( "cannot handle version type [" + versionType + "] on bulk inserts with dialects not supporting parameterSpecs in insert-select statements" ); } } @@ -1023,6 +1030,11 @@ private void createSelectClauseFromFromClause(QueryNode qn) throws SemanticExcep @Override protected void resolve(AST node) throws SemanticException { + resolve(node, null); + } + + @Override + protected void resolve(AST node, AST predicateNode) throws SemanticException { if ( node != null ) { // This is called when it's time to fully resolve a path expression. ResolvableNode r = (ResolvableNode) node; @@ -1030,7 +1042,7 @@ protected void resolve(AST node) throws SemanticException { r.resolveInFunctionCall( false, true ); } else { - r.resolve( false, true ); // Generate implicit joins, only if necessary. + r.resolve( false, true, null, null, predicateNode ); // Generate implicit joins, only if necessary. } } } @@ -1074,64 +1086,114 @@ protected void beforeSelectClause() throws SemanticException { } @Override - protected AST generatePositionalParameter(AST inputNode) throws SemanticException { - if ( namedParameters.size() > 0 ) { + protected AST generatePositionalParameter(AST delimiterNode, AST numberNode) throws SemanticException { + // todo : we check this multiple times + if ( getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() && namedParameters != null ) { throw new SemanticException( - "cannot define positional parameter afterQuery any named parameters have been defined" + "Cannot mix positional and named parameters: " + queryTranslatorImpl.getQueryString() ); } - LOG.warnf( - "[DEPRECATION] Encountered positional parameter near line %s, column %s in HQL: [%s]. Positional parameter " + - "are considered deprecated; use named parameters or JPA-style positional parameters instead.", - inputNode.getLine(), - inputNode.getColumn(), - queryTranslatorImpl.getQueryString() - ); - ParameterNode parameter = (ParameterNode) astFactory.create( PARAM, "?" ); - PositionalParameterSpecification paramSpec = new PositionalParameterSpecification( - inputNode.getLine(), - inputNode.getColumn(), - positionalParameterCount++ + + if ( numberNode == null ) { + throw new QueryException( + String.format( + Locale.ROOT, + ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED, + queryTranslatorImpl.getQueryString() + ) + ); + } + final String positionString = numberNode.getText(); + final int label = Integer.parseInt( positionString ); + trackPositionalParameterPositions( label ); + + final ParameterNode parameter = (ParameterNode) astFactory.create( PARAM, positionString ); + parameter.setText( "?" ); + + final int queryParamtersPosition = isFilter() + ? label + : label - 1; + final PositionalParameterSpecification paramSpec = new PositionalParameterSpecification( + delimiterNode.getLine(), + delimiterNode.getColumn(), + label, + queryParamtersPosition ); parameter.setHqlParameterSpecification( paramSpec ); - parameters.add( paramSpec ); + parameterSpecs.add( paramSpec ); + return parameter; } + @SuppressWarnings("unchecked") + private void trackPositionalParameterPositions(int label) { + if ( positionalParameters == null ) { + positionalParameters = new HashMap(); + } + + final Integer loc = parameterCount++; + + final Object existingValue = positionalParameters.get( label ); + if ( existingValue == null ) { + positionalParameters.put( label, loc ); + } + else if ( existingValue instanceof Integer ) { + final ArrayList list = new ArrayList(); + positionalParameters.put( label, list ); + list.add( existingValue ); + list.add( loc ); + } + else { + ( (List) existingValue ).add( loc ); + } + } + @Override protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException { - String name = nameNode.getText(); + if ( getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() && positionalParameters != null ) { + throw new SemanticException( + "Cannot mix positional and named parameters: " + queryTranslatorImpl.getQueryString() + ); + } + final String name = nameNode.getText(); trackNamedParameterPositions( name ); // create the node initially with the param name so that it shows // appropriately in the "original text" attribute - ParameterNode parameter = (ParameterNode) astFactory.create( NAMED_PARAM, name ); + final ParameterNode parameter = (ParameterNode) astFactory.create( NAMED_PARAM, name ); parameter.setText( "?" ); - NamedParameterSpecification paramSpec = new NamedParameterSpecification( + final NamedParameterSpecification paramSpec = new NamedParameterSpecification( delimiterNode.getLine(), delimiterNode.getColumn(), name ); parameter.setHqlParameterSpecification( paramSpec ); - parameters.add( paramSpec ); + parameterSpecs.add( paramSpec ); + return parameter; } + @SuppressWarnings("unchecked") private void trackNamedParameterPositions(String name) { - Integer loc = parameterCount++; - Object o = namedParameters.get( name ); - if ( o == null ) { + if ( namedParameters == null ) { + namedParameters = new HashMap(); + } + + final Integer loc = parameterCount++; + + final Object existingValue = namedParameters.get( name ); + if ( existingValue == null ) { namedParameters.put( name, loc ); } - else if ( o instanceof Integer ) { - ArrayList list = new ArrayList( 4 ); - list.add( o ); + else if ( existingValue instanceof Integer ) { + ArrayList list = new ArrayList<>( 4 ); + list.add( (Integer) existingValue ); list.add( loc ); namedParameters.put( name, list ); } else { - ( (ArrayList) o ).add( loc ); + ( (List) existingValue ).add( loc ); } } @@ -1274,11 +1336,11 @@ public LiteralProcessor getLiteralProcessor() { } public ASTPrinter getASTPrinter() { - return printer; + return TokenPrinters.SQL_TOKEN_PRINTER; } - public ArrayList getParameters() { - return parameters; + public ArrayList getParameterSpecs() { + return parameterSpecs; } public int getNumberOfParametersInSetClause() { @@ -1349,7 +1411,7 @@ protected void prepareVersioned(AST updateNode, AST versioned) throws SemanticEx versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" ); ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType ); ( (ParameterNode) versionIncrementNode ).setHqlParameterSpecification( paramSpec ); - parameters.add( 0, paramSpec ); + parameterSpecs.add( 0, paramSpec ); } else { // Not possible to simply re-use the versionPropertyNode here as it causes @@ -1415,6 +1477,10 @@ public Set getTreatAsDeclarationsByPath(String path) { return hqlParser.getTreatMap().get( path ); } + public Dialect getDialect() { + return sessionFactoryHelper.getFactory().getServiceRegistry().getService( JdbcServices.class ).getDialect(); + } + public static void panic() { throw new QueryException( "TreeWalker: panic" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/NamedParameterInformationImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/NamedParameterInformationImpl.java new file mode 100644 index 000000000000..6931f50953be --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/NamedParameterInformationImpl.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.hql.internal.ast; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.hql.spi.NamedParameterInformation; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class NamedParameterInformationImpl implements NamedParameterInformation { + private final String name; + + private final List sqlPositions = new ArrayList<>(); + + private Type expectedType; + + NamedParameterInformationImpl(String name, Type initialType) { + this.name = name; + this.expectedType = initialType; + } + + @Override + public String getSourceName() { + return name; + } + + @Override + public int[] getSourceLocations() { + return ArrayHelper.toIntArray( sqlPositions ); + } + + @Override + public Type getExpectedType() { + return expectedType; + } + + public void addSourceLocation(int position) { + sqlPositions.add( position ); + } + + public void setExpectedType(Type expectedType) { + this.expectedType = expectedType; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java index 8348562dbb2f..efefa8c72b51 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java @@ -6,19 +6,17 @@ */ package org.hibernate.hql.internal.ast; -import java.io.Serializable; -import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; +import org.hibernate.hql.spi.NamedParameterInformation; import org.hibernate.hql.spi.ParameterTranslations; -import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.hql.spi.PositionalParameterInformation; import org.hibernate.param.NamedParameterSpecification; import org.hibernate.param.ParameterSpecification; import org.hibernate.param.PositionalParameterSpecification; -import org.hibernate.type.Type; /** * Defines the information available for parameters encountered during @@ -27,52 +25,8 @@ * @author Steve Ebersole */ public class ParameterTranslationsImpl implements ParameterTranslations { - private final Map namedParameters; - private final ParameterInfo[] ordinalParameters; - - @Override - public boolean supportsOrdinalParameterMetadata() { - return true; - } - - @Override - public int getOrdinalParameterCount() { - return ordinalParameters.length; - } - - public ParameterInfo getOrdinalParameterInfo(int ordinalPosition) { - return ordinalParameters[ordinalPosition]; - } - - @Override - public int getOrdinalParameterSqlLocation(int ordinalPosition) { - return getOrdinalParameterInfo( ordinalPosition ).getSqlLocations()[0]; - } - - @Override - public Type getOrdinalParameterExpectedType(int ordinalPosition) { - return getOrdinalParameterInfo( ordinalPosition ).getExpectedType(); - } - - @Override - public Set getNamedParameterNames() { - return namedParameters.keySet(); - } - - public ParameterInfo getNamedParameterInfo(String name) { - return namedParameters.get( name ); - } - - @Override - public int[] getNamedParameterSqlLocations(String name) { - return getNamedParameterInfo( name ).getSqlLocations(); - } - - @Override - public Type getNamedParameterExpectedType(String name) { - return getNamedParameterInfo( name ).getExpectedType(); - } - + private final Map namedParameters; + private final Map ordinalParameters; /** * Constructs a parameter metadata object given a list of parameter * specifications. @@ -82,78 +36,85 @@ public Type getNamedParameterExpectedType(String name) { * * @param parameterSpecifications The parameter specifications */ - public ParameterTranslationsImpl(List parameterSpecifications) { - class NamedParamTempHolder { - private String name; - private Type type; - private List positions = new ArrayList<>(); - } + ParameterTranslationsImpl(List parameterSpecifications) { + Map namedParameters = null; + Map ordinalParameters = null; + + int i = 0; + for ( ParameterSpecification specification : parameterSpecifications ) { + if ( PositionalParameterSpecification.class.isInstance( specification ) ) { + if ( ordinalParameters == null ) { + ordinalParameters = new HashMap<>(); + } - final int size = parameterSpecifications.size(); - final List ordinalParameterList = new ArrayList<>(); - final Map namedParameterMap = new HashMap<>(); - for ( int i = 0; i < size; i++ ) { - final ParameterSpecification spec = parameterSpecifications.get( i ); - if ( PositionalParameterSpecification.class.isInstance( spec ) ) { - final PositionalParameterSpecification ordinalSpec = (PositionalParameterSpecification) spec; - ordinalParameterList.add( new ParameterInfo( i, ordinalSpec.getExpectedType() ) ); + final PositionalParameterSpecification ordinalSpecification = (PositionalParameterSpecification) specification; + final PositionalParameterInformationImpl info = ordinalParameters.computeIfAbsent( + ordinalSpecification.getLabel(), + k -> new PositionalParameterInformationImpl( k, ordinalSpecification.getExpectedType() ) + ); + info.addSourceLocation( i++ ); } - else if ( NamedParameterSpecification.class.isInstance( spec ) ) { - final NamedParameterSpecification namedSpec = (NamedParameterSpecification) spec; - NamedParamTempHolder paramHolder = namedParameterMap.get( namedSpec.getName() ); - if ( paramHolder == null ) { - paramHolder = new NamedParamTempHolder(); - paramHolder.name = namedSpec.getName(); - paramHolder.type = namedSpec.getExpectedType(); - namedParameterMap.put( namedSpec.getName(), paramHolder ); + else if ( NamedParameterSpecification.class.isInstance( specification ) ) { + if ( namedParameters == null ) { + namedParameters = new HashMap<>(); } - else if ( paramHolder.type == null && namedSpec.getExpectedType() != null ) { - // previous reference to the named parameter did not have type determined; - // this time, it can be determined by namedSpec.getExpectedType(). - paramHolder.type = namedSpec.getExpectedType(); + + final NamedParameterSpecification namedSpecification = (NamedParameterSpecification) specification; + final NamedParameterInformationImpl info = namedParameters.computeIfAbsent( + namedSpecification.getName(), + k -> new NamedParameterInformationImpl( k, namedSpecification.getExpectedType() ) + ); + + /* + If a previous reference to the NamedParameter already exists with expected type null and the new + reference expected type is not null then we add the type to the NamedParameterInformation. + + This situation may occur when the same name parameter is used twice in the same query, + an example is ".... where :name is null or name.last = :name" + + If info.getExpectedType() != null and also != namedSpecification.getExpectedType(), should an exception be thrown? + */ + if ( info.getExpectedType() == null && namedSpecification.getExpectedType() != null ) { + info.setExpectedType( namedSpecification.getExpectedType() ); } - paramHolder.positions.add( i ); + info.addSourceLocation( i++ ); } - // don't care about other param types here, just those explicitly user-defined... } - ordinalParameters = ordinalParameterList.toArray( new ParameterInfo[ordinalParameterList.size()] ); + if ( namedParameters == null ) { + this.namedParameters = Collections.emptyMap(); + } + else { + this.namedParameters = Collections.unmodifiableMap( namedParameters ); + } - if ( namedParameterMap.isEmpty() ) { - namedParameters = java.util.Collections.emptyMap(); + if ( ordinalParameters == null ) { + this.ordinalParameters = Collections.emptyMap(); } else { - final Map namedParametersBacking = new HashMap<>( namedParameterMap.size() ); - for ( NamedParamTempHolder holder : namedParameterMap.values() ) { - namedParametersBacking.put( - holder.name, - new ParameterInfo( ArrayHelper.toIntArray( holder.positions ), holder.type ) - ); - } - namedParameters = java.util.Collections.unmodifiableMap( namedParametersBacking ); + this.ordinalParameters = Collections.unmodifiableMap( ordinalParameters ); } } - public static class ParameterInfo implements Serializable { - private final int[] sqlLocations; - private final Type expectedType; - - public ParameterInfo(int[] sqlPositions, Type expectedType) { - this.sqlLocations = sqlPositions; - this.expectedType = expectedType; - } + @Override + @SuppressWarnings("unchecked") + public Map getNamedParameterInformationMap() { + return namedParameters; + } - public ParameterInfo(int sqlPosition, Type expectedType) { - this.sqlLocations = new int[] { sqlPosition }; - this.expectedType = expectedType; - } + @SuppressWarnings("unchecked") + @Override + public Map getPositionalParameterInformationMap() { + return ordinalParameters; + } - public int[] getSqlLocations() { - return sqlLocations; - } + @Override + public PositionalParameterInformation getPositionalParameterInformation(int position) { + return ordinalParameters.get( position ); + } - public Type getExpectedType() { - return expectedType; - } + @Override + public NamedParameterInformation getNamedParameterInformation(String name) { + return namedParameters.get( name ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/PositionalParameterInformationImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/PositionalParameterInformationImpl.java new file mode 100644 index 000000000000..78a412b666e6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/PositionalParameterInformationImpl.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.hql.internal.ast; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.hql.spi.PositionalParameterInformation; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class PositionalParameterInformationImpl implements PositionalParameterInformation { + private final int label; + + private final List sourceLocations = new ArrayList<>(); + + private Type expectedType; + + public PositionalParameterInformationImpl(int label, Type initialType) { + this.label = label; + this.expectedType = initialType; + } + + @Override + public int getLabel() { + return label; + } + + @Override + public int[] getSourceLocations() { + return ArrayHelper.toIntArray( sourceLocations ); + } + + @Override + public Type getExpectedType() { + return expectedType; + } + + @Override + public void setExpectedType(Type expectedType) { + this.expectedType = expectedType; + } + + @Override + public void addSourceLocation(int location) { + sourceLocations.add( location ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java index bb07b51269be..2d4a135c1368 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java @@ -38,9 +38,11 @@ import org.hibernate.hql.internal.ast.tree.InsertStatement; import org.hibernate.hql.internal.ast.tree.QueryNode; import org.hibernate.hql.internal.ast.tree.Statement; +import org.hibernate.hql.internal.ast.tree.UpdateStatement; import org.hibernate.hql.internal.ast.util.ASTPrinter; import org.hibernate.hql.internal.ast.util.ASTUtil; import org.hibernate.hql.internal.ast.util.NodeTraverser; +import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.hibernate.hql.spi.FilterTranslator; import org.hibernate.hql.spi.ParameterTranslations; import org.hibernate.internal.CoreMessageLogger; @@ -232,6 +234,11 @@ private synchronized void doCompile(Map replacements, boolean shallow, String co LOG.trace( "Converted antlr.ANTLRException", e ); throw new QueryException( e.getMessage(), hql ); } + catch ( IllegalArgumentException e ) { + // translate this into QueryException + LOG.trace( "Converted IllegalArgumentException", e ); + throw new QueryException( e.getMessage(), hql ); + } //only needed during compilation phase... this.enabledFilters = null; @@ -247,12 +254,15 @@ private void generate(AST sqlAst) throws QueryException, RecognitionException { LOG.debugf( "SQL: %s", sql ); } gen.getParseErrorHandler().throwQueryException(); - collectedParameterSpecifications = gen.getCollectedParameters(); + if ( collectedParameterSpecifications == null ) { + collectedParameterSpecifications = gen.getCollectedParameters(); + } + else { + collectedParameterSpecifications.addAll( gen.getCollectedParameters() ); + } } } - private static final ASTPrinter SQL_TOKEN_PRINTER = new ASTPrinter( SqlTokenTypes.class ); - private HqlSqlWalker analyze(HqlParser parser, String collectionRole) throws QueryException, RecognitionException { final HqlSqlWalker w = new HqlSqlWalker( this, factory, parser, tokenReplacements, collectionRole ); final AST hqlAst = parser.getAST(); @@ -261,7 +271,7 @@ private HqlSqlWalker analyze(HqlParser parser, String collectionRole) throws Que w.statement( hqlAst ); if ( LOG.isDebugEnabled() ) { - LOG.debug( SQL_TOKEN_PRINTER.showAsString( w.getAST(), "--- SQL AST ---" ) ); + LOG.debug( TokenPrinters.SQL_TOKEN_PRINTER.showAsString( w.getAST(), "--- SQL AST ---" ) ); } w.getParseErrorHandler().throwQueryException(); @@ -269,30 +279,33 @@ private HqlSqlWalker analyze(HqlParser parser, String collectionRole) throws Que return w; } - private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionException { + private HqlParser parse(boolean filter) throws TokenStreamException { // Parse the query string into an HQL AST. final HqlParser parser = HqlParser.getInstance( hql ); parser.setFilter( filter ); LOG.debugf( "parse() - HQL: %s", hql ); - parser.statement(); + try { + parser.statement(); + } + catch (RecognitionException e) { + throw new HibernateException( "Unexpected error parsing HQL", e ); + } final AST hqlAst = parser.getAST(); + parser.getParseErrorHandler().throwQueryException(); final NodeTraverser walker = new NodeTraverser( new JavaConstantConverter( factory ) ); walker.traverseDepthFirst( hqlAst ); showHqlAst( hqlAst ); - parser.getParseErrorHandler().throwQueryException(); return parser; } - private static final ASTPrinter HQL_TOKEN_PRINTER = new ASTPrinter( HqlTokenTypes.class ); - void showHqlAst(AST hqlAst) { if ( LOG.isDebugEnabled() ) { - LOG.debug( HQL_TOKEN_PRINTER.showAsString( hqlAst, "--- HQL AST ---" ) ); + LOG.debug( TokenPrinters.HQL_TOKEN_PRINTER.showAsString( hqlAst, "--- HQL AST ---" ) ); } } @@ -353,11 +366,23 @@ public List list(SharedSessionContractImplementor session, QueryParameters query final QueryNode query = (QueryNode) sqlAst; final boolean hasLimit = queryParameters.getRowSelection() != null && queryParameters.getRowSelection().definesLimits(); - final boolean needsDistincting = ( query.getSelectClause().isDistinct() || hasLimit ) && containsCollectionFetches(); + final boolean needsDistincting = ( + query.getSelectClause().isDistinct() || + getEntityGraphQueryHint() != null || + hasLimit ) + && containsCollectionFetches(); QueryParameters queryParametersToUse; if ( hasLimit && containsCollectionFetches() ) { - LOG.firstOrMaxResultsSpecifiedWithCollectionFetch(); + boolean fail = session.getFactory().getSessionFactoryOptions().isFailOnPaginationOverCollectionFetchEnabled(); + if (fail) { + throw new HibernateException("firstResult/maxResults specified with collection fetch. " + + "In memory pagination was about to be applied. " + + "Failing because 'Fail on pagination over collection fetch' is enabled."); + } + else { + LOG.firstOrMaxResultsSpecifiedWithCollectionFetch(); + } RowSelection selection = new RowSelection(); selection.setFetchSize( queryParameters.getRowSelection().getFetchSize() ); selection.setTimeout( queryParameters.getRowSelection().getTimeout() ); @@ -476,6 +501,10 @@ public boolean isManipulationStatement() { return sqlAst.needsExecutor(); } @Override + public boolean isUpdateStatement() { + return SqlTokenTypes.UPDATE == sqlAst.getStatementType(); + } + @Override public void validateScrollability() throws HibernateException { // Impl Note: allows multiple collection fetches as long as the // entire fecthed graph still "points back" to a single @@ -517,14 +546,14 @@ public void validateScrollability() throws HibernateException { } // This is not strictly true. We actually just need to make sure that - // it is ordered by root-entity PK and that that order-by comes beforeQuery + // it is ordered by root-entity PK and that that order-by comes before // any non-root-entity ordering... AST primaryOrdering = query.getOrderByClause().getFirstChild(); if ( primaryOrdering != null ) { // TODO : this is a bit dodgy, come up with a better way to check this (plus see above comment) String [] idColNames = owner.getQueryable().getIdentifierColumnNames(); - String expectedPrimaryOrderSeq = StringHelper.join( + String expectedPrimaryOrderSeq = String.join( ", ", StringHelper.qualify( owner.getTableAlias(), idColNames ) ); @@ -569,7 +598,7 @@ else if ( walker.getStatementType() == HqlSqlTokenTypes.INSERT ) { @Override public ParameterTranslations getParameterTranslations() { if ( paramTranslations == null ) { - paramTranslations = new ParameterTranslationsImpl( getWalker().getParameters() ); + paramTranslations = new ParameterTranslationsImpl( getWalker().getParameterSpecs() ); } return paramTranslations; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java index bc26ee26fc18..4db85674f02d 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java @@ -23,6 +23,7 @@ import org.hibernate.hql.internal.ast.tree.ParameterContainer; import org.hibernate.hql.internal.ast.tree.ParameterNode; import org.hibernate.hql.internal.ast.util.ASTPrinter; +import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; @@ -57,7 +58,6 @@ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter { private ParseErrorHandler parseErrorHandler; private SessionFactoryImplementor sessionFactory; private LinkedList outputStack = new LinkedList(); - private final ASTPrinter printer = new ASTPrinter( SqlTokenTypes.class ); private List collectedParameters = new ArrayList(); @@ -81,7 +81,7 @@ public void traceIn(String ruleName, AST tree) { private String buildTraceNodeName(AST tree) { return tree == null ? "???" - : tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; + : tree.getText() + " [" + TokenPrinters.SQL_TOKEN_PRINTER.getTokenTypeName( tree.getType() ) + "]"; } @Override @@ -158,7 +158,7 @@ public ParseErrorHandler getParseErrorHandler() { public SqlGenerator(SessionFactoryImplementor sfi) { super(); - parseErrorHandler = new ErrorCounter(); + parseErrorHandler = new ErrorTracker(); sessionFactory = sfi; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java index fd686d8ab813..e000e39ec706 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java @@ -56,7 +56,17 @@ public String[] getSqlStatements() { @Override public int execute(QueryParameters parameters, SharedSessionContractImplementor session) throws HibernateException { - return doExecute( parameters, session, sql, parameterSpecifications ); + return doExecute( + parameters, + session, + session.getJdbcServices().getDialect() + .addSqlHintOrComment( + sql, + parameters, + session.getFactory().getSessionFactoryOptions().isCommentsEnabled() + ), + parameterSpecifications + ); } protected int doExecute(QueryParameters parameters, SharedSessionContractImplementor session, String sql, diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java index 7a8949dbe4c8..849e5c799168 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java @@ -17,7 +17,6 @@ import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.SqlGenerator; import org.hibernate.hql.internal.ast.tree.DeleteStatement; -import org.hibernate.internal.util.StringHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.AbstractCollectionPersister; import org.hibernate.persister.entity.Queryable; @@ -81,9 +80,9 @@ public DeleteExecutor(HqlSqlWalker walker, Queryable persister) { } else { final String idSubselect = "(select " - + StringHelper.join( ", ", persister.getIdentifierColumnNames() ) + " from " + + String.join( ", ", persister.getIdentifierColumnNames() ) + " from " + persister.getTableName() + idSubselectWhere + ")"; - final String where = "(" + StringHelper.join( ", ", cPersister.getKeyColumnNames() ) + final String where = "(" + String.join( ", ", cPersister.getKeyColumnNames() ) + ") in " + idSubselect; final Delete delete = new Delete().setTableName( cPersister.getTableName() ).setWhere( where ); if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java index fe9d522e7b15..60a59e440cfe 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java @@ -10,7 +10,6 @@ import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.ast.util.ColumnHelper; -import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.type.CollectionType; import org.hibernate.type.Type; @@ -45,7 +44,8 @@ public void resolve( boolean generateJoin, boolean implicitJoin, String classAlias, - AST parent) throws SemanticException { + AST parent, + AST parentPredicate) throws SemanticException { if ( mapFromElement == null ) { final FromReferenceNode mapReference = getMapReference(); mapReference.resolve( true, true ); @@ -89,7 +89,7 @@ private boolean isAliasRef(FromReferenceNode mapReference) { } private void initText(String[] columns) { - String text = StringHelper.join( ", ", columns ); + String text = String.join( ", ", columns ); if ( columns.length > 1 && getWalker().isComparativeExpressionClause() ) { text = "(" + text + ")"; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractNullnessCheckNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractNullnessCheckNode.java index 6de22ed23c12..225a579a0e27 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractNullnessCheckNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractNullnessCheckNode.java @@ -24,7 +24,7 @@ public abstract class AbstractNullnessCheckNode extends UnaryLogicOperatorNode { @Override public void initialize() { - // TODO : this really needs to be delayed unitl afterQuery we definitively know the operand node type; + // TODO : this really needs to be delayed until after we definitively know the operand node type; // where this is currently a problem is parameters for which where we cannot unequivocally // resolve an expected type Type operandType = extractDataType( getOperand() ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractRestrictableStatement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractRestrictableStatement.java index 14b24420c135..274f790630d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractRestrictableStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractRestrictableStatement.java @@ -48,7 +48,7 @@ public final AST getWhereClause() { if ( whereClause == null ) { getLog().debug( "getWhereClause() : Creating a new WHERE clause..." ); whereClause = getWalker().getASTFactory().create( HqlSqlTokenTypes.WHERE, "WHERE" ); - // inject the WHERE afterQuery the parent + // inject the WHERE after the parent AST parent = ASTUtil.findTypeInChildren( this, getWhereClauseParentTokenType() ); whereClause.setNextSibling( parent.getNextSibling() ); parent.setNextSibling( whereClause ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java index b2390463279f..e02d065ef94e 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java @@ -66,7 +66,7 @@ public void initialize() throws SemanticException { } protected final void mutateRowValueConstructorSyntaxesIfNecessary(Type lhsType, Type rhsType) { - // TODO : this really needs to be delayed until afterQuery we definitively know all node types + // TODO : this really needs to be delayed until after we definitively know all node types // where this is currently a problem is parameters for which where we cannot unequivocally // resolve an expected type SessionFactoryImplementor sessionFactory = getSessionFactoryHelper().getFactory(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/CastFunctionNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/CastFunctionNode.java index 8cf431802c73..5c450e058a5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/CastFunctionNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/CastFunctionNode.java @@ -30,7 +30,7 @@ public class CastFunctionNode extends AbstractSelectExpression implements Functi /** - * Called from the hql-sql grammar afterQuery the children of the CAST have been resolved. + * Called from the hql-sql grammar after the children of the CAST have been resolved. * * @param inSelect Is this call part of the SELECT clause? */ diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ConstructorNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ConstructorNode.java index f80921f65a26..d2cc28773f93 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ConstructorNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ConstructorNode.java @@ -191,7 +191,7 @@ private String formatMissingContructorExceptionMessage(String className) { ? ( (PrimitiveType) constructorArgumentTypes[j] ).getPrimitiveClass().getName() : constructorArgumentTypes[j].getReturnedClass().getName(); } - String formattedList = params.length == 0 ? "no arguments constructor" : StringHelper.join( ", ", params ); + String formattedList = params.length == 0 ? "no arguments constructor" : String.join( ", ", params ); return String.format( "Unable to locate appropriate constructor on class [%s]. Expected arguments are: %s", className, formattedList 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 a411397fdabd..34cbed457ae2 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 @@ -16,6 +16,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.loader.plan.spi.EntityQuerySpace; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; @@ -194,7 +195,7 @@ public void resolveIndex(AST parent) throws SemanticException { dereferenceCollection( (CollectionType) propertyType, true, true, null, parent ); } - public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent) + public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent, AST parentPredicate) throws SemanticException { // If this dot has already been resolved, stop now. if ( isResolved() ) { @@ -227,7 +228,7 @@ public void resolve(boolean generateJoin, boolean implicitJoin, String classAlia else if ( propertyType.isEntityType() ) { // The property is another class.. checkLhsIsNotCollection(); - dereferenceEntity( (EntityType) propertyType, implicitJoin, classAlias, generateJoin, parent ); + dereferenceEntity( (EntityType) propertyType, implicitJoin, classAlias, generateJoin, parent, parentPredicate ); initText(); } else if ( propertyType.isCollectionType() ) { @@ -248,7 +249,7 @@ else if ( propertyType.isCollectionType() ) { private void initText() { String[] cols = getColumns(); - String text = StringHelper.join( ", ", cols ); + String text = String.join( ", ", cols ); boolean countDistinct = getWalker().isInCountDistinct() && getWalker().getSessionFactoryHelper().getFactory().getDialect().requiresParensForTupleDistinctCounts(); if ( cols.length > 1 && @@ -361,7 +362,8 @@ private void dereferenceEntity( boolean implicitJoin, String classAlias, boolean generateJoin, - AST parent) throws SemanticException { + AST parent, + AST parentPredicate) throws SemanticException { checkForCorrelatedSubquery( "dereferenceEntity" ); // three general cases we check here as to whether to render a physical SQL join: // 1) is our parent a DotNode as well? If so, our property reference is @@ -403,6 +405,10 @@ else if ( regressionStyleJoinSuppression ) { // this is the regression style determination which matches the logic of the classic translator joinIsNeeded = generateJoin && ( !getWalker().isInSelect() || !getWalker().isShallowQuery() ); } + else if ( parentPredicate != null ) { + // Never generate a join when we compare entities directly + joinIsNeeded = generateJoin; + } else { joinIsNeeded = generateJoin || ( getWalker().isInSelect() || getWalker().isInFrom() ); } @@ -478,11 +484,6 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b boolean useFoundFromElement = found && canReuse( classAlias, elem ); if ( !useFoundFromElement ) { - // If this is an implied join in a from element, then use the impled join type which is part of the - // tree parser's state (set by the gramamar actions). - JoinSequence joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns ); - // If the lhs of the join is a "component join", we need to go back to the // first non-component-join as the origin to properly link aliases and // join columns @@ -496,6 +497,27 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b String role = lhsFromElement.getClassName() + "." + propertyName; + 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(); + + AbstractEntityPersister persister = (AbstractEntityPersister) lhsFromElement.getEntityPersister(); + + String[][] polyJoinColumns = persister.getPolymorphicJoinColumns(lhsTableAlias, propertyPath); + + // Special join sequence that uses the poly join columns + joinSequence = getSessionFactoryHelper() + .createJoinSequence( impliedJoin, propertyType, 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 ); + } + FromElementFactory factory = new FromElementFactory( currentFromClause, lhsFromElement, @@ -726,7 +748,7 @@ public void resolveSelectExpression() throws SemanticException { fromElement.setIncludeSubclasses( true ); // Tell the destination fromElement to 'includeSubclasses'. if ( useThetaStyleImplicitJoins ) { fromElement.getJoinSequence().setUseThetaStyle( true ); // Use theta style (for regression) - // Move the node up, afterQuery the origin node. + // Move the node up, after the origin node. FromElement origin = fromElement.getOrigin(); if ( origin != null ) { ASTUtil.makeSiblingOfParent( origin, fromElement ); 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 ccc7993d0f83..77d7dbcb025f 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 @@ -23,7 +23,6 @@ import org.hibernate.hql.spi.QueryTranslator; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.StringHelper; import org.hibernate.param.DynamicFilterParameterSpecification; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.QueryableCollection; @@ -329,7 +328,7 @@ public String getIdentityColumn() { return cols[0]; } else { - return "(" + StringHelper.join( ", ", cols ) + ")"; + return "(" + String.join( ", ", cols ) + ")"; } } @@ -340,14 +339,18 @@ public String[] getIdentityColumns() { throw new IllegalStateException( "No table alias for node " + this ); } - final String propertyName = getIdentifierPropertyName(); - - if ( getWalker().getStatementType() == HqlSqlTokenTypes.SELECT ) { - return getPropertyMapping( propertyName ).toColumns( table, propertyName ); - } - else { - return getPropertyMapping( propertyName ).toColumns( propertyName ); + final String[] propertyNames = getIdentifierPropertyNames(); + List columns = new ArrayList<>(); + for ( int i = 0; i < propertyNames.length; i++ ) { + String[] propertyNameColumns = toColumns( + table, propertyNames[i], + getWalker().getStatementType() == HqlSqlTokenTypes.SELECT + ); + for ( int j = 0; j < propertyNameColumns.length; j++ ) { + columns.add( propertyNameColumns[j] ); + } } + return columns.toArray( new String[columns.size()] ); } public void setCollectionJoin(boolean collectionJoin) { @@ -528,8 +531,8 @@ public CollectionPropertyReference getCollectionPropertyReference(String propert return elementType.getCollectionPropertyReference( propertyName ); } - public String getIdentifierPropertyName() { - return elementType.getIdentifierPropertyName(); + public String[] getIdentifierPropertyNames() { + return elementType.getIdentifierPropertyNames(); } public void setFetch(boolean fetch) { @@ -629,15 +632,6 @@ public void setWithClauseFragment(String withClauseFragment) { this.withClauseFragment = withClauseFragment; } - public boolean hasCacheablePersister() { - if ( getQueryableCollection() != null ) { - return getQueryableCollection().hasCache(); - } - else { - return getQueryable().hasCache(); - } - } - public void handlePropertyBeingDereferenced(Type propertySource, String propertyName) { if ( getQueryableCollection() != null && CollectionProperties.isCollectionProperty( propertyName ) ) { // propertyName refers to something like collection.size... diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java index 7c8d886b44a0..4f24021cb934 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java @@ -111,7 +111,7 @@ private FromElement createFromElementInSubselect( String classAlias) throws SemanticException { LOG.debugf( "createFromElementInSubselect() : path = %s", path ); - // Create an DotNode AST for the path and resolve it. + // Create a DotNode AST for the path and resolve it. FromElement fromElement = evaluateFromElementPath( path, classAlias ); EntityPersister entityPersister = fromElement.getEntityPersister(); 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 0f312e183e7f..9fe8f365e48c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java @@ -14,17 +14,15 @@ import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.CollectionProperties; import org.hibernate.hql.internal.CollectionSubqueryFactory; import org.hibernate.hql.internal.NameGenerator; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; -import org.hibernate.hql.internal.ast.HqlSqlWalker; -import org.hibernate.hql.internal.ast.util.SessionFactoryHelper; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.loader.PropertyPath; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.CollectionPropertyMapping; import org.hibernate.persister.collection.CollectionPropertyNames; @@ -33,6 +31,8 @@ import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.PropertyMapping; import org.hibernate.persister.entity.Queryable; +import org.hibernate.tuple.IdentifierProperty; +import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -130,18 +130,22 @@ public Queryable getQueryable() { String renderScalarIdentifierSelect(int i) { checkInitialized(); - final String idPropertyName = getIdentifierPropertyName(); - String[] cols = getPropertyMapping( idPropertyName ).toColumns( getTableAlias(), idPropertyName ); - + final String[] idPropertyName = getIdentifierPropertyNames(); StringBuilder buf = new StringBuilder(); - // For property references generate . as - for ( int j = 0; j < cols.length; j++ ) { - String column = cols[j]; - if ( j > 0 ) { - buf.append( ", " ); + int counter = 0; + for ( int j = 0; j < idPropertyName.length; j++ ) { + String propertyName = idPropertyName[j]; + String[] toColumns = getPropertyMapping( propertyName ).toColumns( getTableAlias(), propertyName ); + for ( int h = 0; h < toColumns.length; h++, counter++ ) { + String column = toColumns[h]; + if ( j + h > 0 ) { + buf.append( ", " ); + } + buf.append( column ).append( " as " ).append( NameGenerator.scalarName( i, counter ) ); } - buf.append( column ).append( " as " ).append( NameGenerator.scalarName( i, j ) ); } + + LOG.debug( "Rendered scalar ID select column(s): " + buf ); return buf.toString(); } @@ -188,7 +192,7 @@ private String getSuffix(int size, int sequence) { } private static String generateSuffix(int size, int k) { - return size == 1 ? "" : Integer.toString( k ) + '_'; + return size == 1 ? "" : AliasConstantsHelper.get( k ); } private void checkInitialized() { @@ -453,11 +457,16 @@ String[] toColumns(String tableAlias, String path, boolean inSelect, boolean for // executors being used (as this subquery will // actually be used in the "id select" phase // of that multi-table executor) + // for update queries, the real table name of the updated table must be used if not in the top level where + // clause, typically in a SET clause // B) otherwise, we need to use the persister's // table name as the column qualification // 2) otherwise (not correlated), use the given alias if ( isCorrelation() ) { - if ( isMultiTable() ) { + if ( isMultiTable() && ( !isUpdateQuery() || inWhereClause() ) ) { + return propertyMapping.toColumns( tableAlias, path ); + } + else if ( isInsertQuery() ) { return propertyMapping.toColumns( tableAlias, path ); } return propertyMapping.toColumns( extractTableName(), path ); @@ -498,6 +507,14 @@ private String extractTableName() { return fromElement.getQueryable().getTableName(); } + private boolean isInsertQuery() { + return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.INSERT; + } + + private boolean isUpdateQuery() { + return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE; + } + private boolean isManipulationQuery() { return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE || fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.DELETE; @@ -618,7 +635,7 @@ public String[] toColumns(final String tableAlias) { Map enabledFilters = fromElement.getWalker().getEnabledFilters(); String subquery = CollectionSubqueryFactory.createCollectionSubquery( - joinSequence.copy().setUseThetaStyle( true ), + joinSequence.copyForCollectionProperty().setUseThetaStyle( true ), enabledFilters, collectionPropertyMapping.toColumns( tableAlias, propertyName ) ); @@ -678,13 +695,25 @@ public String[] toColumns(String propertyName) throws QueryException, Unsupporte } } - public String getIdentifierPropertyName() { - if ( getEntityPersister() != null && getEntityPersister().getEntityMetamodel() != null - && getEntityPersister().getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) { - return getEntityPersister().getIdentifierPropertyName(); - } - else { - return EntityPersister.ENTITY_ID; + public String[] getIdentifierPropertyNames() { + if ( getEntityPersister() != null ) { + String identifierPropertyName = getEntityPersister().getIdentifierPropertyName(); + if ( identifierPropertyName != null ) { + return new String[] { identifierPropertyName }; + } + else { + final IdentifierProperty identifierProperty = getEntityPersister().getEntityMetamodel() + .getIdentifierProperty(); + if ( identifierProperty.hasIdentifierMapper() && !identifierProperty.isEmbedded() ) { + return new String[] { PropertyPath.IDENTIFIER_MAPPER_PROPERTY }; + } + else { + if ( EmbeddedComponentType.class.isInstance( identifierProperty.getType() ) ) { + return ( (EmbeddedComponentType) identifierProperty.getType() ).getPropertyNames(); + } + } + } } + return new String[] { EntityPersister.ENTITY_ID }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java index 208f5264a793..7e44fabe97ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java @@ -109,6 +109,11 @@ public void resolve(boolean generateJoin, boolean implicitJoin, String classAlia resolve( generateJoin, implicitJoin, classAlias, null ); } + public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent) + throws SemanticException { + resolve( generateJoin, implicitJoin, classAlias, parent, null ); + } + public void prepareForDot(String propertyName) throws SemanticException { } 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 ace7a60da87c..1574c57feb9b 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 @@ -95,14 +95,14 @@ protected String[] resolveColumns(QueryableCollection collectionPersister) { } private void initText(String[] columns) { - String text = StringHelper.join( ", ", columns ); + String text = String.join( ", ", columns ); if ( columns.length > 1 && getWalker().isComparativeExpressionClause() ) { text = "(" + text + ")"; } setText( text ); } - public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent) { + 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() ); @@ -197,14 +197,6 @@ private boolean resolveAsAlias() { String[] columnExpressions = element.getIdentityColumns(); - // determine whether to apply qualification (table alias) to the column(s)... - if ( ! isFromElementUpdateOrDeleteRoot( element ) ) { - if ( StringHelper.isNotEmpty( element.getTableAlias() ) ) { - // apparently we also need to check that they are not already qualified. Ugh! - columnExpressions = StringHelper.qualifyIfNot( element.getTableAlias(), columnExpressions ); - } - } - final Dialect dialect = getWalker().getSessionFactoryHelper().getFactory().getDialect(); final boolean isInCount = getWalker().isInCount(); final boolean isInDistinctCount = isInCount && getWalker().isInCountDistinct(); @@ -216,7 +208,7 @@ private boolean resolveAsAlias() { setText( columnExpressions[0] ); } else { - String joinedFragment = StringHelper.join( ", ", columnExpressions ); + String joinedFragment = String.join( ", ", columnExpressions ); // avoid wrapping in parenthesis (explicit tuple treatment) if possible due to varied support for // tuple syntax across databases.. final boolean shouldSkipWrappingInParenthesis = @@ -277,7 +269,7 @@ private DereferenceType resolveAsNakedPropertyRef() { String[] columns = getWalker().isSelectStatement() ? persister.toColumns(fromElement.getTableAlias(), property) : persister.toColumns(property); - String text = StringHelper.join(", ", columns); + String text = String.join(", ", columns); setText(columns.length == 1 ? text : "(" + text + ")"); setType(SqlTokenTypes.SQL_TOKEN); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IndexNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IndexNode.java index 72ffff7a4cb2..34216f24cf76 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IndexNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IndexNode.java @@ -65,7 +65,7 @@ public void resolveIndex(AST parent) throws SemanticException { } @Override - public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent) + public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent, AST parentPredicate) throws SemanticException { if ( isResolved() ) { return; @@ -81,7 +81,7 @@ public void resolve(boolean generateJoin, boolean implicitJoin, String classAlia String collectionRole = ( (CollectionType) type ).getRole(); QueryableCollection queryableCollection = sessionFactoryHelper.requireQueryableCollection( collectionRole ); if ( !queryableCollection.hasIndex() ) { - throw new QueryException( "unindexed fromElement beforeQuery []: " + collectionNode.getPath() ); + throw new QueryException( "unindexed fromElement before []: " + collectionNode.getPath() ); } // Generate the inner join -- The elements need to be joined to the collection they are in. diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java index 9a99cc49e847..9c709f2354d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java @@ -86,7 +86,7 @@ else if ( AttributeConverterTypeAdapter.class.isInstance( type ) ) { ) ); } - final Object value = converterType.getAttributeConverter().convertToDatabaseColumn( constantValue ); + final Object value = converterType.getAttributeConverter().toRelationalValue( constantValue ); if ( String.class.equals( converterType.getJdbcType() ) ) { return "'" + value + "'"; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java index 0aaa6d2a92c6..43530357df6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java @@ -8,11 +8,11 @@ import java.sql.Types; import java.util.Locale; -import javax.persistence.AttributeConverter; import org.hibernate.QueryException; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.ast.util.ColumnHelper; +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.type.SingleColumnType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; @@ -92,8 +92,8 @@ public void setExpectedType(Type expectedType) { protected String determineConvertedValue(AttributeConverterTypeAdapter converterTypeAdapter, Object literalValue) { if ( getDataType().getReturnedClass().equals( converterTypeAdapter.getModelType() ) ) { // apply the converter - final AttributeConverter converter = converterTypeAdapter.getAttributeConverter(); - final Object converted = converter.convertToDatabaseColumn( getLiteralValue() ); + final JpaAttributeConverter converter = converterTypeAdapter.getAttributeConverter(); + final Object converted = converter.toRelationalValue( getLiteralValue() ); if ( isCharacterData( converterTypeAdapter.sqlType() ) ) { return "'" + converted.toString() + "'"; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java index 8ab79454b64e..d775376864ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MapEntryNode.java @@ -65,11 +65,12 @@ public void resolve( boolean generateJoin, boolean implicitJoin, String classAlias, - AST parent) throws SemanticException { + AST parent, + AST parentPredicate) throws SemanticException { if (parent != null) { throw new SemanticException( expressionDescription() + " expression cannot be further de-referenced" ); } - super.resolve(generateJoin, implicitJoin, classAlias, parent); + super.resolve(generateJoin, implicitJoin, classAlias, parent, parentPredicate); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java index 97f80d3f73d7..219a8411c908 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/MethodNode.java @@ -15,7 +15,6 @@ import org.hibernate.hql.internal.ast.util.ASTUtil; import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.internal.CoreLogging; -import org.hibernate.persister.collection.CollectionPropertyMapping; import org.hibernate.persister.collection.CollectionPropertyNames; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.type.Type; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/OperatorNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/OperatorNode.java index 52aa9a681ef9..721c941b5665 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/OperatorNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/OperatorNode.java @@ -18,7 +18,7 @@ public interface OperatorNode { /** * Called by the tree walker during hql-sql semantic analysis - * afterQuery the operator sub-tree is completely built. + * after the operator sub-tree is completely built. */ public abstract void initialize() throws SemanticException; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/QueryNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/QueryNode.java index d2accfa6f43a..d3d10feb8c11 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/QueryNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/QueryNode.java @@ -61,7 +61,7 @@ public final SelectClause getSelectClause() { // Due to the complexity in initializing the SelectClause, do not generate one here. // If it is not found; simply return null... // - // Also, do not cache since it gets generated well afterQuery we are created. + // Also, do not cache since it gets generated well after we are created. return (SelectClause) ASTUtil.findTypeInChildren( this, SqlTokenTypes.SELECT_CLAUSE ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResolvableNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResolvableNode.java index bdbd0915bb0a..74efd7a4d459 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResolvableNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResolvableNode.java @@ -17,15 +17,19 @@ public interface ResolvableNode { /** * Does the work of resolving an identifier or a dot */ + void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent, AST parentPredicate) throws SemanticException; + /** + * Does the work of resolving an identifier or a dot, but without a parent predicate node + */ void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent) throws SemanticException; /** - * Does the work of resolving an identifier or a dot, but without a parent node + * Does the work of resolving an identifier or a dot, but without a parent predicate node or parent node */ void resolve(boolean generateJoin, boolean implicitJoin, String classAlias) throws SemanticException; /** - * Does the work of resolving an identifier or a dot, but without a parent node or alias + * Does the work of resolving an identifier or a dot, but without a parent predicate node or parent node or alias */ void resolve(boolean generateJoin, boolean implicitJoin) throws SemanticException; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResultVariableRefNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResultVariableRefNode.java index 75d92a0eae03..5257bdd02504 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResultVariableRefNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ResultVariableRefNode.java @@ -7,7 +7,6 @@ package org.hibernate.hql.internal.ast.tree; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.StringHelper; import antlr.SemanticException; @@ -69,6 +68,6 @@ private String getColumnPositionsString(int scalarColumnIndex ) { } private String getColumnNamesString(int scalarColumnIndex) { - return StringHelper.join( ", ", getWalker().getSelectClause().getColumnNames()[scalarColumnIndex] ); + return String.join( ", ", getWalker().getSelectClause().getColumnNames()[scalarColumnIndex] ); } } 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 c5038f55e812..734d553e710c 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 @@ -16,6 +16,7 @@ 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; import antlr.SemanticException; @@ -121,7 +122,7 @@ public void initializeExplicitSelectClause(FromClause fromClause) throws Semanti ArrayList queryReturnTypeList = new ArrayList(); // First, collect all of the select expressions. - // NOTE: This must be done *beforeQuery* invoking setScalarColumnText() because setScalarColumnText() + // NOTE: This must be done *before* invoking setScalarColumnText() because setScalarColumnText() // changes the AST!!! SelectExpression[] selectExpressions = collectSelectExpressions(); @@ -157,7 +158,7 @@ public void initializeExplicitSelectClause(FromClause fromClause) throws Semanti if ( type == null ) { throw new QueryException( "No data type for node: " + selectExpression.getClass().getName() + " " - + new ASTPrinter( SqlTokenTypes.class ).showAsString( (AST) selectExpression, "" ) + + TokenPrinters.SQL_TOKEN_PRINTER.showAsString( (AST) selectExpression, "" ) ); } //sqlResultTypeList.add( type ); @@ -176,7 +177,7 @@ public void initializeExplicitSelectClause(FromClause fromClause) throws Semanti } } - //init the aliases, afterQuery initing the constructornode + //init the aliases, after initing the constructornode initAliases( selectExpressions ); if ( !getWalker().isShallowQuery() ) { @@ -261,7 +262,7 @@ private void finishInitialization(ArrayList queryReturnTypeList) { } private void initializeColumnNames() { - // Generate an 2d array of column names, the first dimension is parallel with the + // Generate a 2d array of column names, the first dimension is parallel with the // return types array. The second dimension is the list of column names for each // type. diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpression.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpression.java index f6377467b7b9..657d736ee3a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpression.java @@ -24,7 +24,7 @@ public interface SelectExpression { Type getDataType(); /** - * Appends AST nodes that represent the columns afterQuery the current AST node. + * Appends AST nodes that represent the columns after the current AST node. * (e.g. 'as col0_O_') * * @param i The index of the select expression in the projection list. diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionImpl.java index 3a53500cf58a..f99caf8eace5 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionImpl.java @@ -24,7 +24,7 @@ public void setScalarColumnText(int i) throws SemanticException { setText( text ); } - public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent) throws SemanticException { + public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent, AST parentPredicate) throws SemanticException { // Generated select expressions are already resolved, nothing to do. return; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionList.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionList.java index d38d4023eedb..46dec1198acc 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionList.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectExpressionList.java @@ -11,6 +11,7 @@ import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.ast.util.ASTPrinter; +import org.hibernate.hql.internal.ast.util.TokenPrinters; import antlr.collections.AST; @@ -44,7 +45,7 @@ else if ( n instanceof ParameterNode ) { else { throw new IllegalStateException( "Unexpected AST: " + n.getClass().getName() + " " - + new ASTPrinter( SqlTokenTypes.class ).showAsString( n, "" ) + + TokenPrinters.SQL_TOKEN_PRINTER.showAsString( n, "" ) ); } p++; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java index d3e4e3304f40..fb32e58f4a25 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java @@ -20,52 +20,24 @@ /** * Utility for generating pretty "ASCII art" representations of syntax trees. * + * This class is threadsafe: reuse instances as needed. + * * @author Joshua Davis * @author Steve Ebersole */ public class ASTPrinter { - private final Map tokenTypeNameCache; - private final boolean showClassNames; - /** - * Constructs a printer. - *

      - * Delegates to {@link #ASTPrinter(Class, boolean)} with {@link #isShowClassNames showClassNames} as true - * - * @param tokenTypeConstants The token types to use during printing; typically the {vocabulary}TokenTypes.java - * interface generated by ANTLR. - */ - public ASTPrinter(Class tokenTypeConstants) { - this( ASTUtil.generateTokenNameCache( tokenTypeConstants ), true ); - } - - public ASTPrinter(boolean showClassNames) { - this( (Map) null, showClassNames ); - } + private final Map tokenTypeNameCache; /** - * Constructs a printer. + * Constructs a printer. Package protected: use the constants from {TokenPrinters} + * @see TokenPrinters * * @param tokenTypeConstants The token types to use during printing; typically the {vocabulary}TokenTypes.java * interface generated by ANTLR. - * @param showClassNames Should the AST class names be shown. */ - public ASTPrinter(Class tokenTypeConstants, boolean showClassNames) { - this( ASTUtil.generateTokenNameCache( tokenTypeConstants ), showClassNames ); - } - - private ASTPrinter(Map tokenTypeNameCache, boolean showClassNames) { - this.tokenTypeNameCache = tokenTypeNameCache; - this.showClassNames = showClassNames; - } - - /** - * Getter for property 'showClassNames'. - * - * @return Value for property 'showClassNames'. - */ - public boolean isShowClassNames() { - return showClassNames; + ASTPrinter(Class tokenTypeConstants) { + this.tokenTypeNameCache = ASTUtil.generateTokenNameCache( tokenTypeConstants ); } /** @@ -117,10 +89,7 @@ public void showAst(AST ast, PrintWriter pw) { */ public String getTokenTypeName(int type) { final Integer typeInteger = type; - String value = null; - if ( tokenTypeNameCache != null ) { - value = (String) tokenTypeNameCache.get( typeInteger ); - } + String value = tokenTypeNameCache.get( typeInteger ); if ( value == null ) { value = typeInteger.toString(); } @@ -161,19 +130,17 @@ private void showAst(ArrayList parents, PrintWriter pw, AST ast) { } private void showNode(PrintWriter pw, AST ast) { - String s = nodeToString( ast, isShowClassNames() ); + String s = nodeToString( ast ); pw.println( s ); } - public String nodeToString(AST ast, boolean showClassName) { + public String nodeToString(AST ast) { if ( ast == null ) { return "{node:null}"; } StringBuilder buf = new StringBuilder(); buf.append( "[" ).append( getTokenTypeName( ast.getType() ) ).append( "] " ); - if ( showClassName ) { - buf.append( StringHelper.unqualify( ast.getClass().getName() ) ).append( ": " ); - } + buf.append( StringHelper.unqualify( ast.getClass().getName() ) ).append( ": " ); buf.append( "'" ); String text = ast.getText(); @@ -184,7 +151,7 @@ public String nodeToString(AST ast, boolean showClassName) { buf.append( "'" ); if ( ast instanceof DisplayableNode ) { DisplayableNode displayableNode = (DisplayableNode) ast; - // Add a space beforeQuery the display text. + // Add a space before the display text. buf.append( " " ).append( displayableNode.getDisplayText() ); } return buf.toString(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java index 6e27f7159a60..15e5af195a73 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java @@ -389,13 +389,13 @@ public List collect(AST root) { * * @return The generated map. */ - public static Map generateTokenNameCache(Class tokenTypeInterface) { + public static Map generateTokenNameCache(Class tokenTypeInterface) { final Field[] fields = tokenTypeInterface.getFields(); Map cache = new HashMap( (int) ( fields.length * .75 ) + 1 ); for ( final Field field : fields ) { if ( Modifier.isStatic( field.getModifiers() ) ) { try { - cache.put( field.get( null ), field.getName() ); + cache.put( Integer.valueOf( field.getInt( null ) ), field.getName() ); } catch (Throwable ignore) { } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java index fae211bb452e..0196149407e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.ListIterator; import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.hibernate.AssertionFailure; import org.hibernate.dialect.Dialect; @@ -46,6 +48,9 @@ public class JoinProcessor implements SqlTokenTypes { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( JoinProcessor.class ); + private static final Pattern DYNAMIC_FILTER_PATTERN = Pattern.compile(":(\\w+\\S*)\\s"); + private static final String LITERAL_DELIMITER = "'"; + private final HqlSqlWalker walker; private final SyntheticAndFactory syntheticAndFactory; @@ -98,7 +103,7 @@ public void processJoins(QueryNode query) { // found it easiest to simply reorder the FromElements here into ascending order // in terms of injecting them into the resulting sql ast in orders relative to those // expected by the old parser; this is definitely another of those "only needed - // for regression purposes". The SyntheticAndFactory, then, simply injects them as it + // for regression purposes". The SyntheticAndFactory, then, simply injects them as it // encounters them. fromElements = new ArrayList(); ListIterator liter = fromClause.getFromElements().listIterator( fromClause.getFromElements().size() ); @@ -118,7 +123,7 @@ public void processJoins(QueryNode query) { && fromElement.getOrigin().getWithClauseFragment().contains( fromElement.getTableAlias() ) ) { fromElement.getOrigin().getJoinSequence().addJoin( (ImpliedFromElement) fromElement ); // This from element will be rendered as part of the origins join sequence - fromElement.setText(""); + fromElement.setText( "" ); } else { fromElements.add( fromElement ); @@ -203,7 +208,7 @@ private void addJoinNodes(QueryNode query, JoinSequence join, FromElement fromEl private String processFromFragment(String frag, JoinSequence join) { String fromFragment = frag.trim(); - // The FROM fragment will probably begin with ', '. Remove this if it is present. + // The FROM fragment will probably begin with ', '. Remove this if it is present. if ( fromFragment.startsWith( ", " ) ) { fromFragment = fromFragment.substring( 2 ); } @@ -215,12 +220,12 @@ public static void processDynamicFilterParameters( final ParameterContainer container, final HqlSqlWalker walker) { if ( walker.getEnabledFilters().isEmpty() - && ( !hasDynamicFilterParam( sqlFragment ) ) + && ( !hasDynamicFilterParam( walker, sqlFragment ) ) && ( !( hasCollectionFilterParam( sqlFragment ) ) ) ) { return; } - Dialect dialect = walker.getSessionFactoryHelper().getFactory().getDialect(); + Dialect dialect = walker.getDialect(); String symbols = ParserHelper.HQL_SEPARATORS + dialect.openQuote() + dialect.closeQuote(); StringTokenizer tokens = new StringTokenizer( sqlFragment, symbols, true ); StringBuilder result = new StringBuilder(); @@ -233,7 +238,7 @@ public static void processDynamicFilterParameters( final FilterImpl filter = (FilterImpl) walker.getEnabledFilters().get( parts[0] ); final Object value = filter.getParameter( parts[1] ); final Type type = filter.getFilterDefinition().getParameterType( parts[1] ); - final String typeBindFragment = StringHelper.join( + final String typeBindFragment = String.join( ",", ArrayHelper.fillArray( "?", @@ -242,7 +247,7 @@ public static void processDynamicFilterParameters( ); final String bindFragment; if ( value != null && Collection.class.isInstance( value ) ) { - bindFragment = StringHelper.join( + bindFragment = String.join( ",", ArrayHelper.fillArray( typeBindFragment, ( (Collection) value ).size() ) ); @@ -261,8 +266,15 @@ public static void processDynamicFilterParameters( container.setText( result.toString() ); } - private static boolean hasDynamicFilterParam(String sqlFragment) { - return !sqlFragment.contains( ParserHelper.HQL_VARIABLE_PREFIX ); + private static boolean hasDynamicFilterParam(HqlSqlWalker walker, String sqlFragment) { + String closeQuote = String.valueOf( walker.getDialect().closeQuote() ); + + Matcher matcher = DYNAMIC_FILTER_PATTERN.matcher( sqlFragment ); + if ( matcher.find() && matcher.groupCount() > 0 ) { + String match = matcher.group( 1 ); + return match.endsWith( closeQuote ) || match.endsWith( LITERAL_DELIMITER ); + } + return true; } private static boolean hasCollectionFilterParam(String sqlFragment) { 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 6d800109b602..9fb7b590c0a4 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 @@ -138,11 +138,9 @@ public static Queryable findQueryableUsingImports(SessionFactoryImplementor sfi, */ public EntityPersister findEntityPersisterByName(String name) throws MappingException { // First, try to get the persister using the given name directly. - try { - return sfi.getMetamodel().entityPersister( name ); - } - catch ( MappingException ignore ) { - // unable to locate it using this name + EntityPersister persister = sfi.getMetamodel().entityPersisters().get( name ); + if ( persister != null ) { + return persister; } // If that didn't work, try using the 'import' name. @@ -267,6 +265,24 @@ public JoinSequence createJoinSequence() { * @return The generated join sequence. */ public JoinSequence createJoinSequence(boolean implicit, AssociationType associationType, String tableAlias, JoinType joinType, String[] columns) { + JoinSequence joinSequence = createJoinSequence(); + joinSequence.setUseThetaStyle( implicit ); // Implicit joins use theta style (WHERE pk = fk), explicit joins use JOIN (after from) + joinSequence.addJoin( associationType, tableAlias, joinType, columns ); + return joinSequence; + } + + /** + * Generate a join sequence representing the given association type. + * + * @param implicit Should implicit joins (theta-style) or explicit joins (ANSI-style) be rendered + * @param associationType The type representing the thing to be joined into. + * @param tableAlias The table alias to use in qualifying the join conditions + * @param joinType The type of join to render (inner, outer, etc); see {@link org.hibernate.sql.JoinFragment} + * @param columns The columns making up the condition of the join. + * + * @return The generated join sequence. + */ + public JoinSequence createJoinSequence(boolean implicit, AssociationType associationType, String tableAlias, JoinType joinType, String[][] columns) { JoinSequence joinSequence = createJoinSequence(); joinSequence.setUseThetaStyle( implicit ); // Implicit joins use theta style (WHERE pk = fk), explicit joins use JOIN (afterQuery from) joinSequence.addJoin( associationType, tableAlias, joinType, columns ); @@ -419,6 +435,6 @@ public String[][] generateColumnNames(Type[] sqlResultTypes) { } public boolean isStrictJPAQLComplianceEnabled() { - return sfi.getSessionFactoryOptions().isStrictJpaQueryLanguageCompliance(); + return sfi.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SyntheticAndFactory.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SyntheticAndFactory.java index 2928c47d3ffe..43938891502e 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SyntheticAndFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SyntheticAndFactory.java @@ -88,8 +88,7 @@ public void addWhereFragment( .getKeyType(); CollectionFilterKeyParameterSpecification paramSpec = new CollectionFilterKeyParameterSpecification( hqlSqlWalker.getCollectionFilterRole(), - collectionFilterKeyType, - 0 + collectionFilterKeyType ); fragment.addEmbeddedParameter( paramSpec ); } @@ -105,7 +104,7 @@ public void addWhereFragment( LOG.debugf( "Using processed WHERE-fragment [%s]", fragment.getText() ); } - // Filter conditions need to be inserted beforeQuery the HQL where condition and the + // Filter conditions need to be inserted before the HQL where condition and the // theta join node. This is because org.hibernate.loader.Loader binds the filter parameters first, // then it binds all the HQL query parameters, see org.hibernate.loader.Loader.processFilterParameters(). if ( fragment.getFromElement().isFilter() || fragment.hasFilterCondition() ) { @@ -114,7 +113,7 @@ public void addWhereFragment( AST where = query.getWhereClause(); // Create a new FILTERS node as a parent of all filters filters = create( FILTERS, "{filter conditions}" ); - // Put the FILTERS node beforeQuery the HQL condition and theta joins + // Put the FILTERS node before the HQL condition and theta joins ASTUtil.insertChild( where, filters ); } @@ -127,7 +126,7 @@ public void addWhereFragment( AST where = query.getWhereClause(); // Create a new THETA_JOINS node as a parent of all filters thetaJoins = create( THETA_JOINS, "{theta joins}" ); - // Put the THETA_JOINS node beforeQuery the HQL condition, afterQuery the filters. + // Put the THETA_JOINS node before the HQL condition, after the filters. if ( filters == null ) { ASTUtil.insertChild( where, thetaJoins ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/TokenPrinters.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/TokenPrinters.java new file mode 100644 index 000000000000..66a03adb3b7b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/TokenPrinters.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 . + */ +package org.hibernate.hql.internal.ast.util; + +import org.hibernate.hql.internal.antlr.HqlTokenTypes; +import org.hibernate.hql.internal.antlr.SqlTokenTypes; +import org.hibernate.sql.ordering.antlr.GeneratedOrderByFragmentRendererTokenTypes; + +/** + * Commonly used token printers expressed as constants. + */ +public interface TokenPrinters { + + ASTPrinter HQL_TOKEN_PRINTER = new ASTPrinter( HqlTokenTypes.class ); + + ASTPrinter SQL_TOKEN_PRINTER = new ASTPrinter( SqlTokenTypes.class ); + + ASTPrinter ORDERBY_FRAGMENT_PRINTER = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class ); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/AbstractParameterInformation.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/AbstractParameterInformation.java new file mode 100644 index 000000000000..2a13d1e4a1a7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/AbstractParameterInformation.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.hql.internal.classic; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.hql.spi.ParameterInformation; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.param.ParameterBinder; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractParameterInformation implements ParameterInformation, ParameterBinder { + private List sqlPositions = new ArrayList<>(); + + @Override + public int[] getSourceLocations() { + return ArrayHelper.toIntArray( sqlPositions ); + } + + public void addSourceLocation(int position) { + sqlPositions.add( position ); + } + + @Override + public Type getExpectedType() { + // the classic translator does not know this information + return null; + } + + @Override + public void setExpectedType(Type expectedType) { + // nothing to do - classic translator does not know this information + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/ClauseParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/ClauseParser.java index 5cf7124d2291..a6ce3441011a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/ClauseParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/ClauseParser.java @@ -34,7 +34,7 @@ else if ( ")".equals( token ) ) { } if ( byExpected && !lcToken.equals( "by" ) ) { - throw new QueryException( "BY expected afterQuery GROUP or ORDER: " + token ); + throw new QueryException( "BY expected after GROUP or ORDER: " + token ); } boolean isClauseStart = parenCount == 0; //ignore subselect keywords @@ -71,7 +71,7 @@ else if ( lcToken.equals( "group" ) ) { } else if ( lcToken.equals( "by" ) ) { if ( !byExpected ) { - throw new QueryException( "GROUP or ORDER expected beforeQuery BY" ); + throw new QueryException( "GROUP or ORDER expected before BY" ); } child.start( q ); byExpected = false; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/NamedParameterInformationImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/NamedParameterInformationImpl.java new file mode 100644 index 000000000000..840e79959726 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/NamedParameterInformationImpl.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.hql.internal.classic; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.hql.spi.NamedParameterInformation; + +/** + * @author Steve Ebersole + */ +public class NamedParameterInformationImpl extends AbstractParameterInformation implements NamedParameterInformation { + private final String name; + + NamedParameterInformationImpl(String name) { + this.name = name; + } + + @Override + public String getSourceName() { + return name; + } + + @Override + public int bind( + PreparedStatement statement, + QueryParameters qp, + SharedSessionContractImplementor session, + int position) throws SQLException { + final TypedValue typedValue = qp.getNamedParameters().get( name ); + typedValue.getType().nullSafeSet( statement, typedValue.getValue(), position, session ); + return typedValue.getType().getColumnSpan( session.getFactory() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/OrderByParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/OrderByParser.java index 853e2ab71d08..d1015c8fc156 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/OrderByParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/OrderByParser.java @@ -5,9 +5,13 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.hql.internal.classic; +import java.util.Locale; + import org.hibernate.QueryException; import org.hibernate.internal.util.StringHelper; +import static org.hibernate.hql.spi.QueryTranslator.ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED; + /** * Parses the ORDER BY clause of a query */ @@ -40,6 +44,29 @@ else if ( token.startsWith( ParserHelper.HQL_VARIABLE_PREFIX ) ) { //named query q.addNamedParameter( token.substring( 1 ) ); q.appendOrderByToken( "?" ); } + else if ( token.startsWith( "?" ) ) { + // ordinal query parameter + if ( token.length() == 1 ) { + throw new QueryException( + String.format( + Locale.ROOT, + ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED, + q.getQueryString() + ) + ); + } + else { + final String labelString = token.substring( 1 ); + try { + final int label = Integer.parseInt( labelString ); + q.addOrdinalParameter( label ); + q.appendOrderByToken( "?" ); + } + catch (NumberFormatException e) { + throw new QueryException( "Ordinal parameter label must be numeric : " + labelString, e ); + } + } + } else { q.appendOrderByToken( token ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/PathExpressionParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/PathExpressionParser.java index c46fb9cae85d..bfbd803dc8f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/PathExpressionParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/PathExpressionParser.java @@ -111,12 +111,12 @@ public void token(String token, QueryTranslatorImpl q) throws QueryException { String alias = q.getPathAlias( path.toString() ); if ( alias != null ) { reset( q ); //reset the dotcount (but not the path) - currentName = alias; //afterQuery reset! + currentName = alias; //after reset! currentPropertyMapping = q.getPropertyMapping( currentName ); if ( !ignoreInitialJoin ) { JoinSequence ojf = q.getPathJoin( path.toString() ); try { - joinSequence.addCondition( ojf.toJoinFragment( q.getEnabledFilters(), true ).toWhereFragmentString() ); //afterQuery reset! + joinSequence.addCondition( ojf.toJoinFragment( q.getEnabledFilters(), true ).toWhereFragmentString() ); //after reset! } catch ( MappingException me ) { throw new QueryException( me ); @@ -344,7 +344,7 @@ private void prepareForIndex(QueryTranslatorImpl q) throws QueryException { QueryableCollection collPersister = q.getCollectionPersister( collectionRole ); if ( !collPersister.hasIndex() ) { - throw new QueryException( "unindexed collection beforeQuery []: " + path ); + throw new QueryException( "unindexed collection before []: " + path ); } String[] indexCols = collPersister.getIndexColumnNames(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/PositionalParameterInformationImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/PositionalParameterInformationImpl.java new file mode 100644 index 000000000000..8782a507265d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/PositionalParameterInformationImpl.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.hql.internal.classic; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.hql.spi.PositionalParameterInformation; + +/** + * @author Steve Ebersole + */ +public class PositionalParameterInformationImpl + extends AbstractParameterInformation + implements PositionalParameterInformation { + private final int label; + + public PositionalParameterInformationImpl(int label) { + this.label = label; + } + + @Override + public int getLabel() { + return label; + } + + @Override + public int bind( + PreparedStatement statement, + QueryParameters qp, + SharedSessionContractImplementor session, + int position) throws SQLException { + final TypedValue typedValue = qp.getNamedParameters().get( Integer.toString( label ) ); + typedValue.getType().nullSafeSet( statement, typedValue.getValue(), position, session ); + return typedValue.getType().getColumnSpan( session.getFactory() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java index ed6562baea0e..1f4be5a6b155 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java @@ -38,7 +38,9 @@ import org.hibernate.hql.internal.HolderInstantiator; import org.hibernate.hql.internal.NameGenerator; import org.hibernate.hql.spi.FilterTranslator; +import org.hibernate.hql.spi.NamedParameterInformation; import org.hibernate.hql.spi.ParameterTranslations; +import org.hibernate.hql.spi.PositionalParameterInformation; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.IteratorImpl; @@ -46,7 +48,10 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.BasicLoader; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.loader.spi.AfterLoadAction; +import org.hibernate.param.CollectionFilterKeyParameterSpecification; +import org.hibernate.param.ParameterBinder; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.Loadable; @@ -78,12 +83,15 @@ public class QueryTranslatorImpl extends BasicLoader implements FilterTranslator private List returnedTypes = new ArrayList(); private final List fromTypes = new ArrayList(); private final List scalarTypes = new ArrayList(); - private final Map namedParameters = new HashMap(); private final Map aliasNames = new HashMap(); private final Map oneToOneOwnerNames = new HashMap(); private final Map uniqueKeyOwnerReferences = new HashMap(); private final Map decoratedPropertyMappings = new HashMap(); + private final Map namedParameters = new HashMap<>(); + private final Map ordinalParameters = new HashMap<>(); + private final List paramValueBinders = new ArrayList<>(); + private final List scalarSelectTokens = new ArrayList(); private final List whereTokens = new ArrayList(); private final List havingTokens = new ArrayList(); @@ -205,6 +213,12 @@ public synchronized void compile( if ( !isCompiled() ) { addFromAssociation( "this", collectionRole ); + paramValueBinders.add( + new CollectionFilterKeyParameterSpecification( + collectionRole, + getFactory().getMetamodel().collectionPersister( collectionRole ).getKeyType() + ) + ); compile( replacements, scalar ); } } @@ -241,7 +255,6 @@ private void compile() throws QueryException, MappingException { } catch (Exception e) { LOG.debug( "Unexpected query compilation problem", e ); - e.printStackTrace(); throw new QueryException( "Incorrect query syntax", queryString, e ); } @@ -530,20 +543,81 @@ void addNamedParameter(String name) { if ( superQuery != null ) { superQuery.addNamedParameter( name ); } - Integer loc = parameterCount++; - Object o = namedParameters.get( name ); - if ( o == null ) { - namedParameters.put( name, loc ); + + final int loc = parameterCount++; + + final NamedParameterInformationImpl info = namedParameters.computeIfAbsent( + name, + k -> new NamedParameterInformationImpl( name ) + ); + paramValueBinders.add( info ); + info.addSourceLocation( loc ); + } + + private enum OrdinalParameterStyle { LABELED, LEGACY } + + private OrdinalParameterStyle ordinalParameterStyle; + + private int legacyPositionalParameterCount = 0; + + void addLegacyPositionalParameter() { + if ( superQuery != null ) { + superQuery.addLegacyPositionalParameter(); } - else if ( o instanceof Integer ) { - ArrayList list = new ArrayList( 4 ); - list.add( o ); - list.add( loc ); - namedParameters.put( name, list ); + + if ( ordinalParameterStyle == null ) { + ordinalParameterStyle = OrdinalParameterStyle.LEGACY; } - else { - ( (ArrayList) o ).add( loc ); + else if ( ordinalParameterStyle != OrdinalParameterStyle.LEGACY ) { + throw new QueryException( "Cannot mix legacy and labeled positional parameters" ); } + + final int label = legacyPositionalParameterCount++; + final PositionalParameterInformationImpl paramInfo = new PositionalParameterInformationImpl( label ); + ordinalParameters.put( label, paramInfo ); + paramValueBinders.add( paramInfo ); + + final int loc = parameterCount++; + paramInfo.addSourceLocation( loc ); + + } + + void addOrdinalParameter(int label) { + if ( superQuery != null ) { + superQuery.addOrdinalParameter( label ); + } + + if ( ordinalParameterStyle == null ) { + ordinalParameterStyle = OrdinalParameterStyle.LABELED; + } + else if ( ordinalParameterStyle != OrdinalParameterStyle.LABELED ) { + throw new QueryException( "Cannot mix legacy and labeled positional parameters" ); + } + + final int loc = parameterCount++; + + final PositionalParameterInformationImpl info = ordinalParameters.computeIfAbsent( + label, + k -> new PositionalParameterInformationImpl( label ) + ); + + paramValueBinders.add( info ); + + info.addSourceLocation( loc ); + } + + @Override + protected int bindParameterValues( + PreparedStatement statement, + QueryParameters queryParameters, + int startIndex, + SharedSessionContractImplementor session) throws SQLException { + + int span = 0; + for ( ParameterBinder binder : paramValueBinders ) { + span += binder.bind( statement, queryParameters, session, startIndex + span ); + } + return span; } @Override @@ -561,7 +635,6 @@ public int[] getNamedParameterLocs(String name) throws QueryException { } private void renderSQL() throws QueryException, MappingException { - final int rtsize; if ( returnedTypes.size() == 0 && scalarTypes.size() == 0 ) { //ie no select clause in HQL @@ -587,7 +660,7 @@ private void renderSQL() throws QueryException, MappingException { //if ( !isName(name) ) throw new QueryException("unknown type: " + name); persisters[i] = getEntityPersisterForName( name ); // TODO: cannot use generateSuffixes() - it handles the initial suffix differently. - suffixes[i] = ( size == 1 ) ? "" : Integer.toString( i ) + '_'; + suffixes[i] = ( size == 1 ) ? "" : AliasConstantsHelper.get( i ); names[i] = name; includeInSelect[i] = !entitiesToFetch.contains( name ); if ( includeInSelect[i] ) { @@ -686,7 +759,7 @@ private void renderIdentifierSelect(QuerySelect sql) { for ( int k = 0; k < size; k++ ) { String name = (String) returnedTypes.get( k ); - String suffix = size == 1 ? "" : Integer.toString( k ) + '_'; + String suffix = size == 1 ? "" : AliasConstantsHelper.get( k ); sql.addSelectFragmentString( persisters[k].identifierSelectFragment( name, suffix ) ); } @@ -711,7 +784,7 @@ private void renderIdentifierSelect(QuerySelect sql) { private void renderPropertiesSelect(QuerySelect sql) { int size = returnedTypes.size(); for ( int k = 0; k < size; k++ ) { - String suffix = size == 1 ? "" : Integer.toString( k ) + '_'; + String suffix = size == 1 ? "" : AliasConstantsHelper.get( k ); String name = (String) returnedTypes.get( k ); sql.addSelectFragmentString( persisters[k].propertySelectFragment( name, suffix, false ) ); } @@ -973,7 +1046,7 @@ public Iterator iterate(QueryParameters queryParameters, EventSource session) } try { - final List afterLoadActions = new ArrayList(); + final List afterLoadActions = new ArrayList<>(); final SqlStatementWrapper wrapper = executeQueryStatement( queryParameters, false, @@ -1267,40 +1340,25 @@ public Class getDynamicInstantiationResultType() { public ParameterTranslations getParameterTranslations() { return new ParameterTranslations() { @Override - public boolean supportsOrdinalParameterMetadata() { - // classic translator does not support collection of ordinal - // param metadata - return false; - } - - @Override - public int getOrdinalParameterCount() { - return 0; // not known! - } - - @Override - public int getOrdinalParameterSqlLocation(int ordinalPosition) { - return 0; // not known! - } - - @Override - public Type getOrdinalParameterExpectedType(int ordinalPosition) { - return null; // not known! + @SuppressWarnings("unchecked") + public Map getNamedParameterInformationMap() { + return namedParameters; } @Override - public Set getNamedParameterNames() { - return namedParameters.keySet(); + @SuppressWarnings("unchecked") + public Map getPositionalParameterInformationMap() { + return ordinalParameters; } @Override - public int[] getNamedParameterSqlLocations(String name) { - return getNamedParameterLocs( name ); + public PositionalParameterInformation getPositionalParameterInformation(int position) { + return ordinalParameters.get( position ); } @Override - public Type getNamedParameterExpectedType(String name) { - return null; // not known! + public NamedParameterInformation getNamedParameterInformation(String name) { + return namedParameters.get( name ); } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/SelectParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/SelectParser.java index c4af19822375..87dc1d6bee25 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/SelectParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/SelectParser.java @@ -109,7 +109,7 @@ else if ( aggregate ) { q.appendScalarSelectToken( token ); } else { - throw new QueryException( "aggregate function expected beforeQuery ( in SELECT" ); + throw new QueryException( "aggregate function expected before ( in SELECT" ); } ready = true; } @@ -127,7 +127,7 @@ else if ( aggregate && ready ) { } } else { - throw new QueryException( "( expected beforeQuery ) in select" ); + throw new QueryException( "( expected before ) in select" ); } } else if ( COUNT_MODIFIERS.contains( lctoken ) ) { @@ -143,7 +143,7 @@ else if ( COUNT_MODIFIERS.contains( lctoken ) ) { else if ( getFunction( lctoken, q ) != null && token.equals( q.unalias( token ) ) ) { // the name of an SQL function if ( !ready ) { - throw new QueryException( ", expected beforeQuery aggregate function in SELECT: " + token ); + throw new QueryException( ", expected before aggregate function in SELECT: " + token ); } aggregate = true; aggregateAddSelectScalar = true; @@ -167,7 +167,7 @@ else if ( getFunction( lctoken, q ) != null && token.equals( q.unalias( token ) else if ( aggregate ) { boolean constantToken = false; if ( !ready ) { - throw new QueryException( "( expected afterQuery aggregate function in SELECT" ); + throw new QueryException( "( expected after aggregate function in SELECT" ); } try { ParserHelper.parse( aggregatePathExpressionParser, q.unalias( token ), ParserHelper.PATH_SEPARATORS, q ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java index 7a014272c2fb..291caa48d7f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java @@ -27,6 +27,8 @@ import org.hibernate.type.LiteralType; import org.hibernate.type.Type; +import static org.hibernate.hql.spi.QueryTranslator.ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED; + /** * Parses the where clause of a hibernate query and translates it to an * SQL where clause. @@ -396,13 +398,33 @@ private void addJoin(JoinSequence joinSequence, QueryTranslatorImpl q) throws Qu } private void doToken(String token, QueryTranslatorImpl q) throws QueryException { - if ( q.isName( StringHelper.root( token ) ) ) { //path expression + if ( q.isName( StringHelper.root( token ) ) ) { + //path expression doPathExpression( q.unalias( token ), q ); } - else if ( token.startsWith( ParserHelper.HQL_VARIABLE_PREFIX ) ) { //named query parameter + else if ( token.startsWith( ParserHelper.HQL_VARIABLE_PREFIX ) ) { + //named query parameter q.addNamedParameter( token.substring( 1 ) ); appendToken( q, "?" ); } + else if ( token.startsWith( "?" ) ) { + // ordinal query parameter + if ( token.length() == 1 ) { + q.addLegacyPositionalParameter(); + appendToken( q, "?" ); + } + else { + final String labelString = token.substring( 1 ); + try { + final int label = Integer.parseInt( labelString ); + q.addOrdinalParameter( label ); + appendToken( q, "?" ); + } + catch (NumberFormatException e) { + throw new QueryException( "Ordinal parameter label must be numeric : " + labelString, e ); + } + } + } else { Queryable persister = q.getEntityPersisterUsingImports( token ); if ( persister != null ) { // the name of a class @@ -493,7 +515,7 @@ private boolean continuePathExpression(String token, QueryTranslatorImpl q) thro PathExpressionParser.CollectionElement element = pathExpressionParser.lastCollectionElement(); - if ( token.startsWith( "." ) ) { // the path expression continues afterQuery a ] + if ( token.startsWith( "." ) ) { // the path expression continues after a ] doPathExpression( getElementName( element, q ) + token, q ); // careful with this! diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/NamedParameterInformation.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/NamedParameterInformation.java new file mode 100644 index 000000000000..da7d6e013312 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/NamedParameterInformation.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 . + */ +package org.hibernate.hql.spi; + +/** + * @author Steve Ebersole + */ +public interface NamedParameterInformation extends ParameterInformation { + String getSourceName(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/ParameterInformation.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/ParameterInformation.java new file mode 100644 index 000000000000..0014303b5dcb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/ParameterInformation.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.hql.spi; + +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public interface ParameterInformation { + /** + * The positions (relative to all parameters) that this parameter + * was discovered in the source query (HQL, etc). E.g., given a query + * like `.. where a.name = :name or a.nickName = :name` this would + * return `[0,1]` + */ + int[] getSourceLocations(); + + Type getExpectedType(); + + void setExpectedType(Type expectedType); + + void addSourceLocation(int position); +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/ParameterTranslations.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/ParameterTranslations.java index fccf36b1a436..efa88402c47d 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/ParameterTranslations.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/ParameterTranslations.java @@ -5,9 +5,8 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.hql.spi; -import java.util.Set; -import org.hibernate.type.Type; +import java.util.Map; /** * Defines available information about the parameters encountered during @@ -16,18 +15,10 @@ * @author Steve Ebersole */ public interface ParameterTranslations { + Map getNamedParameterInformationMap(); + Map getPositionalParameterInformationMap(); - public boolean supportsOrdinalParameterMetadata(); + PositionalParameterInformation getPositionalParameterInformation(int position); - public int getOrdinalParameterCount(); - - public int getOrdinalParameterSqlLocation(int ordinalPosition); - - public Type getOrdinalParameterExpectedType(int ordinalPosition); - - public Set getNamedParameterNames(); - - public int[] getNamedParameterSqlLocations(String name); - - public Type getNamedParameterExpectedType(String name); + NamedParameterInformation getNamedParameterInformation(String name); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/PositionalParameterInformation.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/PositionalParameterInformation.java new file mode 100644 index 000000000000..ef273d1e58ed --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/PositionalParameterInformation.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 . + */ +package org.hibernate.hql.spi; + +/** + * @author Steve Ebersole + */ +public interface PositionalParameterInformation extends ParameterInformation { + int getLabel(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/QueryTranslator.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/QueryTranslator.java index 1095e90f876c..ed8d2d6a7bd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/QueryTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/QueryTranslator.java @@ -29,6 +29,8 @@ public interface QueryTranslator { String ERROR_CANNOT_FETCH_WITH_ITERATE = "fetch may not be used with scroll() or iterate()"; String ERROR_NAMED_PARAMETER_DOES_NOT_APPEAR = "Named parameter does not appear in Query: "; + String ERROR_ORDINAL_PARAMETER_DOES_NOT_APPEAR = "Ordinal parameter [%s] does not appear in Query [%s] "; + String ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED = "Legacy-style query parameters (`?`) are no longer supported; use JPA-style ordinal parameters (e.g., `?1`) instead : %s"; String ERROR_CANNOT_DETERMINE_TYPE = "Could not determine type of: "; String ERROR_CANNOT_FORMAT_LITERAL = "Could not format constant value to SQL literal: "; @@ -169,5 +171,9 @@ int executeUpdate(QueryParameters queryParameters, SharedSessionContractImplemen boolean isManipulationStatement(); + default boolean isUpdateStatement() { + return getQueryString().toLowerCase().trim().startsWith( "update" ); + } + Class getDynamicInstantiationResultType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractIdsBulkIdHandler.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractIdsBulkIdHandler.java index 166d647a9ec1..72ce906f1f7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractIdsBulkIdHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractIdsBulkIdHandler.java @@ -21,6 +21,7 @@ import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.tree.AbstractRestrictableStatement; import org.hibernate.hql.internal.ast.tree.FromElement; +import org.hibernate.internal.util.StringHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.entity.Queryable; @@ -83,6 +84,8 @@ protected List selectIds( position += parameterSpecification.bind( ps, queryParameters, session, position ); } + Dialect dialect = session.getFactory().getServiceRegistry().getService( JdbcServices.class ).getDialect(); + ResultSet rs = session .getJdbcCoordinator() .getResultSetReturn() @@ -90,8 +93,9 @@ protected List selectIds( while ( rs.next() ) { Object[] result = new Object[targetedPersister.getIdentifierColumnNames().length]; for ( String columnName : targetedPersister.getIdentifierColumnNames() ) { - Object column = rs.getObject( columnName ); - result[rs.findColumn( columnName ) - 1] = column; + int columnIndex = rs.findColumn( StringHelper.unquote( columnName, dialect ) ); + Object column = rs.getObject(columnIndex); + result[columnIndex - 1] = column; } ids.add( result ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractTableBasedBulkIdHandler.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractTableBasedBulkIdHandler.java index 370e7c2e5305..71f696f2559c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractTableBasedBulkIdHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractTableBasedBulkIdHandler.java @@ -15,7 +15,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.SqlGenerator; -import org.hibernate.internal.util.StringHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.entity.Queryable; import org.hibernate.sql.InsertSelect; @@ -190,7 +189,7 @@ protected void addAnyExtraIdSelectValues(SelectValues selectClause) { } protected String generateIdSubselect(Queryable persister, IdTableInfo idTableInfo) { - return "select " + StringHelper.join( ", ", persister.getIdentifierColumnNames() ) + + return "select " + String.join( ", ", persister.getIdentifierColumnNames() ) + " from " + idTableInfo.getQualifiedIdTableName(); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupport.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupport.java index d7258ea57ac4..646945a588fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupport.java @@ -14,4 +14,5 @@ public interface IdTableSupport { String getCreateIdTableCommand(); String getCreateIdTableStatementOptions(); String getDropIdTableCommand(); + String getTruncateIdTableCommand(); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupportStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupportStandardImpl.java index c79f5f26ff2c..15f9a8a7ff86 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupportStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableSupportStandardImpl.java @@ -34,4 +34,9 @@ public String getCreateIdTableStatementOptions() { public String getDropIdTableCommand() { return "drop table"; } + + @Override + public String getTruncateIdTableCommand() { + return "delete from"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedDeleteHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedDeleteHandlerImpl.java index dcd18144e87b..f9eee1921262 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedDeleteHandlerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedDeleteHandlerImpl.java @@ -17,7 +17,6 @@ import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.tree.DeleteStatement; import org.hibernate.hql.internal.ast.tree.FromElement; -import org.hibernate.internal.util.StringHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.AbstractCollectionPersister; import org.hibernate.persister.entity.Queryable; @@ -88,7 +87,7 @@ public TableBasedDeleteHandlerImpl( private String generateDelete(String tableName, String[] columnNames, String idSubselect, String comment) { final Delete delete = new Delete() .setTableName( tableName ) - .setWhere( "(" + StringHelper.join( ", ", columnNames ) + ") IN (" + idSubselect + ")" ); + .setWhere( "(" + String.join( ", ", columnNames ) + ") IN (" + idSubselect + ")" ); if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) { delete.setComment( comment ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java index c4586a098466..63f88f40e4ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java @@ -20,7 +20,6 @@ import org.hibernate.hql.internal.ast.tree.AssignmentSpecification; import org.hibernate.hql.internal.ast.tree.FromElement; import org.hibernate.hql.internal.ast.tree.UpdateStatement; -import org.hibernate.internal.util.StringHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.entity.Queryable; import org.hibernate.sql.Update; @@ -75,7 +74,7 @@ public TableBasedUpdateHandlerImpl( final List parameterList = new ArrayList<>(); final Update update = new Update( dialect ) .setTableName( tableNames[tableIndex] ) - .setWhere( "(" + StringHelper.join( ", ", columnNames[tableIndex] ) + ") IN (" + idSubselect + ")" ); + .setWhere( "(" + String.join( ", ", columnNames[tableIndex] ) + ") IN (" + idSubselect + ")" ); if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) { update.setComment( "bulk update" ); } 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 b0bff2b9ec1a..bf3600ced9a2 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 @@ -16,6 +16,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.spi.id.AbstractIdsBulkIdHandler; +import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.Queryable; /** @@ -63,11 +64,16 @@ public AbstractCteValuesListBulkIdHandler( } protected String determineIdTableName(Queryable persister) { + + String qualifiedTableName = jdbcEnvironment.getIdentifierHelper().applyGlobalQuoting( + "HT_" + StringHelper.unquote( persister.getTableName(), jdbcEnvironment.getDialect() ) + ).render(); + return jdbcEnvironment.getQualifiedObjectNameFormatter().format( new QualifiedTableName( Identifier.toIdentifier( catalog ), Identifier.toIdentifier( schema ), - Identifier.toIdentifier( "HT_" + persister.getTableName() ) + Identifier.toIdentifier( qualifiedTableName ) ), jdbcEnvironment.getDialect() ); 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 866e8633cb85..e46c2b398486 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 @@ -76,7 +76,7 @@ public GlobalTemporaryTableBulkIdStrategy(IdTableSupport idTableSupport, AfterUs super( idTableSupport ); this.afterUseAction = afterUseAction; if ( afterUseAction == AfterUseAction.DROP ) { - throw new IllegalArgumentException( "DROP not supported as a afterQuery-use action for global temp table strategy" ); + throw new IllegalArgumentException( "DROP not supported as a after-use action for global temp table strategy" ); } } @@ -165,7 +165,7 @@ protected void releaseFromUse(Queryable persister, SharedSessionContractImplemen } private void cleanUpRows(String tableName, SharedSessionContractImplementor session) { - final String sql = "delete from " + tableName; + final String sql = this.getIdTableSupport().getTruncateIdTableCommand() + " " + tableName; PreparedStatement ps = null; try { ps = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/IdsClauseBuilder.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/IdsClauseBuilder.java index d86027b3f2bb..8221131f0dd9 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/IdsClauseBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/IdsClauseBuilder.java @@ -14,6 +14,7 @@ import org.hibernate.type.LiteralType; import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; +import org.hibernate.type.spi.TypeConfiguration; /** * Builds the where clause that wraps the identifiers to be updated/deleted. @@ -32,6 +33,23 @@ public abstract class IdsClauseBuilder { private final List ids; + protected IdsClauseBuilder( + Dialect dialect, + Type identifierType, + TypeConfiguration typeConfiguration, + String[] columns, + List ids) { + this.dialect = dialect; + this.identifierType = identifierType; + this.typeResolver = typeConfiguration.getTypeResolver(); + this.columns = columns; + this.ids = ids; + } + + /** + * @deprecated Use {{@link IdsClauseBuilder#IdsClauseBuilder(Dialect, Type, TypeConfiguration, String[], List)}} instead. + */ + @Deprecated protected IdsClauseBuilder( Dialect dialect, Type identifierType, @@ -49,6 +67,14 @@ public Type getIdentifierType() { return identifierType; } + /** + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated public TypeResolver getTypeResolver() { return typeResolver; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/Helper.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/Helper.java index 0d291e459a6c..974f56b604ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/Helper.java @@ -192,7 +192,7 @@ public void execute(Connection connection) { } } catch( Exception e ) { - log.warn( "unable to drop temporary id table afterQuery use [" + e.getMessage() + "]" ); + log.warn( "unable to drop temporary id table after use [" + e.getMessage() + "]" ); } } } 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..3dc3649dc376 100755 --- a/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java @@ -32,4 +32,9 @@ public boolean supportsBulkInsertionIdentifierGeneration() { public String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { return null; } + + @Override + public boolean supportsJdbcBatchInserts() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/Assigned.java b/hibernate-core/src/main/java/org/hibernate/id/Assigned.java index 36f565d32ab3..e22faa0f7df7 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/Assigned.java +++ b/hibernate-core/src/main/java/org/hibernate/id/Assigned.java @@ -31,7 +31,7 @@ public Serializable generate(SharedSessionContractImplementor session, Object ob final Serializable id = session.getEntityPersister( entityName, obj ).getIdentifier( obj, session ); if ( id == null ) { throw new IdentifierGenerationException( - "ids for this class must be manually assigned beforeQuery calling save(): " + entityName + "ids for this class must be manually assigned before calling save(): " + entityName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java b/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java index 6719c91af402..a5909ea18fc4 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java @@ -134,6 +134,11 @@ public Object accept(ValueVisitor visitor) { return null; } + @Override + public boolean isSame(Value value) { + return false; + } + @Override public ServiceRegistry getServiceRegistry() { return database.getBuildingOptions().getServiceRegistry(); 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 173dd30ca9f9..a92804464e24 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/ForeignGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/ForeignGenerator.java @@ -14,12 +14,16 @@ import org.hibernate.TransientObjectException; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.loader.PropertyPath; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.EntityType; import org.hibernate.type.Type; +import static org.hibernate.internal.CoreLogging.logger; +import static org.hibernate.internal.CoreLogging.messageLogger; + /** * foreign
      *
      @@ -31,6 +35,8 @@ * @author Gavin King */ public class ForeignGenerator implements IdentifierGenerator, Configurable { + private static final CoreMessageLogger LOG = messageLogger( ForeignGenerator.class ); + private String entityName; private String propertyName; @@ -105,6 +111,12 @@ public Serializable generate(SharedSessionContractImplementor sessionImplementor ); } catch (TransientObjectException toe) { + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "ForeignGenerator detected a transient entity [%s]", + foreignValueSourceType.getAssociatedEntityName() + ); + } id = session.save( foreignValueSourceType.getAssociatedEntityName(), associatedObject ); } 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 2a1eb99fd841..51bc8a355e5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java @@ -7,6 +7,7 @@ package org.hibernate.id; import java.io.Serializable; +import javax.persistence.GeneratedValue; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -34,12 +35,18 @@ public interface IdentifierGenerator { /** * The configuration parameter holding the entity name */ - public static final String ENTITY_NAME = "entity_name"; + String ENTITY_NAME = "entity_name"; /** * The configuration parameter holding the JPA entity name */ - public static final String JPA_ENTITY_NAME = "jpa_entity_name"; + String JPA_ENTITY_NAME = "jpa_entity_name"; + + /** + * Used as a key to pass the name used as {@link GeneratedValue#generator()} to the + * {@link IdentifierGenerator} as it is configured. + */ + String GENERATOR_NAME = "GENERATOR_NAME"; /** * Generate a new identifier. @@ -51,5 +58,14 @@ public interface IdentifierGenerator { * * @throws HibernateException Indicates trouble generating the identifier */ - public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException; + Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException; + + /** + * Check if JDBC batch inserts are supported. + * + * @return JDBC batch inserts are supported. + */ + default boolean supportsJdbcBatchInserts() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java index a84b5cefc846..8f021d2417f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java @@ -306,6 +306,7 @@ public IntegralDataTypeHolder initialize(ResultSet resultSet, long defaultValue) public void bind(PreparedStatement preparedStatement, int position) throws SQLException { // TODO : bind it as 'exact type'? Not sure if that gains us anything... + LOG.tracef( "binding parameter [%s] - [%s]", position, value ); preparedStatement.setLong( position, value ); } 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 cbb907c3a589..6236dd30436a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java @@ -69,7 +69,7 @@ *

    • value_column: hi value column name(default sequence_next_hi_value)
    • *
    • primary_key_value: key value for the current entity (default to the entity's primary table name)
    • *
    • primary_key_length: length of the key column in DB represented as a varchar (default to 255)
    • - *
    • max_lo: max low value beforeQuery increasing hi (default to Short.MAX_VALUE)
    • + *
    • max_lo: max low value before increasing hi (default to Short.MAX_VALUE)
    • *
    * * @author Emmanuel Bernard 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 200611c91b74..2e256864c94f 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 @@ -51,8 +51,8 @@ public interface DatabaseStructure extends ExportableProducer { AccessCallback buildCallback(SharedSessionContractImplementor session); /** - * Prepare this structure for use. Called sometime afterQuery instantiation, - * but beforeQuery first use. + * Prepare this structure for use. Called sometime after instantiation, + * but before first use. * * @param optimizer The optimizer being applied to the generator. */ diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java index 35ef3b55587d..1cf680625453 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/HiLoOptimizer.java @@ -46,7 +46,7 @@ * * And so on... *

    - * Note, 'value' always (afterQuery init) holds the next value to return + * Note, 'value' always (after init) holds the next value to return * * @author Steve Ebersole */ @@ -95,6 +95,7 @@ public synchronized Serializable generate(AccessCallback callback) { else if ( ! generationState.upperLimit.gt( generationState.value ) ) { generationState.lastSourceValue = callback.getNextValue(); generationState.upperLimit = generationState.lastSourceValue.copy().multiplyBy( incrementSize ).increment(); + generationState.value = generationState.upperLimit.copy().subtract( incrementSize ); } return generationState.value.makeValueThenIncrement(); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java index 92c7924d82fa..ad3a8200b636 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/NoopOptimizer.java @@ -38,12 +38,6 @@ public Serializable generate(AccessCallback callback) { // The lastSourceValue field is only accessed by tests, // so this is not a concern. IntegralDataTypeHolder value = callback.getNextValue(); - if ( incrementSize > 0 ) { - while ( value.lt( 1 ) ) { - value = callback.getNextValue(); - } - } - lastSourceValue = value; return value.makeValue(); } @@ -55,6 +49,9 @@ public IntegralDataTypeHolder getLastSourceValue() { @Override public boolean applyIncrementSizeToSourceValues() { - return false; + // We allow the increment size to be 0 for backward-compatibility with legacy + // ID generators; we don't apply a value of 0, so the default will be used instead. + // We don't apply an increment size of 1, since it is already the default. + return getIncrementSize() != 0 && getIncrementSize() != 1; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoThreadLocalOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoThreadLocalOptimizer.java index 03525773b434..e54af6d14812 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoThreadLocalOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledLoThreadLocalOptimizer.java @@ -56,10 +56,9 @@ public PooledLoThreadLocalOptimizer(Class returnClass, int incrementSize) { @Override public Serializable generate(AccessCallback callback) { - GenerationState local = null; if ( callback.getTenantIdentifier() == null ) { - local = localAssignedIds.get(); - if ( local != null && local.value.lt( local.upperLimitValue ) ) { + final GenerationState local = localAssignedIds.get(); + if ( local.value != null && local.value.lt( local.upperLimitValue ) ) { return local.value.makeValueThenIncrement(); } } @@ -82,16 +81,11 @@ public Serializable generate(AccessCallback callback) { } private Map tenantSpecificState; - private final ThreadLocal localAssignedIds = new ThreadLocal(); + private final ThreadLocal localAssignedIds = ThreadLocal.withInitial( GenerationState::new ); private GenerationState locateGenerationState(String tenantIdentifier) { if ( tenantIdentifier == null ) { - GenerationState noTenantState = localAssignedIds.get(); - if ( noTenantState == null ) { - noTenantState = new GenerationState(); - localAssignedIds.set(noTenantState); - } - return noTenantState; + return localAssignedIds.get(); } else { GenerationState state; 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 343bd9840ec4..e4b80e562696 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 @@ -15,13 +15,18 @@ import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; +import org.hibernate.cfg.AvailableSettings; 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.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.internal.CoreMessageLogger; +import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; @@ -29,7 +34,7 @@ import org.jboss.logging.Logger; /** - * Generates identifier values based on an sequence-style database structure. + * Generates identifier values based on a sequence-style database structure. * Variations range from actually using a sequence to using a table to mimic * a sequence. These variations are encapsulated by the {@link DatabaseStructure} * interface internally. @@ -138,12 +143,14 @@ public class SequenceStyleGenerator * Used to create dedicated sequence for each entity based on the entity name. Sequence suffix can be * controlled with {@link #CONFIG_SEQUENCE_PER_ENTITY_SUFFIX} option. */ + @SuppressWarnings("WeakerAccess") public static final String CONFIG_PREFER_SEQUENCE_PER_ENTITY = "prefer_sequence_per_entity"; /** * Indicates the suffix to use in naming the identifier sequence/table name, by appending the suffix to * the name of the entity. Used in conjunction with {@link #CONFIG_PREFER_SEQUENCE_PER_ENTITY}. */ + @SuppressWarnings("WeakerAccess") public static final String CONFIG_SEQUENCE_PER_ENTITY_SUFFIX = "sequence_per_entity_suffix"; /** @@ -168,11 +175,13 @@ public class SequenceStyleGenerator /** * Indicates the name of the column holding the identifier values. The default value is {@link #DEF_VALUE_COLUMN} */ + @SuppressWarnings("WeakerAccess") public static final String VALUE_COLUMN_PARAM = "value_column"; /** * The default value for {@link #VALUE_COLUMN_PARAM} */ + @SuppressWarnings("WeakerAccess") public static final String DEF_VALUE_COLUMN = "next_val"; @@ -220,7 +229,7 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis this.identifierType = type; boolean forceTableUse = ConfigurationHelper.getBoolean( FORCE_TBL_PARAM, params, false ); - final QualifiedName sequenceName = determineSequenceName( params, dialect, jdbcEnvironment ); + final QualifiedName sequenceName = determineSequenceName( params, dialect, jdbcEnvironment, serviceRegistry ); final int initialValue = determineInitialValue( params ); int incrementSize = determineIncrementSize( params ); @@ -264,13 +273,28 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis * @param jdbcEnv The JdbcEnvironment * @return The sequence name */ - @SuppressWarnings("UnusedParameters") - protected QualifiedName determineSequenceName(Properties params, Dialect dialect, JdbcEnvironment jdbcEnv) { + @SuppressWarnings({"UnusedParameters", "WeakerAccess"}) + protected QualifiedName determineSequenceName( + Properties params, + Dialect dialect, + JdbcEnvironment jdbcEnv, + ServiceRegistry serviceRegistry) { final String sequencePerEntitySuffix = ConfigurationHelper.getString( CONFIG_SEQUENCE_PER_ENTITY_SUFFIX, params, DEF_SEQUENCE_SUFFIX ); + + String fallbackSequenceName = DEF_SEQUENCE_NAME; + final Boolean preferGeneratorNameAsDefaultName = serviceRegistry.getService( ConfigurationService.class ) + .getSetting( AvailableSettings.PREFER_GENERATOR_NAME_AS_DEFAULT_SEQUENCE_NAME, StandardConverters.BOOLEAN, true ); + if ( preferGeneratorNameAsDefaultName ) { + final String generatorName = params.getProperty( IdentifierGenerator.GENERATOR_NAME ); + if ( StringHelper.isNotEmpty( generatorName ) ) { + fallbackSequenceName = generatorName; + } + } + // JPA_ENTITY_NAME value honors (HBM) and @Entity#name (JPA) overrides. final String defaultSequenceName = ConfigurationHelper.getBoolean( CONFIG_PREFER_SEQUENCE_PER_ENTITY, params, false ) ? params.getProperty( JPA_ENTITY_NAME ) + sequencePerEntitySuffix - : DEF_SEQUENCE_NAME; + : fallbackSequenceName; final String sequenceName = ConfigurationHelper.getString( SEQUENCE_PARAM, params, defaultSequenceName ); if ( sequenceName.contains( "." ) ) { @@ -303,7 +327,7 @@ protected QualifiedName determineSequenceName(Properties params, Dialect dialect * @param jdbcEnvironment The JDBC environment * @return The value column name */ - @SuppressWarnings("UnusedParameters") + @SuppressWarnings({"UnusedParameters", "WeakerAccess"}) protected Identifier determineValueColumnName(Properties params, JdbcEnvironment jdbcEnvironment) { final String name = ConfigurationHelper.getString( VALUE_COLUMN_PARAM, params, DEF_VALUE_COLUMN ); return jdbcEnvironment.getIdentifierHelper().toIdentifier( name ); @@ -319,6 +343,7 @@ protected Identifier determineValueColumnName(Properties params, JdbcEnvironment * @param params The params supplied in the generator config (plus some standard useful extras). * @return The initial value */ + @SuppressWarnings({"WeakerAccess"}) protected int determineInitialValue(Properties params) { return ConfigurationHelper.getInt( INITIAL_PARAM, params, DEFAULT_INITIAL_VALUE ); } @@ -332,6 +357,7 @@ protected int determineInitialValue(Properties params) { * @param params The params supplied in the generator config (plus some standard useful extras). * @return The increment size */ + @SuppressWarnings("WeakerAccess") protected int determineIncrementSize(Properties params) { return ConfigurationHelper.getInt( INCREMENT_PARAM, params, DEFAULT_INCREMENT_SIZE ); } @@ -345,6 +371,7 @@ protected int determineIncrementSize(Properties params) { * @param incrementSize The {@link #determineIncrementSize determined increment size} * @return The optimizer strategy (name) */ + @SuppressWarnings("WeakerAccess") protected String determineOptimizationStrategy(Properties params, int incrementSize) { return ConfigurationHelper.getString( OPT_PARAM, @@ -361,16 +388,37 @@ protected String determineOptimizationStrategy(Properties params, int incrementS * @param incrementSize The {@link #determineIncrementSize determined increment size} * @return The adjusted increment size. */ + @SuppressWarnings("WeakerAccess") protected int determineAdjustedIncrementSize(String optimizationStrategy, int incrementSize) { - if ( incrementSize > 1 && StandardOptimizerDescriptor.NONE.getExternalName().equals( optimizationStrategy ) ) { - LOG.honoringOptimizerSetting( - StandardOptimizerDescriptor.NONE.getExternalName(), - INCREMENT_PARAM, - incrementSize - ); - incrementSize = 1; + final int resolvedIncrementSize; + if ( Math.abs( incrementSize ) > 1 && + StandardOptimizerDescriptor.NONE.getExternalName().equals( optimizationStrategy ) ) { + if ( incrementSize < -1 ) { + resolvedIncrementSize = -1; + LOG.honoringOptimizerSetting( + StandardOptimizerDescriptor.NONE.getExternalName(), + INCREMENT_PARAM, + incrementSize, + "negative", + resolvedIncrementSize + ); + } + else { + // incrementSize > 1 + resolvedIncrementSize = 1; + LOG.honoringOptimizerSetting( + StandardOptimizerDescriptor.NONE.getExternalName(), + INCREMENT_PARAM, + incrementSize, + "positive", + resolvedIncrementSize + ); + } + } + else { + resolvedIncrementSize = incrementSize; } - return incrementSize; + return resolvedIncrementSize; } /** @@ -382,10 +430,11 @@ protected int determineAdjustedIncrementSize(String optimizationStrategy, int in * @param forceTableUse Should a table be used even if the dialect supports sequences? * @param sequenceName The name to use for the sequence or table. * @param initialValue The initial value. - * @param incrementSize the increment size to use (afterQuery any adjustments). + * @param incrementSize the increment size to use (after any adjustments). * * @return An abstraction for the actual database structure in use (table vs. sequence). */ + @SuppressWarnings("WeakerAccess") protected DatabaseStructure buildDatabaseStructure( Type type, Properties params, @@ -413,6 +462,7 @@ protected DatabaseStructure buildSequenceStructure( return new SequenceStructure( jdbcEnvironment, sequenceName, initialValue, incrementSize, type.getReturnedClass() ); } + @SuppressWarnings("WeakerAccess") protected DatabaseStructure buildTableStructure( Type type, Properties params, 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 daed218a3374..48c63bf5323f 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 @@ -22,10 +22,14 @@ import org.hibernate.MappingException; 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.QualifiedNameParser; +import org.hibernate.cfg.AvailableSettings; 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.spi.JdbcServices; @@ -34,6 +38,7 @@ 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; import org.hibernate.id.IntegralDataTypeHolder; import org.hibernate.id.PersistentIdentifierGenerator; @@ -144,6 +149,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab /** * The default {@link #TABLE_PARAM} value */ + @SuppressWarnings("WeakerAccess") public static final String DEF_TABLE = "hibernate_sequences"; /** @@ -177,17 +183,20 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab /** * The default {@link #SEGMENT_VALUE_PARAM} value, unless {@link #CONFIG_PREFER_SEGMENT_PER_ENTITY} is specified */ + @SuppressWarnings("WeakerAccess") public static final String DEF_SEGMENT_VALUE = "default"; /** * Indicates the length of the column defined by {@link #SEGMENT_COLUMN_PARAM}. Used in schema export. The * default value is {@link #DEF_SEGMENT_LENGTH} */ + @SuppressWarnings("WeakerAccess") public static final String SEGMENT_LENGTH_PARAM = "segment_value_length"; /** * The default {@link #SEGMENT_LENGTH_PARAM} value */ + @SuppressWarnings("WeakerAccess") public static final int DEF_SEGMENT_LENGTH = 255; /** @@ -198,6 +207,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab /** * The default {@link #INITIAL_PARAM} value */ + @SuppressWarnings("WeakerAccess") public static final int DEFAULT_INITIAL_VALUE = 1; /** @@ -208,6 +218,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab /** * The default {@link #INCREMENT_PARAM} value */ + @SuppressWarnings("WeakerAccess") public static final int DEFAULT_INCREMENT_SIZE = 1; /** @@ -216,6 +227,8 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab */ public static final String OPT_PARAM = "optimizer"; + private boolean storeLastUsedValue; + private Type identifierType; @@ -290,7 +303,7 @@ public final String getSegmentValue() { * * @return the column size. */ - @SuppressWarnings("UnusedDeclaration") + @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"}) public final int getSegmentValueLength() { return segmentValueLength; } @@ -345,11 +358,13 @@ public final long getTableAccessCount() { @Override public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { + storeLastUsedValue = serviceRegistry.getService( ConfigurationService.class ) + .getSetting( AvailableSettings.TABLE_GENERATOR_STORE_LAST_USED, StandardConverters.BOOLEAN, true ); identifierType = type; final JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class ); - qualifiedTableName = determineGeneratorTableName( params, jdbcEnvironment ); + qualifiedTableName = determineGeneratorTableName( params, jdbcEnvironment, serviceRegistry ); segmentColumnName = determineSegmentColumnName( params, jdbcEnvironment ); valueColumnName = determineValueColumnName( params, jdbcEnvironment ); @@ -364,11 +379,12 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis params, OptimizerFactory.determineImplicitOptimizerName( incrementSize, params ) ); + int optimizerInitialValue = ConfigurationHelper.getInt( INITIAL_PARAM, params, -1 ); optimizer = OptimizerFactory.buildOptimizer( optimizationStrategy, identifierType.getReturnedClass(), incrementSize, - ConfigurationHelper.getInt( INITIAL_PARAM, params, -1 ) + optimizerInitialValue ); } @@ -382,9 +398,22 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis * @param jdbcEnvironment The JDBC environment * @return The table name to use. */ - @SuppressWarnings("UnusedParameters") - protected QualifiedName determineGeneratorTableName(Properties params, JdbcEnvironment jdbcEnvironment) { - final String tableName = ConfigurationHelper.getString( TABLE_PARAM, params, DEF_TABLE ); + @SuppressWarnings({"UnusedParameters", "WeakerAccess"}) + protected QualifiedName determineGeneratorTableName(Properties params, JdbcEnvironment jdbcEnvironment, ServiceRegistry serviceRegistry) { + + String fallbackTableName = DEF_TABLE; + + final Boolean preferGeneratorNameAsDefaultName = serviceRegistry.getService( ConfigurationService.class ) + .getSetting( AvailableSettings.PREFER_GENERATOR_NAME_AS_DEFAULT_SEQUENCE_NAME, StandardConverters.BOOLEAN, true ); + if ( preferGeneratorNameAsDefaultName ) { + final String generatorName = params.getProperty( IdentifierGenerator.GENERATOR_NAME ); + if ( StringHelper.isNotEmpty( generatorName ) ) { + fallbackTableName = generatorName; + } + } + + + String tableName = ConfigurationHelper.getString( TABLE_PARAM, params, fallbackTableName ); if ( tableName.contains( "." ) ) { return QualifiedNameParser.INSTANCE.parse( tableName ); @@ -416,7 +445,7 @@ protected QualifiedName determineGeneratorTableName(Properties params, JdbcEnvir * @param jdbcEnvironment The JDBC environment * @return The name of the segment column */ - @SuppressWarnings("UnusedParameters") + @SuppressWarnings({"UnusedParameters", "WeakerAccess"}) protected String determineSegmentColumnName(Properties params, JdbcEnvironment jdbcEnvironment) { final String name = ConfigurationHelper.getString( SEGMENT_COLUMN_PARAM, params, DEF_SEGMENT_COLUMN ); return jdbcEnvironment.getIdentifierHelper().toIdentifier( name ).render( jdbcEnvironment.getDialect() ); @@ -432,7 +461,7 @@ protected String determineSegmentColumnName(Properties params, JdbcEnvironment j * @param jdbcEnvironment The JDBC environment * @return The name of the value column */ - @SuppressWarnings("UnusedParameters") + @SuppressWarnings({"UnusedParameters", "WeakerAccess"}) protected String determineValueColumnName(Properties params, JdbcEnvironment jdbcEnvironment) { final String name = ConfigurationHelper.getString( VALUE_COLUMN_PARAM, params, DEF_VALUE_COLUMN ); return jdbcEnvironment.getIdentifierHelper().toIdentifier( name ).render( jdbcEnvironment.getDialect() ); @@ -447,6 +476,7 @@ protected String determineValueColumnName(Properties params, JdbcEnvironment jdb * @param params The params supplied in the generator config (plus some standard useful extras). * @return The name of the value column */ + @SuppressWarnings("WeakerAccess") protected String determineSegmentValue(Properties params) { String segmentValue = params.getProperty( SEGMENT_VALUE_PARAM ); if ( StringHelper.isEmpty( segmentValue ) ) { @@ -462,6 +492,7 @@ protected String determineSegmentValue(Properties params) { * @param params The params supplied in the generator config (plus some standard useful extras). * @return The default segment value to use. */ + @SuppressWarnings("WeakerAccess") protected String determineDefaultSegmentValue(Properties params) { final boolean preferSegmentPerEntity = ConfigurationHelper.getBoolean( CONFIG_PREFER_SEGMENT_PER_ENTITY, params, false ); final String defaultToUse = preferSegmentPerEntity ? params.getProperty( TABLE ) : DEF_SEGMENT_VALUE; @@ -478,19 +509,22 @@ protected String determineDefaultSegmentValue(Properties params) { * @param params The params supplied in the generator config (plus some standard useful extras). * @return The size of the segment column */ + @SuppressWarnings("WeakerAccess") protected int determineSegmentColumnSize(Properties params) { return ConfigurationHelper.getInt( SEGMENT_LENGTH_PARAM, params, DEF_SEGMENT_LENGTH ); } + @SuppressWarnings("WeakerAccess") protected int determineInitialValue(Properties params) { return ConfigurationHelper.getInt( INITIAL_PARAM, params, DEFAULT_INITIAL_VALUE ); } + @SuppressWarnings("WeakerAccess") protected int determineIncrementSize(Properties params) { return ConfigurationHelper.getInt( INCREMENT_PARAM, params, DEFAULT_INCREMENT_SIZE ); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "WeakerAccess"}) protected String buildSelectQuery(Dialect dialect) { final String alias = "tbl"; final String query = "select " + StringHelper.qualify( alias, valueColumnName ) + @@ -502,16 +536,26 @@ protected String buildSelectQuery(Dialect dialect) { return dialect.applyLocksToSql( query, lockOptions, updateTargetColumnsMap ); } + @SuppressWarnings("WeakerAccess") protected String buildUpdateQuery() { return "update " + renderedTableName + " set " + valueColumnName + "=? " + " where " + valueColumnName + "=? and " + segmentColumnName + "=?"; } + @SuppressWarnings("WeakerAccess") protected String buildInsertQuery() { return "insert into " + renderedTableName + " (" + segmentColumnName + ", " + valueColumnName + ") " + " values (?,?)"; } + protected InitCommand generateInsertInitCommand() { + int value = initialValue; + if ( storeLastUsedValue ) { + value = initialValue - 1; + } + return new InitCommand( "insert into " + renderedTableName + "(" + segmentColumnName + ", " + valueColumnName + ")" + " values ('" + segmentValue + "'," + ( value ) + ")" ); + } + private IntegralDataTypeHolder makeValue() { return IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() ); } @@ -534,26 +578,46 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException final IntegralDataTypeHolder value = makeValue(); int rows; do { - final PreparedStatement selectPS = prepareStatement( connection, selectQuery, statementLogger, statsCollector ); - try { + try (PreparedStatement selectPS = prepareStatement( + connection, + selectQuery, + statementLogger, + statsCollector + )) { selectPS.setString( 1, segmentValue ); final ResultSet selectRS = executeQuery( selectPS, statsCollector ); if ( !selectRS.next() ) { - value.initialize( initialValue ); - - final PreparedStatement insertPS = prepareStatement( connection, insertQuery, statementLogger, statsCollector ); - try { + long initializationValue; + if ( storeLastUsedValue ) { + initializationValue = initialValue - 1; + } + else { + initializationValue = initialValue; + } + value.initialize( initializationValue ); + + try (PreparedStatement insertPS = prepareStatement( + connection, + insertQuery, + statementLogger, + statsCollector + )) { + LOG.tracef( "binding parameter [%s] - [%s]", 1, segmentValue ); insertPS.setString( 1, segmentValue ); value.bind( insertPS, 2 ); executeUpdate( insertPS, statsCollector ); } - finally { - insertPS.close(); - } } else { - value.initialize( selectRS, 1 ); + int defaultValue; + if ( storeLastUsedValue ) { + defaultValue = 0; + } + else { + defaultValue = 1; + } + value.initialize( selectRS, defaultValue ); } selectRS.close(); } @@ -561,13 +625,14 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException LOG.unableToReadOrInitHiValue( e ); throw e; } - finally { - selectPS.close(); - } - final PreparedStatement updatePS = prepareStatement( connection, updateQuery, statementLogger, statsCollector ); - try { + try (PreparedStatement updatePS = prepareStatement( + connection, + updateQuery, + statementLogger, + statsCollector + )) { final IntegralDataTypeHolder updateValue = value.copy(); if ( optimizer.applyIncrementSizeToSourceValues() ) { updateValue.add( incrementSize ); @@ -584,15 +649,16 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException LOG.unableToUpdateQueryHiValue( renderedTableName, e ); throw e; } - finally { - updatePS.close(); - } } while ( rows == 0 ); accessCount++; - - return value; + if ( storeLastUsedValue ) { + return value.increment(); + } + else { + return value; + } } }, true @@ -700,10 +766,10 @@ public void registerExportables(Database database) { table.getQualifiedTableName(), dialect ); + table.addInitCommand( generateInsertInitCommand() ); this.selectQuery = buildSelectQuery( dialect ); this.updateQuery = buildUpdateQuery(); this.insertQuery = buildInsertQuery(); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java index 7d3514334c1d..e739cb90bfe5 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java @@ -17,7 +17,7 @@ /** * Abstract InsertGeneratedIdentifierDelegate implementation where the - * underlying strategy requires an subsequent select afterQuery the insert + * underlying strategy requires a subsequent select after the insert * to determine the generated identifier. * * @author Steve Ebersole @@ -84,7 +84,7 @@ public final Serializable performInsert( catch (SQLException sqle) { throw session.getJdbcServices().getSqlExceptionHelper().convert( sqle, - "could not retrieve generated id afterQuery insert: " + MessageHelper.infoString( persister ), + "could not retrieve generated id after insert: " + MessageHelper.infoString( persister ), insertSQL ); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java index 0b4ec40cbbc0..4293b5cc0be6 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java @@ -10,6 +10,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.UUIDGenerationStrategy; +import org.hibernate.internal.build.AllowSysOut; import org.hibernate.internal.util.BytesHelper; /** @@ -62,11 +63,12 @@ public static long generateLeastSignificantBits(long seed) { System.arraycopy( BytesHelper.fromInt( loTime ), 0, loBits, 2, 4 ); System.arraycopy( Helper.getCountBytes(), 0, loBits, 6, 2 ); loBits[0] &= 0x3f; - loBits[0] |= ((byte)2 << (byte)6); + loBits[0] |= ((byte)2 << (byte)6 ); return BytesHelper.asLong( loBits ); } + @AllowSysOut public static void main(String[] args) { CustomVersionOneStrategy strategy = new CustomVersionOneStrategy(); @@ -81,7 +83,7 @@ public static void main(String[] args) { System.arraycopy( BytesHelper.fromInt( loTime ), 0, loBits, 2, 4 ); System.arraycopy( Helper.getCountBytes(), 0, loBits, 6, 2 ); - System.out.println( " beforeQuery bit setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); + System.out.println( " before bit setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); System.out.println( " loBits[0] : " + BytesHelper.toBinaryString( loBits[0] ) ); System.out.println( " lsb : " + BytesHelper.toBinaryString( BytesHelper.asLong( loBits ) ) ); System.out.println( " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); @@ -89,7 +91,7 @@ public static void main(String[] args) { loBits[0] &= 0x3f; loBits[0] |= ((byte)2 << (byte)6); - System.out.println( " afterQuery bit setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); + System.out.println( " after bit setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); System.out.println( " loBits[0] : " + BytesHelper.toBinaryString( loBits[0] ) ); long leastSignificantBits = BytesHelper.asLong( loBits ); System.out.println( " lsb : " + BytesHelper.toBinaryString( leastSignificantBits ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/Helper.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/Helper.java index d383fac69807..a86447ac4856 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/Helper.java @@ -9,6 +9,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; +import org.hibernate.internal.build.AllowSysOut; import org.hibernate.internal.util.BytesHelper; /** @@ -116,6 +117,7 @@ public static String format(short value) { } + @AllowSysOut public static void main(String[] args) throws UnknownHostException { byte[] addressBytes = InetAddress.getLocalHost().getAddress(); System.out.println( "Raw ip address bytes : " + addressBytes.toString() ); diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/LocalObjectUuidHelper.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/LocalObjectUuidHelper.java new file mode 100644 index 000000000000..d3ea0260e674 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/LocalObjectUuidHelper.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.uuid; + +import org.hibernate.type.descriptor.java.UUIDTypeDescriptor; + +/** + * @author Steve Ebersole + */ +public class LocalObjectUuidHelper { + private LocalObjectUuidHelper() { + } + + public static String generateLocalObjectUuid() { + return UUIDTypeDescriptor.ToStringTransformer.INSTANCE.transform( + StandardRandomStrategy.INSTANCE.generateUUID( null ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index bfa7234fe8f1..dca926eb8ce5 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -15,6 +15,7 @@ import java.util.TimeZone; import java.util.UUID; import javax.persistence.FlushModeType; +import javax.persistence.TransactionRequiredException; import javax.persistence.Tuple; import org.hibernate.AssertionFailure; @@ -31,6 +32,7 @@ import org.hibernate.Transaction; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.cache.spi.CacheTransactionSynchronization; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.ResultSetMappingDefinition; @@ -61,6 +63,7 @@ import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.id.uuid.StandardRandomStrategy; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; +import org.hibernate.jpa.spi.NativeQueryTupleTransformer; import org.hibernate.jpa.spi.TupleBuilderTransformer; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.procedure.ProcedureCall; @@ -78,7 +81,6 @@ import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; -import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.type.Type; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -105,7 +107,15 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont private transient SessionFactoryImpl factory; private final String tenantIdentifier; - private final UUID sessionIdentifier; + private UUID sessionIdentifier; + + private transient JdbcConnectionAccess jdbcConnectionAccess; + private transient JdbcSessionContext jdbcSessionContext; + private transient JdbcCoordinator jdbcCoordinator; + + private transient TransactionImplementor currentHibernateTransaction; + private transient TransactionCoordinator transactionCoordinator; + private transient CacheTransactionSynchronization cacheTransactionSync; private final boolean isTransactionCoordinatorShared; private final Interceptor interceptor; @@ -119,26 +129,21 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont protected boolean closed; protected boolean waitingForAutoClose; + private transient boolean disallowOutOfTransactionUpdateOperations; // transient & non-final for Serialization purposes - ugh private transient SessionEventListenerManagerImpl sessionEventsManager = new SessionEventListenerManagerImpl(); private transient EntityNameResolver entityNameResolver; - private transient JdbcConnectionAccess jdbcConnectionAccess; - private transient JdbcSessionContext jdbcSessionContext; - private transient JdbcCoordinator jdbcCoordinator; - private transient TransactionImplementor currentHibernateTransaction; - private transient TransactionCoordinator transactionCoordinator; private transient Boolean useStreamForLobBinding; - private transient long timestamp; - private transient Integer jdbcBatchSize; + private Integer jdbcBatchSize; protected transient ExceptionConverter exceptionConverter; public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) { this.factory = factory; - this.sessionIdentifier = StandardRandomStrategy.INSTANCE.generateUUID( null ); - this.timestamp = factory.getCache().getRegionFactory().nextTimestamp(); + this.cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); + this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); this.flushMode = options.getInitialSessionFlushMode(); @@ -207,6 +212,10 @@ public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreation protected void addSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) { } + protected void removeSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) { + transactionCoordinator.invalidate(); + } + @Override public boolean shouldAutoJoinTransaction() { return autoJoinTransactions; @@ -232,7 +241,6 @@ public SessionFactoryImplementor getFactory() { @Override public Interceptor getInterceptor() { - checkTransactionSynchStatus(); return interceptor; } @@ -262,6 +270,10 @@ public SessionEventListenerManager getEventListenerManager() { @Override public UUID getSessionIdentifier() { + if ( this.sessionIdentifier == null ) { + //Lazily initialized: otherwise all the UUID generations will cause of significant amount of contention. + this.sessionIdentifier = StandardRandomStrategy.INSTANCE.generateUUID( null ); + } return sessionIdentifier; } @@ -270,11 +282,6 @@ public String getTenantIdentifier() { return tenantIdentifier; } - @Override - public long getTimestamp() { - return timestamp; - } - @Override public boolean isOpen() { return !isClosed(); @@ -291,6 +298,18 @@ public void close() { return; } + try { + delayedAfterCompletion(); + } + catch ( HibernateException e ) { + if ( getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { + throw this.exceptionConverter.convert( e ); + } + else { + throw e; + } + } + if ( sessionEventsManager != null ) { sessionEventsManager.end(); } @@ -299,6 +318,10 @@ public void close() { currentHibernateTransaction.invalidate(); } + if ( transactionCoordinator != null ) { + removeSharedSessionTransactionObserver( transactionCoordinator ); + } + try { if ( shouldCloseJdbcCoordinatorOnClose( isTransactionCoordinatorShared ) ) { jdbcCoordinator.close(); @@ -323,10 +346,15 @@ protected void cleanupOnClose() { // nothing to do in base impl, here for SessionImpl hook } + @Override + public boolean isOpenOrWaitingForAutoClose() { + return !isClosed() || waitingForAutoClose; + } + @Override public void checkOpen(boolean markForRollbackIfClosed) { if ( isClosed() ) { - if ( markForRollbackIfClosed ) { + if ( markForRollbackIfClosed && transactionCoordinator.isTransactionActive() ) { markForRollbackOnly(); } throw new IllegalStateException( "Session/EntityManager is closed" ); @@ -337,10 +365,6 @@ protected void checkOpenOrWaitingForAutoClose() { if ( !waitingForAutoClose ) { checkOpen(); } - else if ( factory.isClosed() ) { - markForRollbackOnly(); - throw new IllegalStateException( "SessionFactory is closed" ); - } } /** @@ -353,7 +377,11 @@ protected void errorIfClosed() { @Override public void markForRollbackOnly() { - accessTransaction().markRollbackOnly(); + try { + accessTransaction().markRollbackOnly(); + } + catch (Exception ignore) { + } } @Override @@ -364,28 +392,42 @@ public boolean isTransactionInProgress() { return !isClosed() && transactionCoordinator.isTransactionActive(); } + @Override + public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { + throw new TransactionRequiredException( exceptionMessage ); + } + } + @Override public Transaction getTransaction() throws HibernateException { - if ( getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - // JPA requires that we throw IllegalStateException if this is called - // on a JTA EntityManager - if ( getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() ) { - if ( !getFactory().getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { - throw new IllegalStateException( "A JTA EntityManager cannot use getTransaction()" ); - } - } + if ( !isTransactionAccessible() ) { + throw new IllegalStateException( + "Transaction is not accessible when using JTA with JPA-compliant transaction access enabled" + ); } return accessTransaction(); } + protected boolean isTransactionAccessible() { + // JPA requires that access not be provided to the transaction when using JTA. + // This is overridden when SessionFactoryOptions isJtaTransactionAccessEnabled() is true. + if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() && + getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() && + !getFactory().getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { + return false; + } + return true; + } + @Override public Transaction accessTransaction() { - if ( this.currentHibernateTransaction == null || this.currentHibernateTransaction.getStatus() != TransactionStatus.ACTIVE ) { + if ( this.currentHibernateTransaction == null ) { this.currentHibernateTransaction = new TransactionImpl( getTransactionCoordinator(), - getExceptionConverter() + getExceptionConverter(), + this ); - } if ( !isClosed() || (waitingForAutoClose && factory.isOpen()) ) { getTransactionCoordinator().pulse(); @@ -393,6 +435,31 @@ public Transaction accessTransaction() { return this.currentHibernateTransaction; } + @Override + public void startTransactionBoundary() { + this.getCacheTransactionSynchronization().transactionJoined(); + } + + @Override + public void beforeTransactionCompletion() { + getCacheTransactionSynchronization().transactionCompleting(); + } + + @Override + public void afterTransactionCompletion(boolean successful, boolean delayed) { + getCacheTransactionSynchronization().transactionCompleted( successful ); + } + + @Override + public CacheTransactionSynchronization getCacheTransactionSynchronization() { + return cacheTransactionSync; + } + + @Override + public long getTransactionStartTimestamp() { + return getCacheTransactionSynchronization().getCurrentTransactionStartTimestamp(); + } + @Override public Transaction beginTransaction() { checkOpen(); @@ -400,8 +467,6 @@ public Transaction beginTransaction() { Transaction result = getTransaction(); result.begin(); - this.timestamp = factory.getCache().getRegionFactory().nextTimestamp(); - return result; } @@ -429,7 +494,7 @@ protected TransactionImplementor getCurrentTransaction() { @Override public boolean isConnected() { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return jdbcCoordinator.getLogicalConnection().isOpen(); } @@ -437,7 +502,7 @@ public boolean isConnected() { public JdbcConnectionAccess getJdbcConnectionAccess() { // See class-level JavaDocs for a discussion of the concurrent-access safety of this method if ( jdbcConnectionAccess == null ) { - if ( MultiTenancyStrategy.NONE == factory.getSettings().getMultiTenancyStrategy() ) { + if ( !factory.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider() ) { jdbcConnectionAccess = new NonContextualJdbcConnectionAccess( getEventListenerManager(), factory.getServiceRegistry().getService( ConnectionProvider.class ) @@ -518,6 +583,7 @@ public void setFlushMode(FlushMode flushMode) { @Override public FlushModeType getFlushMode() { + checkOpen(); return FlushModeTypeHelper.getFlushModeType( this.flushMode ); } @@ -552,7 +618,7 @@ protected NativeSQLQueryPlan getNativeQueryPlan(NativeSQLQuerySpecification spec @Override public QueryImplementor getNamedQuery(String name) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); // look as HQL/JPQL first @@ -645,7 +711,7 @@ protected void initQueryFromNamedDefinition(Query query, NamedQueryDefinition nq @Override public QueryImplementor createQuery(String queryString) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -659,6 +725,7 @@ public QueryImplementor createQuery(String queryString) { return query; } catch (RuntimeException e) { + markForRollbackOnly(); throw exceptionConverter.convert( e ); } } @@ -670,7 +737,7 @@ protected void applyQuerySettingsAndHints(Query query) { @SuppressWarnings("unchecked") public QueryImplementor createQuery(String queryString, Class resultClass) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -737,29 +804,32 @@ else if ( queryPlan.getTranslators()[0].getReturnTypes().length == 1 ) { @Override public QueryImplementor createNamedQuery(String name) { - final QueryImplementor query = buildQueryFromName( name, null ); - query.getParameterMetadata().setOrdinalParametersZeroBased( false ); - return query; + return buildQueryFromName( name, null ); } protected QueryImplementor buildQueryFromName(String name, Class resultType) { checkOpen(); - checkTransactionSynchStatus(); - delayedAfterCompletion(); + try { + pulseTransactionCoordinator(); + delayedAfterCompletion(); - // todo : apply stored setting at the JPA Query level too + // todo : apply stored setting at the JPA Query level too - final NamedQueryDefinition namedQueryDefinition = getFactory().getNamedQueryRepository().getNamedQueryDefinition( name ); - if ( namedQueryDefinition != null ) { - return createQuery( namedQueryDefinition, resultType ); - } + final NamedQueryDefinition namedQueryDefinition = getFactory().getNamedQueryRepository().getNamedQueryDefinition( name ); + if ( namedQueryDefinition != null ) { + return createQuery( namedQueryDefinition, resultType ); + } - final NamedSQLQueryDefinition nativeQueryDefinition = getFactory().getNamedQueryRepository().getNamedSQLQueryDefinition( name ); - if ( nativeQueryDefinition != null ) { - return (QueryImplementor) createNativeQuery( nativeQueryDefinition, resultType ); - } + final NamedSQLQueryDefinition nativeQueryDefinition = getFactory().getNamedQueryRepository().getNamedSQLQueryDefinition( name ); + if ( nativeQueryDefinition != null ) { + return (QueryImplementor) createNativeQuery( nativeQueryDefinition, resultType ); + } - throw exceptionConverter.convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) ); + throw exceptionConverter.convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) ); + } + catch (RuntimeException e) { + throw !( e instanceof IllegalArgumentException ) ? new IllegalArgumentException( e ) : e; + } } @SuppressWarnings({"WeakerAccess", "unchecked"}) @@ -773,7 +843,7 @@ protected QueryImplementor createQuery(NamedQueryDefinition namedQueryDef @SuppressWarnings({"WeakerAccess", "unchecked"}) protected NativeQueryImplementor createNativeQuery(NamedSQLQueryDefinition queryDefinition, Class resultType) { - if ( resultType != null ) { + if ( resultType != null && !Tuple.class.equals(resultType)) { resultClassChecking( resultType, queryDefinition ); } @@ -782,6 +852,9 @@ protected NativeQueryImplementor createNativeQuery(NamedSQLQueryDefinition q this, factory.getQueryPlanCache().getSQLParameterMetadata( queryDefinition.getQueryString(), false ) ); + if (Tuple.class.equals(resultType)) { + query.setResultTransformer(new NativeQueryTupleTransformer()); + } query.setHibernateFlushMode( queryDefinition.getFlushMode() ); query.setComment( queryDefinition.getComment() != null ? queryDefinition.getComment() : queryDefinition.getName() ); if ( queryDefinition.getLockOptions() != null ) { @@ -855,20 +928,18 @@ public QueryImplementor createNamedQuery(String name, Class resultClas @Override public NativeQueryImplementor createNativeQuery(String sqlString) { - final NativeQueryImpl query = (NativeQueryImpl) getNativeQueryImplementor( sqlString, false ); - query.setZeroBasedParametersIndex( false ); - return query; + return getNativeQueryImplementor( sqlString, false ); } @Override public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { NativeQueryImplementor query = createNativeQuery( sqlString ); - query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); + handleNativeQueryResult(query, resultClass); return query; } catch ( RuntimeException he ) { @@ -876,10 +947,19 @@ public NativeQueryImplementor createNativeQuery(String sqlString, Class resultCl } } + private void handleNativeQueryResult(NativeQueryImplementor query, Class resultClass) { + if ( Tuple.class.equals( resultClass ) ) { + query.setResultTransformer( new NativeQueryTupleTransformer() ); + } + else { + query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); + } + } + @Override public NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMapping) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -895,7 +975,7 @@ public NativeQueryImplementor createNativeQuery(String sqlString, String resultS @Override public NativeQueryImplementor getNamedNativeQuery(String name) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); final NamedSQLQueryDefinition nativeQueryDefinition = factory.getNamedQueryRepository().getNamedSQLQueryDefinition( name ); @@ -915,7 +995,7 @@ protected NativeQueryImplementor getNativeQueryImplementor( String queryString, boolean isOrdinalParameterZeroBased) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -936,9 +1016,7 @@ protected NativeQueryImplementor getNativeQueryImplementor( @Override public NativeQueryImplementor getNamedSQLQuery(String name) { - final NativeQueryImpl nativeQuery = (NativeQueryImpl) getNamedNativeQuery( name ); - nativeQuery.setZeroBasedParametersIndex( true ); - return nativeQuery; + return getNamedNativeQuery( name ); } @Override @@ -1012,7 +1090,10 @@ public void setJdbcBatchSize(Integer jdbcBatchSize) { @SuppressWarnings("unused") private void writeObject(ObjectOutputStream oos) throws IOException { - log.trace( "Serializing " + getClass().getSimpleName() + " [" ); + if ( log.isTraceEnabled() ) { + log.trace( "Serializing " + getClass().getSimpleName() + " [" ); + } + if ( !jdbcCoordinator.isReadyForSerialization() ) { // throw a more specific (helpful) exception message when this happens from Session, @@ -1043,7 +1124,9 @@ private void writeObject(ObjectOutputStream oos) throws IOException { } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException, SQLException { - log.trace( "Deserializing " + getClass().getSimpleName() ); + if ( log.isTraceEnabled() ) { + log.trace( "Deserializing " + getClass().getSimpleName() ); + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Step 1 :: read back non-transient state... @@ -1058,10 +1141,15 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject() ); jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this ); - this.transactionCoordinator = factory.getServiceRegistry() + cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); + + transactionCoordinator = factory.getServiceRegistry() .getService( TransactionCoordinatorBuilder.class ) .buildTransactionCoordinator( jdbcCoordinator, this ); entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor ); + exceptionConverter = new ExceptionConverterImpl( this ); + this.disallowOutOfTransactionUpdateOperations = !getFactory().getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CacheImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/CacheImpl.java deleted file mode 100644 index ed32eb909539..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/internal/CacheImpl.java +++ /dev/null @@ -1,507 +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.internal; - -import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import javax.persistence.PersistenceException; - -import org.hibernate.HibernateException; -import org.hibernate.SessionFactory; -import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cache.internal.CacheDataDescriptionImpl; -import org.hibernate.cache.internal.StandardQueryCache; -import org.hibernate.cache.spi.CollectionRegion; -import org.hibernate.cache.spi.EntityRegion; -import org.hibernate.cache.spi.NaturalIdRegion; -import org.hibernate.cache.spi.QueryCache; -import org.hibernate.cache.spi.QueryResultsRegion; -import org.hibernate.cache.spi.Region; -import org.hibernate.cache.spi.RegionFactory; -import org.hibernate.cache.spi.TimestampsRegion; -import org.hibernate.cache.spi.UpdateTimestampsCache; -import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; -import org.hibernate.engine.spi.CacheImplementor; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.mapping.Collection; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.persister.collection.CollectionPersister; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.pretty.MessageHelper; - -/** - * @author Steve Ebersole - * @author Strong Liu - */ -public class CacheImpl implements CacheImplementor { - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( CacheImpl.class ); - - private final SessionFactoryImplementor sessionFactory; - private final SessionFactoryOptions settings; - private final transient RegionFactory regionFactory; - private final String cacheRegionPrefix; - - private final transient ConcurrentHashMap allRegionsMap = new ConcurrentHashMap<>(); - - private final transient ConcurrentHashMap entityRegionAccessStrategyMap = new ConcurrentHashMap<>(); - private final transient ConcurrentHashMap collectionRegionAccessStrategyMap = new ConcurrentHashMap<>(); - private final transient ConcurrentHashMap naturalIdRegionAccessStrategyMap = new ConcurrentHashMap<>(); - - private final transient UpdateTimestampsCache updateTimestampsCache; - private final transient QueryCache defaultQueryCache; - private final transient ConcurrentMap queryCaches; - - public CacheImpl(SessionFactoryImplementor sessionFactory) { - this.sessionFactory = sessionFactory; - this.settings = sessionFactory.getSessionFactoryOptions(); - this.regionFactory = settings.getServiceRegistry().getService( RegionFactory.class ); - this.regionFactory.start( settings, sessionFactory.getProperties() ); - - this.cacheRegionPrefix = StringHelper.isEmpty( sessionFactory.getSessionFactoryOptions().getCacheRegionPrefix() ) - ? "" - : sessionFactory.getSessionFactoryOptions().getCacheRegionPrefix() + "."; - - if ( settings.isQueryCacheEnabled() ) { - final TimestampsRegion timestampsRegion = regionFactory.buildTimestampsRegion( - qualifyRegionName( UpdateTimestampsCache.REGION_NAME ), - sessionFactory.getProperties() - ); - updateTimestampsCache = new UpdateTimestampsCache( sessionFactory, timestampsRegion ); - final QueryResultsRegion queryResultsRegion = regionFactory.buildQueryResultsRegion( - StandardQueryCache.class.getName(), - sessionFactory.getProperties() - ); - defaultQueryCache = settings.getQueryCacheFactory().buildQueryCache( queryResultsRegion, this ); - queryCaches = new ConcurrentHashMap<>(); - } - else { - updateTimestampsCache = null; - defaultQueryCache = null; - queryCaches = null; - } - } - - @Override - public SessionFactory getSessionFactory() { - return sessionFactory; - } - - @Override - public RegionFactory getRegionFactory() { - return regionFactory; - } - - @Override - public String qualifyRegionName(String regionName) { - return StringHelper.isEmpty( regionName ) - ? null - : cacheRegionPrefix + regionName; - } - - @Override - public boolean containsEntity(Class entityClass, Serializable identifier) { - return containsEntity( entityClass.getName(), identifier ); - } - - @Override - public boolean containsEntity(String entityName, Serializable identifier) { - EntityPersister p = sessionFactory.getMetamodel().entityPersister( entityName ); - if ( p.hasCache() ) { - EntityRegionAccessStrategy cache = p.getCacheAccessStrategy(); - Object key = cache.generateCacheKey( identifier, p, sessionFactory, null ); // have to assume non tenancy - return cache.getRegion().contains( key ); - } - else { - return false; - } - } - - @Override - public void evictEntity(Class entityClass, Serializable identifier) { - evictEntity( entityClass.getName(), identifier ); - } - - @Override - public void evictEntity(String entityName, Serializable identifier) { - EntityPersister p = sessionFactory.getMetamodel().entityPersister( entityName ); - if ( p.hasCache() ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( - "Evicting second-level cache: %s", - MessageHelper.infoString( p, identifier, sessionFactory ) - ); - } - EntityRegionAccessStrategy cache = p.getCacheAccessStrategy(); - Object key = cache.generateCacheKey( identifier, p, sessionFactory, null ); // have to assume non tenancy - cache.evict( key ); - } - } - - @Override - public void evictEntityRegion(Class entityClass) { - evictEntityRegion( entityClass.getName() ); - } - - @Override - public void evictEntityRegion(String entityName) { - EntityPersister p = sessionFactory.getMetamodel().entityPersister( entityName ); - if ( p.hasCache() ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( "Evicting second-level cache: %s", p.getEntityName() ); - } - p.getCacheAccessStrategy().evictAll(); - } - } - - @Override - public void evictEntityRegions() { - sessionFactory.getMetamodel().entityPersisters().keySet().forEach( this::evictEntityRegion ); - } - - @Override - public void evictNaturalIdRegion(Class entityClass) { - evictNaturalIdRegion( entityClass.getName() ); - } - - @Override - public void evictNaturalIdRegion(String entityName) { - EntityPersister p = sessionFactory.getMetamodel().entityPersister( entityName ); - if ( p.hasNaturalIdCache() ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( "Evicting natural-id cache: %s", p.getEntityName() ); - } - p.getNaturalIdCacheAccessStrategy().evictAll(); - } - } - - @Override - public void evictNaturalIdRegions() { - sessionFactory.getMetamodel().entityPersisters().keySet().forEach( this::evictNaturalIdRegion ); - } - - @Override - public boolean containsCollection(String role, Serializable ownerIdentifier) { - CollectionPersister p = sessionFactory.getMetamodel().collectionPersister( role ); - if ( p.hasCache() ) { - CollectionRegionAccessStrategy cache = p.getCacheAccessStrategy(); - Object key = cache.generateCacheKey( ownerIdentifier, p, sessionFactory, null ); // have to assume non tenancy - return cache.getRegion().contains( key ); - } - else { - return false; - } - } - - @Override - public void evictCollection(String role, Serializable ownerIdentifier) { - CollectionPersister p = sessionFactory.getMetamodel().collectionPersister( role ); - if ( p.hasCache() ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( - "Evicting second-level cache: %s", - MessageHelper.collectionInfoString( p, ownerIdentifier, sessionFactory ) - ); - } - CollectionRegionAccessStrategy cache = p.getCacheAccessStrategy(); - Object key = cache.generateCacheKey( ownerIdentifier, p, sessionFactory, null ); // have to assume non tenancy - cache.evict( key ); - } - } - - @Override - public void evictCollectionRegion(String role) { - CollectionPersister p = sessionFactory.getMetamodel().collectionPersister( role ); - if ( p.hasCache() ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( "Evicting second-level cache: %s", p.getRole() ); - } - p.getCacheAccessStrategy().evictAll(); - } - } - - @Override - public void evictCollectionRegions() { - sessionFactory.getMetamodel().collectionPersisters().keySet().forEach( this::evictCollectionRegion ); - } - - @Override - public boolean containsQuery(String regionName) { - return sessionFactory.getSessionFactoryOptions().isQueryCacheEnabled() - && queryCaches.containsKey( regionName ); - } - - @Override - public void evictDefaultQueryRegion() { - if ( sessionFactory.getSessionFactoryOptions().isQueryCacheEnabled() ) { - if ( LOG.isDebugEnabled() ) { - LOG.debug( "Evicting default query region cache." ); - } - getDefaultQueryCache().clear(); - } - } - - @Override - public void evictQueryRegion(String regionName) { - if ( regionName == null ) { - throw new NullPointerException( - "Region-name cannot be null (use Cache#evictDefaultQueryRegion to evict the default query cache)" - ); - } - if ( sessionFactory.getSessionFactoryOptions().isQueryCacheEnabled() ) { - QueryCache namedQueryCache = queryCaches.get( regionName ); - // TODO : cleanup entries in queryCaches + allCacheRegions ? - if ( namedQueryCache != null ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( "Evicting query cache, region: %s", regionName ); - } - namedQueryCache.clear(); - } - } - } - - @Override - public void evictQueryRegions() { - evictDefaultQueryRegion(); - - if ( CollectionHelper.isEmpty( queryCaches ) ) { - return; - } - if ( LOG.isDebugEnabled() ) { - LOG.debug( "Evicting cache of all query regions." ); - } - - queryCaches.values().forEach( QueryCache::clear ); - } - - @Override - public void close() { - for ( EntityRegionAccessStrategy access : entityRegionAccessStrategyMap.values() ) { - access.getRegion().destroy(); - } - - for ( CollectionRegionAccessStrategy access : collectionRegionAccessStrategyMap.values() ) { - access.getRegion().destroy(); - } - - if ( settings.isQueryCacheEnabled() ) { - defaultQueryCache.destroy(); - - for ( QueryCache cache : queryCaches.values() ) { - cache.destroy(); - } - updateTimestampsCache.destroy(); - } - - regionFactory.stop(); - } - - @Override - public QueryCache getDefaultQueryCache() { - return defaultQueryCache; - } - - @Override - public QueryCache getQueryCache(String regionName) throws HibernateException { - if ( !settings.isQueryCacheEnabled() ) { - return null; - } - - if ( regionName == null ) { - return getDefaultQueryCache(); - } - - QueryCache queryCache = queryCaches.get( regionName ); - if ( queryCache == null ) { - synchronized (queryCaches) { - queryCache = queryCaches.get( regionName ); - if ( queryCache == null ) { - final QueryResultsRegion region = regionFactory.buildQueryResultsRegion( - qualifyRegionName( regionName ), - sessionFactory.getProperties() - ); - - queryCache = settings.getQueryCacheFactory().buildQueryCache( region, this ); - queryCaches.put( regionName, queryCache ); - } - } - } - return queryCache; - } - - @Override - public UpdateTimestampsCache getUpdateTimestampsCache() { - return updateTimestampsCache; - } - - @Override - public void evictQueries() throws HibernateException { - if ( settings.isQueryCacheEnabled() ) { - defaultQueryCache.clear(); - } - } - - @Override - public String[] getSecondLevelCacheRegionNames() { - final Set names = new HashSet<>(); - names.addAll( entityRegionAccessStrategyMap.keySet() ); - names.addAll( collectionRegionAccessStrategyMap.keySet() ); - names.addAll( naturalIdRegionAccessStrategyMap.keySet() ); - if ( settings.isQueryCacheEnabled() ) { - names.add( updateTimestampsCache.getRegion().getName() ); - names.addAll( queryCaches.keySet() ); - } - return ArrayHelper.toStringArray( names ); - } - - @Override - public EntityRegionAccessStrategy getEntityRegionAccess(String regionName) { - return entityRegionAccessStrategyMap.get( regionName ); - } - - @Override - public CollectionRegionAccessStrategy getCollectionRegionAccess(String regionName) { - return collectionRegionAccessStrategyMap.get( regionName ); - } - - @Override - public NaturalIdRegionAccessStrategy getNaturalIdCacheRegionAccessStrategy(String regionName) { - return naturalIdRegionAccessStrategyMap.get( regionName ); - } - - @Override - public void evictAllRegions() { - evictCollectionRegions(); - evictDefaultQueryRegion(); - evictEntityRegions(); - evictQueryRegions(); - evictNaturalIdRegions(); - } - - @Override - public boolean contains(Class cls, Object primaryKey) { - return containsEntity( cls, (Serializable) primaryKey ); - } - - @Override - public void evict(Class cls, Object primaryKey) { - evictEntity( cls, (Serializable) primaryKey ); - } - - @Override - public void evict(Class cls) { - evictEntityRegion( cls ); - } - - @Override - public void evictAll() { - // Evict only the "JPA cache", which is purely defined as the entity regions. - evictEntityRegions(); - // TODO : if we want to allow an optional clearing of all cache data, the additional calls would be: -// evictCollectionRegions(); -// evictQueryRegions(); - } - - @Override - @SuppressWarnings("unchecked") - public T unwrap(Class cls) { - if ( org.hibernate.Cache.class.isAssignableFrom( cls ) ) { - return (T) this; - } - - if ( RegionFactory.class.isAssignableFrom( cls ) ) { - return (T) regionFactory; - } - - throw new PersistenceException( "Hibernate cannot unwrap Cache as " + cls.getName() ); - } - - @Override - public EntityRegionAccessStrategy determineEntityRegionAccessStrategy(PersistentClass model) { - final String cacheRegionName = cacheRegionPrefix + model.getRootClass().getCacheRegionName(); - EntityRegionAccessStrategy accessStrategy = entityRegionAccessStrategyMap.get( cacheRegionName ); - if ( accessStrategy == null && settings.isSecondLevelCacheEnabled() ) { - final AccessType accessType = AccessType.fromExternalName( model.getCacheConcurrencyStrategy() ); - if ( accessType != null ) { - LOG.tracef( "Building shared cache region for entity data [%s]", model.getEntityName() ); - EntityRegion entityRegion = regionFactory.buildEntityRegion( - cacheRegionName, - sessionFactory.getProperties(), - CacheDataDescriptionImpl.decode( model ) - ); - accessStrategy = entityRegion.buildAccessStrategy( accessType ); - entityRegionAccessStrategyMap.put( cacheRegionName, accessStrategy ); - } - } - return accessStrategy; - } - - - @Override - public NaturalIdRegionAccessStrategy determineNaturalIdRegionAccessStrategy(PersistentClass model) { - NaturalIdRegionAccessStrategy naturalIdAccessStrategy = null; - if ( model.hasNaturalId() && model.getNaturalIdCacheRegionName() != null ) { - final String naturalIdCacheRegionName = cacheRegionPrefix + model.getNaturalIdCacheRegionName(); - naturalIdAccessStrategy = naturalIdRegionAccessStrategyMap.get( naturalIdCacheRegionName ); - - if ( naturalIdAccessStrategy == null && settings.isSecondLevelCacheEnabled() ) { - final CacheDataDescriptionImpl cacheDataDescription = CacheDataDescriptionImpl.decode( model ); - - NaturalIdRegion naturalIdRegion = null; - try { - naturalIdRegion = regionFactory.buildNaturalIdRegion( - naturalIdCacheRegionName, - sessionFactory.getProperties(), - cacheDataDescription - ); - } - catch ( UnsupportedOperationException e ) { - LOG.warnf( - "Shared cache region factory [%s] does not support natural id caching; " + - "shared NaturalId caching will be disabled for not be enabled for %s", - regionFactory.getClass().getName(), - model.getEntityName() - ); - } - - if (naturalIdRegion != null) { - naturalIdAccessStrategy = naturalIdRegion.buildAccessStrategy( regionFactory.getDefaultAccessType() ); - naturalIdRegionAccessStrategyMap.put( naturalIdCacheRegionName, naturalIdAccessStrategy ); - } - } - } - return naturalIdAccessStrategy; - } - - @Override - public CollectionRegionAccessStrategy determineCollectionRegionAccessStrategy(Collection model) { - final String cacheRegionName = cacheRegionPrefix + model.getCacheRegionName(); - CollectionRegionAccessStrategy accessStrategy = collectionRegionAccessStrategyMap.get( cacheRegionName ); - if ( accessStrategy == null && settings.isSecondLevelCacheEnabled()) { - final AccessType accessType = AccessType.fromExternalName(model.getCacheConcurrencyStrategy()); - if (accessType != null) { - LOG.tracev("Building shared cache region for collection data [{0}]", model.getRole()); - CollectionRegion collectionRegion = regionFactory.buildCollectionRegion( - cacheRegionName, - sessionFactory.getProperties(), - CacheDataDescriptionImpl.decode( model) - ); - accessStrategy = collectionRegion.buildAccessStrategy( accessType ); - collectionRegionAccessStrategyMap.put( cacheRegionName, accessStrategy ); - } - } - return accessStrategy; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java b/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java index 9f91b9c549fb..3873444cf3a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java @@ -25,7 +25,7 @@ public ConnectionObserverStatsBridge(SessionFactoryImplementor sessionFactory) { @Override public void physicalConnectionObtained(Connection connection) { if ( sessionFactory.getStatistics().isStatisticsEnabled() ) { - sessionFactory.getStatisticsImplementor().connect(); + sessionFactory.getStatistics().connect(); } } @@ -40,7 +40,7 @@ public void logicalConnectionClosed() { @Override public void statementPrepared() { if ( sessionFactory.getStatistics().isStatisticsEnabled() ) { - sessionFactory.getStatisticsImplementor().prepareStatement(); + sessionFactory.getStatistics().prepareStatement(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 3a0e89167473..d9a68fcbfc3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -24,6 +24,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.cache.CacheException; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; import org.hibernate.engine.jndi.JndiException; @@ -42,6 +43,7 @@ import org.jboss.logging.annotations.LogMessage; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageLogger; +import org.jboss.logging.annotations.ValidIdRange; import static org.jboss.logging.Logger.Level.DEBUG; import static org.jboss.logging.Logger.Level.ERROR; @@ -52,9 +54,10 @@ * The jboss-logging {@link MessageLogger} for the hibernate-core module. It reserves message ids ranging from * 00001 to 10000 inclusively. *

    - * New messages must be added afterQuery the last message defined to ensure message codes are unique. + * New messages must be added after the last message defined to ensure message codes are unique. */ @MessageLogger(projectCode = "HHH") +@ValidIdRange( min = 1, max = 10000 ) public interface CoreMessageLogger extends BasicLogger { @LogMessage(level = WARN) @@ -340,12 +343,6 @@ void expectedType(String name, @Message(value = "Found mapping document in jar: %s", id = 109) void foundMappingDocument(String name); - @LogMessage(level = ERROR) - @Message(value = "Getters of lazy classes cannot be final: %s.%s", id = 112) - void gettersOfLazyClassesCannotBeFinal( - String entityName, - String name); - @LogMessage(level = WARN) @Message(value = "GUID identifier generated: %s", id = 113) void guidGenerated(String result); @@ -359,11 +356,14 @@ void gettersOfLazyClassesCannotBeFinal( void hibernateConnectionPoolSize(int poolSize, int minSize); @LogMessage(level = WARN) - @Message(value = "Config specified explicit optimizer of [%s], but [%s=%s; honoring optimizer setting", id = 116) + @Message(value = "Config specified explicit optimizer of [%s], but [%s=%s]; using optimizer [%s] increment default of [%s].", id = 116) void honoringOptimizerSetting( String none, String incrementParam, - int incrementSize); + int incrementSize, + String positiveOrNegative, + int defaultIncrementSize + ); @LogMessage(level = DEBUG) @Message(value = "HQL: %s, time: %sms, rows: %s", id = 117) @@ -444,7 +444,7 @@ void invalidJndiName( void invalidOnDeleteAnnotation(String entityName); @LogMessage(level = WARN) - @Message(value = "Root entity should not hold an PrimaryKeyJoinColum(s), will be ignored: %s", id = 137) + @Message(value = "Root entity should not hold a PrimaryKeyJoinColum(s), will be ignored: %s", id = 137) void invalidPrimaryKeyJoinColumnAnnotation(String className); @LogMessage(level = WARN) @@ -735,7 +735,7 @@ void renamedProperty( void schemaUpdateComplete(); @LogMessage(level = WARN) - @Message(value = "Scoping types to session factory %s afterQuery already scoped %s", id = 233) + @Message(value = "Scoping types to session factory %s after already scoped %s", id = 233) void scopingTypesToSessionFactoryAfterAlreadyScoped( SessionFactoryImplementor factory, SessionFactoryImplementor factory2); @@ -768,12 +768,6 @@ void scopingTypesToSessionFactoryAfterAlreadyScoped( @Message(value = "Sessions opened: %s", id = 242) void sessionsOpened(long sessionOpenCount); - @LogMessage(level = ERROR) - @Message(value = "Setters of lazy classes cannot be final: %s.%s", id = 243) - void settersOfLazyClassesCannotBeFinal( - String entityName, - String name); - @LogMessage(level = WARN) @Message(value = "@Sort not allowed for an indexed collection, annotation ignored.", id = 244) void sortAnnotationIndexedCollection(); @@ -927,7 +921,7 @@ void unableToBindValueToParameter( void unableToCleanUpPreparedStatement(@Cause SQLException e); @LogMessage(level = WARN) - @Message(value = "Unable to cleanup temporary id table afterQuery use [%s]", id = 283) + @Message(value = "Unable to cleanup temporary id table after use [%s]", id = 283) void unableToCleanupTemporaryIdTable(Throwable t); @LogMessage(level = ERROR) @@ -1058,11 +1052,11 @@ void unableToDetermineLockModeValue( @Message(value = "Could not determine transaction status", id = 312) String unableToDetermineTransactionStatus(); - @Message(value = "Could not determine transaction status afterQuery commit", id = 313) + @Message(value = "Could not determine transaction status after commit", id = 313) String unableToDetermineTransactionStatusAfterCommit(); @LogMessage(level = WARN) - @Message(value = "Unable to drop temporary id table afterQuery use [%s]", id = 314) + @Message(value = "Unable to evictData temporary id table after use [%s]", id = 314) void unableToDropTemporaryIdTable(String message); @LogMessage(level = ERROR) @@ -1300,7 +1294,7 @@ void unableToStopService( void unableToUnbindFactoryFromJndi(@Cause JndiException e); @Message(value = "Could not update hi value in: %s", id = 375) - Object unableToUpdateHiValue(String tableName); + String unableToUpdateHiValue(String tableName); @LogMessage(level = ERROR) @Message(value = "Could not updateQuery hi value in: %s", id = 376) @@ -1429,10 +1423,6 @@ void usingDefaultIdGeneratorSegmentValue( @Message(value = "Using java.io streams to persist binary types", id = 407) void usingStreams(); - @LogMessage(level = INFO) - @Message(value = "Using workaround for JVM bug in java.sql.Timestamp", id = 408) - void usingTimestampWorkaround(); - @LogMessage(level = WARN) @Message(value = "Using %s which does not generate IETF RFC 4122 compliant UUID values; consider using %s instead", id = 409) @@ -1597,7 +1587,7 @@ void cannotResolveNonNullableTransientDependencies( @LogMessage(level = WARN) @Message( value = "Encountered request for locking however dialect reports that database prefers locking be done in a " + - "separate select (follow-on locking); results will be locked afterQuery initial query executes", + "separate select (follow-on locking); results will be locked after initial query executes", id = 444 ) void usingFollowOnLocking(); @@ -1738,7 +1728,7 @@ void cannotResolveNonNullableTransientDependencies( void executingImportScript(String scriptName); @LogMessage(level = INFO) - @Message(value = "Starting delayed drop of schema as part of SessionFactory shut-down'", id = 477) + @Message(value = "Starting delayed evictData of schema as part of SessionFactory shut-down'", id = 477) void startingDelayedSchemaDrop(); @LogMessage(level = ERROR) @@ -1766,4 +1756,70 @@ void cannotResolveNonNullableTransientDependencies( "implementing equals/hashCode." ) void unknownJavaTypeNoEqualsHashCode(Class javaType); + + @LogMessage(level = WARN) + @Message(value = "@org.hibernate.annotations.Cache used on a non-root entity: ignored for [%s]. Please see the Hibernate documentation for proper usage.", id = 482) + void cacheOrCacheableAnnotationOnNonRoot(String className); + + @LogMessage(level = WARN) + @Message( + id = 483, + value = "An experimental feature has been enabled (" + + AvailableSettings.CREATE_EMPTY_COMPOSITES_ENABLED + + "=true) that instantiates empty composite/embedded " + + "objects when all of its attribute values are null. This feature has known issues and " + + "should not be used in production until it is stabilized. See Hibernate Jira " + + "issue HHH-11936 for details." + ) + void emptyCompositesEnabled(); + + @LogMessage(level = WARN) + @Message(value = "Vibur properties were encountered, but the Vibur ConnectionProvider was not found on the classpath; these properties are going to be ignored.", + id = 484) + void viburProviderClassNotFound(); + + @LogMessage(level = ERROR) + @Message(value = "Illegally attempted to associate a proxy for entity [%s] with id [%s] with two open sessions.", id = 485) + void attemptToAssociateProxyWithTwoOpenSessions( + String entityName, + Object id + ); + + @LogMessage(level = WARN) + @Message(value = "Agroal properties were encountered, but the Agroal ConnectionProvider was not found on the classpath; these properties are going to be ignored.", + id = 486) + void agroalProviderClassNotFound(); + + @LogMessage(level = WARN) + @Message(value = "The query: [%s] attempts to update an immutable entity: %s", + id = 487) + void immutableEntityUpdateQuery(String sourceQuery, String querySpaces); + + @Message(value = "Bytecode enhancement failed for class: %1$s. It might be due to the Java module system preventing Hibernate ORM from defining an enhanced class " + + "in the same package as class %1$s. In this case, the class should be opened and exported to Hibernate ORM.", id = 488) + String bytecodeEnhancementFailedUnableToGetPrivateLookupFor(String className); + + @LogMessage(level = WARN) + @Message(value = "Setting " + AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE + "=true is not valid with JPA bootstrapping; setting will be ignored.", id = 489 ) + void nativeExceptionHandling51ComplianceJpaBootstrapping(); + + @LogMessage(level = WARN) + @Message(value = "Attempt to merge an uninitialized collection with queued operations; queued operations will be ignored: %s", id = 494) + void ignoreQueuedOperationsOnMerge(String collectionInfoString); + + @LogMessage(level = WARN) + @Message(value = "Attaching an uninitialized collection with queued operations to a session: %s", id = 495) + void queuedOperationWhenAttachToSession(String collectionInfoString); + + @LogMessage(level = INFO) + @Message(value = "Detaching an uninitialized collection with queued operations from a session: %s", id = 496) + void queuedOperationWhenDetachFromSession(String collectionInfoString); + + @LogMessage(level = DEBUG) + @Message(value = "Detaching an uninitialized collection with queued operations from a session due to rollback: %s", id = 498) + void queuedOperationWhenDetachFromSessionOnRollback(String collectionInfoString); + + @LogMessage(level = WARN) + @Message(value = "Using @AttributeOverride or @AttributeOverrides in conjunction with entity inheritance is not supported: %s. The overriding definitions are ignored.", id = 499) + void unsupportedAttributeOverrideWithEntityInheritance(String entityName); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CriteriaImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/CriteriaImpl.java index d6bae42397f5..60f74545a302 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CriteriaImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CriteriaImpl.java @@ -166,6 +166,10 @@ public FetchMode getFetchMode(String path) { } @Override public Criteria setFetchMode(String associationPath, FetchMode mode) { + String rootAliasPathPrefix = rootAlias + "."; + if (rootAlias != null && !associationPath.startsWith(rootAliasPathPrefix)) { + associationPath = rootAliasPathPrefix + associationPath; + } fetchModes.put( associationPath, mode ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java index b506780c55eb..085299a517e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java @@ -23,7 +23,7 @@ * The jboss-logging {@link MessageLogger} for the hibernate-entitymanager module. It reserves message ids ranging from * 15001 to 20000 inclusively. *

    - * New messages must be added afterQuery the last message defined to ensure message codes are unique. + * New messages must be added after the last message defined to ensure message codes are unique. */ @MessageLogger(projectCode = "HHH") public interface EntityManagerMessageLogger extends CoreMessageLogger { @@ -114,7 +114,7 @@ void unableToLocateStaticMetamodelField( @LogMessage(level = WARN) @Message( id = 15016, - value = "Encountered a deprecated javax.persistence.spi.PersistenceProvider [%s]; use [%s] instead." + value = "Encountered a deprecated javax.persistence.spi.PersistenceProvider [%s]; [%s] will be used instead." ) void deprecatedPersistenceProvider(String deprecated, String replacement); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java index 5b957bac7041..ae00fb791676 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java @@ -32,6 +32,8 @@ import org.hibernate.dialect.lock.OptimisticEntityLockException; import org.hibernate.dialect.lock.PessimisticEntityLockException; import org.hibernate.engine.spi.ExceptionConverter; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.exception.LockAcquisitionException; import org.hibernate.loader.MultipleBagFetchException; /** @@ -40,17 +42,24 @@ public class ExceptionConverterImpl implements ExceptionConverter { private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( ExceptionConverterImpl.class ); - private final AbstractSharedSessionContract sharedSessionContract; + private final SharedSessionContractImplementor sharedSessionContract; + private final boolean isJpaBootstrap; + private final boolean nativeExceptionHandling51Compliance; - public ExceptionConverterImpl(AbstractSharedSessionContract sharedSessionContract) { + public ExceptionConverterImpl(SharedSessionContractImplementor sharedSessionContract) { this.sharedSessionContract = sharedSessionContract; + isJpaBootstrap = sharedSessionContract.getFactory().getSessionFactoryOptions().isJpaBootstrap(); + nativeExceptionHandling51Compliance = sharedSessionContract.getFactory().getSessionFactoryOptions().nativeExceptionHandling51Compliance(); } @Override public RuntimeException convertCommitException(RuntimeException e) { - if ( sharedSessionContract.getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { + if ( isJpaBootstrap ) { Throwable wrappedException; - if ( e instanceof PersistenceException ) { + if ( e instanceof HibernateException ) { + wrappedException = convert( (HibernateException) e ); + } + else if ( e instanceof PersistenceException ) { Throwable cause = e.getCause() == null ? e : e.getCause(); if ( cause instanceof HibernateException ) { wrappedException = convert( (HibernateException) cause ); @@ -59,9 +68,6 @@ public RuntimeException convertCommitException(RuntimeException e) { wrappedException = cause; } } - else if ( e instanceof HibernateException ) { - wrappedException = convert( (HibernateException) e ); - } else { wrappedException = e; } @@ -81,72 +87,92 @@ else if ( e instanceof HibernateException ) { @Override public RuntimeException convert(HibernateException e, LockOptions lockOptions) { - Throwable cause = e; - if ( cause instanceof StaleStateException ) { - final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof LockingStrategyException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.exception.LockTimeoutException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.PessimisticLockException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.QueryTimeoutException ) { - final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof ObjectNotFoundException ) { - final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.NonUniqueObjectException ) { - final EntityExistsException converted = new EntityExistsException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.NonUniqueResultException ) { - final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof UnresolvableObjectException ) { - final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof QueryException ) { - return new IllegalArgumentException( cause ); - } - else if ( cause instanceof MultipleBagFetchException ) { - return new IllegalArgumentException( cause ); - } - else if ( cause instanceof TransientObjectException ) { - try { - sharedSessionContract.markForRollbackOnly(); + if ( !nativeExceptionHandling51Compliance ) { + Throwable cause = e; + if ( cause instanceof StaleStateException ) { + final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof LockAcquisitionException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof LockingStrategyException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.PessimisticLockException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; } - catch (Exception ne) { - //we do not want the subsequent exception to swallow the original one - log.unableToMarkForRollbackOnTransientObjectException( ne ); + else if ( cause instanceof org.hibernate.QueryTimeoutException ) { + final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof ObjectNotFoundException ) { + final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.NonUniqueObjectException ) { + final EntityExistsException converted = new EntityExistsException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.NonUniqueResultException ) { + final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof UnresolvableObjectException ) { + final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof QueryException ) { + return new IllegalArgumentException( cause ); + } + else if ( cause instanceof MultipleBagFetchException ) { + return new IllegalArgumentException( cause ); + } + else if ( cause instanceof TransientObjectException ) { + try { + sharedSessionContract.markForRollbackOnly(); + } + catch (Exception ne) { + //we do not want the subsequent exception to swallow the original one + log.unableToMarkForRollbackOnTransientObjectException( ne ); + } + return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules + } + else { + final PersistenceException converted = new PersistenceException( cause ); + handlePersistenceException( converted ); + return converted; } - return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules } else { - final PersistenceException converted = new PersistenceException( cause ); - handlePersistenceException( converted ); - return converted; + if ( e instanceof QueryException ) { + return e; + } + else if ( e instanceof MultipleBagFetchException ) { + return e; + } + else { + try { + sharedSessionContract.markForRollbackOnly(); + } + catch (Exception ne) { + //we do not want the subsequent exception to swallow the original one + log.unableToMarkForRollbackOnTransientObjectException( ne ); + } + return e; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java index 292a4e6ea4e4..182b80e854fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java @@ -72,7 +72,7 @@ public boolean next() { getResultSet(), getSession(), getQueryParameters(), - false + true ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java index 8ae733f23354..f027f8a9759d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java @@ -14,6 +14,8 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * Implementation of FilterHelper. * @@ -44,23 +46,27 @@ public FilterHelper(List filters, SessionFactoryImplementor filterCount = 0; for ( final FilterConfiguration filter : filters ) { filterAutoAliasFlags[filterCount] = false; - filterNames[filterCount] = filter.getName(); - filterConditions[filterCount] = filter.getCondition(); + filterNames[filterCount] = safeInterning( filter.getName() ); + filterConditions[filterCount] = safeInterning( filter.getCondition() ); filterAliasTableMaps[filterCount] = filter.getAliasTableMap( factory ); if ( ( filterAliasTableMaps[filterCount].isEmpty() || isTableFromPersistentClass( filterAliasTableMaps[filterCount] ) ) && filter .useAutoAliasInjection() ) { - filterConditions[filterCount] = Template.renderWhereStringTemplate( - filter.getCondition(), - FilterImpl.MARKER, - factory.getDialect(), - factory.getSqlFunctionRegistry() + filterConditions[filterCount] = safeInterning( + Template.renderWhereStringTemplate( + filter.getCondition(), + FilterImpl.MARKER, + factory.getDialect(), + factory.getSqlFunctionRegistry() + ) ); filterAutoAliasFlags[filterCount] = true; } - filterConditions[filterCount] = StringHelper.replace( - filterConditions[filterCount], - ":", - ":" + filterNames[filterCount] + "." + filterConditions[filterCount] = safeInterning( + StringHelper.replace( + filterConditions[filterCount], + ":", + ":" + filterNames[filterCount] + "." + ) ); filterCount++; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java index db11f4b8c13b..04f34fbf877c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java @@ -137,7 +137,7 @@ public Object getParameter(String name) { /** * Perform validation of the filter state. This is used to verify the - * state of the filter afterQuery its enablement and beforeQuery its use. + * state of the filter after its enablement and before its use. * * @throws HibernateException If the state is not currently valid. */ diff --git a/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java index 3820da6b50f7..92eba0050f0c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java @@ -141,7 +141,7 @@ public void remove() { throw new UnsupportedOperationException( "Not a single column hibernate query result set" ); } if ( currentResult == null ) { - throw new IllegalStateException( "Called Iterator.remove() beforeQuery next()" ); + throw new IllegalStateException( "Called Iterator.remove() before next()" ); } if ( !( types[0] instanceof EntityType ) ) { throw new UnsupportedOperationException( "Not an entity" ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java index 1cfedab6cd87..ef02f759a4a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java @@ -83,4 +83,14 @@ public void jdbcExecuteBatchStart() { public void jdbcExecuteBatchEnd() { session.getEventListenerManager().jdbcExecuteBatchEnd(); } + + @Override + public void jdbcReleaseRegistryResourcesStart() { + session.getJdbcCoordinator().abortBatch(); + } + + @Override + public void jdbcReleaseRegistryResourcesEnd() { + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java index 0af612615f77..da6c9f3ca753 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java @@ -60,6 +60,11 @@ public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() { return connectionHandlingMode; } + @Override + public boolean doesConnectionProviderDisableAutoCommit() { + return settings().doesConnectionProviderDisableAutoCommit(); + } + @Override public ConnectionReleaseMode getConnectionReleaseMode() { return connectionHandlingMode.getReleaseMode(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java index 852602398725..63a0d933756c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java @@ -13,6 +13,7 @@ import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.ScrollableResults; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.hql.internal.HolderInstantiator; @@ -189,21 +190,28 @@ private void prepareCurrentRow(boolean underlyingScrollSuccessful) { return; } - final Object result = getLoader().loadSingleRow( - getResultSet(), - getSession(), - getQueryParameters(), - false - ); - if ( result != null && result.getClass().isArray() ) { - currentRow = (Object[]) result; - } - else { - currentRow = new Object[] {result}; - } - - if ( getHolderInstantiator() != null ) { - currentRow = new Object[] {getHolderInstantiator().instantiate( currentRow )}; + final PersistenceContext persistenceContext = getSession().getPersistenceContext(); + persistenceContext.beforeLoad(); + try { + final Object result = getLoader().loadSingleRow( + getResultSet(), + getSession(), + getQueryParameters(), + true + ); + if ( result != null && result.getClass().isArray() ) { + currentRow = (Object[]) result; + } + else { + currentRow = new Object[] {result}; + } + + if ( getHolderInstantiator() != null ) { + currentRow = new Object[] { getHolderInstantiator().instantiate( currentRow ) }; + } + } + finally { + persistenceContext.afterLoad(); } afterScrollOperation(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java index 282845e44fea..3818d1e90a84 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java @@ -64,4 +64,6 @@ public interface SessionCreationOptions { AfterCompletionAction getAfterCompletionAction(); ManagedFlushChecker getManagedFlushChecker(); + + boolean isQueryParametersValidationEnabled(); } 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 6a773d1b535a..19dcb6c9e7d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -31,7 +31,6 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.spi.PersistenceUnitTransactionType; -import org.hibernate.AssertionFailure; import org.hibernate.ConnectionAcquisitionMode; import org.hibernate.ConnectionReleaseMode; import org.hibernate.CustomEntityDirtinessStrategy; @@ -40,7 +39,6 @@ import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.MappingException; -import org.hibernate.MultiTenancyStrategy; import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionEventListener; @@ -52,8 +50,10 @@ import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService; import org.hibernate.boot.cfgxml.spi.LoadedConfig; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.cfg.Settings; @@ -74,7 +74,6 @@ import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.query.spi.QueryPlanCache; import org.hibernate.engine.query.spi.ReturnMetadata; -import org.hibernate.engine.spi.CacheImplementor; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.NamedQueryDefinition; import org.hibernate.engine.spi.NamedQueryDefinitionBuilder; @@ -88,11 +87,11 @@ import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; import org.hibernate.id.IdentifierGenerator; -import org.hibernate.id.UUIDGenerator; import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.integrator.spi.Integrator; import org.hibernate.integrator.spi.IntegratorService; import org.hibernate.internal.util.config.ConfigurationException; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jpa.internal.AfterCompletionActionLegacyJpaImpl; import org.hibernate.jpa.internal.ExceptionMapperLegacyJpaImpl; import org.hibernate.jpa.internal.ManagedFlushCheckerLegacyJpaImpl; @@ -155,10 +154,9 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( SessionFactoryImpl.class ); - private static final IdentifierGenerator UUID_GENERATOR = UUIDGenerator.buildSessionFactoryUniqueIdentifierGenerator(); - private final String name; private final String uuid; + private transient boolean isClosed; private final transient SessionFactoryObserverChain observer = new SessionFactoryObserverChain(); @@ -174,7 +172,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { // todo : org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor too? - private final transient MetamodelImpl metamodel; + private final transient MetamodelImplementor metamodel; private final transient CriteriaBuilderImpl criteriaBuilder; private final PersistenceUnitUtil jpaPersistenceUnitUtil; private final transient CacheImplementor cacheAccess; @@ -190,20 +188,23 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { private final transient Map filters; private final transient Map fetchProfiles; - private final transient TypeResolver typeResolver; private final transient TypeHelper typeHelper; - private transient StatisticsImplementor statisticsImplementor; - - public SessionFactoryImpl(final MetadataImplementor metadata, SessionFactoryOptions options) { + public SessionFactoryImpl( + final BootstrapContext bootstrapContext, + final MetadataImplementor metadata, + SessionFactoryOptions options) { LOG.debug( "Building session factory" ); this.sessionFactoryOptions = options; this.settings = new Settings( options, metadata ); - this.serviceRegistry = options.getServiceRegistry() + this.serviceRegistry = options + .getServiceRegistry() .getService( SessionFactoryServiceRegistryFactory.class ) - .buildServiceRegistry( this, options ); + .buildServiceRegistry( this, bootstrapContext, options ); + + prepareEventListeners( metadata ); final CfgXmlAccessService cfgXmlAccessService = serviceRegistry.getService( CfgXmlAccessService.class ); @@ -216,12 +217,7 @@ public SessionFactoryImpl(final MetadataImplementor metadata, SessionFactoryOpti } this.name = sfName; - try { - uuid = (String) UUID_GENERATOR.generate( null, null ); - } - catch (Exception e) { - throw new AssertionFailure("Could not generate UUID"); - } + this.uuid = options.getUuid(); final JdbcServices jdbcServices = serviceRegistry.getService( JdbcServices.class ); @@ -235,7 +231,10 @@ public SessionFactoryImpl(final MetadataImplementor metadata, SessionFactoryOpti ); } } + maskOutSensitiveInformation(this.properties); + logIfEmptyCompositesEnabled( this.properties ); + this.sqlFunctionRegistry = new SQLFunctionRegistry( jdbcServices.getJdbcEnvironment().getDialect(), options.getCustomSqlFunctionMap() ); this.cacheAccess = this.serviceRegistry.getService( CacheImplementor.class ); this.criteriaBuilder = new CriteriaBuilderImpl( this ); @@ -245,8 +244,7 @@ public SessionFactoryImpl(final MetadataImplementor metadata, SessionFactoryOpti this.observer.addObserver( sessionFactoryObserver ); } - this.typeResolver = metadata.getTypeResolver().scope( this ); - this.typeHelper = new TypeLocatorImpl( typeResolver ); + this.typeHelper = new TypeLocatorImpl( metadata.getTypeConfiguration().getTypeResolver() ); this.filters = new HashMap<>(); this.filters.putAll( metadata.getFilterDefinitions() ); @@ -293,8 +291,11 @@ public void sessionFactoryClosed(SessionFactory factory) { LOG.debug( "Instantiated session factory" ); - this.metamodel = new MetamodelImpl( this ); - this.metamodel.initialize( metadata, determineJpaMetaModelPopulationSetting( properties ) ); + this.metamodel = metadata.getTypeConfiguration().scope( this , bootstrapContext); + ( (MetamodelImpl) this.metamodel ).initialize( + metadata, + determineJpaMetaModelPopulationSetting( properties ) + ); //Named Queries: this.namedQueryRepository = metadata.buildNamedQueryRepository( this ); @@ -320,17 +321,22 @@ public void sessionFactoryClosed(SessionFactory factory) { final Map errors = checkNamedQueries(); if ( !errors.isEmpty() ) { StringBuilder failingQueries = new StringBuilder( "Errors in named queries: " ); - String sep = ""; + String separator = System.lineSeparator(); + for ( Map.Entry entry : errors.entrySet() ) { LOG.namedQueryError( entry.getKey(), entry.getValue() ); - failingQueries.append( sep ).append( entry.getKey() ); - sep = ", "; + + failingQueries + .append( separator) + .append( entry.getKey() ) + .append( " failed because of: " ) + .append( entry.getValue() ); } throw new HibernateException( failingQueries.toString() ); } } - // this needs to happen afterQuery persisters are all ready to go... + // this needs to happen after persisters are all ready to go... this.fetchProfiles = new HashMap<>(); for ( org.hibernate.mapping.FetchProfile mappingProfile : metadata.getFetchProfiles() ) { final FetchProfile fetchProfile = new FetchProfile( mappingProfile.getName() ); @@ -366,7 +372,7 @@ public void sessionFactoryClosed(SessionFactory factory) { this.observer.sessionFactoryCreated( this ); SessionFactoryRegistry.INSTANCE.addSessionFactory( - uuid, + getUuid(), name, settings.isSessionFactoryNameAlsoJndiName(), this, @@ -378,11 +384,46 @@ public void sessionFactoryClosed(SessionFactory factory) { integrator.disintegrate( this, serviceRegistry ); integratorObserver.integrators.remove( integrator ); } - serviceRegistry.destroy(); + close(); throw e; } } + private void prepareEventListeners(MetadataImplementor metadata) { + final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); + final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class ); + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + + eventListenerRegistry.prepare( metadata ); + + for ( Map.Entry entry : ( (Map) cfgService.getSettings() ).entrySet() ) { + if ( !String.class.isInstance( entry.getKey() ) ) { + continue; + } + final String propertyName = (String) entry.getKey(); + if ( !propertyName.startsWith( org.hibernate.jpa.AvailableSettings.EVENT_LISTENER_PREFIX ) ) { + continue; + } + final String eventTypeName = propertyName.substring( + org.hibernate.jpa.AvailableSettings.EVENT_LISTENER_PREFIX.length() + 1 + ); + final EventType eventType = EventType.resolveEventTypeByName( eventTypeName ); + final EventListenerGroup eventListenerGroup = eventListenerRegistry.getEventListenerGroup( eventType ); + for ( String listenerImpl : ( (String) entry.getValue() ).split( " ," ) ) { + eventListenerGroup.appendListener( instantiate( listenerImpl, classLoaderService ) ); + } + } + } + + private Object instantiate(String listenerImpl, ClassLoaderService classLoaderService) { + try { + return classLoaderService.classForName( listenerImpl ).newInstance(); + } + catch (Exception e) { + throw new HibernateException( "Could not instantiate requested listener [" + listenerImpl + "]", e ); + } + } + private void applyCfgXmlValues(LoadedConfig aggregatedConfig, SessionFactoryServiceRegistry serviceRegistry) { final JaccService jaccService = serviceRegistry.getService( JaccService.class ); if ( jaccService.getContextId() != null ) { @@ -415,14 +456,14 @@ private JdbcConnectionAccess buildLocalConnectionAccess() { return new JdbcConnectionAccess() { @Override public Connection obtainConnection() throws SQLException { - return settings.getMultiTenancyStrategy() == MultiTenancyStrategy.NONE + return !settings.getMultiTenancyStrategy().requiresMultiTenantConnectionProvider() ? serviceRegistry.getService( ConnectionProvider.class ).getConnection() : serviceRegistry.getService( MultiTenantConnectionProvider.class ).getAnyConnection(); } @Override public void releaseConnection(Connection connection) throws SQLException { - if ( settings.getMultiTenancyStrategy() == MultiTenancyStrategy.NONE ) { + if ( !settings.getMultiTenancyStrategy().requiresMultiTenantConnectionProvider() ) { serviceRegistry.getService( ConnectionProvider.class ).closeConnection( connection ); } else { @@ -513,8 +554,16 @@ public IdentifierGeneratorFactory getIdentifierGeneratorFactory() { return null; } + /** + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated public TypeResolver getTypeResolver() { - return typeResolver; + return metamodel.getTypeConfiguration().getTypeResolver(); } public QueryPlanCache getQueryPlanCache() { @@ -530,7 +579,10 @@ public DeserializationResolver getDeserializationResolver() { return new DeserializationResolver() { @Override public SessionFactoryImplementor resolve() { - return (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory( uuid, name ); + return (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory( + uuid, + name + ); } }; } @@ -558,11 +610,13 @@ public List> findEntityGraphsByType(Class entityCl @Override public Session createEntityManager() { + validateNotClosed(); return buildEntityManager( SynchronizationType.SYNCHRONIZED, Collections.emptyMap() ); } private Session buildEntityManager(SynchronizationType synchronizationType, Map map) { - validateNotClosed(); + assert !isClosed; + SessionBuilderImplementor builder = withOptions(); if ( synchronizationType == SynchronizationType.SYNCHRONIZED ) { builder.autoJoinTransactions( true ); @@ -584,11 +638,13 @@ private Session buildEntityManager(SynchronizationType synchronizationType, Map @Override public Session createEntityManager(Map map) { + validateNotClosed(); return buildEntityManager( SynchronizationType.SYNCHRONIZED, map ); } @Override public Session createEntityManager(SynchronizationType synchronizationType) { + validateNotClosed(); errorIfResourceLocalDueToExplicitSynchronizationType(); return buildEntityManager( synchronizationType, Collections.emptyMap() ); } @@ -607,6 +663,7 @@ private void errorIfResourceLocalDueToExplicitSynchronizationType() { @Override public Session createEntityManager(SynchronizationType synchronizationType, Map map) { + validateNotClosed(); errorIfResourceLocalDueToExplicitSynchronizationType(); return buildEntityManager( synchronizationType, map ); } @@ -619,6 +676,7 @@ public CriteriaBuilder getCriteriaBuilder() { @Override public MetamodelImplementor getMetamodel() { + validateNotClosed(); return metamodel; } @@ -632,11 +690,6 @@ public EntityGraph findEntityGraphByName(String name) { return getMetamodel().findEntityGraphByName( name ); } - @Override - public Map getAllSecondLevelCacheRegions() { - return null; - } - @Override public SessionFactoryOptions getSessionFactoryOptions() { return sessionFactoryOptions; @@ -719,13 +772,19 @@ public Type getReferencedPropertyType(String className, String propertyName) * * * Note: Be aware that the sessionFactory instance still can - * be a "heavy" object memory wise afterQuery close() has been called. Thus + * be a "heavy" object memory wise after close() has been called. Thus * it is important to not keep referencing the instance to let the garbage * collector release the memory. * @throws HibernateException */ public void close() throws HibernateException { + //This is an idempotent operation so we can do it even before the checks (it won't hurt): + Environment.getBytecodeProvider().resetCaches(); if ( isClosed ) { + if ( getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled() ) { + throw new IllegalStateException( "EntityManagerFactory is already closed" ); + } + LOG.trace( "Already closed" ); return; } @@ -737,17 +796,27 @@ public void close() throws HibernateException { settings.getMultiTableBulkIdStrategy().release( serviceRegistry.getService( JdbcServices.class ), buildLocalConnectionAccess() ); - cacheAccess.close(); - metamodel.close(); + // NOTE : the null checks below handle cases where close is called from + // a failed attempt to create the SessionFactory - queryPlanCache.cleanup(); + if ( cacheAccess != null ) { + cacheAccess.close(); + } + + if ( metamodel != null ) { + metamodel.close(); + } + + if ( queryPlanCache != null ) { + queryPlanCache.cleanup(); + } if ( delayedDropAction != null ) { delayedDropAction.perform( serviceRegistry ); } SessionFactoryRegistry.INSTANCE.removeSessionFactory( - uuid, + getUuid(), name, settings.isSessionFactoryNameAlsoJndiName(), serviceRegistry.getService( JndiService.class ) @@ -1048,14 +1117,26 @@ public static Interceptor configuredInterceptor(Interceptor interceptor, Session } // then check the Session-scoped interceptor prototype - if ( options.getStatelessInterceptorImplementor() != null ) { + if ( options.getStatelessInterceptorImplementor() != null && options.getStatelessInterceptorImplementorSupplier() != null ) { + throw new HibernateException( + "A session scoped interceptor class or supplier are allowed, but not both!" ); + } + else if ( options.getStatelessInterceptorImplementor() != null ) { try { + /** + * We could remove the getStatelessInterceptorImplementor method and use just the getStatelessInterceptorImplementorSupplier + * since it can cover both cases when the user has given a Supplier or just the + * Class, in which case, we simply instantiate the Interceptor when calling the Supplier. + */ return options.getStatelessInterceptorImplementor().newInstance(); } catch (InstantiationException | IllegalAccessException e) { - throw new HibernateException( "Could not instantiate session-scoped SessionFactory Interceptor", e ); + throw new HibernateException( "Could not supply session-scoped SessionFactory Interceptor", e ); } } + else if ( options.getStatelessInterceptorImplementorSupplier() != null ) { + return options.getStatelessInterceptorImplementorSupplier().get(); + } return null; } @@ -1075,6 +1156,7 @@ static class SessionBuilderImpl implements SessionBuil private boolean autoClear; private String tenantIdentifier; private TimeZone jdbcTimeZone; + private boolean queryParametersValidationEnabled; private List listeners; @@ -1100,6 +1182,7 @@ static class SessionBuilderImpl implements SessionBuil this.jdbcTimeZone = sessionFactory.getSessionFactoryOptions().getJdbcTimeZone(); listeners = sessionFactory.getSessionFactoryOptions().getBaselineSessionEventsListenerBuilder().buildBaselineList(); + queryParametersValidationEnabled = sessionFactory.getSessionFactoryOptions().isQueryParametersValidationEnabled(); } @@ -1143,6 +1226,11 @@ public ManagedFlushChecker getManagedFlushChecker() { : null; } + @Override + public boolean isQueryParametersValidationEnabled() { + return this.queryParametersValidationEnabled; + } + @Override public boolean shouldAutoJoinTransactions() { return autoJoinTransactions; @@ -1318,12 +1406,19 @@ public T jdbcTimeZone(TimeZone timeZone) { jdbcTimeZone = timeZone; return (T) this; } + + @Override + public T setQueryParameterValidation(boolean enabled) { + queryParametersValidationEnabled = enabled; + return (T) this; + } } public static class StatelessSessionBuilderImpl implements StatelessSessionBuilder, SessionCreationOptions { private final SessionFactoryImpl sessionFactory; private Connection connection; private String tenantIdentifier; + private boolean queryParametersValidationEnabled; public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) { this.sessionFactory = sessionFactory; @@ -1331,6 +1426,7 @@ public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) { if ( sessionFactory.getCurrentTenantIdentifierResolver() != null ) { tenantIdentifier = sessionFactory.getCurrentTenantIdentifierResolver().resolveCurrentTenantIdentifier(); } + queryParametersValidationEnabled = sessionFactory.getSessionFactoryOptions().isQueryParametersValidationEnabled(); } @Override @@ -1420,6 +1516,17 @@ public AfterCompletionAction getAfterCompletionAction() { public ManagedFlushChecker getManagedFlushChecker() { return null; } + + @Override + public boolean isQueryParametersValidationEnabled() { + return queryParametersValidationEnabled; + } + + @Override + public StatelessSessionBuilder setQueryParameterValidation(boolean enabled) { + queryParametersValidationEnabled = enabled; + return this; + } } @Override @@ -1443,7 +1550,7 @@ public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() { * @throws IOException Can be thrown by the stream */ private void writeObject(ObjectOutputStream out) throws IOException { - LOG.debugf( "Serializing: %s", uuid ); + LOG.debugf( "Serializing: %s", getUuid() ); out.defaultWriteObject(); LOG.trace( "Serialized" ); } @@ -1459,7 +1566,7 @@ private void writeObject(ObjectOutputStream out) throws IOException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { LOG.trace( "Deserializing" ); in.defaultReadObject(); - LOG.debugf( "Deserialized: %s", uuid ); + LOG.debugf( "Deserialized: %s", getUuid() ); } /** @@ -1473,7 +1580,7 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE */ private Object readResolve() throws InvalidObjectException { LOG.trace( "Resolving serialized SessionFactory" ); - return locateSessionFactoryOnDeserialization( uuid, name ); + return locateSessionFactoryOnDeserialization( getUuid(), name ); } private static SessionFactory locateSessionFactoryOnDeserialization(String uuid, String name) throws InvalidObjectException{ @@ -1503,7 +1610,7 @@ private static SessionFactory locateSessionFactoryOnDeserialization(String uuid, * @throws IOException Indicates problems writing out the serial data stream */ void serialize(ObjectOutputStream oos) throws IOException { - oos.writeUTF( uuid ); + oos.writeUTF( getUuid() ); oos.writeBoolean( name != null ); if ( name != null ) { oos.writeUTF( name ); @@ -1528,6 +1635,8 @@ static SessionFactoryImpl deserialize(ObjectInputStream ois) throws IOException, private void maskOutSensitiveInformation(Map props) { maskOutIfSet( props, AvailableSettings.JPA_JDBC_USER ); + maskOutIfSet( props, AvailableSettings.JPA_JDBC_PASSWORD ); + maskOutIfSet( props, AvailableSettings.USER ); maskOutIfSet( props, AvailableSettings.PASS ); } @@ -1536,4 +1645,20 @@ private void maskOutIfSet(Map props, String setting) { props.put( setting, "****" ); } } + + private void logIfEmptyCompositesEnabled(Map props ) { + final boolean isEmptyCompositesEnabled = ConfigurationHelper.getBoolean( + AvailableSettings.CREATE_EMPTY_COMPOSITES_ENABLED, + props, + false + ); + if ( isEmptyCompositesEnabled ) { + // It would be nice to do this logging in ComponentMetamodel, where + // AvailableSettings.CREATE_EMPTY_COMPOSITES_ENABLED is actually used. + // Unfortunately that would end up logging a message several times for + // each embeddable/composite. Doing it here will log the message only + // once. + LOG.emptyCompositesEnabled(); + } + } } 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 41184982f609..0a80950a13b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -52,6 +52,7 @@ import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.IdentifierLoadAccess; +import org.hibernate.JDBCException; import org.hibernate.LobHelper; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -68,6 +69,7 @@ import org.hibernate.SessionException; import org.hibernate.SharedSessionBuilder; import org.hibernate.SimpleNaturalIdLoadAccess; +import org.hibernate.Transaction; import org.hibernate.TransientObjectException; import org.hibernate.TypeHelper; import org.hibernate.TypeMismatchException; @@ -96,6 +98,7 @@ import org.hibernate.engine.spi.SessionOwner; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; +import org.hibernate.engine.spi.TypedValue; import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.engine.transaction.spi.TransactionObserver; import org.hibernate.event.service.spi.EventListenerGroup; @@ -153,6 +156,7 @@ import org.hibernate.loader.custom.CustomLoader; import org.hibernate.loader.custom.CustomQuery; import org.hibernate.metamodel.spi.MetamodelImplementor; +import org.hibernate.param.CollectionFilterKeyParameterSpecification; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.MultiLoadOptions; @@ -163,6 +167,7 @@ import org.hibernate.procedure.UnknownSqlResultSetMappingException; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; +import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; import org.hibernate.query.Query; import org.hibernate.query.criteria.internal.compile.CompilableCriteria; import org.hibernate.query.criteria.internal.compile.CriteriaCompiler; @@ -207,16 +212,14 @@ public final class SessionImpl private static final boolean TRACE_ENABLED = log.isTraceEnabled(); - private static final List ENTITY_MANAGER_SPECIFIC_PROPERTIES = new ArrayList(); - - static { - ENTITY_MANAGER_SPECIFIC_PROPERTIES.add( JPA_LOCK_SCOPE ); - ENTITY_MANAGER_SPECIFIC_PROPERTIES.add( JPA_LOCK_TIMEOUT ); - ENTITY_MANAGER_SPECIFIC_PROPERTIES.add( AvailableSettings.FLUSH_MODE ); - ENTITY_MANAGER_SPECIFIC_PROPERTIES.add( JPA_SHARED_CACHE_RETRIEVE_MODE ); - ENTITY_MANAGER_SPECIFIC_PROPERTIES.add( JPA_SHARED_CACHE_STORE_MODE ); - ENTITY_MANAGER_SPECIFIC_PROPERTIES.add( QueryHints.SPEC_HINT_TIMEOUT ); - } + private static final String[] ENTITY_MANAGER_SPECIFIC_PROPERTIES = { + JPA_LOCK_SCOPE, + JPA_LOCK_TIMEOUT, + AvailableSettings.FLUSH_MODE, + JPA_SHARED_CACHE_RETRIEVE_MODE, + JPA_SHARED_CACHE_STORE_MODE, + QueryHints.SPEC_HINT_TIMEOUT + }; private transient SessionOwner sessionOwner; @@ -230,20 +233,24 @@ public final class SessionImpl // todo : (5.2) HEM always initialized this. Is that really needed? private LockOptions lockOptions = new LockOptions(); - private transient boolean autoClear; - private transient boolean autoClose; + private boolean autoClear; + private boolean autoClose; + private boolean queryParametersValidationEnabled; private transient int dontFlushFromFind; - private transient boolean disallowOutOfTransactionUpdateOperations; private transient ExceptionMapper exceptionMapper; private transient ManagedFlushChecker managedFlushChecker; + private transient AfterCompletionAction afterCompletionAction; private transient LoadEvent loadEvent; //cached LoadEvent instance private transient boolean discardOnClose; + private transient TransactionObserver transactionObserver; + private transient EventListenerRegistry eventListenerRegistry; + public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); @@ -255,7 +262,8 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { this.autoClear = options.shouldAutoClear(); this.autoClose = options.shouldAutoClose(); - this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + this.queryParametersValidationEnabled = options.isQueryParametersValidationEnabled(); + this.discardOnClose = getFactory().getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); if ( options instanceof SharedSessionCreationOptions && ( (SharedSessionCreationOptions) options ).isTransactionCoordinatorShared() ) { @@ -304,10 +312,10 @@ private void applyProperties() { } private void applyEntityManagerSpecificProperties() { + final Map properties = getFactory().getProperties(); for ( String key : ENTITY_MANAGER_SPECIFIC_PROPERTIES ) { - final Map properties = getFactory().getProperties(); - if ( getFactory().getProperties().containsKey( key ) ) { - this.properties.put( key, getFactory().getProperties().get( key ) ); + if ( properties.containsKey( key ) ) { + this.properties.put( key, properties.get( key ) ); } } } @@ -316,12 +324,12 @@ protected void applyQuerySettingsAndHints(Query query) { if ( lockOptions.getLockMode() != LockMode.NONE ) { query.setLockMode( getLockMode( lockOptions.getLockMode() ) ); } - Object queryTimeout; - if ( (queryTimeout = getProperties().get( QueryHints.SPEC_HINT_TIMEOUT ) ) != null ) { + final Object queryTimeout; + if ( ( queryTimeout = properties.get( QueryHints.SPEC_HINT_TIMEOUT ) ) != null ) { query.setHint( QueryHints.SPEC_HINT_TIMEOUT, queryTimeout ); } - Object lockTimeout; - if( (lockTimeout = getProperties().get( JPA_LOCK_TIMEOUT ))!=null){ + final Object lockTimeout; + if ( ( lockTimeout = properties.get( JPA_LOCK_TIMEOUT ) ) != null ) { query.setHint( JPA_LOCK_TIMEOUT, lockTimeout ); } } @@ -397,8 +405,24 @@ private void internalClear() { @Override + @SuppressWarnings("StatementWithEmptyBody") public void close() throws HibernateException { - log.tracef( "Closing session [%s]", getSessionIdentifier() ); + if ( isClosed() ) { + if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled() ) { + throw new IllegalStateException( "Illegal call to #close() on already closed Session/EntityManager" ); + } + + log.trace( "Already closed" ); + return; + } + + closeWithoutOpenChecks(); + } + + public void closeWithoutOpenChecks() throws HibernateException { + if ( TRACE_ENABLED ) { + log.tracef( "Closing session [%s]", getSessionIdentifier() ); + } // todo : we want this check if usage is JPA, but not native Hibernate usage if ( getSessionFactory().getSessionFactoryOptions().isJpaBootstrap() ) { @@ -415,12 +439,11 @@ public void close() throws HibernateException { } } else { - super.close(); + } - if ( getFactory().getStatistics().isStatisticsEnabled() ) { - getFactory().getStatistics().closeSession(); - } + if ( getFactory().getStatistics().isStatisticsEnabled() ) { + getFactory().getStatistics().closeSession(); } } @@ -441,7 +464,7 @@ protected boolean shouldCloseJdbcCoordinatorOnClose(boolean isTransactionCoordin if ( getActionQueue().hasBeforeTransactionActions() || getActionQueue().hasAfterTransactionActions() ) { log.warn( - "On close, shared Session had beforeQuery/afterQuery transaction actions that have not yet been processed" + "On close, shared Session had before/after transaction actions that have not yet been processed" ); } return false; @@ -452,6 +475,11 @@ public boolean isAutoCloseSessionEnabled() { return autoClose; } + @Override + public boolean isQueryParametersValidationEnabled() { + return queryParametersValidationEnabled; + } + @Override public boolean isOpen() { checkSessionFactoryOpen(); @@ -505,12 +533,12 @@ else if ( sessionOwner != null ) { private void managedClose() { log.trace( "Automatically closing session" ); - close(); + closeWithoutOpenChecks(); } @Override public Connection connection() throws HibernateException { - checkOpen(); + checkOpenOrWaitingForAutoClose(); return getJdbcCoordinator().getLogicalConnection().getPhysicalConnection(); } @@ -531,14 +559,14 @@ public void reconnect(Connection conn) throws HibernateException { @Override public void setAutoClear(boolean enabled) { - checkOpen(); + checkOpenOrWaitingForAutoClose(); autoClear = enabled; } /** * Check if there is a Hibernate or JTA transaction in progress and, * if there is not, flush if necessary, make sure the connection has - * been committed (if it is not in autocommit mode) and run the afterQuery + * been committed (if it is not in autocommit mode) and run the after * completion processing * * @param success Was the operation a success @@ -612,7 +640,7 @@ public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException private void checkNoUnresolvedActionsBeforeOperation() { if ( persistenceContext.getCascadeLevel() == 0 && actionQueue.hasUnresolvedEntityInsertActions() ) { - throw new IllegalStateException( "There are delayed insert actions beforeQuery operation as cascade level 0." ); + throw new IllegalStateException( "There are delayed insert actions before operation as cascade level 0." ); } } @@ -658,7 +686,10 @@ private Iterable listeners(EventType type) { } private EventListenerGroup eventListenerGroup(EventType type) { - return getFactory().getServiceRegistry().getService( EventListenerRegistry.class ).getEventListenerGroup( type ); + if ( this.eventListenerRegistry == null ) { + this.eventListenerRegistry = getFactory().getServiceRegistry().getService( EventListenerRegistry.class ); + } + return eventListenerRegistry.getEventListenerGroup( type ); } @@ -736,7 +767,7 @@ private void fireLock(Object object, LockOptions options) { private void fireLock(LockEvent event) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( LockEventListener listener : listeners( EventType.LOCK ) ) { listener.onLock( event ); } @@ -790,7 +821,7 @@ private void firePersist(PersistEvent event) { } private void firePersist(Map copiedAlready, PersistEvent event) { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); try { for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) { @@ -828,7 +859,7 @@ public void persistOnFlush(String entityName, Object object, Map copiedAlready) private void firePersistOnFlush(Map copiedAlready, PersistEvent event) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( PersistEventListener listener : listeners( EventType.PERSIST_ONFLUSH ) ) { listener.onPersist( event, copiedAlready ); } @@ -891,7 +922,7 @@ private Object fireMerge(MergeEvent event) { private void fireMerge(Map copiedAlready, MergeEvent event) { try { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( MergeEventListener listener : listeners( EventType.MERGE ) ) { listener.onMerge( event, copiedAlready ); } @@ -931,7 +962,7 @@ public void delete(String entityName, Object object, boolean isCascadeDeleteEnab throws HibernateException { checkOpenOrWaitingForAutoClose(); if ( TRACE_ENABLED && persistenceContext.isRemovingOrphanBeforeUpates() ) { - logRemoveOrphanBeforeUpdates( "beforeQuery continuing", entityName, object ); + logRemoveOrphanBeforeUpdates( "before continuing", entityName, object ); } fireDelete( new DeleteEvent( @@ -944,7 +975,7 @@ public void delete(String entityName, Object object, boolean isCascadeDeleteEnab transientEntities ); if ( TRACE_ENABLED && persistenceContext.isRemovingOrphanBeforeUpates() ) { - logRemoveOrphanBeforeUpdates( "afterQuery continuing", entityName, object ); + logRemoveOrphanBeforeUpdates( "after continuing", entityName, object ); } } @@ -971,7 +1002,7 @@ public void removeOrphanBeforeUpdates(String entityName, Object child) { private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Object entity) { final EntityEntry entityEntry = persistenceContext.getEntry( entity ); log.tracef( - "%s remove orphan beforeQuery updates: [%s]", + "%s remove orphan before updates: [%s]", timing, entityEntry == null ? entityName : MessageHelper.infoString( entityName, entityEntry.getId() ) ); @@ -979,7 +1010,7 @@ private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Obje private void fireDelete(DeleteEvent event) { try{ - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { listener.onDelete( event ); } @@ -1001,10 +1032,10 @@ private void fireDelete(DeleteEvent event) { private void fireDelete(DeleteEvent event, Set transientEntities) { try{ - checkTransactionSynchStatus(); - for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { - listener.onDelete( event, transientEntities ); - } + pulseTransactionCoordinator(); + for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { + listener.onDelete( event, transientEntities ); + } } catch ( ObjectDeletedException sse ) { throw exceptionConverter.convert( new IllegalArgumentException( sse ) ); @@ -1085,7 +1116,7 @@ public Object immediateLoad(String entityName, Serializable id) throws Hibernate LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoad( event, LoadEventListener.IMMEDIATE_LOAD ); + fireLoadNoChecks( event, LoadEventListener.IMMEDIATE_LOAD ); Object result = event.getResult(); if ( loadEvent == null ) { event.setEntityClassName( null ); @@ -1096,25 +1127,31 @@ public Object immediateLoad(String entityName, Serializable id) throws Hibernate } return result; } - @Override - public final Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) - throws HibernateException { - // todo : remove - LoadEventListener.LoadType type = nullable - ? LoadEventListener.INTERNAL_LOAD_NULLABLE - : eager - ? LoadEventListener.INTERNAL_LOAD_EAGER - : LoadEventListener.INTERNAL_LOAD_LAZY; + public final Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable) { + final LoadEventListener.LoadType type; + if ( nullable ) { + type = LoadEventListener.INTERNAL_LOAD_NULLABLE; + } + else { + type = eager + ? LoadEventListener.INTERNAL_LOAD_EAGER + : LoadEventListener.INTERNAL_LOAD_LAZY; + } LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoad( event, type ); + fireLoadNoChecks( event, type ); Object result = event.getResult(); if ( !nullable ) { UnresolvableObjectException.throwIfNull( result, id, entityName ); } + if ( loadEvent == null ) { event.setEntityClassName( null ); event.setEntityId( null ); @@ -1224,17 +1261,26 @@ public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass) } private void fireLoad(LoadEvent event, LoadType loadType) { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + fireLoadNoChecks( event, loadType ); + delayedAfterCompletion(); + } + + //Performance note: + // This version of #fireLoad is meant to be invoked by internal methods only, + // so to skip the session open, transaction synch, etc.. checks, + // which have been proven to be not particularly cheap: + // it seems they prevent these hot methods from being inlined. + private void fireLoadNoChecks(LoadEvent event, LoadType loadType) { + pulseTransactionCoordinator(); for ( LoadEventListener listener : listeners( EventType.LOAD ) ) { listener.onLoad( event, loadType ); } - delayedAfterCompletion(); } private void fireResolveNaturalId(ResolveNaturalIdEvent event) { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); for ( ResolveNaturalIdEventListener listener : listeners( EventType.RESOLVE_NATURAL_ID ) ) { listener.onResolveNaturalId( event ); } @@ -1294,7 +1340,7 @@ private void fireRefresh(RefreshEvent event) { } } } - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { listener.onRefresh( event ); } @@ -1315,11 +1361,10 @@ private void fireRefresh(RefreshEvent event) { private void fireRefresh(Map refreshedAlready, RefreshEvent event) { try { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { listener.onRefresh( event, refreshedAlready ); } - delayedAfterCompletion(); } catch (RuntimeException e) { throw exceptionConverter.convert( e ); @@ -1345,7 +1390,7 @@ public void replicate(String entityName, Object obj, ReplicationMode replication private void fireReplicate(ReplicateEvent event) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( ReplicateEventListener listener : listeners( EventType.REPLICATE ) ) { listener.onReplicate( event ); } @@ -1366,7 +1411,7 @@ public void evict(Object object) throws HibernateException { private void fireEvict(EvictEvent event) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( EvictEventListener listener : listeners( EventType.EVICT ) ) { listener.onEvict( event ); } @@ -1384,7 +1429,6 @@ protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException return false; } AutoFlushEvent event = new AutoFlushEvent( querySpaces, this ); - listeners( EventType.AUTO_FLUSH ); for ( AutoFlushEventListener listener : listeners( EventType.AUTO_FLUSH ) ) { listener.onAutoFlush( event ); } @@ -1394,7 +1438,7 @@ protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException @Override public boolean isDirty() throws HibernateException { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); log.debug( "Checking session dirtiness" ); if ( actionQueue.areInsertionsOrDeletionsQueued() ) { log.debug( "Session dirty (scheduled updates and insertions)" ); @@ -1415,8 +1459,8 @@ public void flush() throws HibernateException { } private void doFlush() { - checkTransactionNeeded(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); + checkTransactionNeededForUpdateOperation(); try { if ( persistenceContext.getCascadeLevel() > 0 ) { @@ -1437,6 +1481,7 @@ private void doFlush() { @Override public void setFlushMode(FlushModeType flushModeType) { + checkOpen(); setHibernateFlushMode( FlushModeTypeHelper.getFlushMode( flushModeType ) ); } @@ -1462,8 +1507,8 @@ public void forceFlush(EntityEntry entityEntry) throws HibernateException { @Override public List list(String query, QueryParameters queryParameters) throws HibernateException { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); HQLQueryPlan plan = queryParameters.getQueryPlan(); @@ -1473,7 +1518,7 @@ public List list(String query, QueryParameters queryParameters) throws Hibernate autoFlushIfRequired( plan.getQuerySpaces() ); - List results = Collections.EMPTY_LIST; + final List results; boolean success = false; dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called @@ -1491,12 +1536,14 @@ public List list(String query, QueryParameters queryParameters) throws Hibernate @Override public int executeUpdate(String query, QueryParameters queryParameters) throws HibernateException { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); HQLQueryPlan plan = getQueryPlan( query, false ); autoFlushIfRequired( plan.getQuerySpaces() ); + verifyImmutableEntityUpdate( plan ); + boolean success = false; int result = 0; try { @@ -1510,12 +1557,48 @@ public int executeUpdate(String query, QueryParameters queryParameters) throws H return result; } + private void verifyImmutableEntityUpdate(HQLQueryPlan plan) { + if ( plan.isUpdate() ) { + for ( EntityPersister entityPersister : getSessionFactory().getMetamodel().entityPersisters().values() ) { + if ( !entityPersister.isMutable() ) { + List entityQuerySpaces = new ArrayList<>( + Arrays.asList( entityPersister.getQuerySpaces() ) + ); + entityQuerySpaces.retainAll( plan.getQuerySpaces() ); + + if ( !entityQuerySpaces.isEmpty() ) { + ImmutableEntityUpdateQueryHandlingMode immutableEntityUpdateQueryHandlingMode = getSessionFactory() + .getSessionFactoryOptions() + .getImmutableEntityUpdateQueryHandlingMode(); + + String querySpaces = Arrays.toString( entityQuerySpaces.toArray() ); + + switch ( immutableEntityUpdateQueryHandlingMode ) { + case WARNING: + log.immutableEntityUpdateQuery(plan.getSourceQuery(), querySpaces); + break; + case EXCEPTION: + throw new HibernateException( + "The query: [" + plan.getSourceQuery() + "] attempts to update an immutable entity: " + querySpaces + ); + default: + throw new UnsupportedOperationException( + "The "+ immutableEntityUpdateQueryHandlingMode + " is not supported!" + ); + + } + } + } + } + } + } + @Override public int executeNativeUpdate( NativeSQLQuerySpecification nativeQuerySpecification, QueryParameters queryParameters) throws HibernateException { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); NativeSQLQueryPlan plan = getNativeQueryPlan( nativeQuerySpecification ); @@ -1537,8 +1620,8 @@ public int executeNativeUpdate( @Override public Iterator iterate(String query, QueryParameters queryParameters) throws HibernateException { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); HQLQueryPlan plan = queryParameters.getQueryPlan(); @@ -1560,14 +1643,14 @@ public Iterator iterate(String query, QueryParameters queryParameters) throws Hi @Override public ScrollableResultsImplementor scroll(String query, QueryParameters queryParameters) throws HibernateException { - checkOpen(); - checkTransactionSynchStatus(); - + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); + HQLQueryPlan plan = queryParameters.getQueryPlan(); if ( plan == null ) { plan = getQueryPlan( query, false ); } - + autoFlushIfRequired( plan.getQuerySpaces() ); dontFlushFromFind++; @@ -1583,7 +1666,7 @@ public ScrollableResultsImplementor scroll(String query, QueryParameters queryPa @Override public org.hibernate.query.Query createFilter(Object collection, String queryString) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); CollectionFilterImpl filter = new CollectionFilterImpl( queryString, collection, @@ -1607,7 +1690,7 @@ public Object instantiate(String entityName, Serializable id) throws HibernateEx @Override public Object instantiate(EntityPersister persister, Serializable id) throws HibernateException { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); Object result = getInterceptor().instantiate( persister.getEntityName(), persister.getEntityMetamodel().getEntityMode(), @@ -1700,6 +1783,8 @@ private FilterQueryPlan getFilterQueryPlan( final CollectionPersister roleBeforeFlush = ( entry == null ) ? null : entry.getLoadedPersister(); FilterQueryPlan plan = null; + final Map enabledFilters = getLoadQueryInfluencers().getEnabledFilters(); + final SessionFactoryImplementor factory = getFactory(); if ( roleBeforeFlush == null ) { // if it was previously unreferenced, we need to flush in order to // get its state into the database in order to execute query @@ -1709,24 +1794,24 @@ private FilterQueryPlan getFilterQueryPlan( if ( roleAfterFlush == null ) { throw new QueryException( "The collection was unreferenced" ); } - plan = getFactory().getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryPlanCache().getFilterQueryPlan( filter, roleAfterFlush.getRole(), shallow, - getLoadQueryInfluencers().getEnabledFilters() + enabledFilters ); } else { // otherwise, we only need to flush if there are in-memory changes // to the queried tables - plan = getFactory().getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryPlanCache().getFilterQueryPlan( filter, roleBeforeFlush.getRole(), shallow, - getLoadQueryInfluencers().getEnabledFilters() + enabledFilters ); if ( autoFlushIfRequired( plan.getQuerySpaces() ) ) { - // might need to run a different filter entirely afterQuery the flush + // might need to run a different filter entirely after the flush // because the collection role may have changed entry = persistenceContext.getCollectionEntryOrNull( collection ); CollectionPersister roleAfterFlush = ( entry == null ) ? null : entry.getLoadedPersister(); @@ -1734,19 +1819,24 @@ private FilterQueryPlan getFilterQueryPlan( if ( roleAfterFlush == null ) { throw new QueryException( "The collection was dereferenced" ); } - plan = getFactory().getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryPlanCache().getFilterQueryPlan( filter, roleAfterFlush.getRole(), shallow, - getLoadQueryInfluencers().getEnabledFilters() + enabledFilters ); } } } if ( parameters != null ) { - parameters.getPositionalParameterValues()[0] = entry.getLoadedKey(); - parameters.getPositionalParameterTypes()[0] = entry.getLoadedPersister().getKeyType(); + parameters.getNamedParameters().put( + CollectionFilterKeyParameterSpecification.PARAM_KEY, + new TypedValue( + entry.getLoadedPersister().getKeyType(), + entry.getLoadedKey() + ) + ); } return plan; @@ -1754,8 +1844,8 @@ private FilterQueryPlan getFilterQueryPlan( @Override public List listFilter(Object collection, String filter, QueryParameters queryParameters) { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); FilterQueryPlan plan = getFilterQueryPlan( collection, filter, queryParameters, false ); List results = Collections.EMPTY_LIST; @@ -1775,8 +1865,8 @@ public List listFilter(Object collection, String filter, QueryParameters queryPa @Override public Iterator iterateFilter(Object collection, String filter, QueryParameters queryParameters) { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); FilterQueryPlan plan = getFilterQueryPlan( collection, filter, queryParameters, true ); Iterator itr = plan.performIterate( queryParameters, this ); delayedAfterCompletion(); @@ -1820,8 +1910,8 @@ public ScrollableResultsImplementor scroll(Criteria criteria, ScrollMode scrollM // TODO: Is this guaranteed to always be CriteriaImpl? CriteriaImpl criteriaImpl = (CriteriaImpl) criteria; - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); String entityName = criteriaImpl.getEntityOrClassName(); CriteriaLoader loader = new CriteriaLoader( @@ -1857,7 +1947,7 @@ public List list(Criteria criteria) throws HibernateException { } - checkOpen(); + checkOpenOrWaitingForAutoClose(); // checkTransactionSynchStatus(); String[] implementors = getFactory().getMetamodel().getImplementors( criteriaImpl.getEntityOrClassName() ); @@ -1974,7 +2064,7 @@ private OuterJoinLoadable getOuterJoinLoadable(String entityName) throws Mapping @Override public boolean contains(Object object) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); if ( object == null ) { return false; @@ -2015,7 +2105,7 @@ public boolean contains(Object object) { if ( entityName == null ) { throw new IllegalArgumentException( "Could not resolve entity-name [" + object + "]" ); } - getSessionFactory().getMetamodel().entityPersister( object.getClass() ); + getSessionFactory().getMetamodel().entityPersister( entityName ); } catch (HibernateException e) { throw new IllegalArgumentException( "Not an entity [" + object.getClass() + "]", e ); @@ -2037,8 +2127,8 @@ public boolean contains(Object object) { @Override public boolean contains(String entityName, Object object) { - checkOpen(); - checkTransactionSynchStatus(); + checkOpenOrWaitingForAutoClose(); + pulseTransactionCoordinator(); if ( object == null ) { return false; @@ -2112,14 +2202,14 @@ public ProcedureCall createStoredProcedureCall(String procedureName, Class... re @Override public ScrollableResultsImplementor scrollCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) { - checkOpen(); + checkOpenOrWaitingForAutoClose(); // checkTransactionSynchStatus(); if ( log.isTraceEnabled() ) { log.tracev( "Scroll SQL query: {0}", customQuery.getSQL() ); } - CustomLoader loader = new CustomLoader( customQuery, getFactory() ); + CustomLoader loader = getFactory().getQueryPlanCache().getNativeQueryInterpreter().createCustomLoader( customQuery, getFactory() ); autoFlushIfRequired( loader.getQuerySpaces() ); @@ -2136,14 +2226,14 @@ public ScrollableResultsImplementor scrollCustomQuery(CustomQuery customQuery, Q // basically just an adapted copy of find(CriteriaImpl) @Override public List listCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) { - checkOpen(); + checkOpenOrWaitingForAutoClose(); // checkTransactionSynchStatus(); if ( log.isTraceEnabled() ) { log.tracev( "SQL query: {0}", customQuery.getSQL() ); } - CustomLoader loader = new CustomLoader( customQuery, getFactory() ); + CustomLoader loader = getFactory().getQueryPlanCache().getNativeQueryInterpreter().createCustomLoader( customQuery, getFactory() ); autoFlushIfRequired( loader.getQuerySpaces() ); @@ -2170,7 +2260,7 @@ public SessionFactoryImplementor getSessionFactory() { @Override public void initializeCollection(PersistentCollection collection, boolean writing) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); InitializeCollectionEvent event = new InitializeCollectionEvent( collection, this ); for ( InitializeCollectionEventListener listener : listeners( EventType.INIT_COLLECTION ) ) { listener.onInitializeCollection( event ); @@ -2218,14 +2308,14 @@ public String getEntityName(Object object) { private void throwTransientObjectException(Object object) throws HibernateException { throw new TransientObjectException( - "object references an unsaved transient instance - save the transient instance beforeQuery flushing: " + + "object references an unsaved transient instance - save the transient instance before flushing: " + guessEntityName( object ) ); } @Override public String guessEntityName(Object object) throws HibernateException { - checkOpen(); + checkOpenOrWaitingForAutoClose(); return getEntityNameResolver().resolveEntityName( object ); } @@ -2244,11 +2334,16 @@ public int getDontFlushFromFind() { @Override public String toString() { StringBuilder buf = new StringBuilder( 500 ) - .append( "SessionImpl(" ); + .append( "SessionImpl(" ).append( System.identityHashCode( this ) ); if ( !isClosed() ) { - buf.append( persistenceContext ) + if ( TRACE_ENABLED ) { + buf.append( persistenceContext ) .append( ";" ) .append( actionQueue ); + } + else { + buf.append( "" ); + } } else { buf.append( "" ); @@ -2272,13 +2367,13 @@ public PersistenceContext getPersistenceContext() { @Override public SessionStatistics getStatistics() { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return new SessionStatisticsImpl( this ); } @Override public boolean isEventSource() { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return true; } @@ -2347,21 +2442,21 @@ public LoadQueryInfluencers getLoadQueryInfluencers() { @Override public Filter getEnabledFilter(String filterName) { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return loadQueryInfluencers.getEnabledFilter( filterName ); } @Override public Filter enableFilter(String filterName) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return loadQueryInfluencers.enableFilter( filterName ); } @Override public void disableFilter(String filterName) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); loadQueryInfluencers.disableFilter( filterName ); } @@ -2398,17 +2493,25 @@ public LobHelper getLobHelper() { private transient LobHelperImpl lobHelper; + private Transaction getTransactionIfAccessible() { + // We do not want an exception to be thrown if the transaction + // is not accessible. If the transaction is not accessible, + // then return null. + return isTransactionAccessible() ? accessTransaction() : null; + } + @Override public void beforeTransactionCompletion() { log.tracef( "SessionImpl#beforeTransactionCompletion()" ); flushBeforeTransactionCompletion(); actionQueue.beforeTransactionCompletion(); try { - getInterceptor().beforeTransactionCompletion( getCurrentTransaction() ); + getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInBeforeTransactionCompletionInterceptor( t ); } + super.beforeTransactionCompletion(); } @Override @@ -2431,7 +2534,7 @@ public void afterTransactionCompletion(boolean successful, boolean delayed) { } try { - getInterceptor().afterTransactionCompletion( getCurrentTransaction() ); + getInterceptor().afterTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInAfterTransactionCompletionInterceptor( t ); @@ -2442,6 +2545,8 @@ public void afterTransactionCompletion(boolean successful, boolean delayed) { managedClose(); } } + + super.afterTransactionCompletion( successful, delayed ); } private static class LobHelperImpl implements LobHelper { @@ -2458,7 +2563,7 @@ public Blob createBlob(byte[] bytes) { private LobCreator lobCreator() { // Always use NonContextualLobCreator. If ContextualLobCreator is - // used both here and in WrapperOptions, + // used both here and in WrapperOptions, return NonContextualLobCreator.INSTANCE; } @@ -2578,6 +2683,10 @@ public ActionQueue.TransactionCompletionProcesses getTransactionCompletionProces null; } + @Override + public boolean isQueryParametersValidationEnabled() { + return session.isQueryParametersValidationEnabled(); + } } private class LockRequestImpl implements LockRequest { @@ -2634,35 +2743,40 @@ public void lock(Object object) throws HibernateException { @Override protected void addSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) { - transactionCoordinator.addObserver( - new TransactionObserver() { - @Override - public void afterBegin() { - } + this.transactionObserver = new TransactionObserver() { + @Override + public void afterBegin() { + } - @Override - public void beforeCompletion() { - if ( isOpen() && getHibernateFlushMode() != FlushMode.MANUAL ) { - managedFlush(); - } - actionQueue.beforeTransactionCompletion(); - try { - getInterceptor().beforeTransactionCompletion( getCurrentTransaction() ); - } - catch (Throwable t) { - log.exceptionInBeforeTransactionCompletionInterceptor( t ); - } - } + @Override + public void beforeCompletion() { + if ( isOpen() && getHibernateFlushMode() != FlushMode.MANUAL ) { + managedFlush(); + } + actionQueue.beforeTransactionCompletion(); + try { + getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() ); + } + catch (Throwable t) { + log.exceptionInBeforeTransactionCompletionInterceptor( t ); + } + } - @Override - public void afterCompletion(boolean successful, boolean delayed) { - afterTransactionCompletion( successful, delayed ); - if ( !isClosed() && autoClose ) { - managedClose(); - } - } + @Override + public void afterCompletion(boolean successful, boolean delayed) { + afterTransactionCompletion( successful, delayed ); + if ( !isClosed() && autoClose ) { + managedClose(); } - ); + } + }; + transactionCoordinator.addObserver(transactionObserver); + } + + @Override + protected void removeSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) { + super.removeSharedSessionTransactionObserver( transactionCoordinator ); + transactionCoordinator.removeObserver( transactionObserver ); } private class IdentifierLoadAccessImpl implements IdentifierLoadAccess { @@ -3174,10 +3288,16 @@ public Optional loadOptional(Serializable naturalIdValue) { } } + @Override + public void startTransactionBoundary() { + checkOpenOrWaitingForAutoClose(); + super.startTransactionBoundary(); + } + @Override public void afterTransactionBegin() { checkOpenOrWaitingForAutoClose(); - getInterceptor().afterTransactionBegin( getCurrentTransaction() ); + getInterceptor().afterTransactionBegin( getTransactionIfAccessible() ); } @Override @@ -3381,7 +3501,7 @@ public T find(Class entityClass, Object primaryKey, LockModeType lockMode if ( lockModeType != null ) { if ( !LockModeType.NONE.equals( lockModeType) ) { - checkTransactionNeeded(); + checkTransactionNeededForUpdateOperation(); } lockOptions = buildLockOptions( lockModeType, properties ); loadAccess.with( lockOptions ); @@ -3410,6 +3530,21 @@ public T find(Class entityClass, Object primaryKey, LockModeType lockMode catch ( MappingException | TypeMismatchException | ClassCastException e ) { throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) ); } + catch ( JDBCException e ) { + if ( accessTransaction().isActive() && accessTransaction().getRollbackOnly() ) { + // Assume this is the similar to the WildFly / IronJacamar "feature" described under HHH-12472. + // Just log the exception and return null. + if ( log.isDebugEnabled() ) { + log.debug( "JDBCException was thrown for a transaction marked for rollback; " + + "this is probably due to an operation failing fast due to the " + + "transaction marked for rollback.", e ); + } + return null; + } + else { + throw exceptionConverter.convert( e, lockOptions ); + } + } catch ( RuntimeException e ) { throw exceptionConverter.convert( e, lockOptions ); } @@ -3445,10 +3580,8 @@ private CacheStoreMode determineCacheStoreMode(Map settings) { return ( CacheStoreMode ) settings.get( JPA_SHARED_CACHE_STORE_MODE ); } - private void checkTransactionNeeded() { - if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { - throw new TransactionRequiredException( "no transaction is in progress" ); - } + private void checkTransactionNeededForUpdateOperation() { + checkTransactionNeededForUpdateOperation( "no transaction is in progress" ); } @Override @@ -3474,7 +3607,7 @@ public void lock(Object entity, LockModeType lockModeType) { @Override public void lock(Object entity, LockModeType lockModeType, Map properties) { checkOpen(); - checkTransactionNeeded(); + checkTransactionNeededForUpdateOperation(); if ( !contains( entity ) ) { throw new IllegalArgumentException( "entity not in the persistence context" ); @@ -3516,7 +3649,7 @@ public void refresh(Object entity, LockModeType lockModeType, Map T unwrap(Class clazz) { @Override public Object getDelegate() { + checkOpen(); return this; } @Override public SessionFactoryImplementor getEntityManagerFactory() { + checkOpen(); return getFactory(); } @Override public CriteriaBuilder getCriteriaBuilder() { + checkOpen(); return getFactory().getCriteriaBuilder(); } @Override public MetamodelImplementor getMetamodel() { + checkOpen(); return getFactory().getMetamodel(); } @@ -3854,7 +3986,9 @@ public List> getEntityGraphs(Class entityClass) { * @throws IOException Indicates a general IO stream exception */ private void writeObject(ObjectOutputStream oos) throws IOException { - log.tracef( "Serializing Session [%s]", getSessionIdentifier() ); + if ( TRACE_ENABLED ) { + log.tracef( "Serializing Session [%s]", getSessionIdentifier() ); + } oos.defaultWriteObject(); @@ -3873,7 +4007,9 @@ private void writeObject(ObjectOutputStream oos) throws IOException { * @throws ClassNotFoundException Indicates a class resolution issue */ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException, SQLException { - log.tracef( "Deserializing Session [%s]", getSessionIdentifier() ); + if ( TRACE_ENABLED ) { + log.tracef( "Deserializing Session [%s]", getSessionIdentifier() ); + } ois.defaultReadObject(); @@ -3883,10 +4019,14 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound loadQueryInfluencers = (LoadQueryInfluencers) ois.readObject(); // LoadQueryInfluencers.getEnabledFilters() tries to validate each enabled - // filter, which will fail when called beforeQuery FilterImpl.afterDeserialize( factory ); + // filter, which will fail when called before FilterImpl.afterDeserialize( factory ); // Instead lookup the filter by name and then call FilterImpl.afterDeserialize( factory ). for ( String filterName : loadQueryInfluencers.getEnabledFilterNames() ) { ( (FilterImpl) loadQueryInfluencers.getEnabledFilter( filterName ) ).afterDeserialize( getFactory() ); } + + initializeFromSessionOwner( null ); + + this.discardOnClose = getFactory().getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index 873308ba640e..3cd3e7e332b2 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -24,7 +24,8 @@ import org.hibernate.SessionException; import org.hibernate.StatelessSession; import org.hibernate.UnresolvableObjectException; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.StatefulPersistenceContext; import org.hibernate.engine.internal.Versioning; @@ -46,6 +47,7 @@ import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.query.spi.ScrollableResultsImplementor; +import org.hibernate.tuple.entity.EntityMetamodel; /** * @author Gavin King @@ -65,10 +67,15 @@ public void setInternalFetchProfile(String internalFetchProfile) { } }; - private PersistenceContext temporaryPersistenceContext = new StatefulPersistenceContext( this ); + private final PersistenceContext temporaryPersistenceContext = new StatefulPersistenceContext( this ); + + private final boolean connectionProvided; + private final boolean allowBytecodeProxy; StatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); + connectionProvided = options.getConnection() != null; + allowBytecodeProxy = getFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(); } @Override @@ -219,11 +226,19 @@ public void refresh(String entityName, Object entity, LockMode lockMode) { // ); // } - if ( persister.hasCache() ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); - final Object ck = cache.generateCacheKey( id, persister, getFactory(), getTenantIdentifier() ); - cache.evict( ck ); + if ( persister.canWriteToCache() ) { + final EntityDataAccess cacheAccess = persister.getCacheAccessStrategy(); + if ( cacheAccess != null ) { + final Object ck = cacheAccess.generateCacheKey( + id, + persister, + getFactory(), + getTenantIdentifier() + ); + cacheAccess.evict( ck ); + } } + String previousFetchProfile = this.getLoadQueryInfluencers().getInternalFetchProfile(); Object result = null; try { @@ -237,9 +252,12 @@ public void refresh(String entityName, Object entity, LockMode lockMode) { } @Override - public Object immediateLoad(String entityName, Serializable id) - throws HibernateException { - throw new SessionException( "proxies cannot be fetched by a stateless session" ); + public Object immediateLoad(String entityName, Serializable id) throws HibernateException { + if ( getPersistenceContext().isLoadFinished() ) { + throw new SessionException( "proxies cannot be fetched by a stateless session" ); + } + // unless we are still in the process of handling a top-level load + return get( entityName, id ); } @Override @@ -264,21 +282,90 @@ public Object internalLoad( boolean eager, boolean nullable) throws HibernateException { checkOpen(); - EntityPersister persister = getFactory().getMetamodel().entityPersister( entityName ); + + final EntityPersister persister = getFactory().getMetamodel().entityPersister( entityName ); + final EntityKey entityKey = generateEntityKey( id, persister ); + // first, try to load it from the temp PC associated to this SS - Object loaded = temporaryPersistenceContext.getEntity( generateEntityKey( id, persister ) ); + final PersistenceContext persistenceContext = getPersistenceContext(); + Object loaded = persistenceContext.getEntity( entityKey ); if ( loaded != null ) { // we found it in the temp PC. Should indicate we are in the midst of processing a result set // containing eager fetches via join fetch return loaded; } - if ( !eager && persister.hasProxy() ) { - // if the metadata allowed proxy creation and caller did not request forceful eager loading, - // generate a proxy - return persister.createProxy( id, this ); + + if ( !eager ) { + // caller did not request forceful eager loading, see if we can create + // some form of proxy + + // first, check to see if we can use "bytecode proxies" + + final EntityMetamodel entityMetamodel = persister.getEntityMetamodel(); + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata(); + if ( allowBytecodeProxy && bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { + + // if the entity defines a HibernateProxy factory, see if there is an + // existing proxy associated with the PC - and if so, use it + if ( persister.getEntityMetamodel().getTuplizer().getProxyFactory() != null ) { + final Object proxy = persistenceContext.getProxy( entityKey ); + + if ( proxy != null ) { + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Entity proxy found in session cache" ); + } + if ( LOG.isDebugEnabled() && ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUnwrap() ) { + LOG.debug( "Ignoring NO_PROXY to honor laziness" ); + } + + return persistenceContext.narrowProxy( proxy, persister, entityKey, null ); + } + + // specialized handling for entities with subclasses with a HibernateProxy factory + if ( entityMetamodel.hasSubclasses() ) { + // entities with subclasses that define a ProxyFactory can create + // a HibernateProxy. + LOG.debugf( "Creating a HibernateProxy for to-one association with subclasses to honor laziness" ); + return createProxy( entityKey ); + } + return bytecodeEnhancementMetadata.createEnhancedProxy( entityKey, false, this ); + } + else if ( !entityMetamodel.hasSubclasses() ) { + return bytecodeEnhancementMetadata.createEnhancedProxy( entityKey, false, this ); + } + // If we get here, then the entity class has subclasses and there is no HibernateProxy factory. + // The entity will get loaded below. + } + else { + if ( persister.hasProxy() ) { + final Object existingProxy = persistenceContext.getProxy( entityKey ); + if ( existingProxy != null ) { + return persistenceContext.narrowProxy( existingProxy, persister, entityKey, null ); + } + else { + return createProxy( entityKey ); + } + } + } } + // otherwise immediately materialize it - return get( entityName, id ); + + // IMPLEMENTATION NOTE: increment/decrement the load count before/after getting the value + // to ensure that #get does not clear the PersistenceContext. + persistenceContext.beforeLoad(); + try { + return get( entityName, id ); + } + finally { + persistenceContext.afterLoad(); + } + } + + private Object createProxy(EntityKey entityKey) { + final Object proxy = entityKey.getPersister().createProxy( entityKey.getIdentifier(), this ); + getPersistenceContext().addProxy( entityKey, proxy ); + return proxy; } @Override @@ -413,6 +500,18 @@ public EntityPersister getEntityPersister(String entityName, Object object) @Override public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException { checkOpen(); + + final Object result = getPersistenceContext().getEntity( key ); + if ( result != null ) { + return result; + } + + final Object newObject = getInterceptor().getEntity( key.getEntityName(), key.getIdentifier() ); + if ( newObject != null ) { + getPersistenceContext().addEntity( key, newObject ); + return newObject; + } + return null; } @@ -648,6 +747,11 @@ public void afterTransactionCompletion(boolean successful, boolean delayed) { } } + @Override + public boolean isTransactionInProgress() { + return connectionProvided || super.isTransactionInProgress(); + } + @Override public void flushBeforeTransactionCompletion() { boolean flush = false; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/build/AllowPrintStacktrace.java b/hibernate-core/src/main/java/org/hibernate/internal/build/AllowPrintStacktrace.java new file mode 100644 index 000000000000..10a53157bbd6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/build/AllowPrintStacktrace.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.internal.build; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to indicate to the Forbidden APIs library that a specific usage + * of {@link Exception#printStackTrace} is allowable. + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.CLASS ) +public @interface AllowPrintStacktrace { +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/build/AllowSysOut.java b/hibernate-core/src/main/java/org/hibernate/internal/build/AllowSysOut.java new file mode 100644 index 000000000000..68c730a53ae1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/build/AllowSysOut.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.internal.build; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to indicate to the Forbidden APIs library that a specific usage + * of {@link System#out} is allowable. + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.CLASS ) +public @interface AllowSysOut { +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java index 344779fd208d..455b09d2447b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java @@ -24,9 +24,11 @@ @MessageLogger( projectCode = "HHH" ) @ValidIdRange( min = 90000001, max = 90001000 ) public interface DeprecationLogger extends BasicLogger { - public static final DeprecationLogger DEPRECATION_LOGGER = Logger.getMessageLogger( + String CATEGORY = "org.hibernate.orm.deprecation"; + + DeprecationLogger DEPRECATION_LOGGER = Logger.getMessageLogger( DeprecationLogger.class, - "org.hibernate.orm.deprecation" + CATEGORY ); /** @@ -238,4 +240,13 @@ void connectionProviderClassDeprecated( "or [hibernate.connection.release_mode]; use [hibernate.connection.handling_mode] instead" ) void logUseOfDeprecatedConnectionHandlingSettings(); + + @LogMessage(level = WARN) + @Message( + id = 90000024, + value = "Application requested zero be used as the base for JDBC-style parameters found in native-queries; " + + "this is a *temporary* backwards-compatibility setting to help applications using versions prior to " + + "5.3 in upgrading. It will be removed in a later version." + ) + void logUseOfDeprecatedZeroBasedJdbcStyleParams(); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/log/UnsupportedLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/log/UnsupportedLogger.java new file mode 100644 index 000000000000..e0ee907c35b3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/log/UnsupportedLogger.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 . + */ +package org.hibernate.internal.log; + +import org.hibernate.cfg.AvailableSettings; + +import org.jboss.logging.annotations.LogMessage; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; +import org.jboss.logging.annotations.ValidIdRange; + +import static org.jboss.logging.Logger.Level.WARN; + +/** + * Class to consolidate logging about usage of features which should + * never be used. + * Such features might have been introduced for practical reasons so + * that people who really know what they want can use them, with the + * understanding that they should find a better alternative. + * + * @author Sanne Grinovero + */ +@MessageLogger( projectCode = "HHH" ) +@ValidIdRange( min = 90002001, max = 90003000 ) +public interface UnsupportedLogger { + + @LogMessage(level = WARN) + @Message(value = "Global configuration option '" + AvailableSettings.ENFORCE_LEGACY_PROXY_CLASSNAMES + "' was enabled. " + + "Generated proxies will use backwards compatible classnames. This option is unsupported and will be removed.", + id = 90002001) + void usingLegacyClassnamesForProxies(); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/BytesHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/BytesHelper.java index 4d7a779726c5..a822200afb0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/BytesHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/BytesHelper.java @@ -68,16 +68,29 @@ public static byte[] fromInt(int intValue) { */ public static byte[] fromLong(long longValue) { byte[] bytes = new byte[8]; - bytes[0] = (byte) ( longValue >> 56 ); - bytes[1] = (byte) ( ( longValue << 8 ) >> 56 ); - bytes[2] = (byte) ( ( longValue << 16 ) >> 56 ); - bytes[3] = (byte) ( ( longValue << 24 ) >> 56 ); - bytes[4] = (byte) ( ( longValue << 32 ) >> 56 ); - bytes[5] = (byte) ( ( longValue << 40 ) >> 56 ); - bytes[6] = (byte) ( ( longValue << 48 ) >> 56 ); - bytes[7] = (byte) ( ( longValue << 56 ) >> 56 ); + fromLong(longValue, bytes, 0); return bytes; } + + /** + * Interpret a long as its binary form + * + * @param longValue The long to interpret to binary + * @param dest the destination array. + * @param destPos starting position in the destination array. + * @return The binary + */ + public static void fromLong(long longValue, byte[] dest, int destPos) { + + dest[destPos] = (byte) ( longValue >> 56 ); + dest[destPos + 1] = (byte) ( ( longValue << 8 ) >> 56 ); + dest[destPos + 2] = (byte) ( ( longValue << 16 ) >> 56 ); + dest[destPos + 3] = (byte) ( ( longValue << 24 ) >> 56 ); + dest[destPos + 4] = (byte) ( ( longValue << 32 ) >> 56 ); + dest[destPos + 5] = (byte) ( ( longValue << 40 ) >> 56 ); + dest[destPos + 6] = (byte) ( ( longValue << 48 ) >> 56 ); + dest[destPos + 7] = (byte) ( ( longValue << 56 ) >> 56 ); + } /** * Interpret the binary representation of a long. @@ -87,14 +100,27 @@ public static byte[] fromLong(long longValue) { * @return The long */ public static long asLong(byte[] bytes) { + return asLong(bytes, 0); + } + + /** + * Interpret the binary representation of a long. + * + * @param bytes The bytes to interpret. + * @param srcPos starting position in the source array. + * + * @return The long + */ + public static long asLong(byte[] bytes, int srcPos) { if ( bytes == null ) { return 0; } - if ( bytes.length != 8 ) { + final int size = srcPos + 8; + if ( bytes.length < size ) { throw new IllegalArgumentException( "Expecting 8 byte values to construct a long" ); } long value = 0; - for (int i=0; i<8; i++) { + for (int i=srcPos; i. + */ +package org.hibernate.internal.util; + +/** + * @author Vlad Mihalcea + */ +public final class MathHelper { + + private MathHelper() { /* static methods only - hide constructor */ + } + + /** + * Returns the smallest power of two number that is greater than or equal to {@code value}. + * + * @param value reference number + * @return smallest power of two number + */ + public static int ceilingPowerOfTwo(int value) { + return 1 << -Integer.numberOfLeadingZeros(value - 1); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/NullnessHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/NullnessHelper.java new file mode 100644 index 000000000000..416257e60a97 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/NullnessHelper.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.internal.util; + +import java.util.function.Supplier; + +/** + * @author Steve Ebersole + */ +public class NullnessHelper { + + /** + * Operates like SQL coalesce expression, except empty strings are treated as null. Return the first non-empty value + * + * @param values The list of values. + * @param Generic type of values to coalesce + * + * @return The first non-empty value, or null if all values were empty + */ + public static T coalesce(T... values) { + if ( values == null ) { + return null; + } + for ( T value : values ) { + if ( value != null ) { + if ( String.class.isInstance( value ) ) { + if ( !( (String) value ).isEmpty() ) { + return value; + } + } + else { + return value; + } + } + } + return null; + } + + /** + * Find the first non-null value supplied by the given suppliers + */ + public static T coalesceSuppliedValues(Supplier... valueSuppliers) { + if ( valueSuppliers == null ) { + return null; + } + + for ( Supplier valueSupplier : valueSuppliers ) { + final T value = valueSupplier.get(); + if ( value != null ) { + return value; + } + } + + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index 7dca09c84ff9..4fbff725c4bf 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 @@ -7,6 +7,7 @@ package org.hibernate.internal.util; import java.beans.Introspector; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; @@ -14,6 +15,7 @@ import java.lang.reflect.Modifier; import java.util.Locale; import java.util.regex.Pattern; +import javax.persistence.Transient; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; @@ -31,6 +33,7 @@ * * @author Gavin King * @author Steve Ebersole + * @author Chris Cranford */ @SuppressWarnings("unchecked") public final class ReflectHelper { @@ -137,7 +140,7 @@ public static boolean implementsInterface(Class clazz, Class intf) { /** * Perform resolution of a class name. *

    - * Here we first check the context classloader, if one, beforeQuery delegating to + * Here we first check the context classloader, if one, before delegating to * {@link Class#forName(String, boolean, ClassLoader)} using the caller's classloader * * @param name The class name @@ -271,7 +274,7 @@ public static Constructor getDefaultConstructor(Class clazz) throws Pr try { Constructor constructor = clazz.getDeclaredConstructor( NO_PARAM_SIGNATURE ); - constructor.setAccessible( true ); + ensureAccessibility( constructor ); return constructor; } catch ( NoSuchMethodException nme ) { @@ -331,7 +334,7 @@ public static Constructor getConstructor(Class clazz, Type[] types) throws Prope } if ( found ) { numberOfMatchingConstructors ++; - candidate.setAccessible( true ); + ensureAccessibility( candidate ); constructor = candidate; } } @@ -374,23 +377,40 @@ else if ( containerClass == Object.class ) { ); } - field.setAccessible( true ); + ensureAccessibility( field ); + return field; } + public static void ensureAccessibility(AccessibleObject accessibleObject) { + if ( accessibleObject.isAccessible() ) { + return; + } + + accessibleObject.setAccessible( true ); + } + private static Field locateField(Class clazz, String propertyName) { if ( clazz == null || Object.class.equals( clazz ) ) { return null; } try { - return clazz.getDeclaredField( propertyName ); + Field field = clazz.getDeclaredField( propertyName ); + if ( !isStaticField( field ) ) { + return field; + } + return locateField( clazz.getSuperclass(), propertyName ); } catch ( NoSuchFieldException nsfe ) { return locateField( clazz.getSuperclass(), propertyName ); } } + private static boolean isStaticField(Field field) { + return field != null && ( field.getModifiers() & Modifier.STATIC ) == Modifier.STATIC; + } + public static Method findGetterMethod(Class containerClass, String propertyName) { Class checkClass = containerClass; Method getter = null; @@ -402,19 +422,16 @@ public static Method findGetterMethod(Class containerClass, String propertyName) } getter = getGetterOrNull( checkClass, propertyName ); - checkClass = checkClass.getSuperclass(); - } - // if no getter found yet, check all implemented interfaces - if ( getter == null ) { - for ( Class theInterface : containerClass.getInterfaces() ) { - getter = getGetterOrNull( theInterface, propertyName ); - if ( getter != null ) { - break; - } + // if no getter found yet, check all implemented interfaces + if ( getter == null ) { + getter = getGetterOrNull( checkClass.getInterfaces(), propertyName ); } + + checkClass = checkClass.getSuperclass(); } + if ( getter == null ) { throw new PropertyNotFoundException( String.format( @@ -426,7 +443,21 @@ public static Method findGetterMethod(Class containerClass, String propertyName) ); } - getter.setAccessible( true ); + ensureAccessibility( getter ); + + return getter; + } + + private static Method getGetterOrNull(Class[] interfaces, String propertyName) { + Method getter = null; + for ( int i = 0; getter == null && i < interfaces.length; ++i ) { + final Class anInterface = interfaces[i]; + getter = getGetterOrNull( anInterface, propertyName ); + if ( getter == null ) { + // if no getter found yet, check all implemented interfaces of interface + getter = getGetterOrNull( anInterface.getInterfaces(), propertyName ); + } + } return getter; } @@ -442,6 +473,14 @@ private static Method getGetterOrNull(Class containerClass, String propertyName) continue; } + if ( method.getAnnotation( Transient.class ) != null ) { + continue; + } + + if ( Modifier.isStatic( method.getModifiers() ) ) { + continue; + } + final String methodName = method.getName(); // try "get" @@ -477,9 +516,11 @@ private static void verifyNoIsVariantExists( // verify that the Class does not also define a method with the same stem name with 'is' try { final Method isMethod = containerClass.getDeclaredMethod( "is" + stemName ); - // No such method should throw the caught exception. So if we get here, there was - // such a method. - checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); + if ( !Modifier.isStatic( isMethod.getModifiers() ) && isMethod.getAnnotation( Transient.class ) == null ) { + // No such method should throw the caught exception. So if we get here, there was + // such a method. + checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); + } } catch (NoSuchMethodException ignore) { } @@ -517,13 +558,24 @@ private static void verifyNoGetVariantExists( final Method getMethod = containerClass.getDeclaredMethod( "get" + stemName ); // No such method should throw the caught exception. So if we get here, there was // such a method. - checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); + if ( !Modifier.isStatic( getMethod.getModifiers() ) && getMethod.getAnnotation( Transient.class ) == null ) { + checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); + } } catch (NoSuchMethodException ignore) { } } - public static Method findSetterMethod(Class containerClass, String propertyName, Class propertyType) { + public static Method getterMethodOrNull(Class containerJavaType, String propertyName) { + try { + return findGetterMethod( containerJavaType, propertyName ); + } + catch (PropertyNotFoundException e) { + return null; + } + } + + public static Method setterMethodOrNull(final Class containerClass, final String propertyName, final Class propertyType) { Class checkClass = containerClass; Method setter = null; @@ -534,19 +586,22 @@ public static Method findSetterMethod(Class containerClass, String propertyName, } setter = setterOrNull( checkClass, propertyName, propertyType ); - checkClass = checkClass.getSuperclass(); - } - // if no setter found yet, check all implemented interfaces - if ( setter == null ) { - for ( Class theInterface : containerClass.getInterfaces() ) { - setter = setterOrNull( theInterface, propertyName, propertyType ); - if ( setter != null ) { - break; - } + // if no setter found yet, check all implemented interfaces + if ( setter == null ) { + setter = setterOrNull( checkClass.getInterfaces(), propertyName, propertyType ); + } + else { + ensureAccessibility( setter ); } + + checkClass = checkClass.getSuperclass(); } + return setter; // might be null + } + public static Method findSetterMethod(final Class containerClass, final String propertyName, final Class propertyType) { + final Method setter = setterMethodOrNull( containerClass, propertyName, propertyType ); if ( setter == null ) { throw new PropertyNotFoundException( String.format( @@ -557,8 +612,19 @@ public static Method findSetterMethod(Class containerClass, String propertyName, ) ); } + return setter; + } - setter.setAccessible( true ); + private static Method setterOrNull(Class[] interfaces, String propertyName, Class propertyType) { + Method setter = null; + for ( int i = 0; setter == null && i < interfaces.length; ++i ) { + final Class anInterface = interfaces[i]; + setter = setterOrNull( anInterface, propertyName, propertyType ); + if ( setter == null ) { + // if no setter found yet, check all implemented interfaces of interface + setter = setterOrNull( anInterface.getInterfaces(), propertyName, propertyType ); + } + } return setter; } @@ -581,4 +647,51 @@ private static Method setterOrNull(Class theClass, String propertyName, Class pr return potentialSetter; } + + /** + * Similar to {@link #getterMethodOrNull}, except that here we are just looking for the + * corresponding getter for a field (defined as field access) if one exists. + * + * We do not look at supers, although conceivably the super could declare the method + * as an abstract - but again, that is such an edge case... + */ + public static Method findGetterMethodForFieldAccess(Field field, String propertyName) { + for ( Method method : field.getDeclaringClass().getDeclaredMethods() ) { + // if the method has parameters, skip it + if ( method.getParameterCount() != 0 ) { + continue; + } + + if ( Modifier.isStatic( method.getModifiers() ) ) { + continue; + } + + if ( ! method.getReturnType().isAssignableFrom( field.getType() ) ) { + continue; + } + + final String methodName = method.getName(); + + // try "get" + if ( methodName.startsWith( "get" ) ) { + final String stemName = methodName.substring( 3 ); + final String decapitalizedStemName = Introspector.decapitalize( stemName ); + if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) { + return method; + } + + } + + // if not "get", then try "is" + if ( methodName.startsWith( "is" ) ) { + final String stemName = methodName.substring( 2 ); + String decapitalizedStemName = Introspector.decapitalize( stemName ); + if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) { + return method; + } + } + } + + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index 3154732dffcf..4a6651c9736d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -18,6 +18,7 @@ import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.loader.internal.AliasConstantsHelper; public final class StringHelper { @@ -39,23 +40,6 @@ public static int lastIndexOfLetter(String string) { return string.length() - 1; } - public static String join(String seperator, String[] strings) { - int length = strings.length; - if ( length == 0 ) { - return ""; - } - // Allocate space for length * firstStringLength; - // If strings[0] is null, then its length is defined as 4, since that's the - // length of "null". - final int firstStringLength = strings[0] != null ? strings[0].length() : 4; - StringBuilder buf = new StringBuilder( length * firstStringLength ) - .append( strings[0] ); - for ( int i = 1; i < length; i++ ) { - buf.append( seperator ).append( strings[i] ); - } - return buf.toString(); - } - public static String joinWithQualifierAndSuffix( String[] values, String qualifier, @@ -73,7 +57,7 @@ public static String joinWithQualifierAndSuffix( return buf.toString(); } - public static String join(String seperator, Iterator objects) { + public static String join(String seperator, Iterator objects) { StringBuilder buf = new StringBuilder(); if ( objects.hasNext() ) { buf.append( objects.next() ); @@ -84,10 +68,6 @@ public static String join(String seperator, Iterator objects) { return buf.toString(); } - public static String join(String separator, Iterable objects) { - return join( separator, objects.iterator() ); - } - public static String[] add(String[] x, String sep, String[] y) { final String[] result = new String[x.length]; for ( int i = 0; i < x.length; i++ ) { @@ -212,6 +192,28 @@ public static String replace( return buf.toString(); } + /** + * Used to find the ordinal parameters (e.g. '?1') in a string. + */ + public static int indexOfIdentifierWord(String str, String word) { + if ( str == null || str.length() == 0 || word == null || word.length() == 0 ) { + return -1; + } + + int position = str.indexOf( word ); + while ( position >= 0 && position < str.length() ) { + if ( + ( position == 0 || !Character.isJavaIdentifierPart( str.charAt( position - 1 ) ) ) && + ( position + word.length() == str.length() || !Character.isJavaIdentifierPart( str.charAt( position + word.length() ) ) ) + ) { + return position; + } + position = str.indexOf( word, position + 1 ); + } + + return -1; + } + public static char getLastNonWhitespaceCharacter(String str) { if ( str != null && str.length() > 0 ) { for ( int i = str.length() - 1; i >= 0; i-- ) { @@ -340,7 +342,7 @@ public static String partiallyUnqualify(String name, String qualifierBase) { if ( name == null || !name.startsWith( qualifierBase ) ) { return name; } - return name.substring( qualifierBase.length() + 1 ); // +1 to start afterQuery the following '.' + return name.substring( qualifierBase.length() + 1 ); // +1 to start after the following '.' } /** @@ -581,8 +583,7 @@ public static String generateAlias(String description) { */ public static String generateAlias(String description, int unique) { return generateAliasRoot( description ) - + Integer.toString( unique ) - + '_'; + + AliasConstantsHelper.get( unique ); } /** @@ -707,12 +708,15 @@ public static String unquote(String name) { * * @return True if quoted, false otherwise */ - public static boolean isQuoted(String name, Dialect dialect) { - return name != null && name.length() != 0 - && ( ( name.charAt( 0 ) == '`' && name.charAt( name.length() - 1 ) == '`' ) - || ( name.charAt( 0 ) == '"' && name.charAt( name.length() - 1 ) == '"' ) - || ( name.charAt( 0 ) == dialect.openQuote() - && name.charAt( name.length() - 1 ) == dialect.closeQuote() ) ); + public static boolean isQuoted(final String name, final Dialect dialect) { + if ( name == null || name.isEmpty() ) { + return false; + } + final char first = name.charAt( 0 ); + final char last = name.charAt( name.length() - 1 ); + + return ( ( first == last ) && ( first == '`' || first == '"' ) ) + || ( first == dialect.openQuote() && last == dialect.closeQuote() ); } /** @@ -735,15 +739,32 @@ public static String unquote(String name, Dialect dialect) { * * @return The unquoted versions. */ - public static String[] unquote(String[] names, Dialect dialect) { + public static String[] unquote(final String[] names, final Dialect dialect) { if ( names == null ) { return null; } - String[] unquoted = new String[names.length]; - for ( int i = 0; i < names.length; i++ ) { - unquoted[i] = unquote( names[i], dialect ); + int failedIndex = -1; + final int length = names.length; + for ( int i = 0; i < length; i++ ) { + if ( isQuoted( names[i], dialect ) ) { + failedIndex = i; + break; + } + } + if ( failedIndex == -1 ) { + //In this case all strings are already unquoted, so return the same array as the input: + //this is a good optimisation to skip an array copy as typically either all names are consistently quoted, or none are; + //yet for safety we need to deal with mixed scenarios as well. + return names; + } + else { + String[] unquoted = new String[length]; + System.arraycopy( names, 0, unquoted, 0, failedIndex ); + for ( int i = failedIndex; i < length; i++ ) { + unquoted[i] = unquote( names[i], dialect ); + } + return unquoted; } - return unquoted; } @@ -838,18 +859,9 @@ public static List parseCommaSeparatedString(String incomingString) { public static String join(Collection values, Renderer renderer) { final StringBuilder buffer = new StringBuilder(); - boolean firstPass = true; for ( T value : values ) { - if ( firstPass ) { - firstPass = false; - } - else { - buffer.append( ", " ); - } - - buffer.append( renderer.render( value ) ); + buffer.append( String.join(", ", renderer.render( value )) ); } - return buffer.toString(); } @@ -860,4 +872,59 @@ public static String join(T[] values, Renderer renderer) { public interface Renderer { String render(T value); } + + /** + * @param firstExpression the first expression + * @param secondExpression the second expression + * @return if {@code firstExpression} and {@code secondExpression} are both non-empty, + * then "( " + {@code firstExpression} + " ) and ( " + {@code secondExpression} + " )" is returned; + * if {@code firstExpression} is non-empty and {@code secondExpression} is empty, + * then {@code firstExpression} is returned; + * if {@code firstExpression} is empty and {@code secondExpression} is non-empty, + * then {@code secondExpression} is returned; + * if both {@code firstExpression} and {@code secondExpression} are empty, then null is returned. + */ + public static String getNonEmptyOrConjunctionIfBothNonEmpty( String firstExpression, String secondExpression ) { + final boolean isFirstExpressionNonEmpty = StringHelper.isNotEmpty( firstExpression ); + final boolean isSecondExpressionNonEmpty = StringHelper.isNotEmpty( secondExpression ); + if ( isFirstExpressionNonEmpty && isSecondExpressionNonEmpty ) { + final StringBuilder buffer = new StringBuilder(); + buffer.append( "( " ) + .append( firstExpression ) + .append( " ) and ( ") + .append( secondExpression ) + .append( " )" ); + return buffer.toString(); + } + else if ( isFirstExpressionNonEmpty ) { + return firstExpression; + } + else if ( isSecondExpressionNonEmpty ) { + return secondExpression; + } + else { + return null; + } + } + + /** + * Return the interned form of a String, or null if the parameter is null. + *

    + * Use with caution: excessive interning is known to cause issues. + * Best to use only with strings which are known to be long lived constants, + * and for which the chances of being actual duplicates is proven. + * (Even better: avoid needing interning by design changes such as reusing + * the known reference) + * @param string The string to intern. + * @return The interned string. + */ + public static String safeInterning(final String string) { + if ( string == null ) { + return null; + } + else { + return string.intern(); + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ValueHolder.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ValueHolder.java index 4c4e8c54eb00..f124fef985d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ValueHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ValueHolder.java @@ -8,7 +8,7 @@ /** * Represents a "final" value that is initialized either {@link #ValueHolder(Object) up front} or once at some point - * {@link #ValueHolder(ValueHolder.DeferredInitializer) afterQuery} declaration. + * {@link #ValueHolder(ValueHolder.DeferredInitializer) after} declaration. * * Note: If a Serializable class has a {@link ValueHolder} property, that property should be declared transient! * diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java index bd6abf56fbea..a7f48dd26e2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java @@ -17,6 +17,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.internal.build.AllowSysOut; import org.hibernate.type.Type; public final class ArrayHelper { @@ -33,12 +34,12 @@ public static int indexOf(Object[] array, Object object) { } return -1; } - - /*public static Object[] clone(Class elementClass, Object[] array) { - Object[] result = (Object[]) Array.newInstance( elementClass, array.length ); - System.arraycopy(array, 0, result, 0, array.length); - return result; - }*/ + + public static T[] filledArray(T value, Class valueJavaType, int size) { + final T[] array = (T[]) Array.newInstance( valueJavaType, size ); + Arrays.fill( array, value ); + return array; + } public static String[] toStringArray(Object[] objects) { int length = objects.length; @@ -227,14 +228,6 @@ public static int countTrue(boolean... array) { return result; } - /*public static int countFalse(boolean[] array) { - int result=0; - for ( int i=0; i extends AbstractMap /** * Number of unsynchronized retries in size and containsValue - * methods beforeQuery resorting to locking. This is used to avoid + * methods before resorting to locking. This is used to avoid * unbounded retries if tables undergo continuous modification * which would make it impossible to obtain an accurate result. */ @@ -796,7 +796,7 @@ private void removeFromStack() { } /** - * Inserts this entry beforeQuery the specified existing entry in the stack. + * Inserts this entry before the specified existing entry in the stack. */ private void addToStackBefore(LIRSHashEntry existingEntry) { previousInStack = existingEntry.previousInStack; @@ -843,7 +843,7 @@ private void removeFromQueue() { } /** - * Inserts this entry beforeQuery the specified existing entry in the queue. + * Inserts this entry before the specified existing entry in the queue. */ private void addToQueueBefore(LIRSHashEntry existingEntry) { previousInQueue = existingEntry.previousInQueue; @@ -1164,7 +1164,7 @@ static final class Segment extends ReentrantLock { * it is 0. * * - All (synchronized) write operations should write to - * the "count" field afterQuery structurally changing any bin. + * the "count" field after structurally changing any bin. * The operations must not take any action that could even * momentarily cause a concurrent read operation to see * inconsistent data. This is made easier by the nature of @@ -1763,7 +1763,7 @@ public boolean isEmpty() { } } // If mcsum happens to be zero, then we know we got a snapshot - // beforeQuery any modifications at all were made. This is + // before any modifications at all were made. This is // probably common enough to bother tracking. if ( mcsum != 0 ) { for ( int i = 0; i < segments.length; ++i ) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ConcurrentReferenceHashMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ConcurrentReferenceHashMap.java index 0c87c4247b3d..f44b75b3bfe0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ConcurrentReferenceHashMap.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ConcurrentReferenceHashMap.java @@ -65,7 +65,7 @@ * that a value never refers, either directly or indirectly, to its key, thereby * preventing reclamation. If this is unavoidable, then it is recommended to use * the same reference type in use for the key. However, it should be noted that - * non-strong values may disappear beforeQuery their corresponding key. + * non-strong values may disappear before their corresponding key. *

    * While this table does allow the use of both strong keys and values, it is * recommended to use {@link java.util.concurrent.ConcurrentHashMap} for such a @@ -211,7 +211,7 @@ public static enum Option { /** * Number of unsynchronized retries in size and containsValue - * methods beforeQuery resorting to locking. This is used to avoid + * methods before resorting to locking. This is used to avoid * unbounded retries if tables undergo continuous modification * which would make it impossible to obtain an accurate result. */ @@ -489,7 +489,7 @@ static final class Segment extends ReentrantLock implements Serializable { * it is 0. * * - All (synchronized) write operations should write to - * the "count" field afterQuery structurally changing any bin. + * the "count" field after structurally changing any bin. * The operations must not take any action that could even * momentarily cause a concurrent read operation to see * inconsistent data. This is made easier by the nature of @@ -1110,7 +1110,7 @@ public boolean isEmpty() { } } // If mcsum happens to be zero, then we know we got a snapshot - // beforeQuery any modifications at all were made. This is + // before any modifications at all were made. This is // probably common enough to bother tracking. if ( mcsum != 0 ) { for ( int i = 0; i < segments.length; ++i ) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/EmptyIterator.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/EmptyIterator.java deleted file mode 100644 index b62062d5edd4..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/EmptyIterator.java +++ /dev/null @@ -1,32 +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.internal.util.collections; - -import java.util.Iterator; - -/** - * @author Gavin King - */ -public final class EmptyIterator implements Iterator { - - public static final Iterator INSTANCE = new EmptyIterator(); - - public boolean hasNext() { - return false; - } - - public Object next() { - throw new UnsupportedOperationException(); - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - private EmptyIterator() {} - -} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java index fbd9ac38267e..eeda7b1ab1e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java @@ -13,16 +13,17 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; /** * A Map where keys are compared by object identity, * rather than equals(). */ public final class IdentityMap implements Map { - private final Map,V> map; + + private final LinkedHashMap,V> map; @SuppressWarnings( {"unchecked"}) - private transient Entry,V>[] entryArray = new Entry[0]; - private transient boolean dirty; + private transient Map.Entry,V>[] entryArray = null; /** * Return a new instance of this class, with iteration @@ -32,7 +33,7 @@ public final class IdentityMap implements Map { * @return The map */ public static IdentityMap instantiateSequenced(int size) { - return new IdentityMap( new LinkedHashMap,V>( size ) ); + return new IdentityMap( new LinkedHashMap<>( size << 1, 0.6f ) ); } /** @@ -40,9 +41,8 @@ public static IdentityMap instantiateSequenced(int size) { * * @param underlyingMap The delegate map. */ - private IdentityMap(Map,V> underlyingMap) { + private IdentityMap(LinkedHashMap,V> underlyingMap) { map = underlyingMap; - dirty = true; } /** @@ -57,6 +57,11 @@ public static Map.Entry[] concurrentEntries(Map map) { return ( (IdentityMap) map ).entryArray(); } + public static void onEachKey(Map map, Consumer consumer) { + final IdentityMap identityMap = (IdentityMap) map; + identityMap.map.forEach( (kIdentityKey, v) -> consumer.accept( kIdentityKey.key ) ); + } + public Iterator keyIterator() { return new KeyIterator( map.keySet().iterator() ); } @@ -79,26 +84,27 @@ public boolean containsKey(Object key) { @Override public boolean containsValue(Object val) { - return map.containsValue(val); + throw new UnsupportedOperationException( "Avoid this operation: does not perform well" ); + //return map.containsValue( val ); } @Override @SuppressWarnings( {"unchecked"}) public V get(Object key) { - return map.get( new IdentityKey(key) ); + return map.get( new IdentityKey( key ) ); } @Override public V put(K key, V value) { - dirty = true; - return map.put( new IdentityKey(key), value ); + this.entryArray = null; + return map.put( new IdentityKey( key ), value ); } @Override @SuppressWarnings( {"unchecked"}) public V remove(Object key) { - dirty = true; - return map.remove( new IdentityKey(key) ); + this.entryArray = null; + return map.remove( new IdentityKey( key ) ); } @Override @@ -110,7 +116,6 @@ public void putAll(Map otherMap) { @Override public void clear() { - dirty = true; entryArray = null; map.clear(); } @@ -118,6 +123,7 @@ public void clear() { @Override public Set keySet() { // would need an IdentitySet for this! + // (and we just don't use this method so it's ok) throw new UnsupportedOperationException(); } @@ -130,22 +136,21 @@ public Collection values() { public Set> entrySet() { Set> set = new HashSet>( map.size() ); for ( Entry, V> entry : map.entrySet() ) { - set.add( new IdentityMapEntry( entry.getKey().getRealKey(), entry.getValue() ) ); + set.add( new IdentityMapEntry( entry.getKey().key, entry.getValue() ) ); } return set; } @SuppressWarnings( {"unchecked"}) public Map.Entry[] entryArray() { - if (dirty) { + if ( entryArray == null ) { entryArray = new Map.Entry[ map.size() ]; - Iterator itr = map.entrySet().iterator(); - int i=0; + final Iterator, V>> itr = map.entrySet().iterator(); + int i = 0; while ( itr.hasNext() ) { - Map.Entry me = (Map.Entry) itr.next(); - entryArray[i++] = new IdentityMapEntry( ( (IdentityKey) me.getKey() ).key, me.getValue() ); + final Entry, V> me = itr.next(); + entryArray[i++] = new IdentityMapEntry( me.getKey().key, me.getValue() ); } - dirty = false; } return entryArray; } @@ -155,7 +160,7 @@ public String toString() { return map.toString(); } - static final class KeyIterator implements Iterator { + private static final class KeyIterator implements Iterator { private final Iterator> identityKeyIterator; private KeyIterator(Iterator> iterator) { @@ -167,7 +172,7 @@ public boolean hasNext() { } public K next() { - return identityKeyIterator.next().getRealKey(); + return identityKeyIterator.next().key; } public void remove() { @@ -175,9 +180,11 @@ public void remove() { } } - public static final class IdentityMapEntry implements java.util.Map.Entry { + + private static final class IdentityMapEntry implements java.util.Map.Entry { + private final K key; - private V value; + private final V value; IdentityMapEntry(final K key, final V value) { this.key=key; @@ -193,21 +200,16 @@ public V getValue() { } public V setValue(final V value) { - V result = this.value; - this.value = value; - return result; + throw new UnsupportedOperationException(); } } /** - * We need to base the identity on {@link System#identityHashCode(Object)} but - * attempt to lazily initialize and cache this value: being a native invocation - * it is an expensive value to retrieve. + * We need to base the identity on {@link System#identityHashCode(Object)} */ - public static final class IdentityKey implements Serializable { + private static final class IdentityKey implements Serializable { private final K key; - private int hash; IdentityKey(K key) { this.key = key; @@ -221,21 +223,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - if ( this.hash == 0 ) { - //We consider "zero" as non-initialized value - final int newHash = System.identityHashCode( key ); - if ( newHash == 0 ) { - //So make sure we don't store zeros as it would trigger initialization again: - //any value is fine as long as we're deterministic. - this.hash = -1; - return -1; - } - else { - this.hash = newHash; - return newHash; - } - } - return hash; + return System.identityHashCode( key ); } @Override @@ -243,9 +231,6 @@ public String toString() { return key.toString(); } - public K getRealKey() { - return key; - } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java index ea3bcd1afe43..80db86d8f5f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java @@ -6,12 +6,13 @@ */ package org.hibernate.internal.util.collections; +import java.util.Collections; import java.util.Iterator; import java.util.List; /** - * An JoinedIterable is an Iterable that wraps a number of Iterables. + * A JoinedIterable is an Iterable that wraps a number of Iterables. * * This class makes multiple iterables look like one to the caller. * When any method from the Iterator interface is called on the @@ -69,20 +70,20 @@ public void remove() { lastUsedIterator.remove(); } - // call this beforeQuery any Iterator method to make sure that the current Iterator + // call this before any Iterator method to make sure that the current Iterator // is not exhausted @SuppressWarnings( {"unchecked"}) protected void updateCurrentIterator() { if ( currentIterator == null) { if( iterables.size() == 0 ) { - currentIterator = EmptyIterator.INSTANCE; + currentIterator = Collections.emptyIterator(); } else { currentIterator = iterables.get( 0 ).iterator(); } // set last used iterator here, in case the user calls remove - // beforeQuery calling hasNext() or next() (although they shouldn't) + // before calling hasNext() or next() (although they shouldn't) lastUsedIterator = currentIterator; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterator.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterator.java index d121151214b3..5ece18e46924 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterator.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterator.java @@ -53,7 +53,7 @@ public void remove() { } - // call this beforeQuery any Iterator method to make sure that the current Iterator + // call this before any Iterator method to make sure that the current Iterator // is not exhausted protected void updateCurrentIterator() { if ( currentIterator == null ) { @@ -64,7 +64,7 @@ protected void updateCurrentIterator() { currentIterator = wrappedIterators[0]; } // set last used iterator here, in case the user calls remove - // beforeQuery calling hasNext() or next() (although they shouldn't) + // before calling hasNext() or next() (although they shouldn't) lastUsedIterator = currentIterator; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/compare/EqualsHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/compare/EqualsHelper.java deleted file mode 100644 index 6a83a58b743b..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/compare/EqualsHelper.java +++ /dev/null @@ -1,101 +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.internal.util.compare; - -import java.util.Arrays; - -/** - * Helper for equality determination - * - * @author Gavin King - * @author Steve Ebersole - */ -public final class EqualsHelper { - - @SuppressWarnings("SimplifiableIfStatement") - public static boolean equals(final Object x, final Object y) { - if ( x == y ) { - return true; - } - - if ( x == null || y == null ) { - // One is null, but the other is not (otherwise the `x == y` check would have passed). - // null can never equal a non-null - return false; - } - - return x.equals( y ); - } - - /** - * Like the legacy {@link #equals} method, but handles array-specific checks - * - * @param x One value to check - * @param y The other value - * - * @return {@code true} if the 2 values are equal; {@code false} otherwise. - */ - public static boolean areEqual(final Object x, final Object y) { - if ( x == y ) { - return true; - } - - if ( x == null || y == null ) { - // One is null, but the other is not (otherwise the `x == y` check would have passed). - // null can never equal a non-null - return false; - } - - if ( x.equals( y ) ) { - return true; - } - - // Check for possibility of arrays - final Class xClass = x.getClass(); - final Class yClass = y.getClass(); - - if ( xClass.isArray() && yClass.isArray() ) { - if ( xClass.equals( yClass ) ) { - if ( x instanceof boolean[] ) { - return Arrays.equals( (boolean[]) x, (boolean[]) y ); - } - else if ( x instanceof byte[] ) { - return Arrays.equals( (byte[]) x, (byte[]) y ); - } - else if ( x instanceof char[] ) { - return Arrays.equals( (char[]) x, (char[]) y ); - } - else if ( x instanceof short[] ) { - return Arrays.equals( (short[]) x, (short[]) y ); - } - else if ( x instanceof int[] ) { - return Arrays.equals( (int[]) x, (int[]) y ); - } - else if ( x instanceof long[] ) { - return Arrays.equals( (long[]) x, (long[]) y ); - } - else if ( x instanceof double[] ) { - return Arrays.equals( (double[]) x, (double[]) y ); - } - else if ( x instanceof float[] ) { - return Arrays.equals( (float[]) x, (float[]) y ); - } - } - return Arrays.equals( (Object[]) x, (Object[]) y ); - } - - return false; - } - - /** - * Private ctor - disallow instantiation - */ - private EqualsHelper() { - // disallow instantiation - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java index 98b7ec7fba4b..186efba22df1 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java @@ -82,7 +82,7 @@ public static String getString(String name, Map values, String defaultValue, Str if ( !defaultValue.equals( value ) && ArrayHelper.indexOf( otherSupportedValues, value ) == -1 ) { throw new ConfigurationException( "Unsupported configuration [name=" + name + ", value=" + value + "]. " + - "Choose value between: '" + defaultValue + "', '" + StringHelper.join( "', '", otherSupportedValues ) + "'." + "Choose value between: '" + defaultValue + "', '" + String.join( "', '", otherSupportedValues ) + "'." ); } return value; @@ -110,19 +110,32 @@ public static boolean getBoolean(String name, Map values) { * @return The value. */ public static boolean getBoolean(String name, Map values, boolean defaultValue) { - Object value = values.get( name ); + final Object raw = values.get( name ); + + final Boolean value = toBoolean( raw, defaultValue ); + if ( value == null ) { + throw new ConfigurationException( + "Could not determine how to handle configuration raw [name=" + name + ", value=" + raw + "] as boolean" + ); + } + + return value; + } + + public static Boolean toBoolean(Object value, boolean defaultValue) { if ( value == null ) { return defaultValue; } + if ( Boolean.class.isInstance( value ) ) { - return ( (Boolean) value ).booleanValue(); + return (Boolean) value; } + if ( String.class.isInstance( value ) ) { return Boolean.parseBoolean( (String) value ); } - throw new ConfigurationException( - "Could not determine how to handle configuration value [name=" + name + ", value=" + value + "] as boolean" - ); + + return null; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BufferedXMLEventReader.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BufferedXMLEventReader.java index e5e3a9cb674a..cf9d0b809085 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BufferedXMLEventReader.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BufferedXMLEventReader.java @@ -165,7 +165,7 @@ public int bufferSize() { } /** - * If reading from the buffer afterQuery a {@link #reset()} call an {@link IllegalStateException} will be thrown. + * If reading from the buffer after a {@link #reset()} call an {@link IllegalStateException} will be thrown. */ @Override public void remove() { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/FilteringXMLEventReader.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/FilteringXMLEventReader.java index 5153ab935851..a57cbb27d7f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/FilteringXMLEventReader.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/FilteringXMLEventReader.java @@ -19,7 +19,7 @@ /** * Base class for {@link XMLEventReader}s that want to modify or remove events from the reader stream. * If a {@link StartElement} event is removed the subclass's {@link #filterEvent(XMLEvent, boolean)} will - * not see any events until afterQuery the matching {@link EndElement} event. + * not see any events until after the matching {@link EndElement} event. * * Note, copied from the uPortal project by permission of author. See * https://github.com/Jasig/uPortal/blob/master/uportal-war/src/main/java/org/jasig/portal/xml/stream/FilteringXMLEventReader.java diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java deleted file mode 100644 index 4eb276dbfca2..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java +++ /dev/null @@ -1,57 +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.internal.util.xml; - -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; - -import org.dom4j.DocumentFactory; -import org.dom4j.io.SAXReader; -import org.xml.sax.EntityResolver; - -/** - * Small helper class that lazy loads DOM and SAX reader and keep them for fast use afterwards. - * - * @deprecated Currently only used for integration with HCANN. The rest of Hibernate uses StAX now - * for XML processing. See {@link org.hibernate.boot.jaxb.internal.stax} - */ -@Deprecated -public final class XMLHelper { - private final DocumentFactory documentFactory; - - public XMLHelper(ClassLoaderService classLoaderService) { - this.documentFactory = classLoaderService.workWithClassLoader( - new ClassLoaderService.Work() { - @Override - public DocumentFactory doWork(ClassLoader classLoader) { - final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader( classLoader ); - return DocumentFactory.getInstance(); - } - finally { - Thread.currentThread().setContextClassLoader( originalTccl ); - } - } - } - ); - - } - - public DocumentFactory getDocumentFactory() { - return documentFactory; - } - - public SAXReader createSAXReader(ErrorLogger errorLogger, EntityResolver entityResolver) { - SAXReader saxReader = new SAXReader(); - saxReader.setMergeAdjacentText( true ); - saxReader.setValidation( true ); - saxReader.setErrorHandler( errorLogger ); - saxReader.setEntityResolver( entityResolver ); - - return saxReader; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java index fcc9844c7cde..64b7967e1cec 100644 --- a/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java @@ -23,6 +23,7 @@ import org.hibernate.jmx.spi.JmxService; import org.hibernate.service.Service; import org.hibernate.service.spi.Manageable; +import org.hibernate.service.spi.OptionallyManageable; import org.hibernate.service.spi.Stoppable; /** @@ -106,6 +107,13 @@ public void stop() { @Override public void registerService(Manageable service, Class serviceRole) { + if ( OptionallyManageable.class.isInstance( service ) ) { + for ( Manageable realManageable : ( (OptionallyManageable) service ).getRealManageables() ) { + registerService( realManageable,serviceRole ); + } + return; + } + final String domain = service.getManagementDomain() == null ? AvailableSettings.JMX_DEFAULT_OBJ_NAME_DOMAIN : service.getManagementDomain(); @@ -173,7 +181,7 @@ private MBeanServer findServer() { } for ( MBeanServer mbeanServer : mbeanServers ) { - // they did specify a domain, so attempt to locate an MBEanServer with a matching default domain, returning it + // they did specify a domain, so attempt to locate an MBeanServer with a matching default domain, returning it // if we find it. if ( defaultDomain.equals( mbeanServer.getDefaultDomain() ) ) { return mbeanServer; diff --git a/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java b/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java index d4895f6a53d2..5050696583c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java +++ b/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java @@ -23,7 +23,7 @@ public interface JmxService extends Service { * @param service The manageable service * @param serviceRole The service's role. */ - public void registerService(Manageable service, Class serviceRole); + void registerService(Manageable service, Class serviceRole); /** * Registers the given {@code mBean} under the given {@code objectName} @@ -31,5 +31,5 @@ public interface JmxService extends Service { * @param objectName The name under which to register the MBean * @param mBean The MBean to register */ - public void registerMBean(ObjectName objectName, Object mBean); + void registerMBean(ObjectName objectName, Object mBean); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java index f924981b9b1b..d8a9a9769599 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java @@ -62,10 +62,10 @@ public interface AvailableSettings { String JDBC_USER = org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER; /** - * @deprecated (since 5.2) use {@link org.hibernate.cfg.AvailableSettings#JPA_JDBC_USER} instead + * @deprecated (since 5.2) use {@link org.hibernate.cfg.AvailableSettings#JPA_JDBC_PASSWORD} instead */ @Deprecated - String JDBC_PASSWORD = org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER; + String JDBC_PASSWORD = org.hibernate.cfg.AvailableSettings.JPA_JDBC_PASSWORD; /** * @deprecated (since 5.2) use {@link org.hibernate.cfg.AvailableSettings#JPA_SHARED_CACHE_MODE} instead @@ -228,6 +228,27 @@ public interface AvailableSettings { // Hibernate specific settings // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * Setting that indicates whether to build the JPA types. Accepts + * 3 values:

      + *
    • + * enabled - Do the build + *
    • + *
    • + * disabled - Do not so the build + *
    • + *
    • + * ignoreUnsupported - Do the build, but ignore any non-JPA features that would otherwise + * result in a failure. + *
    • + *
    + * + * @deprecated use {@link org.hibernate.cfg.AvailableSettings#STATIC_METAMODEL_POPULATION} instead + */ + @Deprecated + String JPA_METAMODEL_POPULATION = "hibernate.ejb.metamodel.population"; + /** * @deprecated (since 5.2) use {@link org.hibernate.cfg.AvailableSettings#INTERCEPTOR} instead */ diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/HibernateEntityManagerFactory.java b/hibernate-core/src/main/java/org/hibernate/jpa/HibernateEntityManagerFactory.java index 4024fc2d368d..d1416ce7e2d9 100755 --- a/hibernate-core/src/main/java/org/hibernate/jpa/HibernateEntityManagerFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/HibernateEntityManagerFactory.java @@ -13,6 +13,7 @@ import javax.persistence.metamodel.EntityType; import org.hibernate.Metamodel; +import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; /** @@ -50,6 +51,22 @@ default SessionFactoryImplementor getSessionFactory() { @Override Metamodel getMetamodel(); + /** + * Returns the name of the factory. The name is either can be specified via the property hibernate.ejb.entitymanager_factory_name. + * If the property is not set the persistence unit name is used. If persistence unit name is not available, a unique + * name will be generated. + * + * @return the name of the factory. + * + * @deprecated - no longer necessary. all references can be directly replaced with + * calls to {@link SessionFactoryOptions#getSessionFactoryName()} + * via {@link #getSessionFactory()} -> {@link SessionFactoryImplementor#getSessionFactoryOptions()} + */ + @Deprecated + default String getEntityManagerFactoryName() { + return (String) getProperties().get( AvailableSettings.ENTITY_MANAGER_FACTORY_NAME ); + } + /** * Find an entity type by name * diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java b/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java index cfbefbc7cd9a..dd76d476478f 100755 --- a/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java @@ -47,23 +47,13 @@ public class HibernatePersistenceProvider implements PersistenceProvider { @Override public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map properties) { log.tracef( "Starting createEntityManagerFactory for persistenceUnitName %s", persistenceUnitName ); - - try { - final EntityManagerFactoryBuilder builder = getEntityManagerFactoryBuilderOrNull( persistenceUnitName, properties ); - if ( builder == null ) { - log.trace( "Could not obtain matching EntityManagerFactoryBuilder, returning null" ); - return null; - } - else { - return builder.build(); - } - } - catch (PersistenceException pe) { - throw pe; + final EntityManagerFactoryBuilder builder = getEntityManagerFactoryBuilderOrNull( persistenceUnitName, properties ); + if ( builder == null ) { + log.trace( "Could not obtain matching EntityManagerFactoryBuilder, returning null" ); + return null; } - catch (Exception e) { - log.debug( "Unable to build entity manager factory", e ); - throw new PersistenceException( "Unable to build entity manager factory", e ); + else { + return builder.build(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java b/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java index 45832904d4eb..dfc654293dcf 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java @@ -19,6 +19,7 @@ import static org.hibernate.annotations.QueryHints.FOLLOW_ON_LOCKING; import static org.hibernate.annotations.QueryHints.LOADGRAPH; import static org.hibernate.annotations.QueryHints.NATIVE_LOCKMODE; +import static org.hibernate.annotations.QueryHints.NATIVE_SPACES; import static org.hibernate.annotations.QueryHints.PASS_DISTINCT_THROUGH; import static org.hibernate.annotations.QueryHints.READ_ONLY; import static org.hibernate.annotations.QueryHints.TIMEOUT_HIBERNATE; @@ -26,8 +27,6 @@ /** * Defines the supported JPA query hints - * - * @author Steve Ebersole */ public class QueryHints { /** @@ -105,10 +104,13 @@ public class QueryHints { public static final String HINT_PASS_DISTINCT_THROUGH = PASS_DISTINCT_THROUGH; + public static final String HINT_NATIVE_SPACES = NATIVE_SPACES; + + private static final Set HINTS = buildHintsSet(); private static Set buildHintsSet() { - HashSet hints = new HashSet(); + HashSet hints = new HashSet<>(); hints.add( HINT_TIMEOUT ); hints.add( SPEC_HINT_TIMEOUT ); hints.add( HINT_COMMENT ); @@ -121,6 +123,7 @@ private static Set buildHintsSet() { hints.add( HINT_NATIVE_LOCKMODE ); hints.add( HINT_FETCHGRAPH ); hints.add( HINT_LOADGRAPH ); + hints.add( HINT_NATIVE_SPACES ); return java.util.Collections.unmodifiableSet( hints ); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index 2cb933e3ddbd..734b84d75982 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -24,19 +24,15 @@ import javax.persistence.spi.PersistenceUnitTransactionType; import javax.sql.DataSource; - import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.boot.CacheRegionDefinition; -import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.archive.scan.internal.StandardScanOptions; -import org.hibernate.boot.cfgxml.internal.ConfigLoader; import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService; import org.hibernate.boot.cfgxml.spi.LoadedConfig; import org.hibernate.boot.cfgxml.spi.MappingReference; -import org.hibernate.boot.model.TypeContributor; import org.hibernate.boot.model.process.spi.ManagedResources; import org.hibernate.boot.model.process.spi.MetadataBuildingProcess; import org.hibernate.boot.registry.BootstrapServiceRegistry; @@ -47,6 +43,7 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.boot.spi.MetadataBuilderContributor; import org.hibernate.boot.spi.MetadataBuilderImplementor; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryBuilderImplementor; @@ -61,20 +58,22 @@ import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; import org.hibernate.integrator.spi.Integrator; import org.hibernate.internal.EntityManagerMessageLogger; +import org.hibernate.internal.util.NullnessHelper; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.IntegratorProvider; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.boot.spi.StrategyRegistrationProviderList; import org.hibernate.jpa.boot.spi.TypeContributorList; -import org.hibernate.jpa.event.spi.JpaIntegrator; import org.hibernate.jpa.internal.util.LogHelper; import org.hibernate.jpa.internal.util.PersistenceUnitTransactionTypeHelper; import org.hibernate.jpa.spi.IdentifierGeneratorStrategyProvider; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl; +import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.secure.spi.GrantedPermission; import org.hibernate.secure.spi.JaccPermissionDeclarations; import org.hibernate.service.ServiceRegistry; @@ -87,6 +86,7 @@ import static org.hibernate.cfg.AvailableSettings.DATASOURCE; import static org.hibernate.cfg.AvailableSettings.DRIVER; import static org.hibernate.cfg.AvailableSettings.JACC_CONTEXT_ID; +import static org.hibernate.cfg.AvailableSettings.JACC_ENABLED; import static org.hibernate.cfg.AvailableSettings.JACC_PREFIX; import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_DRIVER; import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_PASSWORD; @@ -133,6 +133,11 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil */ public static final String TYPE_CONTRIBUTORS = "hibernate.type_contributors"; + /** + * Names a {@link MetadataBuilderImplementor} + */ + public static final String METADATA_BUILDER_CONTRIBUTOR = "hibernate.metadata_builder_contributor"; + /** * Names a Jandex {@link Index} instance to use. */ @@ -195,24 +200,31 @@ private EntityManagerFactoryBuilderImpl( final BootstrapServiceRegistry bsr = buildBootstrapServiceRegistry( integrationSettings, providedClassLoader, providedClassLoaderService); // merge configuration sources and build the "standard" service registry - final StandardServiceRegistryBuilder ssrBuilder = new StandardServiceRegistryBuilder( bsr ); + final StandardServiceRegistryBuilder ssrBuilder = StandardServiceRegistryBuilder.forJpa( bsr ); + final MergedSettings mergedSettings = mergeSettings( persistenceUnit, integrationSettings, ssrBuilder ); + + // flush before completion validation + if ( "true".equals( mergedSettings.configurationValues.get( Environment.FLUSH_BEFORE_COMPLETION ) ) ) { + LOG.definingFlushBeforeCompletionIgnoredInHem( Environment.FLUSH_BEFORE_COMPLETION ); + mergedSettings.configurationValues.put( Environment.FLUSH_BEFORE_COMPLETION, "false" ); + } + + // keep the merged config values for phase-2 this.configurationValues = mergedSettings.getConfigurationValues(); // Build the "standard" service registry ssrBuilder.applySettings( configurationValues ); - configure( ssrBuilder ); + this.standardServiceRegistry = ssrBuilder.build(); - configure( standardServiceRegistry, mergedSettings ); + + configureIdentifierGenerators( standardServiceRegistry ); final MetadataSources metadataSources = new MetadataSources( bsr ); - List attributeConverterDefinitions = populate( - metadataSources, - mergedSettings, - standardServiceRegistry - ); + List attributeConverterDefinitions = applyMappingResources( metadataSources ); + this.metamodelBuilder = (MetadataBuilderImplementor) metadataSources.getMetadataBuilder( standardServiceRegistry ); - populate( metamodelBuilder, mergedSettings, standardServiceRegistry, attributeConverterDefinitions ); + applyMetamodelBuilderSettings( mergedSettings, attributeConverterDefinitions ); // todo : would be nice to have MetadataBuilder still do the handling of CfgXmlAccessService here // another option is to immediately handle them here (probably in mergeSettings?) as we encounter them... @@ -227,9 +239,12 @@ private EntityManagerFactoryBuilderImpl( this.managedResources = MetadataBuildingProcess.prepare( metadataSources, - metamodelBuilder.getMetadataBuildingOptions() + metamodelBuilder.getBootstrapContext() ); + applyMetadataBuilderContributor(); + + withValidatorFactory( configurationValues.get( org.hibernate.cfg.AvailableSettings.JPA_VALIDATION_FACTORY ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -254,9 +269,53 @@ private EntityManagerFactoryBuilderImpl( metamodelBuilder.applyTempClassLoader( null ); } + private void applyMetadataBuilderContributor() { + + Object metadataBuilderContributorSetting = configurationValues.get( METADATA_BUILDER_CONTRIBUTOR ); + + if ( metadataBuilderContributorSetting == null ) { + return; + } + + MetadataBuilderContributor metadataBuilderContributor = null; + Class metadataBuilderContributorImplClass = null; + + if ( metadataBuilderContributorSetting instanceof MetadataBuilderContributor ) { + metadataBuilderContributor = (MetadataBuilderContributor) metadataBuilderContributorSetting; + } + else if ( metadataBuilderContributorSetting instanceof Class ) { + metadataBuilderContributorImplClass = (Class) metadataBuilderContributorSetting; + } + else if ( metadataBuilderContributorSetting instanceof String ) { + final ClassLoaderService classLoaderService = standardServiceRegistry.getService( ClassLoaderService.class ); + + metadataBuilderContributorImplClass = classLoaderService.classForName( (String) metadataBuilderContributorSetting ); + } + else { + throw new IllegalArgumentException( + "The provided " + METADATA_BUILDER_CONTRIBUTOR + " setting value [" + metadataBuilderContributorSetting + "] is not supported!" + ); + } + + if ( metadataBuilderContributorImplClass != null ) { + try { + metadataBuilderContributor = metadataBuilderContributorImplClass.newInstance(); + } + catch (InstantiationException | IllegalAccessException e) { + throw new IllegalArgumentException( + "The MetadataBuilderContributor class [" + metadataBuilderContributorImplClass + "] could not be instantiated!", + e + ); + } + } + + if ( metadataBuilderContributor != null ) { + metadataBuilderContributor.contribute( metamodelBuilder ); + } + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // temporary! - @SuppressWarnings("unchecked") public Map getConfigurationValues() { return Collections.unmodifiableMap( configurationValues ); } @@ -275,7 +334,10 @@ private boolean readBooleanConfigurationValue(String propertyName) { * @param associationManagementEnabled To enable association management feature * @return An enhancement context for classes managed by this EM */ - protected EnhancementContext getEnhancementContext(final boolean dirtyTrackingEnabled, final boolean lazyInitializationEnabled, final boolean associationManagementEnabled ) { + protected EnhancementContext getEnhancementContext( + final boolean dirtyTrackingEnabled, + final boolean lazyInitializationEnabled, + final boolean associationManagementEnabled ) { return new DefaultEnhancementContext() { @Override @@ -333,8 +395,6 @@ private BootstrapServiceRegistry buildBootstrapServiceRegistry( ClassLoaderService providedClassLoaderService) { final BootstrapServiceRegistryBuilder bsrBuilder = new BootstrapServiceRegistryBuilder(); - bsrBuilder.applyIntegrator( new JpaIntegrator() ); - final IntegratorProvider integratorProvider = (IntegratorProvider) integrationSettings.get( INTEGRATOR_PROVIDER ); if ( integratorProvider != null ) { for ( Integrator integrator : integratorProvider.getIntegrators() ) { @@ -405,62 +465,25 @@ else if ( ClassLoader.class.isInstance( classLoadersSetting ) ) { return bsrBuilder.build(); } - @SuppressWarnings("unchecked") private MergedSettings mergeSettings( PersistenceUnitDescriptor persistenceUnit, Map integrationSettings, StandardServiceRegistryBuilder ssrBuilder) { final MergedSettings mergedSettings = new MergedSettings(); - - // first, apply persistence.xml-defined settings - if ( persistenceUnit.getProperties() != null ) { - mergedSettings.configurationValues.putAll( persistenceUnit.getProperties() ); - } - - mergedSettings.configurationValues.put( PERSISTENCE_UNIT_NAME, persistenceUnit.getName() ); - - final ConfigLoader configLoader = new ConfigLoader( ssrBuilder.getBootstrapServiceRegistry() ); + mergedSettings.processPersistenceUnitDescriptorProperties( persistenceUnit ); // see if the persistence.xml settings named a Hibernate config file.... - final String cfgXmlResourceName1 = (String) mergedSettings.configurationValues.remove( CFG_FILE ); - if ( StringHelper.isNotEmpty( cfgXmlResourceName1 ) ) { - final LoadedConfig loadedCfg = configLoader.loadConfigXmlResource( cfgXmlResourceName1 ); - processConfigXml( loadedCfg, mergedSettings, ssrBuilder ); + String cfgXmlResourceName = (String) mergedSettings.configurationValues.remove( CFG_FILE ); + if ( StringHelper.isEmpty( cfgXmlResourceName ) ) { + // see if integration settings named a Hibernate config file.... + cfgXmlResourceName = (String) integrationSettings.get( CFG_FILE ); } - // see if integration settings named a Hibernate config file.... - final String cfgXmlResourceName2 = (String) integrationSettings.get( CFG_FILE ); - if ( StringHelper.isNotEmpty( cfgXmlResourceName2 ) ) { - integrationSettings.remove( CFG_FILE ); - final LoadedConfig loadedCfg = configLoader.loadConfigXmlResource( cfgXmlResourceName2 ); - processConfigXml( loadedCfg, mergedSettings, ssrBuilder ); + if ( StringHelper.isNotEmpty( cfgXmlResourceName ) ) { + processHibernateConfigXmlResources( ssrBuilder, mergedSettings, cfgXmlResourceName ); } - // finally, apply integration-supplied settings (per JPA spec, integration settings should override other sources) - for ( Map.Entry entry : integrationSettings.entrySet() ) { - if ( entry.getKey() == null ) { - continue; - } - - if ( entry.getValue() == null ) { - mergedSettings.configurationValues.remove( entry.getKey() ); - } - else { - mergedSettings.configurationValues.put( entry.getKey(), entry.getValue() ); - } - } - - if ( !mergedSettings.configurationValues.containsKey( JPA_VALIDATION_MODE ) ) { - if ( persistenceUnit.getValidationMode() != null ) { - mergedSettings.configurationValues.put( JPA_VALIDATION_MODE, persistenceUnit.getValidationMode() ); - } - } - - if ( !mergedSettings.configurationValues.containsKey( JPA_SHARED_CACHE_MODE ) ) { - if ( persistenceUnit.getSharedCacheMode() != null ) { - mergedSettings.configurationValues.put( JPA_SHARED_CACHE_MODE, persistenceUnit.getSharedCacheMode() ); - } - } + normalizeSettings( persistenceUnit, integrationSettings, mergedSettings ); final String jaccContextId = (String) mergedSettings.configurationValues.get( JACC_CONTEXT_ID ); @@ -468,7 +491,7 @@ private MergedSettings mergeSettings( // 1) additional JACC permissions // 2) additional cache region declarations // - // we will also clean up an references with null entries + // we will also clean up any references with null entries Iterator itr = mergedSettings.configurationValues.entrySet().iterator(); while ( itr.hasNext() ) { final Map.Entry entry = (Map.Entry) itr.next(); @@ -483,15 +506,17 @@ private MergedSettings mergeSettings( final String valueString = (String) entry.getValue(); if ( keyString.startsWith( JACC_PREFIX ) ) { - if ( jaccContextId == null ) { - LOG.debug( - "Found JACC permission grant [%s] in properties, but no JACC context id was specified; ignoring" - ); - } - else { - mergedSettings.getJaccPermissions( jaccContextId ).addPermissionDeclaration( - parseJaccConfigEntry( keyString, valueString ) - ); + if( !JACC_CONTEXT_ID.equals( keyString ) && !JACC_ENABLED.equals( keyString )) { + if ( jaccContextId == null ) { + LOG.debug( + "Found JACC permission grant [%s] in properties, but no JACC context id was specified; ignoring" + ); + } + else { + mergedSettings.getJaccPermissions( jaccContextId ).addPermissionDeclaration( + parseJaccConfigEntry( keyString, valueString ) + ); + } } } else if ( keyString.startsWith( CLASS_CACHE_PREFIX ) ) { @@ -519,22 +544,446 @@ else if ( keyString.startsWith( COLLECTION_CACHE_PREFIX ) ) { return mergedSettings; } + /** + * Handles normalizing the settings coming from multiple sources, applying proper precedences + */ @SuppressWarnings("unchecked") - private void processConfigXml( - LoadedConfig loadedConfig, + private void normalizeSettings( + PersistenceUnitDescriptor persistenceUnit, + Map integrationSettings, + MergedSettings mergedSettings) { + // make a copy so we can remove things as we process them + final HashMap integrationSettingsCopy = new HashMap<>( integrationSettings ); + + normalizeConnectionAccessUserAndPass( integrationSettingsCopy, mergedSettings ); + + normalizeTransactionCoordinator( persistenceUnit, integrationSettingsCopy, mergedSettings ); + + normalizeDataAccess( integrationSettingsCopy, mergedSettings, persistenceUnit ); + + // normalize ValidationMode + final Object intgValidationMode = integrationSettingsCopy.remove( JPA_VALIDATION_MODE ); + if ( intgValidationMode != null ) { + mergedSettings.configurationValues.put( JPA_VALIDATION_MODE, intgValidationMode ); + } + else if ( persistenceUnit.getValidationMode() != null ) { + mergedSettings.configurationValues.put( JPA_VALIDATION_MODE, persistenceUnit.getValidationMode() ); + } + + // normalize SharedCacheMode + final Object intgCacheMode = integrationSettingsCopy.remove( JPA_SHARED_CACHE_MODE ); + if ( intgCacheMode != null ) { + mergedSettings.configurationValues.put( JPA_SHARED_CACHE_MODE, intgCacheMode ); + } + else if ( persistenceUnit.getSharedCacheMode() != null ) { + mergedSettings.configurationValues.put( JPA_SHARED_CACHE_MODE, persistenceUnit.getSharedCacheMode() ); + } + + // Apply all "integration overrides" as the last step. By specification, + // these should have precedence. + // + // NOTE that this occurs after the specialized normalize calls above which remove + // any specially-handled settings. + for ( Map.Entry entry : integrationSettingsCopy.entrySet() ) { + if ( entry.getKey() == null ) { + continue; + } + + if ( entry.getValue() == null ) { + mergedSettings.configurationValues.remove( entry.getKey() ); + } + else { + mergedSettings.configurationValues.put( entry.getKey(), entry.getValue() ); + } + } + } + + /** + * Because a DataSource can be secured (requiring Hibernate to pass the USER/PASSWORD when accessing the DataSource) + * we apply precedence to the USER and PASS separately + */ + private void normalizeConnectionAccessUserAndPass( + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + //noinspection unchecked + final Object effectiveUser = NullnessHelper.coalesceSuppliedValues( + () -> integrationSettingsCopy.remove( USER ), + () -> integrationSettingsCopy.remove( JPA_JDBC_USER ), + () -> extractPuProperty( persistenceUnit, USER ), + () -> extractPuProperty( persistenceUnit, JPA_JDBC_USER ) + ); + + //noinspection unchecked + final Object effectivePass = NullnessHelper.coalesceSuppliedValues( + () -> integrationSettingsCopy.remove( PASS ), + () -> integrationSettingsCopy.remove( JPA_JDBC_PASSWORD ), + () -> extractPuProperty( persistenceUnit, PASS ), + () -> extractPuProperty( persistenceUnit, JPA_JDBC_PASSWORD ) + ); + + if ( effectiveUser != null || effectivePass != null ) { + applyUserAndPass( effectiveUser, effectivePass, mergedSettings ); + } + } + + private T extractPuProperty(PersistenceUnitDescriptor persistenceUnit, String propertyName) { + //noinspection unchecked + return persistenceUnit.getProperties() == null ? null : (T) persistenceUnit.getProperties().get( propertyName ); + } + + @SuppressWarnings("unchecked") + private void applyUserAndPass(Object effectiveUser, Object effectivePass, MergedSettings mergedSettings) { + if ( effectiveUser != null ) { + mergedSettings.configurationValues.put( USER, effectiveUser ); + mergedSettings.configurationValues.put( JPA_JDBC_USER, effectiveUser ); + } + + if ( effectivePass != null ) { + mergedSettings.configurationValues.put( PASS, effectivePass ); + mergedSettings.configurationValues.put( JPA_JDBC_PASSWORD, effectivePass ); + } + } + + private static final String IS_JTA_TXN_COORD = "local.setting.IS_JTA_TXN_COORD"; + + @SuppressWarnings("unchecked") + private void normalizeTransactionCoordinator( + PersistenceUnitDescriptor persistenceUnit, + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + PersistenceUnitTransactionType txnType = null; + + final Object intgTxnType = integrationSettingsCopy.remove( JPA_TRANSACTION_TYPE ); + + if ( intgTxnType != null ) { + txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType( intgTxnType ); + } + else if ( persistenceUnit.getTransactionType() != null ) { + txnType = persistenceUnit.getTransactionType(); + } + else { + final Object puPropTxnType = mergedSettings.configurationValues.get( JPA_TRANSACTION_TYPE ); + if ( puPropTxnType != null ) { + txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType( puPropTxnType ); + } + } + + if ( txnType == null ) { + // is it more appropriate to have this be based on bootstrap entry point (EE vs SE)? + LOG.debugf( "PersistenceUnitTransactionType not specified - falling back to RESOURCE_LOCAL" ); + txnType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + } + + boolean hasTxStrategy = mergedSettings.configurationValues.containsKey( TRANSACTION_COORDINATOR_STRATEGY ); + final Boolean definiteJtaCoordinator; + + if ( hasTxStrategy ) { + LOG.overridingTransactionStrategyDangerous( TRANSACTION_COORDINATOR_STRATEGY ); + + // see if we can tell whether it is a JTA coordinator + final Object strategy = mergedSettings.configurationValues.get( TRANSACTION_COORDINATOR_STRATEGY ); + if ( strategy instanceof TransactionCoordinatorBuilder ) { + definiteJtaCoordinator = ( (TransactionCoordinatorBuilder) strategy ).isJta(); + } + else { + definiteJtaCoordinator = false; + } + } + else { + if ( txnType == PersistenceUnitTransactionType.JTA ) { + mergedSettings.configurationValues.put( TRANSACTION_COORDINATOR_STRATEGY, JtaTransactionCoordinatorBuilderImpl.class ); + definiteJtaCoordinator = true; + } + else if ( txnType == PersistenceUnitTransactionType.RESOURCE_LOCAL ) { + mergedSettings.configurationValues.put( TRANSACTION_COORDINATOR_STRATEGY, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class ); + definiteJtaCoordinator = false; + } + else { + throw new IllegalStateException( "Could not determine TransactionCoordinator strategy to use" ); + } + } + + mergedSettings.configurationValues.put( IS_JTA_TXN_COORD, definiteJtaCoordinator ); + } + + private void normalizeDataAccess( + HashMap integrationSettingsCopy, MergedSettings mergedSettings, - StandardServiceRegistryBuilder ssrBuilder) { - if ( ! mergedSettings.configurationValues.containsKey( SESSION_FACTORY_NAME ) ) { - // there is not already a SF-name in the merged settings - final String sfName = loadedConfig.getSessionFactoryName(); - if ( sfName != null ) { - // but the cfg.xml file we are processing named one.. - mergedSettings.configurationValues.put( SESSION_FACTORY_NAME, sfName ); + PersistenceUnitDescriptor persistenceUnit) { + if ( dataSource != null ) { + applyDataSource( + dataSource, + // we don't explicitly know + null, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( integrationSettingsCopy.containsKey( DATASOURCE ) ) { + final Object dataSourceRef = integrationSettingsCopy.remove( DATASOURCE ); + if ( dataSourceRef != null ) { + applyDataSource( + dataSourceRef, + null, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( integrationSettingsCopy.containsKey( JPA_JTA_DATASOURCE ) ) { + final Object dataSourceRef = integrationSettingsCopy.remove( JPA_JTA_DATASOURCE ); + if ( dataSourceRef != null ) { + applyDataSource( + dataSourceRef, + true, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( integrationSettingsCopy.containsKey( JPA_NON_JTA_DATASOURCE ) ) { + final Object dataSourceRef = integrationSettingsCopy.remove( JPA_NON_JTA_DATASOURCE ); + + applyDataSource( + dataSourceRef, + false, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( integrationSettingsCopy.containsKey( URL ) ) { + // these have precedence over the JPA ones + final Object integrationJdbcUrl = integrationSettingsCopy.get( URL ); + if ( integrationJdbcUrl != null ) { + //noinspection unchecked + applyJdbcSettings( + integrationJdbcUrl, + NullnessHelper.coalesceSuppliedValues( + () -> ConfigurationHelper.getString( DRIVER, integrationSettingsCopy ), + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, integrationSettingsCopy ), + () -> ConfigurationHelper.getString( DRIVER, mergedSettings.configurationValues ), + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, mergedSettings.configurationValues ) + ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( integrationSettingsCopy.containsKey( JPA_JDBC_URL ) ) { + final Object integrationJdbcUrl = integrationSettingsCopy.get( JPA_JDBC_URL ); + + if ( integrationJdbcUrl != null ) { + //noinspection unchecked + applyJdbcSettings( + integrationJdbcUrl, + NullnessHelper.coalesceSuppliedValues( + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, integrationSettingsCopy ), + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, mergedSettings.configurationValues ) + ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( persistenceUnit.getJtaDataSource() != null ) { + applyDataSource( + persistenceUnit.getJtaDataSource(), + true, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( persistenceUnit.getNonJtaDataSource() != null ) { + applyDataSource( + persistenceUnit.getNonJtaDataSource(), + false, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( mergedSettings.configurationValues.containsKey( URL ) ) { + final Object url = mergedSettings.configurationValues.get( URL ); + + if ( url != null && ( ! ( url instanceof String ) || StringHelper.isNotEmpty( (String) url ) ) ) { + applyJdbcSettings( + url, + ConfigurationHelper.getString( DRIVER, mergedSettings.configurationValues ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( mergedSettings.configurationValues.containsKey( JPA_JDBC_URL ) ) { + final Object url = mergedSettings.configurationValues.get( JPA_JDBC_URL ); + + if ( url != null && ( ! ( url instanceof String ) || StringHelper.isNotEmpty( (String) url ) ) ) { + applyJdbcSettings( + url, + ConfigurationHelper.getString( JPA_JDBC_DRIVER, mergedSettings.configurationValues ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; } } - mergedSettings.configurationValues.putAll( loadedConfig.getConfigurationValues() ); - ssrBuilder.configure( loadedConfig ); + // any other conditions to account for? + } + + @SuppressWarnings("unchecked") + private void applyDataSource( + Object dataSourceRef, + Boolean useJtaDataSource, + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + + // `IS_JTA_TXN_COORD` is a value set during `#normalizeTransactionCoordinator` to indicate whether + // the execution environment "is JTA" as best as it can tell.. + // + // we use this value when JTA was not explicitly specified in regards the DataSource + final boolean isJtaTransactionCoordinator = (boolean) mergedSettings.configurationValues.remove( IS_JTA_TXN_COORD ); + final boolean isJta = useJtaDataSource == null ? isJtaTransactionCoordinator : useJtaDataSource; + + // add to EMF properties (questionable - see HHH-13432) + final String emfKey; + final String inverseEmfKey; + if ( isJta ) { + emfKey = JPA_JTA_DATASOURCE; + inverseEmfKey = JPA_NON_JTA_DATASOURCE; + } + else { + emfKey = JPA_NON_JTA_DATASOURCE; + inverseEmfKey = JPA_JTA_DATASOURCE; + } + mergedSettings.configurationValues.put( emfKey, dataSourceRef ); + + // clear any settings logically overridden by this datasource + cleanUpConfigKeys( + integrationSettingsCopy, + mergedSettings, + inverseEmfKey, + JPA_JDBC_DRIVER, + DRIVER, + JPA_JDBC_URL, + URL + ); + + + // clean-up the entries in the "integration overrides" so they do not get get picked + // up in the general "integration overrides" handling + cleanUpConfigKeys( integrationSettingsCopy, DATASOURCE, JPA_JTA_DATASOURCE, JPA_NON_JTA_DATASOURCE ); + + // add under Hibernate's DATASOURCE setting where the ConnectionProvider will find it + mergedSettings.configurationValues.put( DATASOURCE, dataSourceRef ); + } + + private void cleanUpConfigKeys(HashMap integrationSettingsCopy, MergedSettings mergedSettings, String... keys) { + for ( String key : keys ) { + final Object removedIntgSetting = integrationSettingsCopy.remove( key ); + if ( removedIntgSetting != null ) { + LOG.debugf( "Removed integration override setting [%s] due to normalization", key ); + } + + final Object removedMergedSetting = mergedSettings.configurationValues.remove( key ); + if ( removedMergedSetting != null ) { + LOG.debugf( "Removed merged setting [%s] due to normalization", key ); + } + } + } + + private void cleanUpConfigKeys(Map settings, String... keys) { + for ( String key : keys ) { + settings.remove( key ); + } + } + + @SuppressWarnings("unchecked") + private void applyJdbcSettings( + Object url, + String driver, + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + mergedSettings.configurationValues.put( URL, url ); + mergedSettings.configurationValues.put( JPA_JDBC_URL, url ); + + if ( driver != null ) { + mergedSettings.configurationValues.put( DRIVER, driver ); + mergedSettings.configurationValues.put( JPA_JDBC_DRIVER, driver ); + } + else { + mergedSettings.configurationValues.remove( DRIVER ); + mergedSettings.configurationValues.remove( JPA_JDBC_DRIVER ); + } + + // clean up the integration-map values + cleanUpConfigKeys( + integrationSettingsCopy, + DRIVER, + JPA_JDBC_DRIVER, + URL, + JPA_JDBC_URL, + USER, + JPA_JDBC_USER, + PASS, + JPA_JDBC_PASSWORD + ); + + cleanUpConfigKeys( + integrationSettingsCopy, + mergedSettings, + DATASOURCE, + JPA_JTA_DATASOURCE, + JPA_NON_JTA_DATASOURCE + ); + } + + private void processHibernateConfigXmlResources( + StandardServiceRegistryBuilder ssrBuilder, + MergedSettings mergedSettings, + String cfgXmlResourceName) { + final LoadedConfig loadedConfig = ssrBuilder.getConfigLoader().loadConfigXmlResource( cfgXmlResourceName ); + + mergedSettings.processHibernateConfigXmlResources( loadedConfig ); + + ssrBuilder.getAggregatedCfgXml().merge( loadedConfig ); } private GrantedPermission parseJaccConfigEntry(String keyString, String valueString) { @@ -590,90 +1039,7 @@ private CacheRegionDefinition parseCacheRegionDefinitionEntry(String role, Strin return new CacheRegionDefinition( cacheType, role, usage, region, lazyProperty ); } - private void configure(StandardServiceRegistryBuilder ssrBuilder) { - - applyJdbcConnectionProperties( ssrBuilder ); - applyTransactionProperties( ssrBuilder ); - - // flush beforeQuery completion validation - if ( "true".equals( configurationValues.get( Environment.FLUSH_BEFORE_COMPLETION ) ) ) { - ssrBuilder.applySetting( Environment.FLUSH_BEFORE_COMPLETION, "false" ); - LOG.definingFlushBeforeCompletionIgnoredInHem( Environment.FLUSH_BEFORE_COMPLETION ); - } - -// final StrategySelector strategySelector = ssrBuilder.getBootstrapServiceRegistry().getService( StrategySelector.class ); -// final Object interceptorSetting = configurationValues.remove( AvailableSettings.SESSION_INTERCEPTOR ); -// if ( interceptorSetting != null ) { -// settings.setSessionInterceptorClass( -// loadSessionInterceptorClass( interceptorSetting, strategySelector ) -// ); -// } - } - - private void applyJdbcConnectionProperties(StandardServiceRegistryBuilder ssrBuilder) { - if ( dataSource != null ) { - ssrBuilder.applySetting( DATASOURCE, dataSource ); - } - else if ( persistenceUnit.getJtaDataSource() != null ) { - if ( ! ssrBuilder.getSettings().containsKey( DATASOURCE ) ) { - ssrBuilder.applySetting( DATASOURCE, persistenceUnit.getJtaDataSource() ); - // HHH-8121 : make the PU-defined value available to EMF.getProperties() - configurationValues.put( JPA_JTA_DATASOURCE, persistenceUnit.getJtaDataSource() ); - } - } - else if ( persistenceUnit.getNonJtaDataSource() != null ) { - if ( ! ssrBuilder.getSettings().containsKey( DATASOURCE ) ) { - ssrBuilder.applySetting( DATASOURCE, persistenceUnit.getNonJtaDataSource() ); - // HHH-8121 : make the PU-defined value available to EMF.getProperties() - configurationValues.put( JPA_NON_JTA_DATASOURCE, persistenceUnit.getNonJtaDataSource() ); - } - } - else { - final String driver = (String) configurationValues.get( JPA_JDBC_DRIVER ); - if ( StringHelper.isNotEmpty( driver ) ) { - ssrBuilder.applySetting( DRIVER, driver ); - } - final String url = (String) configurationValues.get( JPA_JDBC_URL ); - if ( StringHelper.isNotEmpty( url ) ) { - ssrBuilder.applySetting( URL, url ); - } - final String user = (String) configurationValues.get( JPA_JDBC_USER ); - if ( StringHelper.isNotEmpty( user ) ) { - ssrBuilder.applySetting( USER, user ); - } - final String pass = (String) configurationValues.get( JPA_JDBC_PASSWORD ); - if ( StringHelper.isNotEmpty( pass ) ) { - ssrBuilder.applySetting( PASS, pass ); - } - } - } - - private void applyTransactionProperties(StandardServiceRegistryBuilder ssrBuilder) { - PersistenceUnitTransactionType txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType( - configurationValues.get( JPA_TRANSACTION_TYPE ) - ); - if ( txnType == null ) { - txnType = persistenceUnit.getTransactionType(); - } - if ( txnType == null ) { - // is it more appropriate to have this be based on bootstrap entry point (EE vs SE)? - txnType = PersistenceUnitTransactionType.RESOURCE_LOCAL; - } - boolean hasTxStrategy = configurationValues.containsKey( TRANSACTION_COORDINATOR_STRATEGY ); - if ( hasTxStrategy ) { - LOG.overridingTransactionStrategyDangerous( TRANSACTION_COORDINATOR_STRATEGY ); - } - else { - if ( txnType == PersistenceUnitTransactionType.JTA ) { - ssrBuilder.applySetting( TRANSACTION_COORDINATOR_STRATEGY, JtaTransactionCoordinatorBuilderImpl.class ); - } - else if ( txnType == PersistenceUnitTransactionType.RESOURCE_LOCAL ) { - ssrBuilder.applySetting( TRANSACTION_COORDINATOR_STRATEGY, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class ); - } - } - } - - private void configure(StandardServiceRegistry ssr, MergedSettings mergedSettings) { + private void configureIdentifierGenerators(StandardServiceRegistry ssr) { final StrategySelector strategySelector = ssr.getService( StrategySelector.class ); // apply id generators @@ -695,10 +1061,9 @@ private void configure(StandardServiceRegistry ssr, MergedSettings mergedSetting } @SuppressWarnings("unchecked") - protected List populate( - MetadataSources metadataSources, - MergedSettings mergedSettings, - StandardServiceRegistry ssr) { + private List applyMappingResources(MetadataSources metadataSources) { + // todo : where in the heck are `org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor.getManagedClassNames` handled?!? + // final ClassLoaderService classLoaderService = ssr.getService( ClassLoaderService.class ); // // // todo : make sure MetadataSources/Metadata are capable of handling duplicate sources @@ -769,19 +1134,17 @@ protected List populate( // add any explicit orm.xml references passed in final List explicitOrmXmlList = (List) configurationValues.remove( AvailableSettings.XML_FILE_NAMES ); if ( explicitOrmXmlList != null ) { - for ( String ormXml : explicitOrmXmlList ) { - metadataSources.addResource( ormXml ); - } + explicitOrmXmlList.forEach( metadataSources::addResource ); } return attributeConverterDefinitions; } - protected void populate( - MetadataBuilder metamodelBuilder, + private void applyMetamodelBuilderSettings( MergedSettings mergedSettings, - StandardServiceRegistry ssr, List attributeConverterDefinitions) { + metamodelBuilder.getBootstrapContext().markAsJpaBootstrap(); + if ( persistenceUnit.getTempClassLoader() != null ) { metamodelBuilder.applyTempClassLoader( persistenceUnit.getTempClassLoader() ); } @@ -795,24 +1158,18 @@ protected void populate( ); if ( mergedSettings.cacheRegionDefinitions != null ) { - for ( CacheRegionDefinition localCacheRegionDefinition : mergedSettings.cacheRegionDefinitions ) { - metamodelBuilder.applyCacheRegionDefinition( localCacheRegionDefinition ); - } + mergedSettings.cacheRegionDefinitions.forEach( metamodelBuilder::applyCacheRegionDefinition ); } final TypeContributorList typeContributorList = (TypeContributorList) configurationValues.remove( TYPE_CONTRIBUTORS ); if ( typeContributorList != null ) { - for ( TypeContributor typeContributor : typeContributorList.getTypeContributors() ) { - metamodelBuilder.applyTypes( typeContributor ); - } + typeContributorList.getTypeContributors().forEach( metamodelBuilder::applyTypes ); } if ( attributeConverterDefinitions != null ) { - for ( AttributeConverterDefinition attributeConverterDefinition : attributeConverterDefinitions ) { - metamodelBuilder.applyAttributeConverter( attributeConverterDefinition ); - } + attributeConverterDefinitions.forEach( metamodelBuilder::applyAttributeConverter ); } } @@ -855,7 +1212,11 @@ public void cancel() { private MetadataImplementor metadata() { if ( this.metadata == null ) { - this.metadata = MetadataBuildingProcess.complete( managedResources, metamodelBuilder.getMetadataBuildingOptions() ); + this.metadata = MetadataBuildingProcess.complete( + managedResources, + metamodelBuilder.getBootstrapContext(), + metamodelBuilder.getMetadataBuildingOptions() + ); } return metadata; } @@ -866,7 +1227,7 @@ public void generateSchema() { // Metamodel will clean this up... try { SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder(); - populate( sfBuilder, standardServiceRegistry ); + populateSfBuilder( sfBuilder, standardServiceRegistry ); SchemaManagementToolCoordinator.process( metadata, standardServiceRegistry, configurationValues, DelayedDropRegistryNotAvailableImpl.INSTANCE @@ -880,10 +1241,10 @@ public void generateSchema() { cancel(); } - @SuppressWarnings("unchecked") + @Override public EntityManagerFactory build() { - SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder(); - populate( sfBuilder, standardServiceRegistry ); + final SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder(); + populateSfBuilder( sfBuilder, standardServiceRegistry ); try { return sfBuilder.build(); @@ -893,8 +1254,7 @@ public EntityManagerFactory build() { } } - protected void populate(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) { - ( ( SessionFactoryBuilderImplementor) sfBuilder ).markAsJpaBootstrap(); + protected void populateSfBuilder(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) { final StrategySelector strategySelector = ssr.getService( StrategySelector.class ); @@ -979,7 +1339,36 @@ private static class MergedSettings { private Map jaccPermissionsByContextId; private List cacheRegionDefinitions; + /** + * MergedSettings is initialized with hibernate.properties + */ private MergedSettings() { + configurationValues.putAll( Environment.getProperties() ); + } + + public void processPersistenceUnitDescriptorProperties(PersistenceUnitDescriptor persistenceUnit) { + if ( persistenceUnit.getProperties() != null ) { + configurationValues.putAll( persistenceUnit.getProperties() ); + } + + configurationValues.put( PERSISTENCE_UNIT_NAME, persistenceUnit.getName() ); + + } + + public void processHibernateConfigXmlResources(LoadedConfig loadedConfig){ + if ( ! configurationValues.containsKey( SESSION_FACTORY_NAME ) ) { + // there is not already a SF-name in the merged settings + final String sfName = loadedConfig.getSessionFactoryName(); + if ( sfName != null ) { + // but the cfg.xml file we are processing named one.. + configurationValues.put( SESSION_FACTORY_NAME, sfName ); + } + } + else { + // make sure they match? + } + + configurationValues.putAll( loadedConfig.getConfigurationValues() ); } public Map getConfigurationValues() { diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java index 9b1b4af37b68..ae9203dcc990 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java @@ -18,23 +18,19 @@ import java.util.concurrent.ConcurrentHashMap; import javax.persistence.PersistenceException; import javax.persistence.spi.PersistenceUnitTransactionType; -import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.hibernate.boot.archive.internal.ArchiveHelper; import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.xsd.ConfigXsdSupport; import org.hibernate.cfg.AvailableSettings; import org.hibernate.internal.EntityManagerMessageLogger; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.xml.XsdException; import org.hibernate.jpa.internal.util.ConfigurationHelper; import org.w3c.dom.Document; @@ -68,8 +64,8 @@ public static List locatePersistenceUnits(Map in ClassLoaderServiceImpl.fromConfigSettings( integration ), PersistenceUnitTransactionType.RESOURCE_LOCAL ); - - return new ArrayList<>( parser.doResolve( integration ).values() ); + parser.doResolve( integration ); + return new ArrayList<>( parser.persistenceUnits.values() ); } /** @@ -118,10 +114,11 @@ public static ParsedPersistenceXmlDescriptor locateIndividualPersistenceUnit( transactionType ); - final Map persistenceUnits = parser.parsePersistenceXml( persistenceXmlUrl, integration ); - assert persistenceUnits.size() == 1; + parser.parsePersistenceXml( persistenceXmlUrl, integration ); + + assert parser.persistenceUnits.size() == 1; - return persistenceUnits.values().iterator().next(); + return parser.persistenceUnits.values().iterator().next(); } /** @@ -173,10 +170,10 @@ public static ParsedPersistenceXmlDescriptor locateNamedPersistenceUnit( transactionType ); - final Map persistenceUnits = parser.parsePersistenceXml( persistenceXmlUrl, integration ); - assert persistenceUnits.containsKey( name ); + parser.parsePersistenceXml( persistenceXmlUrl, integration ); + assert parser.persistenceUnits.containsKey( name ); - return persistenceUnits.get( name ); + return parser.persistenceUnits.get( name ); } /** @@ -184,7 +181,7 @@ public static ParsedPersistenceXmlDescriptor locateNamedPersistenceUnit( *

    * Parses a specific persistence.xml file... */ - public static Map parse( + public static Map parse( URL persistenceXmlUrl, PersistenceUnitTransactionType transactionType) { return parse( persistenceXmlUrl, transactionType, Collections.emptyMap() ); @@ -200,7 +197,7 @@ public static Map parse( * * @return Map of persistence-unit descriptors keyed by the PU name */ - public static Map parse( + public static Map parse( URL persistenceXmlUrl, PersistenceUnitTransactionType transactionType, Map integration) { @@ -209,42 +206,42 @@ public static Map parse( transactionType ); - return parser.doResolve( integration ); + parser.doResolve( integration ); + return parser.persistenceUnits; } - private final ClassLoaderService classLoaderService; private final PersistenceUnitTransactionType defaultTransactionType; + private final Map persistenceUnits; private PersistenceXmlParser(ClassLoaderService classLoaderService, PersistenceUnitTransactionType defaultTransactionType) { this.classLoaderService = classLoaderService; this.defaultTransactionType = defaultTransactionType; + this.persistenceUnits = new ConcurrentHashMap<>(); } - private Map doResolve(Map integration) { - final Map persistenceUnits = new ConcurrentHashMap<>(); - + private void doResolve(Map integration) { final List xmlUrls = classLoaderService.locateResources( "META-INF/persistence.xml" ); if ( xmlUrls.isEmpty() ) { LOG.unableToFindPersistenceXmlInClasspath(); } else { - for ( URL xmlUrl : xmlUrls ) { - persistenceUnits.putAll( parsePersistenceXml( xmlUrl, integration ) ); - } + parsePersistenceXml( xmlUrls, integration ); } + } - return persistenceUnits; + private void parsePersistenceXml(List xmlUrls, Map integration) { + for ( URL xmlUrl : xmlUrls ) { + parsePersistenceXml( xmlUrl, integration ); + } } - private Map parsePersistenceXml(URL xmlUrl, Map integration) { + private void parsePersistenceXml(URL xmlUrl, Map integration) { LOG.tracef( "Attempting to parse persistence.xml file : %s", xmlUrl.toExternalForm() ); final Document doc = loadUrl( xmlUrl ); final Element top = doc.getDocumentElement(); - final Map persistenceUnits = new ConcurrentHashMap<>(); - final NodeList children = top.getChildNodes(); for ( int i = 0; i < children.getLength() ; i++ ) { if ( children.item( i ).getNodeType() == Node.ELEMENT_NODE ) { @@ -285,7 +282,6 @@ private Map parsePersistenceXml(URL xmlUr } } } - return persistenceUnits; } private void decodeTransactionType(ParsedPersistenceXmlDescriptor persistenceUnit) { @@ -434,8 +430,7 @@ private Document loadUrl(URL xmlUrl) { URLConnection conn = xmlUrl.openConnection(); conn.setUseCaches( false ); //avoid JAR locking on Windows and Tomcat try { - InputStream inputStream = conn.getInputStream(); - try { + try (InputStream inputStream = conn.getInputStream()) { final InputSource inputSource = new InputSource( inputStream ); try { DocumentBuilder documentBuilder = documentBuilderFactory().newDocumentBuilder(); @@ -444,22 +439,15 @@ private Document loadUrl(URL xmlUrl) { validate( document ); return document; } - catch (SAXException e) { - throw new PersistenceException( "Unexpected error parsing [" + resourceName + "]", e ); - } - catch (IOException e) { + catch (SAXException | IOException e) { throw new PersistenceException( "Unexpected error parsing [" + resourceName + "]", e ); } } catch (ParserConfigurationException e) { - throw new PersistenceException( "Unable to generate javax.xml.parsers.DocumentBuilder instance", e ); - } - } - finally { - try { - inputStream.close(); - } - catch (Exception ignored) { + throw new PersistenceException( + "Unable to generate javax.xml.parsers.DocumentBuilder instance", + e + ); } } } @@ -475,22 +463,10 @@ private Document loadUrl(URL xmlUrl) { private void validate(Document document) { // todo : add ability to disable validation... - final Validator validator; final String version = document.getDocumentElement().getAttribute( "version" ); - if ( "2.1".equals( version ) ) { - validator = v21Schema().newValidator(); - } - else if ( "2.0".equals( version ) ) { - validator = v2Schema().newValidator(); - } - else if ( "1.0".equals( version ) ) { - validator = v1Schema().newValidator(); - } - else { - throw new PersistenceException( "Unrecognized persistence.xml version [" + version + "]" ); - } + final Validator validator = ConfigXsdSupport.INSTANCE.jpaXsd( version ).getSchema().newValidator(); - List errors = new ArrayList(); + List errors = new ArrayList<>(); validator.setErrorHandler( new ErrorHandlerImpl( errors ) ); try { validator.validate( new DOMSource( document ) ); @@ -527,68 +503,6 @@ private DocumentBuilderFactory buildDocumentBuilderFactory() { return documentBuilderFactory; } - private Schema v21Schema; - - private Schema v21Schema() { - if ( v21Schema == null ) { - v21Schema = resolveLocalSchema( "org/hibernate/jpa/persistence_2_1.xsd" ); - } - return v21Schema; - } - - private Schema v2Schema; - - private Schema v2Schema() { - if ( v2Schema == null ) { - v2Schema = resolveLocalSchema( "org/hibernate/jpa/persistence_2_0.xsd" ); - } - return v2Schema; - } - - private Schema v1Schema; - - private Schema v1Schema() { - if ( v1Schema == null ) { - v1Schema = resolveLocalSchema( "org/hibernate/jpa/persistence_1_0.xsd" ); - } - return v1Schema; - } - - - private Schema resolveLocalSchema(String schemaName) { - // These XSD resources should be available on the Hibernate ClassLoader - final URL url = classLoaderService.locateResource( schemaName ); - if ( url == null ) { - throw new XsdException( "Unable to locate schema [" + schemaName + "] via classpath", schemaName ); - } - try { - InputStream schemaStream = url.openStream(); - try { - StreamSource source = new StreamSource( url.openStream() ); - SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ); - return schemaFactory.newSchema( source ); - } - catch ( SAXException e ) { - throw new XsdException( "Unable to load schema [" + schemaName + "]", e, schemaName ); - } - catch ( IOException e ) { - throw new XsdException( "Unable to load schema [" + schemaName + "]", e, schemaName ); - } - finally { - try { - schemaStream.close(); - } - catch ( IOException e ) { - LOG.debugf( "Problem closing schema stream [%s]", e.toString() ); - } - } - } - catch ( IOException e ) { - throw new XsdException( "Stream error handling schema url [" + url.toExternalForm() + "]", schemaName ); - } - } - - public static class ErrorHandlerImpl implements ErrorHandler { private List errors; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java index 80961ce7fea9..a88d09bc55f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java @@ -8,7 +8,9 @@ import java.util.Map; -import org.hibernate.jpa.AvailableSettings; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.EntityManagerMessageLogger; +import org.hibernate.internal.HEMLogging; import org.hibernate.jpa.HibernatePersistenceProvider; import org.jboss.logging.Logger; @@ -20,12 +22,8 @@ * @author Steve Ebersole */ public final class ProviderChecker { - private static final Logger log = Logger.getLogger( ProviderChecker.class ); - @SuppressWarnings("deprecation") - private static String[] HIBERNATE_PROVIDER_NAMES = new String[] { - HibernatePersistenceProvider.class.getName() - }; + private static final Logger log = Logger.getLogger( ProviderChecker.class ); /** * Does the descriptor and/or integration request Hibernate as the @@ -54,20 +52,21 @@ public static boolean hibernateProviderNamesContain(String requestedProviderName "Checking requested PersistenceProvider name [%s] against Hibernate provider names", requestedProviderName ); - - for ( String hibernateProviderName : HIBERNATE_PROVIDER_NAMES ) { - if ( requestedProviderName.equals( hibernateProviderName ) ) { - return true; - } + final String deprecatedPersistenceProvider = "org.hibernate.ejb.HibernatePersistence"; + if ( deprecatedPersistenceProvider.equals( requestedProviderName) ) { + HEMLogging.messageLogger( ProviderChecker.class ) + .deprecatedPersistenceProvider( + deprecatedPersistenceProvider, + HibernatePersistenceProvider.class.getName() + ); + return true; } - - log.tracef( "Found no match against Hibernate provider names" ); - return false; + return HibernatePersistenceProvider.class.getName().equals( requestedProviderName ); } /** * Extract the requested persistence provider name using the algorithm Hibernate uses. Namely, a provider named - * in the 'integration' map (under the key '{@value AvailableSettings#PROVIDER}') is preferred, as per-spec, over + * in the 'integration' map (under the key '{@value AvailableSettings#JPA_PERSISTENCE_PROVIDER}') is preferred, as per-spec, over * value specified in persistence unit. * * @param persistenceUnit The {@code } descriptor. @@ -101,7 +100,7 @@ private static String extractProviderName(Map integration) { if ( integration == null ) { return null; } - final String setting = (String) integration.get( AvailableSettings.PROVIDER ); + final String setting = (String) integration.get( AvailableSettings.JPA_PERSISTENCE_PROVIDER ); return setting == null ? null : setting.trim(); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/AbstractCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/AbstractCallback.java new file mode 100644 index 000000000000..e7441e2a8920 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/AbstractCallback.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.jpa.event.internal; + +import org.hibernate.jpa.event.spi.Callback; +import org.hibernate.jpa.event.spi.CallbackType; + +/** + * Abstract support for Callback implementations + * + * @author Steve Ebersole + */ +abstract class AbstractCallback implements Callback { + + private final CallbackType callbackType; + + AbstractCallback(CallbackType callbackType) { + this.callbackType = callbackType; + } + + @Override + public CallbackType getCallbackType() { + return callbackType; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java new file mode 100644 index 000000000000..1a6851ba3ce9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java @@ -0,0 +1,341 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.event.internal; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.ExcludeDefaultListeners; +import javax.persistence.ExcludeSuperclassListeners; +import javax.persistence.MappedSuperclass; +import javax.persistence.PersistenceException; + +import org.hibernate.MappingException; +import org.hibernate.annotations.common.reflection.ClassLoadingException; +import org.hibernate.annotations.common.reflection.ReflectionManager; +import org.hibernate.annotations.common.reflection.XClass; +import org.hibernate.annotations.common.reflection.XMethod; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.jpa.event.spi.Callback; +import org.hibernate.jpa.event.spi.CallbackBuilder; +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.mapping.Property; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; + +import org.jboss.logging.Logger; + +/** + * EntityCallbackBuilder implementation using HCANN ReflectionManager. "legacy" in that + * we want to move to Jandex instead. + * + * @author Steve Ebersole + */ +public class CallbackBuilderLegacyImpl implements CallbackBuilder { + private static final Logger log = Logger.getLogger( CallbackBuilderLegacyImpl.class ); + + private final ManagedBeanRegistry managedBeanRegistry; + private final ReflectionManager reflectionManager; + + public CallbackBuilderLegacyImpl(ManagedBeanRegistry managedBeanRegistry, ReflectionManager reflectionManager) { + this.managedBeanRegistry = managedBeanRegistry; + this.reflectionManager = reflectionManager; + } + + @Override + public void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar) { + final boolean debugEnabled = log.isDebugEnabled(); + try { + final XClass entityXClass = reflectionManager.classForName( entityClassName ); + final Class entityClass = reflectionManager.toClass( entityXClass ); + for ( CallbackType callbackType : CallbackType.values() ) { + if ( callbackRegistrar.hasRegisteredCallbacks( entityClass, callbackType ) ) { + // this most likely means we have a class mapped multiple times using the hbm.xml + // "entity name" feature + if ( debugEnabled ) { + log.debugf( + "CallbackRegistry reported that Class [%s] already had %s callbacks registered; " + + "assuming this means the class was mapped twice " + + "(using hbm.xml entity-name support) - skipping subsequent registrations", + entityClassName, + callbackType.getCallbackAnnotation().getSimpleName() + ); + } + continue; + } + final Callback[] callbacks = resolveEntityCallbacks( entityXClass, callbackType, reflectionManager ); + callbackRegistrar.registerCallbacks( entityClass, callbacks ); + } + } + catch (ClassLoadingException e) { + throw new MappingException( "entity class not found: " + entityClassName, e ); + } + } + + @Override + public void buildCallbacksForEmbeddable( + Property embeddableProperty, String entityClassName, CallbackRegistrar callbackRegistrar) { + try { + final XClass entityXClass = reflectionManager.classForName( entityClassName ); + final Class entityClass = reflectionManager.toClass( entityXClass ); + + for ( CallbackType callbackType : CallbackType.values() ) { + final Callback[] callbacks = resolveEmbeddableCallbacks( + entityClass, + embeddableProperty, + callbackType, + reflectionManager + ); + callbackRegistrar.registerCallbacks( entityClass, callbacks ); + } + } + catch (ClassLoadingException e) { + throw new MappingException( "Class not found: ", e ); + } + } + + @Override + public void release() { + // nothing to do + } + + @SuppressWarnings({"unchecked", "WeakerAccess"}) + public Callback[] resolveEntityCallbacks(XClass beanClass, CallbackType callbackType, ReflectionManager reflectionManager) { + List callbacks = new ArrayList<>(); + List callbacksMethodNames = new ArrayList<>(); + List orderedListeners = new ArrayList<>(); + XClass currentClazz = beanClass; + boolean stopListeners = false; + boolean stopDefaultListeners = false; + final boolean debugEnabled = log.isDebugEnabled(); + do { + Callback callback = null; + List methods = currentClazz.getDeclaredMethods(); + for ( final XMethod xMethod : methods ) { + if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { + Method method = reflectionManager.toMethod( xMethod ); + final String methodName = method.getName(); + if ( !callbacksMethodNames.contains( methodName ) ) { + //overridden method, remove the superclass overridden method + if ( callback == null ) { + callback = new EntityCallback( method, callbackType ); + Class returnType = method.getReturnType(); + Class[] args = method.getParameterTypes(); + if ( returnType != Void.TYPE || args.length != 0 ) { + throw new RuntimeException( + "Callback methods annotated on the bean class must return void and take no arguments: " + + callbackType.getCallbackAnnotation().getName() + " - " + xMethod + ); + } + ReflectHelper.ensureAccessibility( method ); + if ( debugEnabled ) { + log.debugf( + "Adding %s as %s callback for entity %s", + methodName, + callbackType.getCallbackAnnotation().getSimpleName(), + beanClass.getName() + ); + } + callbacks.add( 0, callback ); //superclass first + callbacksMethodNames.add( 0, methodName ); + } + else { + throw new PersistenceException( + "You can only annotate one callback method with " + + callbackType.getCallbackAnnotation().getName() + " in bean class: " + beanClass.getName() + ); + } + } + } + } + if ( !stopListeners ) { + getListeners( currentClazz, orderedListeners ); + stopListeners = currentClazz.isAnnotationPresent( ExcludeSuperclassListeners.class ); + stopDefaultListeners = currentClazz.isAnnotationPresent( ExcludeDefaultListeners.class ); + } + + do { + currentClazz = currentClazz.getSuperclass(); + } + while ( currentClazz != null + && !( currentClazz.isAnnotationPresent( Entity.class ) + || currentClazz.isAnnotationPresent( MappedSuperclass.class ) ) + ); + } + while ( currentClazz != null ); + + //handle default listeners + if ( !stopDefaultListeners ) { + List defaultListeners = (List) reflectionManager.getDefaults().get( EntityListeners.class ); + + if ( defaultListeners != null ) { + int defaultListenerSize = defaultListeners.size(); + for ( int i = defaultListenerSize - 1; i >= 0; i-- ) { + orderedListeners.add( defaultListeners.get( i ) ); + } + } + } + + for ( Class listener : orderedListeners ) { + Callback callback = null; + if ( listener != null ) { + XClass xListener = reflectionManager.toXClass( listener ); + callbacksMethodNames = new ArrayList<>(); + List methods = xListener.getDeclaredMethods(); + for ( final XMethod xMethod : methods ) { + if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { + final Method method = reflectionManager.toMethod( xMethod ); + final String methodName = method.getName(); + if ( !callbacksMethodNames.contains( methodName ) ) { + //overridden method, remove the superclass overridden method + if ( callback == null ) { + callback = new ListenerCallback( + managedBeanRegistry.getBean( listener ), + method, + callbackType + ); + + Class returnType = method.getReturnType(); + Class[] args = method.getParameterTypes(); + if ( returnType != Void.TYPE || args.length != 1 ) { + throw new PersistenceException( + "Callback methods annotated in a listener bean class must return void and take one argument: " + + callbackType.getCallbackAnnotation().getName() + " - " + method + ); + } + ReflectHelper.ensureAccessibility( method ); + if ( debugEnabled ) { + log.debugf( + "Adding %s as %s callback for entity %s", + methodName, + callbackType.getCallbackAnnotation().getSimpleName(), + beanClass.getName() + ); + } + callbacks.add( 0, callback ); // listeners first + } + else { + throw new PersistenceException( + "You can only annotate one callback method with " + + callbackType.getCallbackAnnotation().getName() + + " in bean class: " + beanClass.getName() + + " and callback listener: " + listener.getName() + ); + } + } + } + } + } + } + return callbacks.toArray( new Callback[callbacks.size()] ); + } + + @SuppressWarnings({"unchecked", "WeakerAccess"}) + public Callback[] resolveEmbeddableCallbacks(Class entityClass, Property embeddableProperty, CallbackType callbackType, ReflectionManager reflectionManager) { + + final String embeddableClassName = embeddableProperty.getType().getReturnedClass().getName(); + final XClass embeddableXClass = reflectionManager.classForName( embeddableClassName ); + final Getter embeddableGetter = embeddableProperty.getGetter( entityClass ); + final boolean debugEnabled = log.isDebugEnabled(); + final List callbacks = new ArrayList<>(); + final List callbacksMethodNames = new ArrayList<>(); + XClass currentClazz = embeddableXClass; + do { + Callback callback = null; + List methods = currentClazz.getDeclaredMethods(); + for ( final XMethod xMethod : methods ) { + if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { + Method method = reflectionManager.toMethod( xMethod ); + final String methodName = method.getName(); + if ( !callbacksMethodNames.contains( methodName ) ) { + //overridden method, remove the superclass overridden method + if ( callback == null ) { + callback = new EmbeddableCallback( embeddableGetter, method, callbackType ); + Class returnType = method.getReturnType(); + Class[] args = method.getParameterTypes(); + if ( returnType != Void.TYPE || args.length != 0 ) { + throw new RuntimeException( + "Callback methods annotated on the bean class must return void and take no arguments: " + + callbackType.getCallbackAnnotation().getName() + " - " + xMethod + ); + } + ReflectHelper.ensureAccessibility( method ); + if ( debugEnabled ) { + log.debugf( + "Adding %s as %s callback for entity %s", + methodName, + callbackType.getCallbackAnnotation().getSimpleName(), + embeddableXClass.getName() + ); + } + callbacks.add( 0, callback ); //superclass first + callbacksMethodNames.add( 0, methodName ); + } + else { + throw new PersistenceException( + "You can only annotate one callback method with " + + callbackType.getCallbackAnnotation().getName() + " in bean class: " + embeddableXClass.getName() + ); + } + } + } + } + + do { + currentClazz = currentClazz.getSuperclass(); + } + while ( currentClazz != null && !currentClazz.isAnnotationPresent( MappedSuperclass.class ) ); + } + while ( currentClazz != null ); + + return callbacks.toArray( new Callback[callbacks.size()] ); + } + + private static boolean useAnnotationAnnotatedByListener; + + static { + //check whether reading annotations of annotations is useful or not + useAnnotationAnnotatedByListener = false; + Target target = EntityListeners.class.getAnnotation( Target.class ); + if ( target != null ) { + for ( ElementType type : target.value() ) { + if ( type.equals( ElementType.ANNOTATION_TYPE ) ) { + useAnnotationAnnotatedByListener = true; + } + } + } + } + + private static void getListeners(XClass currentClazz, List orderedListeners) { + EntityListeners entityListeners = currentClazz.getAnnotation( EntityListeners.class ); + if ( entityListeners != null ) { + Class[] classes = entityListeners.value(); + int size = classes.length; + for ( int index = size - 1; index >= 0; index-- ) { + orderedListeners.add( classes[index] ); + } + } + if ( useAnnotationAnnotatedByListener ) { + Annotation[] annotations = currentClazz.getAnnotations(); + for ( Annotation annot : annotations ) { + entityListeners = annot.getClass().getAnnotation( EntityListeners.class ); + if ( entityListeners != null ) { + Class[] classes = entityListeners.value(); + int size = classes.length; + for ( int index = size - 1; index >= 0; index-- ) { + orderedListeners.add( classes[index] ); + } + } + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/CallbackRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java similarity index 90% rename from hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/CallbackRegistryImpl.java rename to hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java index 4d12117c7556..0a50d33de140 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/CallbackRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java @@ -1,18 +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 . + * License: GNU Lesser General Public License (LGPL), version 2.1 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.event.internal.jpa; +package org.hibernate.jpa.event.internal; import java.util.HashMap; import javax.persistence.PersistenceException; -import org.hibernate.jpa.event.spi.jpa.Callback; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; -import org.hibernate.jpa.event.spi.jpa.CallbackType; -import org.hibernate.jpa.event.spi.jpa.CallbackBuilder; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.jpa.event.spi.Callback; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.jpa.event.spi.CallbackBuilder; /** * Keep track of all lifecycle callbacks and listeners for a given persistence unit @@ -43,8 +44,10 @@ public void registerCallbacks(Class entityClass, Callback[] callbacks) { } final HashMap map = determineAppropriateCallbackMap( callbacks[0].getCallbackType() ); - if ( map.containsKey( entityClass ) ) { - throw new PersistenceException( "Error build callback listeners; entity [" + entityClass.getName() + " was already processed" ); + Callback[] entityCallbacks = map.get( entityClass ); + + if ( entityCallbacks != null ) { + callbacks = ArrayHelper.join( entityCallbacks, callbacks ); } map.put( entityClass, callbacks ); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmbeddableCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmbeddableCallback.java new file mode 100644 index 000000000000..e5fc177e0c93 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmbeddableCallback.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.event.internal; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.property.access.spi.Getter; + +/** + * Represents a JPA callback on the embeddable type + * + * @author Vlad Mihalcea + */ +final class EmbeddableCallback extends AbstractCallback { + + private final Getter embeddableGetter; + private final Method callbackMethod; + + EmbeddableCallback(Getter embeddableGetter, Method callbackMethod, CallbackType callbackType) { + super( callbackType ); + this.embeddableGetter = embeddableGetter; + this.callbackMethod = callbackMethod; + } + + @Override + public boolean performCallback(Object entity) { + try { + Object embeddable = embeddableGetter.get( entity ); + if ( embeddable != null ) { + callbackMethod.invoke( embeddable ); + } + return true; + } + catch (InvocationTargetException e) { + //keep runtime exceptions as is + if ( e.getTargetException() instanceof RuntimeException ) { + throw (RuntimeException) e.getTargetException(); + } + else { + throw new RuntimeException( e.getTargetException() ); + } + } + catch (Exception e) { + throw new RuntimeException( e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EntityCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EntityCallback.java new file mode 100644 index 000000000000..8bae4ed6a777 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EntityCallback.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.jpa.event.internal; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.hibernate.jpa.event.spi.CallbackType; + +/** + * Represents a JPA callback on the entity itself + * + * @author Kabir Khan + * @author Steve Ebersole + */ +final class EntityCallback extends AbstractCallback { + + private final Method callbackMethod; + + EntityCallback(Method callbackMethod, CallbackType callbackType) { + super( callbackType ); + this.callbackMethod = callbackMethod; + } + + @Override + public boolean performCallback(Object entity) { + try { + callbackMethod.invoke( entity ); + return true; + } + catch (InvocationTargetException e) { + //keep runtime exceptions as is + if ( e.getTargetException() instanceof RuntimeException ) { + throw (RuntimeException) e.getTargetException(); + } + else { + throw new RuntimeException( e.getTargetException() ); + } + } + catch (Exception e) { + throw new RuntimeException( e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/ListenerCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/ListenerCallback.java new file mode 100644 index 000000000000..e47c47a4d7c2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/ListenerCallback.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.jpa.event.internal; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.resource.beans.spi.ManagedBean; + +/** + * Represents a JPA callback using a dedicated listener + * + * @author Kabir Khan + * @author Steve Ebersole + */ +class ListenerCallback extends AbstractCallback { + + private final Method callbackMethod; + private final ManagedBean listenerManagedBean; + + ListenerCallback(ManagedBean listenerManagedBean, Method callbackMethod, CallbackType callbackType) { + super( callbackType ); + this.listenerManagedBean = listenerManagedBean; + this.callbackMethod = callbackMethod; + } + + @Override + public boolean performCallback(Object entity) { + try { + callbackMethod.invoke( listenerManagedBean.getBeanInstance(), entity ); + return true; + } + catch (InvocationTargetException e) { + //keep runtime exceptions as is + if ( e.getTargetException() instanceof RuntimeException ) { + throw (RuntimeException) e.getTargetException(); + } + else { + throw new RuntimeException( e.getTargetException() ); + } + } + catch (RuntimeException e) { + throw e; + } + catch (Exception e) { + throw new RuntimeException( e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/HibernateEntityManagerEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/HibernateEntityManagerEventListener.java deleted file mode 100644 index 0e0bab1d3194..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/HibernateEntityManagerEventListener.java +++ /dev/null @@ -1,15 +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.jpa.event.internal.core; - -/** - * Marker interface for handling listener duplication checking (to avoid multiple registrations). - * - * @author Steve Ebersole - */ -public interface HibernateEntityManagerEventListener { -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaAutoFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaAutoFlushEventListener.java deleted file mode 100644 index 119a73f027c1..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaAutoFlushEventListener.java +++ /dev/null @@ -1,38 +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.jpa.event.internal.core; - -import java.util.IdentityHashMap; - -import org.hibernate.engine.spi.CascadingAction; -import org.hibernate.engine.spi.CascadingActions; -import org.hibernate.event.internal.DefaultAutoFlushEventListener; -import org.hibernate.event.spi.AutoFlushEventListener; - -/** - * In JPA, it is the create operation that is cascaded to unmanaged entities at flush time (instead of the save-update - * operation in Hibernate). - * - * @author Gavin King - */ -public class JpaAutoFlushEventListener - extends DefaultAutoFlushEventListener - implements HibernateEntityManagerEventListener { - - public static final AutoFlushEventListener INSTANCE = new JpaAutoFlushEventListener(); - - @Override - protected CascadingAction getCascadingAction() { - return CascadingActions.PERSIST_ON_FLUSH; - } - - @Override - protected Object getAnything() { - return new IdentityHashMap( 10 ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaDeleteEventListener.java deleted file mode 100644 index 4e062abb8909..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaDeleteEventListener.java +++ /dev/null @@ -1,54 +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.jpa.event.internal.core; - -import java.io.Serializable; - -import org.hibernate.event.internal.DefaultDeleteEventListener; -import org.hibernate.event.spi.DeleteEvent; -import org.hibernate.event.spi.EventSource; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; -import org.hibernate.persister.entity.EntityPersister; - -/** - * Overrides the LifeCycle OnSave call to call the PreRemove operation - * - * @author Emmanuel Bernard - */ -public class JpaDeleteEventListener extends DefaultDeleteEventListener implements CallbackRegistryConsumer { - private CallbackRegistry callbackRegistry; - - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaDeleteEventListener() { - super(); - } - - public JpaDeleteEventListener(CallbackRegistry callbackRegistry) { - this(); - this.callbackRegistry = callbackRegistry; - } - - @Override - protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) { - callbackRegistry.preRemove( entity ); - return super.invokeDeleteLifecycle( session, entity, persister ); - } - - @Override - protected void performDetachedEntityDeletionCheck(DeleteEvent event) { - EventSource source = event.getSession(); - String entityName = event.getEntityName(); - EntityPersister persister = source.getEntityPersister( entityName, event.getObject() ); - Serializable id = persister.getIdentifier( event.getObject(), source ); - entityName = entityName == null ? source.guessEntityName( event.getObject() ) : entityName; - throw new IllegalArgumentException("Removing a detached instance "+ entityName + "#" + id); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaFlushEntityEventListener.java deleted file mode 100644 index 99ae17c4d1a6..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaFlushEntityEventListener.java +++ /dev/null @@ -1,74 +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.jpa.event.internal.core; - -import org.hibernate.SessionFactory; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.engine.spi.Status; -import org.hibernate.event.internal.DefaultFlushEntityEventListener; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; -import org.hibernate.metadata.ClassMetadata; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.Type; - -/** - * Overrides the LifeCycle OnSave call to call the PreUpdate operation - * - * @author Emmanuel Bernard - */ -public class JpaFlushEntityEventListener extends DefaultFlushEntityEventListener implements CallbackRegistryConsumer { - private CallbackRegistry callbackRegistry; - - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaFlushEntityEventListener() { - super(); - } - - public JpaFlushEntityEventListener(CallbackRegistry callbackRegistry) { - super(); - this.callbackRegistry = callbackRegistry; - } - - @Override - protected boolean invokeInterceptor( - SessionImplementor session, - Object entity, - EntityEntry entry, - Object[] values, - EntityPersister persister) { - boolean isDirty = false; - if ( entry.getStatus() != Status.DELETED ) { - if ( callbackRegistry.preUpdate( entity ) ) { - isDirty = copyState( entity, persister.getPropertyTypes(), values, session.getFactory() ); - } - } - return super.invokeInterceptor( session, entity, entry, values, persister ) || isDirty; - } - - private boolean copyState(Object entity, Type[] types, Object[] state, SessionFactory sf) { - // copy the entity state into the state array and return true if the state has changed - ClassMetadata metadata = sf.getClassMetadata( entity.getClass() ); - Object[] newState = metadata.getPropertyValues( entity ); - int size = newState.length; - boolean isDirty = false; - for ( int index = 0; index < size ; index++ ) { - if ( ( state[index] == LazyPropertyInitializer.UNFETCHED_PROPERTY && - newState[index] != LazyPropertyInitializer.UNFETCHED_PROPERTY ) || - ( state[index] != newState[index] && !types[index].isEqual( state[index], newState[index] ) ) ) { - isDirty = true; - state[index] = newState[index]; - } - } - return isDirty; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaFlushEventListener.java deleted file mode 100644 index 5ba63d200a00..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaFlushEventListener.java +++ /dev/null @@ -1,35 +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.jpa.event.internal.core; - -import java.util.IdentityHashMap; - -import org.hibernate.engine.spi.CascadingAction; -import org.hibernate.engine.spi.CascadingActions; -import org.hibernate.event.internal.DefaultFlushEventListener; -import org.hibernate.event.spi.FlushEventListener; - -/** - * In JPA, it is the create operation that is cascaded to unmanaged entities at flush time (instead of the - * save-update operation in Hibernate). - * - * @author Gavin King - */ -public class JpaFlushEventListener extends DefaultFlushEventListener implements HibernateEntityManagerEventListener { - public static final FlushEventListener INSTANCE = new JpaFlushEventListener(); - - @Override - protected CascadingAction getCascadingAction() { - return CascadingActions.PERSIST_ON_FLUSH; - } - - @Override - protected Object getAnything() { - return new IdentityHashMap( 10 ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaMergeEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaMergeEventListener.java deleted file mode 100644 index 7e0c4cfaa1c4..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaMergeEventListener.java +++ /dev/null @@ -1,58 +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.jpa.event.internal.core; - -import java.io.Serializable; - -import org.hibernate.event.internal.DefaultMergeEventListener; -import org.hibernate.event.spi.EventSource; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; - -/** - * Overrides the LifeCycle OnSave call to call the PrePersist operation - * - * @author Emmanuel Bernard - */ -public class JpaMergeEventListener extends DefaultMergeEventListener implements CallbackRegistryConsumer { - private CallbackRegistry callbackRegistry; - - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaMergeEventListener() { - super(); - } - - public JpaMergeEventListener(CallbackRegistry callbackRegistry) { - super(); - this.callbackRegistry = callbackRegistry; - } - - @Override - protected Serializable saveWithRequestedId( - Object entity, - Serializable requestedId, - String entityName, - Object anything, - EventSource source) { - callbackRegistry.preCreate( entity ); - return super.saveWithRequestedId( entity, requestedId, entityName, anything, source ); - } - - @Override - protected Serializable saveWithGeneratedId( - Object entity, - String entityName, - Object anything, - EventSource source, - boolean requiresImmediateIdAccess) { - callbackRegistry.preCreate( entity ); - return super.saveWithGeneratedId( entity, entityName, anything, source, requiresImmediateIdAccess ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPersistEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPersistEventListener.java deleted file mode 100644 index db33d4763b9b..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPersistEventListener.java +++ /dev/null @@ -1,100 +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.jpa.event.internal.core; - -import java.io.Serializable; -import java.util.Iterator; -import java.util.Map; - -import org.hibernate.HibernateException; -import org.hibernate.engine.spi.CascadingAction; -import org.hibernate.engine.spi.CascadingActions; -import org.hibernate.event.internal.DefaultPersistEventListener; -import org.hibernate.event.spi.EventSource; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; -import org.hibernate.type.CollectionType; - -import org.jboss.logging.Logger; - -/** - * Overrides the LifeCycle OnSave call to call the PrePersist operation - * - * @author Emmanuel Bernard - */ -public class JpaPersistEventListener extends DefaultPersistEventListener implements CallbackRegistryConsumer { - private static final Logger log = Logger.getLogger( JpaPersistEventListener.class ); - - private CallbackRegistry callbackRegistry; - - @Override - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaPersistEventListener() { - super(); - } - - public JpaPersistEventListener(CallbackRegistry callbackRegistry) { - super(); - this.callbackRegistry = callbackRegistry; - } - - @Override - protected Serializable saveWithRequestedId( - Object entity, - Serializable requestedId, - String entityName, - Object anything, - EventSource source) { - callbackRegistry.preCreate( entity ); - return super.saveWithRequestedId( entity, requestedId, entityName, anything, source ); - } - - @Override - protected Serializable saveWithGeneratedId( - Object entity, - String entityName, - Object anything, - EventSource source, - boolean requiresImmediateIdAccess) { - callbackRegistry.preCreate( entity ); - return super.saveWithGeneratedId( entity, entityName, anything, source, requiresImmediateIdAccess ); - } - - @Override - protected CascadingAction getCascadeAction() { - return PERSIST_SKIPLAZY; - } - - public static final CascadingAction PERSIST_SKIPLAZY = new CascadingActions.BaseCascadingAction() { - @Override - public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled) - throws HibernateException { - log.trace( "Cascading persist to : " + entityName ); - session.persist( entityName, child, (Map) anything ); - } - @Override - public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) { - // persists don't cascade to uninitialized collections - return CascadingActions.getLoadedElementsIterator( session, collectionType, collection ); - } - @Override - public boolean deleteOrphans() { - return false; - } - @Override - public boolean performOnLazyProperty() { - return false; - } - @Override - public String toString() { - return "ACTION_PERSIST_SKIPLAZY"; - } - }; -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPersistOnFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPersistOnFlushEventListener.java deleted file mode 100644 index 4e1b668a9408..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPersistOnFlushEventListener.java +++ /dev/null @@ -1,20 +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.jpa.event.internal.core; - -import org.hibernate.engine.spi.CascadingAction; -import org.hibernate.engine.spi.CascadingActions; - -/** - * @author Emmanuel Bernard - */ -public class JpaPersistOnFlushEventListener extends JpaPersistEventListener { - @Override - protected CascadingAction getCascadeAction() { - return CascadingActions.PERSIST_ON_FLUSH; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostDeleteEventListener.java deleted file mode 100644 index f094af9eb0a7..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostDeleteEventListener.java +++ /dev/null @@ -1,43 +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.jpa.event.internal.core; - -import org.hibernate.event.spi.PostDeleteEvent; -import org.hibernate.event.spi.PostDeleteEventListener; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; -import org.hibernate.jpa.event.spi.jpa.CallbackType; -import org.hibernate.persister.entity.EntityPersister; - -/** - * @author Kabir Khan - */ -public class JpaPostDeleteEventListener implements PostDeleteEventListener, CallbackRegistryConsumer { - private CallbackRegistry callbackRegistry; - - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaPostDeleteEventListener() { - super(); - } - - public JpaPostDeleteEventListener(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public void onPostDelete(PostDeleteEvent event) { - Object entity = event.getEntity(); - callbackRegistry.postRemove( entity ); - } - - @Override - public boolean requiresPostCommitHanding(EntityPersister persister) { - return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_REMOVE ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostInsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostInsertEventListener.java deleted file mode 100644 index 720ebc13ff94..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostInsertEventListener.java +++ /dev/null @@ -1,46 +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.jpa.event.internal.core; - -import org.hibernate.event.spi.PostInsertEvent; -import org.hibernate.event.spi.PostInsertEventListener; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; -import org.hibernate.jpa.event.spi.jpa.CallbackType; -import org.hibernate.persister.entity.EntityPersister; - -/** - * @author Kabir Khan - * @author Steve Ebersole - */ -public class JpaPostInsertEventListener implements PostInsertEventListener, CallbackRegistryConsumer { - private CallbackRegistry callbackRegistry; - - @Override - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaPostInsertEventListener() { - super(); - } - - public JpaPostInsertEventListener(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - @Override - public void onPostInsert(PostInsertEvent event) { - Object entity = event.getEntity(); - callbackRegistry.postCreate( entity ); - } - - @Override - public boolean requiresPostCommitHanding(EntityPersister persister) { - return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_PERSIST ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostLoadEventListener.java deleted file mode 100644 index 9ab9e0e0cfae..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostLoadEventListener.java +++ /dev/null @@ -1,39 +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.jpa.event.internal.core; - -import org.hibernate.event.spi.PostLoadEvent; -import org.hibernate.event.spi.PostLoadEventListener; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; - -/** - * @author Kabir Khan - */ -public class JpaPostLoadEventListener implements PostLoadEventListener, CallbackRegistryConsumer { - CallbackRegistry callbackRegistry; - - @Override - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaPostLoadEventListener() { - super(); - } - - public JpaPostLoadEventListener(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - @Override - public void onPostLoad(PostLoadEvent event) { - Object entity = event.getEntity(); - callbackRegistry.postLoad( entity ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostUpdateEventListener.java deleted file mode 100644 index 2844ab7eec5a..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostUpdateEventListener.java +++ /dev/null @@ -1,89 +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.jpa.event.internal.core; - -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.Status; -import org.hibernate.event.spi.EventSource; -import org.hibernate.event.spi.PostCollectionRecreateEvent; -import org.hibernate.event.spi.PostCollectionRecreateEventListener; -import org.hibernate.event.spi.PostCollectionRemoveEvent; -import org.hibernate.event.spi.PostCollectionRemoveEventListener; -import org.hibernate.event.spi.PostCollectionUpdateEvent; -import org.hibernate.event.spi.PostCollectionUpdateEventListener; -import org.hibernate.event.spi.PostUpdateEvent; -import org.hibernate.event.spi.PostUpdateEventListener; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; -import org.hibernate.jpa.event.spi.jpa.CallbackType; -import org.hibernate.persister.entity.EntityPersister; - -/** - * Implementation of the post update listeners. - * - * @author Kabir Khan - */ -@SuppressWarnings("serial") -public class JpaPostUpdateEventListener - implements PostUpdateEventListener, CallbackRegistryConsumer, PostCollectionRecreateEventListener, - PostCollectionRemoveEventListener, PostCollectionUpdateEventListener { - private CallbackRegistry callbackRegistry; - - @Override - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaPostUpdateEventListener() { - super(); - } - - public JpaPostUpdateEventListener(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - @Override - public void onPostUpdate(PostUpdateEvent event) { - Object entity = event.getEntity(); - EventSource eventSource = event.getSession(); - handlePostUpdate(entity, eventSource); - } - - private void handlePostUpdate(Object entity, EventSource source) { - EntityEntry entry = (EntityEntry) source.getPersistenceContext().getEntry( entity ); - // mimic the preUpdate filter - if ( Status.DELETED != entry.getStatus()) { - callbackRegistry.postUpdate(entity); - } - } - - @Override - public boolean requiresPostCommitHanding(EntityPersister persister) { - return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_UPDATE ); - } - - @Override - public void onPostRecreateCollection(PostCollectionRecreateEvent event) { - Object entity = event.getCollection().getOwner(); - EventSource eventSource = event.getSession(); - handlePostUpdate(entity, eventSource); - } - - @Override - public void onPostRemoveCollection(PostCollectionRemoveEvent event) { - Object entity = event.getCollection().getOwner(); - EventSource eventSource = event.getSession(); - handlePostUpdate(entity, eventSource); - } - - @Override - public void onPostUpdateCollection(PostCollectionUpdateEvent event) { - Object entity = event.getCollection().getOwner(); - EventSource eventSource = event.getSession(); - handlePostUpdate(entity, eventSource); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaSaveEventListener.java deleted file mode 100644 index d72a7d9a2aca..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaSaveEventListener.java +++ /dev/null @@ -1,58 +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.jpa.event.internal.core; - -import java.io.Serializable; - -import org.hibernate.event.internal.DefaultSaveEventListener; -import org.hibernate.event.spi.EventSource; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; - -/** - * Overrides the LifeCycle OnSave call to call the PrePersist operation - * - * @author Emmanuel Bernard - */ -public class JpaSaveEventListener extends DefaultSaveEventListener implements CallbackRegistryConsumer { - private CallbackRegistry callbackRegistry; - - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaSaveEventListener() { - super(); - } - - public JpaSaveEventListener(CallbackRegistry callbackRegistry) { - super(); - this.callbackRegistry = callbackRegistry; - } - - @Override - protected Serializable saveWithRequestedId( - Object entity, - Serializable requestedId, - String entityName, - Object anything, - EventSource source) { - callbackRegistry.preCreate( entity ); - return super.saveWithRequestedId( entity, requestedId, entityName, anything, source ); - } - - @Override - protected Serializable saveWithGeneratedId( - Object entity, - String entityName, - Object anything, - EventSource source, - boolean requiresImmediateIdAccess) { - callbackRegistry.preCreate( entity ); - return super.saveWithGeneratedId( entity, entityName, anything, source, requiresImmediateIdAccess ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaSaveOrUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaSaveOrUpdateEventListener.java deleted file mode 100644 index 73446a1805dc..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/JpaSaveOrUpdateEventListener.java +++ /dev/null @@ -1,58 +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.jpa.event.internal.core; - -import java.io.Serializable; - -import org.hibernate.event.internal.DefaultSaveOrUpdateEventListener; -import org.hibernate.event.spi.EventSource; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistry; - -/** - * Overrides the LifeCycle OnSave call to call the PrePersist operation - * - * @author Emmanuel Bernard - */ -public class JpaSaveOrUpdateEventListener extends DefaultSaveOrUpdateEventListener implements CallbackRegistryConsumer { - private CallbackRegistry callbackRegistry; - - public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { - this.callbackRegistry = callbackRegistry; - } - - public JpaSaveOrUpdateEventListener() { - super(); - } - - public JpaSaveOrUpdateEventListener(CallbackRegistry callbackRegistry) { - super(); - this.callbackRegistry = callbackRegistry; - } - - @Override - protected Serializable saveWithRequestedId( - Object entity, - Serializable requestedId, - String entityName, - Object anything, - EventSource source) { - callbackRegistry.preCreate( entity ); - return super.saveWithRequestedId( entity, requestedId, entityName, anything, source ); - } - - @Override - protected Serializable saveWithGeneratedId( - Object entity, - String entityName, - Object anything, - EventSource source, - boolean requiresImmediateIdAccess) { - callbackRegistry.preCreate( entity ); - return super.saveWithGeneratedId( entity, entityName, anything, source, requiresImmediateIdAccess ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/package-info.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/package-info.java deleted file mode 100644 index 750a3460a1de..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/core/package-info.java +++ /dev/null @@ -1,15 +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.jpa.event.internal.core; - -/** - * Hibernate EntityManager specific implementations of Hibernate event listeners. Generally the listeners - * here either:

      - *
    • provide tweaks to internal processing to conform with JPA spec
    • - *
    • bridge to JPA event callbacks
    • - *
    - */ diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/AbstractCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/AbstractCallback.java deleted file mode 100644 index cac0130a4277..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/AbstractCallback.java +++ /dev/null @@ -1,31 +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.jpa.event.internal.jpa; - -import org.hibernate.jpa.event.spi.jpa.Callback; -import org.hibernate.jpa.event.spi.jpa.CallbackType; - -/** - * @author Steve Ebersole - */ -public abstract class AbstractCallback implements Callback { - private final CallbackType callbackType; - - public AbstractCallback(CallbackType callbackType) { - this.callbackType = callbackType; - } - - @Override - public boolean isActive() { - return true; - } - - @Override - public CallbackType getCallbackType() { - return callbackType; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/CallbackBuilderLegacyImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/CallbackBuilderLegacyImpl.java deleted file mode 100644 index fe3a8007c24d..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/CallbackBuilderLegacyImpl.java +++ /dev/null @@ -1,248 +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.jpa.event.internal.jpa; - -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import javax.persistence.Entity; -import javax.persistence.EntityListeners; -import javax.persistence.ExcludeDefaultListeners; -import javax.persistence.ExcludeSuperclassListeners; -import javax.persistence.MappedSuperclass; -import javax.persistence.PersistenceException; - -import org.hibernate.MappingException; -import org.hibernate.annotations.common.reflection.ClassLoadingException; -import org.hibernate.annotations.common.reflection.ReflectionManager; -import org.hibernate.annotations.common.reflection.XClass; -import org.hibernate.annotations.common.reflection.XMethod; -import org.hibernate.jpa.event.spi.jpa.Callback; -import org.hibernate.jpa.event.spi.jpa.CallbackType; -import org.hibernate.jpa.event.spi.jpa.CallbackBuilder; -import org.hibernate.jpa.event.spi.jpa.ListenerFactory; - -import org.jboss.logging.Logger; - -/** - * EntityCallbackBuilder implementation using HCANN ReflectionManager. "legacy" in that - * we want to move to Jandex instead. - * - * @author Steve Ebersole - */ -public class CallbackBuilderLegacyImpl implements CallbackBuilder { - private static final Logger log = Logger.getLogger( CallbackBuilderLegacyImpl.class ); - - private final ListenerFactory jpaListenerFactory; - private final ReflectionManager reflectionManager; - - public CallbackBuilderLegacyImpl(ListenerFactory jpaListenerFactory, ReflectionManager reflectionManager) { - this.jpaListenerFactory = jpaListenerFactory; - this.reflectionManager = reflectionManager; - } - - @Override - public void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar) { - try { - final XClass entityXClass = reflectionManager.classForName( entityClassName ); - final Class entityClass = reflectionManager.toClass( entityXClass ); - for ( CallbackType callbackType : CallbackType.values() ) { - if ( callbackRegistrar.hasRegisteredCallbacks( entityClass, callbackType ) ) { - // this most likely means we have a class mapped multiple times using the hbm.xml - // "entity name" feature - log.debugf( - "CallbackRegistry reported that Class [%s] already had %s callbacks registered; " + - "assuming this means the class was mapped twice " + - "(using hbm.xml entity-name support) - skipping subsequent registrations", - entityClassName, - callbackType.getCallbackAnnotation().getSimpleName() - ); - continue; - } - final Callback[] callbacks = resolveCallbacks( entityXClass, callbackType, reflectionManager ); - callbackRegistrar.registerCallbacks( entityClass, callbacks ); - } - } - catch (ClassLoadingException e) { - throw new MappingException( "entity class not found: " + entityClassName, e ); - } - } - - @Override - public void release() { - // nothign to do - } - - public Callback[] resolveCallbacks(XClass beanClass, CallbackType callbackType, ReflectionManager reflectionManager) { - List callbacks = new ArrayList(); - List callbacksMethodNames = new ArrayList(); //used to track overridden methods - List orderedListeners = new ArrayList(); - XClass currentClazz = beanClass; - boolean stopListeners = false; - boolean stopDefaultListeners = false; - do { - Callback callback = null; - List methods = currentClazz.getDeclaredMethods(); - for ( final XMethod xMethod : methods ) { - if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { - Method method = reflectionManager.toMethod( xMethod ); - final String methodName = method.getName(); - if ( !callbacksMethodNames.contains( methodName ) ) { - //overridden method, remove the superclass overridden method - if ( callback == null ) { - callback = new EntityCallback( method, callbackType ); - Class returnType = method.getReturnType(); - Class[] args = method.getParameterTypes(); - if ( returnType != Void.TYPE || args.length != 0 ) { - throw new RuntimeException( - "Callback methods annotated on the bean class must return void and take no arguments: " - + callbackType.getCallbackAnnotation().getName() + " - " + xMethod - ); - } - method.setAccessible( true ); - log.debugf( - "Adding %s as %s callback for entity %s", - methodName, - callbackType.getCallbackAnnotation().getSimpleName(), - beanClass.getName() - ); - callbacks.add( 0, callback ); //superclass first - callbacksMethodNames.add( 0, methodName ); - } - else { - throw new PersistenceException( - "You can only annotate one callback method with " - + callbackType.getCallbackAnnotation().getName() + " in bean class: " + beanClass.getName() - ); - } - } - } - } - if ( !stopListeners ) { - getListeners( currentClazz, orderedListeners ); - stopListeners = currentClazz.isAnnotationPresent( ExcludeSuperclassListeners.class ); - stopDefaultListeners = currentClazz.isAnnotationPresent( ExcludeDefaultListeners.class ); - } - - do { - currentClazz = currentClazz.getSuperclass(); - } - while ( currentClazz != null - && !( currentClazz.isAnnotationPresent( Entity.class ) - || currentClazz.isAnnotationPresent( MappedSuperclass.class ) ) - ); - } - while ( currentClazz != null ); - - //handle default listeners - if ( !stopDefaultListeners ) { - List defaultListeners = (List) reflectionManager.getDefaults().get( EntityListeners.class ); - - if ( defaultListeners != null ) { - int defaultListenerSize = defaultListeners.size(); - for ( int i = defaultListenerSize - 1; i >= 0; i-- ) { - orderedListeners.add( defaultListeners.get( i ) ); - } - } - } - - for ( Class listener : orderedListeners ) { - Callback callback = null; - if ( listener != null ) { - XClass xListener = reflectionManager.toXClass( listener ); - callbacksMethodNames = new ArrayList(); - List methods = xListener.getDeclaredMethods(); - for ( final XMethod xMethod : methods ) { - if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { - final Method method = reflectionManager.toMethod( xMethod ); - final String methodName = method.getName(); - if ( !callbacksMethodNames.contains( methodName ) ) { - //overridden method, remove the superclass overridden method - if ( callback == null ) { - callback = new ListenerCallback( - jpaListenerFactory.buildListener( listener ), - method, - callbackType - ); - - Class returnType = method.getReturnType(); - Class[] args = method.getParameterTypes(); - if ( returnType != Void.TYPE || args.length != 1 ) { - throw new PersistenceException( - "Callback methods annotated in a listener bean class must return void and take one argument: " - + callbackType.getCallbackAnnotation().getName() + " - " + method - ); - } - if ( !method.isAccessible() ) { - method.setAccessible( true ); - } - log.debugf( - "Adding %s as %s callback for entity %s", - methodName, - callbackType.getCallbackAnnotation().getSimpleName(), - beanClass.getName() - ); - callbacks.add( 0, callback ); // listeners first - } - else { - throw new PersistenceException( - "You can only annotate one callback method with " - + callbackType.getCallbackAnnotation().getName() - + " in bean class: " + beanClass.getName() - + " and callback listener: " + listener.getName() - ); - } - } - } - } - } - } - return callbacks.toArray( new Callback[callbacks.size()] ); - } - - private static boolean useAnnotationAnnotatedByListener; - - static { - //check whether reading annotations of annotations is useful or not - useAnnotationAnnotatedByListener = false; - Target target = EntityListeners.class.getAnnotation( Target.class ); - if ( target != null ) { - for ( ElementType type : target.value() ) { - if ( type.equals( ElementType.ANNOTATION_TYPE ) ) { - useAnnotationAnnotatedByListener = true; - } - } - } - } - - private static void getListeners(XClass currentClazz, List orderedListeners) { - EntityListeners entityListeners = currentClazz.getAnnotation( EntityListeners.class ); - if ( entityListeners != null ) { - Class[] classes = entityListeners.value(); - int size = classes.length; - for ( int index = size - 1; index >= 0; index-- ) { - orderedListeners.add( classes[index] ); - } - } - if ( useAnnotationAnnotatedByListener ) { - Annotation[] annotations = currentClazz.getAnnotations(); - for ( Annotation annot : annotations ) { - entityListeners = annot.getClass().getAnnotation( EntityListeners.class ); - if ( entityListeners != null ) { - Class[] classes = entityListeners.value(); - int size = classes.length; - for ( int index = size - 1; index >= 0; index-- ) { - orderedListeners.add( classes[index] ); - } - } - } - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/EntityCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/EntityCallback.java deleted file mode 100644 index 8e72c9543d3f..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/EntityCallback.java +++ /dev/null @@ -1,48 +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.jpa.event.internal.jpa; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import org.hibernate.jpa.event.spi.jpa.Callback; -import org.hibernate.jpa.event.spi.jpa.CallbackType; - -/** - * Represents a JPA callback on the entity itself - * - * @author Kabir Khan - * @author Steve Ebersole - */ -public class EntityCallback extends AbstractCallback implements Callback { - private final Method callbackMethod; - - public EntityCallback(Method callbackMethod, CallbackType callbackType) { - super( callbackType ); - this.callbackMethod = callbackMethod; - } - - @Override - public boolean performCallback(Object entity) { - try { - callbackMethod.invoke( entity ); - return true; - } - catch (InvocationTargetException e) { - //keep runtime exceptions as is - if ( e.getTargetException() instanceof RuntimeException ) { - throw (RuntimeException) e.getTargetException(); - } - else { - throw new RuntimeException( e.getTargetException() ); - } - } - catch (Exception e) { - throw new RuntimeException( e ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerCallback.java deleted file mode 100644 index 946eda3efa44..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerCallback.java +++ /dev/null @@ -1,51 +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.jpa.event.internal.jpa; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import org.hibernate.jpa.event.spi.jpa.Callback; -import org.hibernate.jpa.event.spi.jpa.CallbackType; -import org.hibernate.jpa.event.spi.jpa.Listener; - -/** - * Represents a JPA callback using a dedicated listener - * - * @author Kabir Khan - * @author Steve Ebersole - */ -public class ListenerCallback extends AbstractCallback implements Callback { - private final Method callbackMethod; - private final Listener listenerInstance; - - public ListenerCallback(Listener listenerInstance, Method callbackMethod, CallbackType callbackType) { - super( callbackType ); - this.listenerInstance = listenerInstance; - this.callbackMethod = callbackMethod; - } - - @Override - public boolean performCallback(Object entity) { - try { - callbackMethod.invoke( listenerInstance.getListener(), entity ); - return true; - } - catch (InvocationTargetException e) { - //keep runtime exceptions as is - if ( e.getTargetException() instanceof RuntimeException ) { - throw (RuntimeException) e.getTargetException(); - } - else { - throw new RuntimeException( e.getTargetException() ); - } - } - catch (Exception e) { - throw new RuntimeException( e ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerDelayedImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerDelayedImpl.java deleted file mode 100644 index d3e9d9098d92..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerDelayedImpl.java +++ /dev/null @@ -1,126 +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.jpa.event.internal.jpa; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.InjectionTarget; - -import org.hibernate.jpa.event.spi.jpa.Listener; -import org.hibernate.jpa.event.spi.jpa.ListenerFactory; - -import org.jboss.logging.Logger; - -/** - * CDI-based implementation of the ListenerFactory contract. This implementation - * delayes access to the CDI BeanManager until first need. - * - * @author Steve Ebersole - */ -@SuppressWarnings("unused") -public class ListenerFactoryBeanManagerDelayedImpl implements ListenerFactory { - private static final Logger log = Logger.getLogger( ListenerFactoryBeanManagerDelayedImpl.class ); - - private final BeanManager beanManager; - private final Map listenerMap = new ConcurrentHashMap(); - - /** - * Used via reflection from JpaIntegrator, the intent being to isolate CDI dependency - * to just this class and its delegates in the case that a BeanManager is passed. - * - * @param reference The BeanManager reference - * - * @return A instantiated ListenerFactoryBeanManagerImpl - */ - @SuppressWarnings("unused") - public static ListenerFactoryBeanManagerDelayedImpl fromBeanManagerReference(Object reference) { - if ( !BeanManager.class.isInstance( reference ) ) { - throw new IllegalArgumentException( - "Expecting BeanManager reference that implements CDI BeanManager contract : " + - reference - ); - } - return new ListenerFactoryBeanManagerDelayedImpl( (BeanManager) reference ); - } - - public ListenerFactoryBeanManagerDelayedImpl(BeanManager beanManager) { - this.beanManager = beanManager; - log.debugf( "Delayed access requested to CDI BeanManager : " + beanManager ); - } - - @Override - @SuppressWarnings("unchecked") - public Listener buildListener(Class listenerClass) { - ListenerImpl listenerImpl = listenerMap.get( listenerClass ); - if ( listenerImpl == null ) { - listenerImpl = new ListenerImpl( listenerClass ); - listenerMap.put( listenerClass, listenerImpl ); - } - return (Listener) listenerImpl; - } - - @Override - public void release() { - for ( ListenerImpl listenerImpl : listenerMap.values() ) { - listenerImpl.release(); - } - listenerMap.clear(); - } - - private class ListenerImpl implements Listener { - private final Class listenerClass; - - private boolean initialized = false; - - private InjectionTarget injectionTarget; - private CreationalContext creationalContext; - private T listenerInstance; - - private ListenerImpl(Class listenerClass) { - this.listenerClass = listenerClass; - log.debugf( "Delayed CDI listener built : " + listenerClass ); - } - - public void initialize() { - log.debug( "Initializing delayed CDI listener on first use : " + listenerClass ); - AnnotatedType annotatedType = beanManager.createAnnotatedType( listenerClass ); - this.injectionTarget = beanManager.createInjectionTarget( annotatedType ); - this.creationalContext = beanManager.createCreationalContext( null ); - - this.listenerInstance = injectionTarget.produce( creationalContext ); - injectionTarget.inject( this.listenerInstance, creationalContext ); - - injectionTarget.postConstruct( this.listenerInstance ); - - this.initialized = true; - } - - @Override - public T getListener() { - if ( !initialized ) { - initialize(); - } - return listenerInstance; - } - - public void release() { - if ( !initialized ) { - log.debug( "Skipping release for CDI listener [" + listenerClass + "] as it was not initialized" ); - return; - } - - log.debug( "Releasing CDI listener : " + listenerClass ); - - injectionTarget.preDestroy( listenerInstance ); - injectionTarget.dispose( listenerInstance ); - creationalContext.release(); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerExtendedImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerExtendedImpl.java deleted file mode 100644 index 8fd6435f20ee..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerExtendedImpl.java +++ /dev/null @@ -1,130 +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.jpa.event.internal.jpa; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.InjectionTarget; - -import org.hibernate.HibernateException; -import org.hibernate.jpa.event.spi.jpa.ExtendedBeanManager; -import org.hibernate.jpa.event.spi.jpa.Listener; -import org.hibernate.jpa.event.spi.jpa.ListenerFactory; - -import org.jboss.logging.Logger; - -/** - * CDI-based implementation of the ListenerFactory contract. Further, this - * implementation leverages the ExtendedBeanManager contract to delay CDI calls. - * - * @author Steve Ebersole - */ -@SuppressWarnings("unused") -public class ListenerFactoryBeanManagerExtendedImpl implements ListenerFactory, ExtendedBeanManager.LifecycleListener { - private static final Logger log = Logger.getLogger( ListenerFactoryBeanManagerExtendedImpl.class ); - - private final Map listenerMap = new ConcurrentHashMap(); - - /** - * Used via reflection from JpaIntegrator, the intent being to isolate CDI dependency - * to just this class and its delegates in the case that a BeanManager is passed. - * - * @param reference The BeanManager reference - * - * @return A instantiated ListenerFactoryBeanManagerImpl - */ - @SuppressWarnings("unused") - public static ListenerFactoryBeanManagerExtendedImpl fromBeanManagerReference(Object reference) { - if ( !ExtendedBeanManager.class.isInstance( reference ) ) { - throw new IllegalArgumentException( - "Expecting BeanManager reference that implements optional ExtendedBeanManager contract : " + - reference - ); - } - return new ListenerFactoryBeanManagerExtendedImpl( (ExtendedBeanManager) reference ); - } - - public ListenerFactoryBeanManagerExtendedImpl(ExtendedBeanManager beanManager) { - beanManager.registerLifecycleListener( this ); - log.debugf( "ExtendedBeanManager access requested to CDI BeanManager : " + beanManager ); - } - - @Override - @SuppressWarnings("unchecked") - public Listener buildListener(Class listenerClass) { - ListenerImpl listenerImpl = listenerMap.get( listenerClass ); - if ( listenerImpl == null ) { - listenerImpl = new ListenerImpl( listenerClass ); - listenerMap.put( listenerClass, listenerImpl ); - } - return (Listener) listenerImpl; - } - - @Override - public void release() { - for ( ListenerImpl listenerImpl : listenerMap.values() ) { - listenerImpl.release(); - } - listenerMap.clear(); - } - - @Override - public void beanManagerInitialized(BeanManager beanManager) { - for ( ListenerImpl listenerImpl : listenerMap.values() ) { - listenerImpl.initialize( beanManager ); - } - } - - private static class ListenerImpl implements Listener { - private final Class listenerClass; - - private boolean initialized = false; - - private InjectionTarget injectionTarget; - private CreationalContext creationalContext; - private T listenerInstance; - - private ListenerImpl(Class listenerClass) { - this.listenerClass = listenerClass; - } - - public void initialize(BeanManager beanManager) { - AnnotatedType annotatedType = beanManager.createAnnotatedType( listenerClass ); - this.injectionTarget = beanManager.createInjectionTarget( annotatedType ); - this.creationalContext = beanManager.createCreationalContext( null ); - - this.listenerInstance = injectionTarget.produce( creationalContext ); - injectionTarget.inject( this.listenerInstance, creationalContext ); - - injectionTarget.postConstruct( this.listenerInstance ); - - this.initialized = true; - } - - @Override - public T getListener() { - if ( !initialized ) { - throw new HibernateException( "CDI not initialized as expected" ); - } - return listenerInstance; - } - - public void release() { - if ( !initialized ) { - // log - return; - } - - injectionTarget.preDestroy( listenerInstance ); - injectionTarget.dispose( listenerInstance ); - creationalContext.release(); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerStandardImpl.java deleted file mode 100644 index e5a7a265ebdf..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryBeanManagerStandardImpl.java +++ /dev/null @@ -1,109 +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.jpa.event.internal.jpa; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.InjectionTarget; - -import org.hibernate.jpa.event.spi.jpa.Listener; -import org.hibernate.jpa.event.spi.jpa.ListenerFactory; - -import org.jboss.logging.Logger; - -/** - * CDI-based implementation of the ListenerFactory contract. This CDI-based - * implementation works in the JPA standard prescribed manner. - *

    - * See {@link ListenerFactoryBeanManagerExtendedImpl} for an alt implementation that - * is still JPA compliant, but that works based on delayed CDI calls. Works - * on a non-CDI-defined CDI extension; we plan to propose this extension to the - * CDI expert group for the next CDI iteration - * - * @author Steve Ebersole - */ -@SuppressWarnings("unused") -public class ListenerFactoryBeanManagerStandardImpl implements ListenerFactory { - private static final Logger log = Logger.getLogger( ListenerFactoryBeanManagerStandardImpl.class ); - - private final BeanManager beanManager; - private final Map listenerMap = new ConcurrentHashMap(); - - /** - * Used via reflection from JpaIntegrator, the intent being to isolate CDI dependency - * to just this class and its delegates in the case that a BeanManager is passed. - * - * @param reference The BeanManager reference - * - * @return A instantiated ListenerFactoryBeanManagerImpl - */ - @SuppressWarnings("unused") - public static ListenerFactoryBeanManagerStandardImpl fromBeanManagerReference(Object reference) { - if ( !BeanManager.class.isInstance( reference ) ) { - throw new IllegalArgumentException( - "Expecting BeanManager reference that implements CDI BeanManager contract : " + - reference - ); - } - return new ListenerFactoryBeanManagerStandardImpl( (BeanManager) reference ); - } - - public ListenerFactoryBeanManagerStandardImpl(BeanManager beanManager) { - this.beanManager = beanManager; - log.debugf( "Standard access requested to CDI BeanManager : " + beanManager ); - } - - @Override - @SuppressWarnings("unchecked") - public Listener buildListener(Class listenerClass) { - ListenerImpl listenerImpl = listenerMap.get( listenerClass ); - if ( listenerImpl == null ) { - listenerImpl = new ListenerImpl( listenerClass ); - listenerMap.put( listenerClass, listenerImpl ); - } - return (Listener) listenerImpl; - } - - @Override - public void release() { - for ( ListenerImpl listenerImpl : listenerMap.values() ) { - listenerImpl.release(); - } - listenerMap.clear(); - } - - private class ListenerImpl implements Listener { - private final InjectionTarget injectionTarget; - private final CreationalContext creationalContext; - private final T listenerInstance; - - private ListenerImpl(Class listenerClass) { - AnnotatedType annotatedType = beanManager.createAnnotatedType( listenerClass ); - this.injectionTarget = beanManager.createInjectionTarget( annotatedType ); - this.creationalContext = beanManager.createCreationalContext( null ); - - this.listenerInstance = injectionTarget.produce( creationalContext ); - injectionTarget.inject( this.listenerInstance, creationalContext ); - - injectionTarget.postConstruct( this.listenerInstance ); - } - - @Override - public T getListener() { - return listenerInstance; - } - - public void release() { - injectionTarget.preDestroy( listenerInstance ); - injectionTarget.dispose( listenerInstance ); - creationalContext.release(); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryStandardImpl.java deleted file mode 100644 index db9498373be0..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/ListenerFactoryStandardImpl.java +++ /dev/null @@ -1,66 +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.jpa.event.internal.jpa; - -import java.util.concurrent.ConcurrentHashMap; -import javax.persistence.PersistenceException; - -import org.hibernate.jpa.event.spi.jpa.Listener; -import org.hibernate.jpa.event.spi.jpa.ListenerFactory; - -/** - * Standard implementation of the ListenerFactory contract using simple instantiation. - * - * @author Steve Ebersole - */ -public class ListenerFactoryStandardImpl implements ListenerFactory { - private final ConcurrentHashMap listenerInstances = new ConcurrentHashMap(); - - @Override - @SuppressWarnings("unchecked") - public Listener buildListener(Class listenerClass) { - ListenerImpl listenerImpl = listenerInstances.get( listenerClass ); - if ( listenerImpl == null ) { - try { - T listenerInstance = listenerClass.newInstance(); - listenerImpl = new ListenerImpl( listenerInstance ); - } - catch (Exception e) { - throw new PersistenceException( - "Unable to create instance of " + listenerClass.getName() + " as a JPA callback listener", - e - ); - } - ListenerImpl existing = listenerInstances.putIfAbsent( - listenerClass, - listenerImpl - ); - if ( existing != null ) { - listenerImpl = existing; - } - } - return (Listener) listenerImpl; - } - - @Override - public void release() { - listenerInstances.clear(); - } - - private static class ListenerImpl implements Listener { - private final T listenerInstance; - - public ListenerImpl(T listenerInstance) { - this.listenerInstance = listenerInstance; - } - - @Override - public T getListener() { - return listenerInstance; - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/package-info.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/package-info.java deleted file mode 100644 index 55be2b31bdb4..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/jpa/package-info.java +++ /dev/null @@ -1,11 +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.jpa.event.internal.jpa; - -/** - * Classes for integrating with JPA event callbacks - */ diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/package-info.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/package-info.java new file mode 100644 index 000000000000..3bc9a13f50a5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/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 . + */ + +/** + * Internal details of implementing support for JPA callbacks + */ +package org.hibernate.jpa.event.internal; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/package-info.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/package-info.java new file mode 100644 index 000000000000..5cda005f42d8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/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 . + */ + +/** + * Support for JPA lifecycle callbacks. + */ +package org.hibernate.jpa.event; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/Callback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/Callback.java new file mode 100644 index 000000000000..58752b33813d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/Callback.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.jpa.event.spi; + +import java.io.Serializable; + +/** + * Represents a JPA event callback (the method). + * + * Generally there are 2 flavors of this; either an annotated method on the entity itself + * or an annotated method on a separate "listener" class. This contract presents + * a unified abstraction for both cases + * + * @author Kabir Khan + * @author Steve Ebersole + */ +public interface Callback extends Serializable { + /** + * The type of callback (pre-update, pre-persist, etc) handled + */ + CallbackType getCallbackType(); + + /** + * Contract for performing the callback + * + * @param entity Reference to the entity for which the callback is triggered. + * + * @return Did a callback actually happen? + */ + boolean performCallback(Object entity); +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackBuilder.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackBuilder.java new file mode 100644 index 000000000000..dd84e74e6034 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackBuilder.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.event.spi; + +import org.hibernate.mapping.Property; + +/** + * Contract for walking an entity hierarchy and building a list of JPA callbacks + * + * @author Steve Ebersole + */ +public interface CallbackBuilder { + /** + * Represents the target of JPA callback registrations as part the EntityCallbackBuilder + */ + interface CallbackRegistrar extends CallbackRegistry { + + /** + * Register the callback against the given entity. + * + * @param entityClass The entity Class to register the Callbacks against + * @param callbacks The Callbacks to register against the given entity Class + */ + void registerCallbacks(Class entityClass, Callback[] callbacks); + } + + void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar); + + void buildCallbacksForEmbeddable( + Property embeddableProperty, + String entityClassName, + CallbackRegistrar callbackRegistrar); + + void release(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackRegistry.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackRegistry.java similarity index 92% rename from hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackRegistry.java rename to hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackRegistry.java index 7beb2236518f..052f8e854adf 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackRegistry.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 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.event.spi.jpa; +package org.hibernate.jpa.event.spi; import java.io.Serializable; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackRegistryConsumer.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackRegistryConsumer.java new file mode 100644 index 000000000000..9eaa6c58369a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackRegistryConsumer.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.jpa.event.spi; + +/** + * Contract for injecting the registry of Callbacks into event listeners. + * + * @author Emmanuel Bernard + * @author Steve Ebersole + */ +public interface CallbackRegistryConsumer { + /** + * Injection of the CallbackRegistry + * + * @param callbackRegistry The CallbackRegistry + */ + void injectCallbackRegistry(CallbackRegistry callbackRegistry); +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackType.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackType.java similarity index 87% rename from hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackType.java rename to hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackType.java index 34ec1a1f7036..99b45cdd8cf7 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackType.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackType.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 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.event.spi.jpa; +package org.hibernate.jpa.event.spi; import java.lang.annotation.Annotation; import javax.persistence.PostLoad; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/JpaIntegrator.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/JpaIntegrator.java deleted file mode 100644 index 61323cc5dfdd..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/JpaIntegrator.java +++ /dev/null @@ -1,213 +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.jpa.event.spi; - -import java.util.Map; - -import org.hibernate.HibernateException; -import org.hibernate.annotations.common.reflection.ReflectionManager; -import org.hibernate.boot.Metadata; -import org.hibernate.boot.internal.MetadataImpl; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.hibernate.engine.config.spi.ConfigurationService; -import org.hibernate.engine.spi.CascadeStyle; -import org.hibernate.engine.spi.CascadeStyles; -import org.hibernate.engine.spi.CascadingAction; -import org.hibernate.engine.spi.CascadingActions; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.service.spi.DuplicationStrategy; -import org.hibernate.event.service.spi.EventListenerGroup; -import org.hibernate.event.service.spi.EventListenerRegistry; -import org.hibernate.event.spi.EventType; -import org.hibernate.integrator.spi.Integrator; -import org.hibernate.jpa.AvailableSettings; -import org.hibernate.jpa.event.internal.core.HibernateEntityManagerEventListener; -import org.hibernate.jpa.event.internal.core.JpaAutoFlushEventListener; -import org.hibernate.jpa.event.internal.core.JpaDeleteEventListener; -import org.hibernate.jpa.event.internal.core.JpaFlushEntityEventListener; -import org.hibernate.jpa.event.internal.core.JpaFlushEventListener; -import org.hibernate.jpa.event.internal.core.JpaMergeEventListener; -import org.hibernate.jpa.event.internal.core.JpaPersistEventListener; -import org.hibernate.jpa.event.internal.core.JpaPersistOnFlushEventListener; -import org.hibernate.jpa.event.internal.core.JpaPostDeleteEventListener; -import org.hibernate.jpa.event.internal.core.JpaPostInsertEventListener; -import org.hibernate.jpa.event.internal.core.JpaPostLoadEventListener; -import org.hibernate.jpa.event.internal.core.JpaPostUpdateEventListener; -import org.hibernate.jpa.event.internal.core.JpaSaveEventListener; -import org.hibernate.jpa.event.internal.core.JpaSaveOrUpdateEventListener; -import org.hibernate.jpa.event.internal.jpa.CallbackBuilderLegacyImpl; -import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer; -import org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl; -import org.hibernate.jpa.event.spi.jpa.CallbackBuilder; -import org.hibernate.jpa.event.spi.jpa.ListenerFactory; -import org.hibernate.jpa.event.spi.jpa.ListenerFactoryBuilder; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.service.spi.ServiceRegistryImplementor; -import org.hibernate.service.spi.SessionFactoryServiceRegistry; - -/** - * Hibernate EntityManager specific Integrator, performing JPA setup. - * - * @author Steve Ebersole - */ -public class JpaIntegrator implements Integrator { - private ListenerFactory jpaListenerFactory; - private CallbackBuilder callbackBuilder; - private CallbackRegistryImpl callbackRegistry; - private CascadeStyle oldPersistCascadeStyle; - - private static final DuplicationStrategy JPA_DUPLICATION_STRATEGY = new JPADuplicationStrategy(); - - /** - * Perform integration. - * - * @param metadata The "compiled" representation of the mapping information - * @param sessionFactory The session factory being created - * @param serviceRegistry The session factory's service registry - */ - public void integrate( - Metadata metadata, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - - // first, register the JPA-specific persist cascade style - try { - oldPersistCascadeStyle = CascadeStyles.getCascadeStyle( "persist" ); - } - catch (Exception e) { - - } - CascadeStyles.registerCascadeStyle( - "persist", - new PersistCascadeStyle() - ); - - - // then prepare listeners - final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); - - eventListenerRegistry.addDuplicationStrategy( JPA_DUPLICATION_STRATEGY ); - - // op listeners - eventListenerRegistry.setListeners( EventType.AUTO_FLUSH, JpaAutoFlushEventListener.INSTANCE ); - eventListenerRegistry.setListeners( EventType.DELETE, new JpaDeleteEventListener() ); - eventListenerRegistry.setListeners( EventType.FLUSH_ENTITY, new JpaFlushEntityEventListener() ); - eventListenerRegistry.setListeners( EventType.FLUSH, JpaFlushEventListener.INSTANCE ); - eventListenerRegistry.setListeners( EventType.MERGE, new JpaMergeEventListener() ); - eventListenerRegistry.setListeners( EventType.PERSIST, new JpaPersistEventListener() ); - eventListenerRegistry.setListeners( EventType.PERSIST_ONFLUSH, new JpaPersistOnFlushEventListener() ); - eventListenerRegistry.setListeners( EventType.SAVE, new JpaSaveEventListener() ); - eventListenerRegistry.setListeners( EventType.SAVE_UPDATE, new JpaSaveOrUpdateEventListener() ); - - // post op listeners - eventListenerRegistry.prependListeners( EventType.POST_DELETE, new JpaPostDeleteEventListener() ); - eventListenerRegistry.prependListeners( EventType.POST_INSERT, new JpaPostInsertEventListener() ); - eventListenerRegistry.prependListeners( EventType.POST_LOAD, new JpaPostLoadEventListener() ); - eventListenerRegistry.prependListeners( EventType.POST_UPDATE, new JpaPostUpdateEventListener() ); - - final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class ); - - for ( Map.Entry entry : ( (Map) cfgService.getSettings() ).entrySet() ) { - if ( !String.class.isInstance( entry.getKey() ) ) { - continue; - } - final String propertyName = (String) entry.getKey(); - if ( !propertyName.startsWith( AvailableSettings.EVENT_LISTENER_PREFIX ) ) { - continue; - } - final String eventTypeName = propertyName.substring( AvailableSettings.EVENT_LISTENER_PREFIX.length() + 1 ); - final EventType eventType = EventType.resolveEventTypeByName( eventTypeName ); - final EventListenerGroup eventListenerGroup = eventListenerRegistry.getEventListenerGroup( eventType ); - for ( String listenerImpl : ( (String) entry.getValue() ).split( " ," ) ) { - eventListenerGroup.appendListener( instantiate( listenerImpl, serviceRegistry ) ); - } - } - - // handle JPA "entity listener classes"... - final ReflectionManager reflectionManager = ( (MetadataImpl) metadata ).getMetadataBuildingOptions() - .getReflectionManager(); - - this.callbackRegistry = new CallbackRegistryImpl(); - this.jpaListenerFactory = ListenerFactoryBuilder.buildListenerFactory( sessionFactory.getSessionFactoryOptions() ); - this.callbackBuilder = new CallbackBuilderLegacyImpl( jpaListenerFactory, reflectionManager ); - for ( PersistentClass persistentClass : metadata.getEntityBindings() ) { - if ( persistentClass.getClassName() == null ) { - // we can have non java class persisted by hibernate - continue; - } - callbackBuilder.buildCallbacksForEntity( persistentClass.getClassName(), callbackRegistry ); - } - - for ( EventType eventType : EventType.values() ) { - final EventListenerGroup eventListenerGroup = eventListenerRegistry.getEventListenerGroup( eventType ); - for ( Object listener : eventListenerGroup.listeners() ) { - if ( CallbackRegistryConsumer.class.isInstance( listener ) ) { - ( (CallbackRegistryConsumer) listener ).injectCallbackRegistry( callbackRegistry ); - } - } - } - } - - @Override - public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { - if ( oldPersistCascadeStyle == null ) { - CascadeStyles.registerCascadeStyle( - "persist", - null - ); - } - CascadeStyles.registerCascadeStyle( - "persist", - (CascadeStyles.BaseCascadeStyle) oldPersistCascadeStyle - ); - - if ( callbackRegistry != null ) { - callbackRegistry.release(); - } - if ( callbackBuilder != null ) { - callbackBuilder.release(); - } - if ( jpaListenerFactory != null ) { - jpaListenerFactory.release(); - } - } - - private Object instantiate(String listenerImpl, ServiceRegistryImplementor serviceRegistry) { - try { - return serviceRegistry.getService( ClassLoaderService.class ).classForName( listenerImpl ).newInstance(); - } - catch (Exception e) { - throw new HibernateException( "Could not instantiate requested listener [" + listenerImpl + "]", e ); - } - } - - private static class PersistCascadeStyle extends CascadeStyles.BaseCascadeStyle { - @Override - public boolean doCascade(CascadingAction action) { - return action == JpaPersistEventListener.PERSIST_SKIPLAZY - || action == CascadingActions.PERSIST_ON_FLUSH; - } - - @Override - public String toString() { - return "STYLE_PERSIST_SKIPLAZY"; - } - } - - private static class JPADuplicationStrategy implements DuplicationStrategy { - @Override - public boolean areMatch(Object listener, Object original) { - return listener.getClass().equals( original.getClass() ) && - HibernateEntityManagerEventListener.class.isInstance( original ); - } - - @Override - public Action getAction() { - return Action.KEEP_ORIGINAL; - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/Callback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/Callback.java deleted file mode 100644 index 98b9ded213b8..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/Callback.java +++ /dev/null @@ -1,43 +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.jpa.event.spi.jpa; - -import java.io.Serializable; - -/** - * Represents a JPA event callback (the method). - *

    - * Generally there are 2 flavors of this; either an annotated method on the entity itself - * or an annotated method on a separate "listener" class. This contract unifies both of - * these cases. - * - * @author Kabir Khan - * @author Steve Ebersole - */ -public interface Callback extends Serializable { - /** - * Is this callback active (will it do anything)? - * - * @return {@code true} if the callback is active, {@code false} otherwise. - * - * @deprecated I can actually find no usages of this method and have no idea - * why it is here :) - */ - @Deprecated - boolean isActive(); - - CallbackType getCallbackType(); - - /** - * Contract for performing the callback - * - * @param entity Reference to the entity for which the callback is triggered. - * - * @return Did a callback actually happen? - */ - boolean performCallback(Object entity); -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackBuilder.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackBuilder.java deleted file mode 100644 index fb5a14402bee..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackBuilder.java +++ /dev/null @@ -1,32 +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.jpa.event.spi.jpa; - -/** - * Contract for walking an entity hierarchy and building a list of JPA callbacks - * - * @author Steve Ebersole - */ -public interface CallbackBuilder { - /** - * Represents the target of JPA callback registrations as part the EntityCallbackBuilder - */ - interface CallbackRegistrar extends CallbackRegistry { - - /** - * Register the callback against the given entity. - * - * @param entityClass The entity Class to register the Callbacks against - * @param callbacks The Callbacks to register against the given entity Class - */ - void registerCallbacks(Class entityClass, Callback[] callbacks); - } - - void buildCallbacksForEntity(String entityName, CallbackRegistrar callbackRegistrar); - - void release(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackRegistryConsumer.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackRegistryConsumer.java deleted file mode 100644 index 4a5666602461..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/CallbackRegistryConsumer.java +++ /dev/null @@ -1,24 +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.jpa.event.spi.jpa; - -import org.hibernate.jpa.event.internal.core.HibernateEntityManagerEventListener; - -/** - * Contract for injecting the registry of Callbacks into event listeners. - * - * @author Emmanuel Bernard - * @author Steve Ebersole - */ -public interface CallbackRegistryConsumer extends HibernateEntityManagerEventListener { - /** - * Injection of the CallbackRegistry - * - * @param callbackRegistry The CallbackRegistry - */ - void injectCallbackRegistry(CallbackRegistry callbackRegistry); -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ExtendedBeanManager.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ExtendedBeanManager.java index f91c30a114d7..784ddf5684b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ExtendedBeanManager.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ExtendedBeanManager.java @@ -6,32 +6,22 @@ */ package org.hibernate.jpa.event.spi.jpa; -import javax.enterprise.inject.spi.BeanManager; - /** - * This contract and the nested LifecycleListener contract represent the changes - * we'd like to propose to the CDI spec. The idea being simply to allow contextual - * registration of BeanManager lifecycle callbacks - * - * @author Steve Ebersole + * @deprecated Use {@link org.hibernate.resource.beans.container.spi.ExtendedBeanManager} instead */ -public interface ExtendedBeanManager { - /** - * Register a BeanManager LifecycleListener - * - * @param lifecycleListener The listener to register - */ +@Deprecated +public interface ExtendedBeanManager extends org.hibernate.resource.beans.container.spi.ExtendedBeanManager { void registerLifecycleListener(LifecycleListener lifecycleListener); + @Override + default void registerLifecycleListener(org.hibernate.resource.beans.container.spi.ExtendedBeanManager.LifecycleListener lifecycleListener) { + registerLifecycleListener( (LifecycleListener) lifecycleListener ); + } + /** - * Contract for things interested in receiving notifications of - * BeanManager lifecycle events. - *

    - * A "beanManagerDestroyed" notifications would probably also be generally - * useful, although we do not need it here and not sure WildFly can really - * tell us that reliably. + * @deprecated Use {@link org.hibernate.resource.beans.container.spi.ExtendedBeanManager.LifecycleListener} instead */ - interface LifecycleListener { - void beanManagerInitialized(BeanManager beanManager); + @Deprecated + interface LifecycleListener extends org.hibernate.resource.beans.container.spi.ExtendedBeanManager.LifecycleListener { } } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/Listener.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/Listener.java deleted file mode 100644 index 29ba58b01efb..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/Listener.java +++ /dev/null @@ -1,17 +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.jpa.event.spi.jpa; - -/** - * Encapsulates access to the listener instance for listener callbacks - * ({@link javax.persistence.EntityListeners}). - * - * @author Steve Ebersole - */ -public interface Listener { - T getListener(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ListenerFactory.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ListenerFactory.java deleted file mode 100644 index a4be7e77db35..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ListenerFactory.java +++ /dev/null @@ -1,22 +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.jpa.event.spi.jpa; - -/** - * Contract for building instances of JPA callback listener classes. - *

    - * Listener instances should be uniqued by Class such that the same instance is - * returned for any calls to {@link #buildListener} with the same class. - * - * @see javax.persistence.EntityListeners - * - * @author Steve Ebersole - */ -public interface ListenerFactory { - Listener buildListener(Class listenerClass); - void release(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ListenerFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ListenerFactoryBuilder.java deleted file mode 100644 index fce61b9e23a9..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/ListenerFactoryBuilder.java +++ /dev/null @@ -1,102 +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.jpa.event.spi.jpa; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import org.hibernate.HibernateException; -import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.engine.config.spi.ConfigurationService; -import org.hibernate.engine.config.spi.StandardConverters; -import org.hibernate.jpa.AvailableSettings; -import org.hibernate.jpa.event.internal.jpa.ListenerFactoryStandardImpl; - -/** - * Builder for ListenerFactory based on configuration options - * - * @author Steve Ebersole - */ -public class ListenerFactoryBuilder { - - public static ListenerFactory buildListenerFactory(SessionFactoryOptions options) { - final Object beanManagerRef = options.getBeanManagerReference(); - if ( beanManagerRef == null ) { - return new ListenerFactoryStandardImpl(); - } - else if ( ExtendedBeanManager.class.isInstance( beanManagerRef ) ) { - return buildExtendedBeanManagerListenerFactory( beanManagerRef ); - } - else { - final boolean delayAccessToCdi = options.getServiceRegistry() - .getService( ConfigurationService.class ) - .getSetting( AvailableSettings.DELAY_CDI_ACCESS, StandardConverters.BOOLEAN, false ); - if ( delayAccessToCdi ) { - return buildDelayedBeanManagerListenerFactory( beanManagerRef ); - } - else { - return buildStandardBeanManagerListenerFactory( beanManagerRef ); - } - } - } - - - private static final String CDI_LISTENER_FACTORY_EXTENDED_CLASS = "org.hibernate.jpa.event.internal.jpa.ListenerFactoryBeanManagerExtendedImpl"; - private static final String CDI_LISTENER_FACTORY_STANDARD_CLASS = "org.hibernate.jpa.event.internal.jpa.ListenerFactoryBeanManagerStandardImpl"; - private static final String CDI_LISTENER_FACTORY_DELAYED_CLASS = "org.hibernate.jpa.event.internal.jpa.ListenerFactoryBeanManagerDelayedImpl"; - private static final String CDI_LISTENER_FACTORY_METHOD_NAME = "fromBeanManagerReference"; - - - private static ListenerFactory buildExtendedBeanManagerListenerFactory(Object beanManagerRef) { - return buildBeanManagerListenerFactory( beanManagerRef, CDI_LISTENER_FACTORY_EXTENDED_CLASS ); - } - - private static ListenerFactory buildStandardBeanManagerListenerFactory(Object beanManagerRef) { - return buildBeanManagerListenerFactory( beanManagerRef, CDI_LISTENER_FACTORY_STANDARD_CLASS ); - } - - private static ListenerFactory buildDelayedBeanManagerListenerFactory(Object beanManagerRef) { - return buildBeanManagerListenerFactory( beanManagerRef, CDI_LISTENER_FACTORY_DELAYED_CLASS ); - } - - @SuppressWarnings("unchecked") - private static ListenerFactory buildBeanManagerListenerFactory( - Object beanManagerRef, - String listenerClass) { - try { - // specifically using our ClassLoader here... - final Class beanManagerListenerFactoryClass = ListenerFactoryBuilder.class.getClassLoader() - .loadClass( listenerClass ); - final Method beanManagerListenerFactoryBuilderMethod = beanManagerListenerFactoryClass.getMethod( - CDI_LISTENER_FACTORY_METHOD_NAME, - Object.class - ); - - try { - return (ListenerFactory) beanManagerListenerFactoryBuilderMethod.invoke( null, beanManagerRef ); - } - catch (InvocationTargetException e) { - throw e.getTargetException(); - } - } - catch (ClassNotFoundException e) { - throw new HibernateException( - "Could not locate BeanManager ListenerFactory class [" + listenerClass + "] to handle CDI extensions", - e - ); - } - catch (HibernateException e) { - throw e; - } - catch (Throwable e) { - throw new HibernateException( - "Could not access BeanManager ListenerFactory class [" + listenerClass + "] to handle CDI extensions", - e - ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/package-info.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/package-info.java deleted file mode 100644 index f97059869508..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/jpa/package-info.java +++ /dev/null @@ -1,11 +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.jpa.event.spi.jpa; - -/** - * SPI classes for integrating with JPA event callbacks - */ diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/package-info.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/package-info.java new file mode 100644 index 000000000000..fd269b18f569 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/spi/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 . + */ + +/** + * The SPI contracts for supporting JPA lifecycle callbacks. + */ +package org.hibernate.jpa.event.spi; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/JpaComplianceImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/JpaComplianceImpl.java new file mode 100644 index 000000000000..30cf871735db --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/JpaComplianceImpl.java @@ -0,0 +1,134 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.internal; + +import org.hibernate.jpa.spi.JpaCompliance; + +/** + * @author Andrea Boriero + */ +public class JpaComplianceImpl implements JpaCompliance { + private boolean queryCompliance; + private boolean transactionCompliance; + private boolean listCompliance; + private boolean closedCompliance; + private boolean proxyCompliance; + private boolean cachingCompliance; + private boolean globalGeneratorNameScopeCompliance; + + private JpaComplianceImpl( + boolean queryCompliance, + boolean transactionCompliance, + boolean listCompliance, + boolean closedCompliance, + boolean proxyCompliance, + boolean cachingCompliance, + boolean globalGeneratorNameScopeCompliance) { + this.queryCompliance = queryCompliance; + this.transactionCompliance = transactionCompliance; + this.listCompliance = listCompliance; + this.closedCompliance = closedCompliance; + this.proxyCompliance = proxyCompliance; + this.cachingCompliance = cachingCompliance; + this.globalGeneratorNameScopeCompliance = globalGeneratorNameScopeCompliance; + } + + @Override + public boolean isJpaQueryComplianceEnabled() { + return queryCompliance; + } + + @Override + public boolean isJpaTransactionComplianceEnabled() { + return transactionCompliance; + } + + @Override + public boolean isJpaListComplianceEnabled() { + return listCompliance; + } + + @Override + public boolean isJpaClosedComplianceEnabled() { + return closedCompliance; + } + + @Override + public boolean isJpaProxyComplianceEnabled() { + return proxyCompliance; + } + + @Override + public boolean isJpaCacheComplianceEnabled() { + return cachingCompliance; + } + + @Override + public boolean isGlobalGeneratorScopeEnabled() { + return globalGeneratorNameScopeCompliance; + } + + public static class JpaComplianceBuilder { + private boolean queryCompliance; + private boolean transactionCompliance; + private boolean listCompliance; + private boolean closedCompliance; + private boolean proxyCompliance; + private boolean cachingCompliance; + private boolean globalGeneratorNameScopeCompliance; + + public JpaComplianceBuilder() { + } + + public JpaComplianceBuilder setQueryCompliance(boolean queryCompliance) { + this.queryCompliance = queryCompliance; + return this; + } + + public JpaComplianceBuilder setTransactionCompliance(boolean transactionCompliance) { + this.transactionCompliance = transactionCompliance; + return this; + } + + public JpaComplianceBuilder setListCompliance(boolean listCompliance) { + this.listCompliance = listCompliance; + return this; + } + + public JpaComplianceBuilder setClosedCompliance(boolean closedCompliance) { + this.closedCompliance = closedCompliance; + return this; + } + + public JpaComplianceBuilder setProxyCompliance(boolean proxyCompliance) { + this.proxyCompliance = proxyCompliance; + return this; + } + + public JpaComplianceBuilder setCachingCompliance(boolean cachingCompliance) { + this.cachingCompliance = cachingCompliance; + return this; + } + + public JpaComplianceBuilder setGlobalGeneratorNameCompliance(boolean globalGeneratorNameCompliance) { + this.globalGeneratorNameScopeCompliance = globalGeneratorNameCompliance; + return this; + } + + JpaCompliance createJpaCompliance() { + return new JpaComplianceImpl( + queryCompliance, + transactionCompliance, + listCompliance, + closedCompliance, + proxyCompliance, + cachingCompliance, + globalGeneratorNameScopeCompliance + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/MutableJpaComplianceImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/MutableJpaComplianceImpl.java new file mode 100644 index 000000000000..cf26d2e09670 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/MutableJpaComplianceImpl.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.internal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.spi.JpaCompliance; +import org.hibernate.jpa.spi.MutableJpaCompliance; + +/** + * @author Steve Ebersole + */ +public class MutableJpaComplianceImpl implements MutableJpaCompliance { + private boolean queryCompliance; + private boolean transactionCompliance; + private boolean listCompliance; + private boolean closedCompliance; + private boolean proxyCompliance; + private boolean cachingCompliance; + private final boolean globalGeneratorNameScopeCompliance; + + @SuppressWarnings("ConstantConditions") + public MutableJpaComplianceImpl(Map configurationSettings, boolean jpaByDefault) { + final Object legacyQueryCompliance = configurationSettings.get( AvailableSettings.JPAQL_STRICT_COMPLIANCE ); + + queryCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_QUERY_COMPLIANCE, + configurationSettings, + ConfigurationHelper.toBoolean( legacyQueryCompliance, jpaByDefault ) + ); + transactionCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_TRANSACTION_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + listCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_LIST_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + closedCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_CLOSED_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + proxyCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_PROXY_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + cachingCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_CACHING_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + globalGeneratorNameScopeCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + } + + @Override + public boolean isJpaQueryComplianceEnabled() { + return queryCompliance; + } + + @Override + public boolean isJpaTransactionComplianceEnabled() { + return transactionCompliance; + } + + @Override + public boolean isJpaListComplianceEnabled() { + return listCompliance; + } + + @Override + public boolean isJpaClosedComplianceEnabled() { + return closedCompliance; + } + + @Override + public boolean isJpaProxyComplianceEnabled() { + return proxyCompliance; + } + + @Override + public boolean isJpaCacheComplianceEnabled() { + return cachingCompliance; + } + + @Override + public boolean isGlobalGeneratorScopeEnabled() { + return globalGeneratorNameScopeCompliance; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Mutators + + public void setQueryCompliance(boolean queryCompliance) { + this.queryCompliance = queryCompliance; + } + + public void setTransactionCompliance(boolean transactionCompliance) { + this.transactionCompliance = transactionCompliance; + } + + public void setListCompliance(boolean listCompliance) { + this.listCompliance = listCompliance; + } + + public void setClosedCompliance(boolean closedCompliance) { + this.closedCompliance = closedCompliance; + } + + public void setProxyCompliance(boolean proxyCompliance) { + this.proxyCompliance = proxyCompliance; + } + + public void setCachingCompliance(boolean cachingCompliance) { + this.cachingCompliance = cachingCompliance; + } + + @Override + public JpaCompliance immutableCopy() { + JpaComplianceImpl.JpaComplianceBuilder builder = new JpaComplianceImpl.JpaComplianceBuilder(); + builder.setQueryCompliance( queryCompliance ) + .setTransactionCompliance( transactionCompliance ) + .setListCompliance( listCompliance ) + .setClosedCompliance( closedCompliance ) + .setProxyCompliance( proxyCompliance ) + .setCachingCompliance( cachingCompliance ) + .setGlobalGeneratorNameCompliance( globalGeneratorNameScopeCompliance ); + return builder.createJpaCompliance(); + } +} 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 2caf603ebb7b..a39f57e52a67 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 @@ -11,6 +11,7 @@ import javax.persistence.spi.LoadState; import org.hibernate.Hibernate; +import org.hibernate.MappingException; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -92,9 +93,15 @@ else if ( entity instanceof ManagedEntity ) { private Object getIdentifierFromPersister(Object entity) { Class entityClass = Hibernate.getClass( entity ); - EntityPersister persister = sessionFactory.getMetamodel().entityPersister( entityClass ); - if ( persister == null ) { - throw new IllegalArgumentException( entityClass.getName() + " is not an entity" ); + final EntityPersister persister; + try { + persister = sessionFactory.getMetamodel().entityPersister( entityClass ); + if ( persister == null ) { + throw new IllegalArgumentException( entityClass.getName() + " is not an entity" ); + } + } + catch (MappingException ex) { + throw new IllegalArgumentException( entityClass.getName() + " is not an entity", ex ); } return persister.getIdentifier( entity, null ); } 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 04f1791044a7..436747bc5539 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 @@ -23,6 +23,7 @@ import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -242,7 +243,7 @@ public static class FieldAttributeAccess implements AttributeAccess { public FieldAttributeAccess(Field field) { this.name = field.getName(); try { - field.setAccessible( true ); + ReflectHelper.ensureAccessibility( field ); } catch (Exception e) { this.field = null; @@ -276,7 +277,7 @@ public static class MethodAttributeAccess implements AttributeAccess { public MethodAttributeAccess(String attributeName, Method method) { this.name = attributeName; try { - method.setAccessible( true ); + ReflectHelper.ensureAccessibility( method ); } catch (Exception e) { this.method = null; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PessimisticNumberParser.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PessimisticNumberParser.java index 8bdaef1f2938..d7bf2fc03074 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PessimisticNumberParser.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PessimisticNumberParser.java @@ -30,7 +30,7 @@ public static Integer toNumberOrNull(final String parameterName) { return Integer.valueOf( parameterName ); } catch (NumberFormatException e) { - //It wasn't valid afterQuery all, so return null + //It wasn't valid after all, so return null } } return null; 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 e92182a334c3..41c3f1d9de5b 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 @@ -140,6 +140,7 @@ public X get(int i, Class type) { } public Object[] toArray() { + // todo : make a copy? return tuples; } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java index 0891f98bd324..2bd4e68d5ff4 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java @@ -15,8 +15,8 @@ import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.StaleStateException; +import org.hibernate.ejb.HibernateEntityManager; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.jpa.HibernateEntityManager; import org.hibernate.query.Query; import org.hibernate.query.criteria.internal.ValueHandlerFactory; import org.hibernate.type.Type; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java new file mode 100644 index 000000000000..c480e36cd4a9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.spi; + +import org.hibernate.Transaction; + +/** + * Encapsulates settings controlling whether certain aspects of the JPA spec + * should be strictly followed. + * + * @author Steve Ebersole + */ +public interface JpaCompliance { + /** + * Controls whether Hibernate's handling of JPA's + * {@link javax.persistence.Query} (JPQL, Criteria and native-query) should + * strictly follow the JPA spec. This includes both in terms of parsing or + * translating a query as well as calls to the {@link javax.persistence.Query} + * methods throwing spec defined exceptions where as Hibernate might not. + * + * Deviations result in an exception if enabled + * + * @return {@code true} indicates to behave in the spec-defined way + */ + boolean isJpaQueryComplianceEnabled(); + + /** + * Indicates that Hibernate's {@link Transaction} should behave as + * defined by the spec for JPA's {@link javax.persistence.EntityTransaction} + * since it extends the JPA one. + * + * @return {@code true} indicates to behave in the spec-defined way + */ + boolean isJpaTransactionComplianceEnabled(); + + /** + * Controls how Hibernate interprets a mapped List without an "order columns" + * specified. Historically Hibernate defines this as a "bag", which is a concept + * JPA does not have. + * + * If enabled, Hibernate will recognize this condition as defining + * a {@link org.hibernate.collection.internal.PersistentList}, otherwise + * Hibernate will treat is as a {@link org.hibernate.collection.internal.PersistentBag} + * + * @return {@code true} indicates to behave in the spec-defined way, interpreting the + * mapping as a "list", rather than a "bag" + */ + boolean isJpaListComplianceEnabled(); + + /** + * JPA defines specific exceptions on specific methods when called on + * {@link javax.persistence.EntityManager} and {@link javax.persistence.EntityManagerFactory} + * when those objects have been closed. This setting controls + * whether the spec defined behavior or Hibernate's behavior will be used. + * + * If enabled Hibernate will operate in the JPA specified way throwing + * exceptions when the spec says it should with regard to close checking + * + * @return {@code true} indicates to behave in the spec-defined way + */ + boolean isJpaClosedComplianceEnabled(); + + /** + * JPA spec says that an {@link javax.persistence.EntityNotFoundException} + * should be thrown when accessing an entity Proxy which does not have an associated + * table row in the database. + * + * Traditionally, Hibernate does not initialize an entity Proxy when accessing its + * identifier since we already know the identifier value, hence we can save a database roundtrip. + * + * If enabled Hibernate will initialize the entity Proxy even when accessing its identifier. + * + * @return {@code true} indicates to behave in the spec-defined way + */ + boolean isJpaProxyComplianceEnabled(); + + /** + * Should Hibernate comply with all aspects of caching as defined by JPA? Or can + * it deviate to perform things it believes will be "better"? + * + * @implNote Effects include marking all secondary tables as non-optional. The reason + * being that optional secondary tables can lead to entity cache being invalidated rather + * than updated. + * + * @return {@code true} says to act the spec-defined way. + */ + boolean isJpaCacheComplianceEnabled(); + + /** + * Should the the scope of {@link javax.persistence.TableGenerator#name()} and {@link javax.persistence.SequenceGenerator#name()} be + * considered globally or locally defined? + * + * @return {@code true} indicates the generator name scope is considered global. + */ + boolean isGlobalGeneratorScopeEnabled(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/MutableJpaCompliance.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/MutableJpaCompliance.java new file mode 100644 index 000000000000..37f068d7f676 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/MutableJpaCompliance.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.spi; + +/** + * @author Steve Ebersole + */ +public interface MutableJpaCompliance extends JpaCompliance { + void setQueryCompliance(boolean queryCompliance); + + void setTransactionCompliance(boolean transactionCompliance); + + void setListCompliance(boolean listCompliance); + + void setClosedCompliance(boolean closedCompliance); + + void setProxyCompliance(boolean proxyCompliance); + + void setCachingCompliance(boolean cachingCompliance); + + JpaCompliance immutableCopy(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryTupleTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryTupleTransformer.java new file mode 100644 index 000000000000..c68fe35d7302 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryTupleTransformer.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.spi; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.persistence.Tuple; +import javax.persistence.TupleElement; + +import org.hibernate.HibernateException; +import org.hibernate.transform.BasicTransformerAdapter; + +/** + * ResultTransformer adapter for handling Tuple results from Native queries + * + * @author Arnold Galovics + */ +public class NativeQueryTupleTransformer extends BasicTransformerAdapter { + + @Override + public Object transformTuple(Object[] tuple, String[] aliases) { + return new NativeTupleImpl( tuple, aliases ); + } + + private static class NativeTupleElementImpl implements TupleElement { + + private final Class javaType; + + private final String alias; + + public NativeTupleElementImpl(Class javaType, String alias) { + this.javaType = javaType; + this.alias = alias; + } + + @Override + public Class getJavaType() { + return javaType; + } + + @Override + public String getAlias() { + return alias; + } + } + + private static class NativeTupleImpl implements Tuple { + + private Object[] tuple; + + private Map aliasToValue = new LinkedHashMap<>(); + private Map aliasReferences = new LinkedHashMap<>(); + + public NativeTupleImpl(Object[] tuple, String[] aliases) { + if ( tuple == null ) { + throw new HibernateException( "Tuple must not be null" ); + } + if ( aliases == null ) { + throw new HibernateException( "Aliases must not be null" ); + } + if ( tuple.length != aliases.length ) { + throw new HibernateException( "Got different size of tuples and aliases" ); + } + this.tuple = tuple; + for ( int i = 0; i < tuple.length; i++ ) { + aliasToValue.put( aliases[i], tuple[i] ); + aliasReferences.put( aliases[i].toLowerCase(), aliases[i] ); + } + } + + @Override + public X get(String alias, Class type) { + final Object untyped = get( alias ); + + return ( untyped != null ) ? type.cast( untyped ) : null; + } + + @Override + public Object get(String alias) { + final String aliasReference = aliasReferences.get( alias.toLowerCase() ); + if ( aliasReference != null && aliasToValue.containsKey( aliasReference ) ) { + return aliasToValue.get( aliasReference ); + } + throw new IllegalArgumentException( "Unknown alias [" + alias + "]" ); + } + + @Override + public X get(int i, Class type) { + final Object untyped = get( i ); + + return ( untyped != null ) ? type.cast( untyped ) : null; + } + + @Override + public Object get(int i) { + if ( i < 0 ) { + throw new IllegalArgumentException( "requested tuple index must be greater than zero" ); + } + if ( i >= aliasToValue.size() ) { + throw new IllegalArgumentException( "requested tuple index exceeds actual tuple size" ); + } + return tuple[i]; + } + + @Override + public Object[] toArray() { + // todo : make a copy? + return tuple; + } + + @Override + public List> getElements() { + List> elements = new ArrayList<>( aliasToValue.size() ); + + for ( Map.Entry entry : aliasToValue.entrySet() ) { + elements.add( new NativeTupleElementImpl<>( getValueClass( entry.getValue() ), entry.getKey() ) ); + } + return elements; + } + + private Class getValueClass(Object value) { + Class valueClass = Object.class; + if ( value != null ) { + valueClass = value.getClass(); + } + return valueClass; + } + + @Override + public X get(TupleElement tupleElement) { + return get( tupleElement.getAlias(), tupleElement.getJavaType() ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java old mode 100755 new mode 100644 index 2a2ff7f9b023..d102859fee74 --- a/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java @@ -151,7 +151,7 @@ protected final boolean isJoinFetchEnabledByProfile(OuterJoinLoadable persister, String relativePropertyPath = pos >= 0 ? fullPath.substring( pos ) : rootPropertyName; - String fetchRole = persister.getEntityName() + "." + relativePropertyPath; + String fetchRole = persister.getEntityName() + '.' + relativePropertyPath; for ( String profileName : getLoadQueryInfluencers().getEnabledFetchProfileNames() ) { final FetchProfile profile = getFactory().getFetchProfile( profileName ); @@ -186,7 +186,7 @@ public final String getAlias() { } /** - * For entities, orderings added by, for example, Criteria#addOrder need to come beforeQuery the associations' @OrderBy + * For entities, orderings added by, for example, Criteria#addOrder need to come before the associations' @OrderBy * values. However, other sub-classes of JoinWalker (BasicCollectionJoinWalker, OneToManyJoinWalker, etc.) * still need the other way around. So, override here instead. See HHH-7116. */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java index bd2b3f4706f6..456a72fe4137 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java @@ -9,6 +9,7 @@ import java.util.List; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.Loadable; import org.hibernate.type.BagType; @@ -100,7 +101,7 @@ public static String[] generateSuffixes(int seed, int length) { String[] suffixes = new String[length]; for ( int i = 0; i < length; i++ ) { - suffixes[i] = Integer.toString( i + seed ) + "_"; + suffixes[i] = AliasConstantsHelper.get( i + seed ); } return suffixes; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java index d72d466457aa..72e47f77fa2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java @@ -9,18 +9,21 @@ import java.util.Collections; import java.util.Map; +import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.Loadable; /** * EntityAliases which handles the logic of selecting user provided aliases (via return-property), - * beforeQuery using the default aliases. + * before using the default aliases. * * @author max * */ public class DefaultEntityAliases implements EntityAliases { + private static final String[][] EMPTY_ARRAY_OF_ARRAY_OF_STRINGS = new String[0][]; + private final String[] suffixedKeyColumns; private final String[] suffixedVersionColumn; private final String[][] suffixedPropertyColumns; @@ -40,18 +43,31 @@ public DefaultEntityAliases( Map userProvidedAliases, Loadable persister, String suffix) { - this.suffix = suffix; - this.userProvidedAliases = userProvidedAliases; + this( userProvidedAliases, persister, suffix, false ); + } + + public DefaultEntityAliases(Loadable persister, String suffix) { + this( Collections.EMPTY_MAP, persister, suffix, true ); + } + private DefaultEntityAliases( + Map userProvidedAliases, + Loadable persister, + String suffix, + boolean interns) { + if ( interns ) { + this.suffix = suffix.intern(); + this.rowIdAlias = (Loadable.ROWID_ALIAS + suffix).intern(); // TODO: not visible to the user! + } + else { + this.suffix = suffix; + this.rowIdAlias = (Loadable.ROWID_ALIAS + suffix); + } + this.userProvidedAliases = userProvidedAliases; suffixedKeyColumns = determineKeyAlias( persister, suffix ); suffixedPropertyColumns = determinePropertyAliases( persister ); suffixedDiscriminatorColumn = determineDiscriminatorAlias( persister, suffix ); suffixedVersionColumn = determineVersionAlias( persister ); - rowIdAlias = Loadable.ROWID_ALIAS + suffix; // TODO: not visible to the user! - } - - public DefaultEntityAliases(Loadable persister, String suffix) { - this( Collections.EMPTY_MAP, persister, suffix ); } private String[] determineKeyAlias(Loadable persister, String suffix) { @@ -66,9 +82,7 @@ private String[] determineKeyAlias(Loadable persister, String suffix) { else { aliases = keyColumnsCandidates; } - final String[] rtn = StringHelper.unquote( aliases, persister.getFactory().getDialect() ); - intern( rtn ); - return rtn; + return StringHelper.unquote( aliases, persister.getFactory().getDialect() ); } private String[][] determinePropertyAliases(Loadable persister) { @@ -120,15 +134,22 @@ private String getUserProvidedAlias(String propertyPath, String defaultAlias) { @Override public String[][] getSuffixedPropertyAliases(Loadable persister) { - final int size = persister.getPropertyNames().length; - final String[][] suffixedPropertyAliases = new String[size][]; - for ( int j = 0; j < size; j++ ) { - suffixedPropertyAliases[j] = getUserProvidedAliases( - persister.getPropertyNames()[j], - getPropertyAliases( persister, j ) - ); - suffixedPropertyAliases[j] = StringHelper.unquote( suffixedPropertyAliases[j], persister.getFactory().getDialect() ); - intern( suffixedPropertyAliases[j] ); + final String[] propertyNames = persister.getPropertyNames(); + final int size = propertyNames.length; + final String[][] suffixedPropertyAliases; + if ( size > 0 ) { + suffixedPropertyAliases = new String[size][]; + final Dialect dialect = persister.getFactory().getDialect(); + for ( int j = 0; j < size; j++ ) { + suffixedPropertyAliases[j] = getUserProvidedAliases( + propertyNames[j], + getPropertyAliases( persister, j ) + ); + suffixedPropertyAliases[j] = StringHelper.unquote( suffixedPropertyAliases[j], dialect ); + } + } + else { + suffixedPropertyAliases = EMPTY_ARRAY_OF_ARRAY_OF_STRINGS; } return suffixedPropertyAliases; } @@ -163,9 +184,4 @@ public String getSuffix() { return suffix; } - private static void intern(String[] strings) { - for (int i=0; i afterLoadActions) throws HibernateException { - sql = applyLocks( sql, parameters, dialect, afterLoadActions ); - // Keep this here, rather than moving to Select. Some Dialects may need the hint to be appended to the very - // end or beginning of the finalized SQL statement, so wait until everything is processed. - if ( parameters.getQueryHints() != null && parameters.getQueryHints().size() > 0 ) { - sql = dialect.getQueryHintString( sql, parameters.getQueryHints() ); - } + Dialect dialect = sessionFactory.getServiceRegistry().getService( JdbcServices.class ).getDialect(); - sql = processDistinctKeyword( sql, parameters); + sql = applyLocks( sql, parameters, dialect, afterLoadActions ); + + sql = dialect.addSqlHintOrComment( + sql, + parameters, + sessionFactory.getSessionFactoryOptions().isCommentsEnabled() + ); - return getFactory().getSessionFactoryOptions().isCommentsEnabled() - ? prependComment( sql, parameters ) - : sql; + return processDistinctKeyword( sql, parameters ); } protected boolean shouldUseFollowOnLocking( @@ -297,16 +309,6 @@ protected LockMode determineFollowOnLockMode(LockOptions lockOptions) { return lockModeToUse; } - private String prependComment(String sql, QueryParameters parameters) { - String comment = parameters.getComment(); - if ( comment == null ) { - return sql; - } - else { - return "/* " + comment + " */ " + sql; - } - } - /** * Execute an SQL query and attempt to instantiate instances of the class mapped by the given * persister from each row of the ResultSet. If an object is supplied, will attempt to @@ -577,8 +579,8 @@ public Object loadSequentialRowsReverse( EntityKey keyToRead = null; // This check is needed since processing leaves the cursor - // afterQuery the last physical row for the current logical row; - // thus if we are afterQuery the last physical row, this might be + // after the last physical row for the current logical row; + // thus if we are after the last physical row, this might be // caused by either: // 1) scrolling to the last logical row // 2) scrolling past the last logical row @@ -601,7 +603,7 @@ public Object loadSequentialRowsReverse( } else { // Since the result set cursor is always left at the first - // physical row afterQuery the "last processed", we need to jump + // physical row after the "last processed", we need to jump // back one position to get the key value we are interested // in skipping resultSet.previous(); @@ -838,6 +840,7 @@ protected void extractKeysFromResultSet( keys[targetIndex], object, lockModes[targetIndex], + hydratedObjects, session ); } @@ -858,7 +861,21 @@ protected void extractKeysFromResultSet( } } } - final Serializable resolvedId = (Serializable) idType.resolve( hydratedKeyState[i], session, null ); + // If hydratedKeyState[i] is null, then we know the association should be null. + // Don't bother resolving the ID if hydratedKeyState[i] is null. + + // Implementation note: if the ID is a composite ID, then resolving a null value will + // result in instantiating an empty composite if AvailableSettings#CREATE_EMPTY_COMPOSITES_ENABLED + // is true. By not resolving a null value for a composite ID, we avoid the overhead of instantiating + // an empty composite, checking if it is equivalent to null (it should be), then ultimately throwing + // out the empty value. + final Serializable resolvedId; + if ( hydratedKeyState[i] != null ) { + resolvedId = (Serializable) idType.resolve( hydratedKeyState[i], session, null ); + } + else { + resolvedId = null; + } keys[i] = resolvedId == null ? null : session.generateEntityKey( resolvedId, persisters[i] ); } } @@ -1117,7 +1134,7 @@ private void initializeEntitiesAndCollections( if ( collectionPersisters != null ) { for ( CollectionPersister collectionPersister : collectionPersisters ) { if ( collectionPersister.isArray() ) { - //for arrays, we should end the collection load beforeQuery resolving + //for arrays, we should end the collection load before resolving //the entities, since the actual array instances are not instantiated //during loading //TODO: or we could do this polymorphically, and have two @@ -1142,15 +1159,26 @@ private void initializeEntitiesAndCollections( if ( hydratedObjects != null ) { int hydratedObjectsSize = hydratedObjects.size(); LOG.tracev( "Total objects hydrated: {0}", hydratedObjectsSize ); - for ( Object hydratedObject : hydratedObjects ) { - TwoPhaseLoad.initializeEntity( hydratedObject, readOnly, session, pre ); + + if ( hydratedObjectsSize != 0 ) { + final Iterable listeners = session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.PRE_LOAD ) + .listeners(); + + for ( Object hydratedObject : hydratedObjects ) { + TwoPhaseLoad.initializeEntity( hydratedObject, readOnly, session, pre, listeners ); + } + } } if ( collectionPersisters != null ) { for ( CollectionPersister collectionPersister : collectionPersisters ) { if ( !collectionPersister.isArray() ) { - //for sets, we should end the collection load afterQuery resolving + //for sets, we should end the collection load after resolving //the entities, since we might call hashCode() on the elements //TODO: or we could do this polymorphically, and have two // different operations implemented differently for arrays @@ -1160,19 +1188,32 @@ private void initializeEntitiesAndCollections( } // Until this entire method is refactored w/ polymorphism, postLoad was - // split off from initializeEntity. It *must* occur afterQuery + // split off from initializeEntity. It *must* occur after // endCollectionLoad to ensure the collection is in the // persistence context. - if ( hydratedObjects != null ) { + if ( hydratedObjects != null && hydratedObjects.size() > 0 ) { + + final Iterable postLoadEventListeners; + if ( session.isEventSource() ) { + final EventListenerGroup listenerGroup = session.getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.POST_LOAD ); + postLoadEventListeners = listenerGroup.listeners(); + } + else { + postLoadEventListeners = Collections.emptyList(); + } + for ( Object hydratedObject : hydratedObjects ) { - TwoPhaseLoad.postLoad( hydratedObject, session, post ); + TwoPhaseLoad.postLoad( hydratedObject, session, post, postLoadEventListeners ); if ( afterLoadActions != null ) { for ( AfterLoadAction afterLoadAction : afterLoadActions ) { final EntityEntry entityEntry = session.getPersistenceContext().getEntry( hydratedObject ); if ( entityEntry == null ) { // big problem throw new HibernateException( - "Could not locate EntityEntry immediately afterQuery two-phase load" + "Could not locate EntityEntry immediately after two-phase load" ); } afterLoadAction.afterLoad( session, hydratedObject, (Loadable) entityEntry.getPersister() ); @@ -1210,7 +1251,7 @@ protected List getResultList(List results, ResultTransformer resultTransformer) } /** - * Are rows transformed immediately afterQuery being read from the ResultSet? + * Are rows transformed immediately after being read from the ResultSet? * * @return true, if getResultColumnOrRow() transforms the results; false, otherwise */ @@ -1486,7 +1527,8 @@ private void checkVersion( Object version = session.getPersistenceContext().getEntry( entity ).getVersion(); - if ( version != null ) { //null version means the object is in the process of being loaded somewhere else in the ResultSet + if ( version != null ) { + // null version means the object is in the process of being loaded somewhere else in the ResultSet final VersionType versionType = persister.getVersionType(); final Object currentVersion = versionType.nullSafeGet( rs, @@ -1521,7 +1563,7 @@ private Object[] getRow( final List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { final int cols = persisters.length; - final EntityAliases[] descriptors = getEntityAliases(); + final EntityAliases[] entityAliases = getEntityAliases(); if ( LOG.isDebugEnabled() ) { LOG.debugf( "Result row: %s", StringHelper.toString( keys ) ); @@ -1541,7 +1583,6 @@ private Object[] getRow( //If the object is already loaded, return the loaded one object = session.getEntityUsingInterceptor( key ); if ( object != null ) { - //its already loaded so don't need to hydrate it instanceAlreadyLoaded( rs, i, @@ -1549,6 +1590,7 @@ private Object[] getRow( key, object, lockModes[i], + hydratedObjects, session ); } @@ -1557,7 +1599,7 @@ private Object[] getRow( rs, i, persisters[i], - descriptors[i].getRowIdAlias(), + entityAliases[i].getRowIdAlias(), key, lockModes[i], optionalObjectKey, @@ -1585,6 +1627,7 @@ private void instanceAlreadyLoaded( final EntityKey key, final Object object, final LockMode requestedLockMode, + List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { if ( !persister.isInstance( object ) ) { @@ -1595,7 +1638,42 @@ private void instanceAlreadyLoaded( ); } - if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { //no point doing this if NONE was requested + if ( persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() && enhancementAsProxyEnabled ) { + if ( "merge".equals( session.getLoadQueryInfluencers().getInternalFetchProfile() ) ) { + assert this instanceof CascadeEntityLoader; + // we are processing a merge and have found an existing "managed copy" in the + // session - we need to check if this copy is an enhanced-proxy and, if so, + // perform the hydration just as if it were "not yet loaded" + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) object; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + hydrateEntityState( + rs, + i, + persister, + getEntityAliases()[i].getRowIdAlias(), + key, + hydratedObjects, + session, + getInstanceClass( + rs, + i, + persister, + key.getIdentifier(), + session + ), + object, + requestedLockMode + ); + + // EARLY EXIT!!! + // - to skip the version check + return; + } + } + } + + if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { final EntityEntry entry = session.getPersistenceContext().getEntry( object ); if ( entry.getLockMode().lessThan( requestedLockMode ) ) { //we only check the version when _upgrading_ lock modes @@ -1634,7 +1712,7 @@ private Object instanceNotYetLoaded( // see if the entity defines reference caching, and if so use the cached reference (if one). if ( session.getCacheMode().isGetEnabled() && persister.canUseReferenceCacheEntries() ) { - final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + final EntityDataAccess cache = persister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( key.getIdentifier(), persister, @@ -1664,6 +1742,33 @@ private Object instanceNotYetLoaded( // (but don't yet initialize the object itself) // note that we acquire LockMode.READ even if it was not requested LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode; + hydrateEntityState( + rs, + i, + persister, + rowIdAlias, + key, + hydratedObjects, + session, + instanceClass, + object, + acquiredLockMode + ); + + return object; + } + + private void hydrateEntityState( + ResultSet rs, + int i, + Loadable persister, + String rowIdAlias, + EntityKey key, + List hydratedObjects, + SharedSessionContractImplementor session, + String instanceClass, + Object object, + LockMode acquiredLockMode) throws SQLException { loadFromResultSet( rs, i, @@ -1678,8 +1783,6 @@ private Object instanceNotYetLoaded( //materialize associations (and initialize the object) later hydratedObjects.add( object ); - - return object; } private boolean isEagerPropertyFetchEnabled(int i) { @@ -1904,18 +2007,57 @@ protected SqlStatementWrapper executeQueryStatement( String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); // Adding locks and comments. - sql = preprocessSQL( sql, queryParameters, getFactory().getDialect(), afterLoadActions ); + sql = preprocessSQL( sql, queryParameters, getFactory(), afterLoadActions ); final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session ); - return new SqlStatementWrapper( - st, getResultSet( + + final ResultSet rs; + + if( queryParameters.isCallable() && isTypeOf( st, CallableStatement.class ) ) { + final CallableStatement cs = st.unwrap( CallableStatement.class ); + + rs = getResultSet( + cs, + queryParameters.getRowSelection(), + limitHandler, + queryParameters.hasAutoDiscoverScalarTypes(), + session + ); + } + else { + rs = getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session - ) + ); + } + + return new SqlStatementWrapper( + st, + rs ); + + } + + private boolean isTypeOf(final Statement statement, final Class type) { + if ( isJdbc4 ) { + try { + // This is "more correct" than #isInstance, but not always supported. + return statement.isWrapperFor( type ); + } + catch (SQLException e) { + // No operation + } + catch (Throwable e) { + // No operation. Note that this catches more than just SQLException to + // cover edge cases where a driver might throw an UnsupportedOperationException, AbstractMethodError, + // etc. If so, skip permanently. + isJdbc4 = false; + } + } + return type.isInstance( statement ); } /** @@ -2028,7 +2170,7 @@ protected int bindParameterValues( *

    * Positional parameters are those specified by JDBC-style ? parameters * in the source query. It is (currently) expected that these come - * beforeQuery any named parameters in the source query. + * before any named parameters in the source query. * * @param statement The JDBC prepared statement * @param queryParameters The encapsulation of the parameter values to be bound. @@ -2121,16 +2263,29 @@ protected final ResultSet getResultSet( final SharedSessionContractImplementor session) throws SQLException, HibernateException { try { ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( st ); - rs = wrapResultSetIfEnabled( rs, session ); - if ( !limitHandler.supportsLimitOffset() || !LimitHelper.useLimit( limitHandler, selection ) ) { - advance( rs, selection ); - } + return processResultSet(rs, selection, limitHandler, autodiscovertypes, session); + } + catch (SQLException | HibernateException e) { + session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st ); + session.getJdbcCoordinator().afterStatementExecution(); + throw e; + } + } - if ( autodiscovertypes ) { - autoDiscoverTypes( rs ); - } - return rs; + /** + * Execute given CallableStatement, advance to the first result and return SQL ResultSet. + */ + protected final ResultSet getResultSet( + final CallableStatement st, + final RowSelection selection, + final LimitHandler limitHandler, + final boolean autodiscovertypes, + final SharedSessionContractImplementor session) throws SQLException, HibernateException { + try { + ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( st ); + + return processResultSet(rs, selection, limitHandler, autodiscovertypes, session); } catch (SQLException | HibernateException e) { session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st ); @@ -2139,6 +2294,25 @@ protected final ResultSet getResultSet( } } + private ResultSet processResultSet( + ResultSet rs, + final RowSelection selection, + final LimitHandler limitHandler, + final boolean autodiscovertypes, + final SharedSessionContractImplementor session + ) throws SQLException, HibernateException { + rs = wrapResultSetIfEnabled( rs, session ); + + if ( !limitHandler.supportsLimitOffset() || !LimitHelper.useLimit( limitHandler, selection ) ) { + advance( rs, selection ); + } + + if ( autodiscovertypes ) { + autoDiscoverTypes( rs ); + } + return rs; + } + protected void autoDiscoverTypes(ResultSet rs) { throw new AssertionFailure( "Auto discover types not supported in this loader" ); @@ -2151,7 +2325,7 @@ private ResultSet wrapResultSetIfEnabled(final ResultSet rs, final SharedSession return session.getFactory() .getServiceRegistry() .getService( JdbcServices.class ) - .getResultSetWrapper().wrap( rs, retreiveColumnNameToIndexCache( rs ) ); + .getResultSetWrapper().wrap( rs, retrieveColumnNameToIndexCache( rs ) ); } catch (SQLException e) { LOG.unableToWrapResultSet( e ); @@ -2163,7 +2337,7 @@ private ResultSet wrapResultSetIfEnabled(final ResultSet rs, final SharedSession } } - private ColumnNameCache retreiveColumnNameToIndexCache(final ResultSet rs) throws SQLException { + private ColumnNameCache retrieveColumnNameToIndexCache(final ResultSet rs) throws SQLException { final ColumnNameCache cache = columnNameCache; if ( cache == null ) { //there is no need for a synchronized second check, as in worst case @@ -2436,7 +2610,7 @@ private List listUsingQueryCache( final Set querySpaces, final Type[] resultTypes) { - QueryCache queryCache = factory.getCache().getQueryCache( queryParameters.getCacheRegion() ); + QueryResultsCache queryCache = factory.getCache().getQueryResultsCache( queryParameters.getCacheRegion() ); QueryKey key = generateQueryKey( session, queryParameters ); @@ -2513,7 +2687,7 @@ private List getResultFromQueryCache( final QueryParameters queryParameters, final Set querySpaces, final Type[] resultTypes, - final QueryCache queryCache, + final QueryResultsCache queryCache, final QueryKey key) { List result = null; @@ -2541,9 +2715,8 @@ private List getResultFromQueryCache( try { result = queryCache.get( key, - key.getResultTransformer().getCachedResultTypes( resultTypes ), - isImmutableNaturalKeyLookup, querySpaces, + key.getResultTransformer().getCachedResultTypes( resultTypes ), session ); } @@ -2572,15 +2745,14 @@ protected void putResultInQueryCache( final SharedSessionContractImplementor session, final QueryParameters queryParameters, final Type[] resultTypes, - final QueryCache queryCache, + final QueryResultsCache queryCache, final QueryKey key, final List result) { if ( session.getCacheMode().isPutEnabled() ) { boolean put = queryCache.put( key, - key.getResultTransformer().getCachedResultTypes( resultTypes ), result, - queryParameters.isNaturalKeyLookup(), + key.getResultTransformer().getCachedResultTypes( resultTypes ), session ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { @@ -2743,7 +2915,7 @@ protected ScrollableResultsImplementor scroll( /** * Calculate and cache select-clause suffixes. Must be - * called by subclasses afterQuery instantiation. + * called by subclasses after instantiation. */ protected void postInstantiate() { } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/collection/CollectionJoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/collection/CollectionJoinWalker.java index 2ffd804d7dc4..23ac1903d5b5 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/collection/CollectionJoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/collection/CollectionJoinWalker.java @@ -32,7 +32,7 @@ protected StringBuilder whereString(String alias, String[] columnNames, String s if (columnNames.length>1) { buf.append('('); } - buf.append( StringHelper.join(", ", StringHelper.qualify(alias, columnNames) ) ); + buf.append( String.join(", ", StringHelper.qualify(alias, columnNames) ) ); if (columnNames.length>1) { buf.append(')'); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java index 5f8c26e6a4ae..b50be383b3f5 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java @@ -135,7 +135,14 @@ protected JoinType getJoinType( resolvedJoinType = JoinType.NONE; } else { - FetchMode fetchMode = translator.getRootCriteria().getFetchMode( path.getFullPath() ); + String fullPathWithAlias = path.getFullPath(); + String rootAlias = translator.getRootCriteria().getAlias(); + String rootAliasPathPrefix = rootAlias + "."; + if (rootAlias != null && !fullPathWithAlias.startsWith(rootAliasPathPrefix)) { + fullPathWithAlias = rootAliasPathPrefix + fullPathWithAlias; + } + + FetchMode fetchMode = translator.getRootCriteria().getFetchMode( fullPathWithAlias ); if ( isDefaultFetchMode( fetchMode ) ) { if ( persister != null ) { if ( isJoinFetchEnabledByProfile( persister, path, propertyNumber ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java index 7400b392e70e..6a8b9e51208d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java @@ -44,8 +44,8 @@ */ public class CriteriaLoader extends OuterJoinLoader { - //TODO: this class depends directly upon CriteriaImpl, - // in the impl package ... add a CriteriaImplementor + //TODO: this class depends directly upon CriteriaImpl, + // in the impl package ... add a CriteriaImplementor // interface //NOTE: unlike all other Loaders, this one is NOT @@ -73,7 +73,7 @@ public CriteriaLoader( criteria, rootEntityName, CriteriaQueryTranslator.ROOT_SQL_ALIAS - ); + ); querySpaces = translator.getQuerySpaces(); @@ -84,9 +84,9 @@ public CriteriaLoader( criteria, rootEntityName, loadQueryInfluencers - ); + ); - initFromWalker( walker ); + initFromWalker(walker); userAliases = walker.getUserAliases(); resultTypes = walker.getResultTypes(); @@ -98,14 +98,14 @@ public CriteriaLoader( } public ScrollableResultsImplementor scroll(SharedSessionContractImplementor session, ScrollMode scrollMode) - throws HibernateException { + throws HibernateException { QueryParameters qp = translator.getQueryParameters(); - qp.setScrollMode( scrollMode ); - return scroll( qp, resultTypes, null, session ); + qp.setScrollMode(scrollMode); + return scroll(qp, resultTypes, null, session); } public List list(SharedSessionContractImplementor session) - throws HibernateException { + throws HibernateException { return list( session, translator.getQueryParameters(), querySpaces, resultTypes ); } @@ -136,9 +136,9 @@ protected Object getResultColumnOrRow( ResultTransformer transformer, ResultSet rs, SharedSessionContractImplementor session) - throws SQLException, HibernateException { + throws SQLException, HibernateException { return resolveResultTransformer( transformer ).transformTuple( - getResultRow( row, rs, session ), + getResultRow( row, rs, session), getResultRowAliases() ); } @@ -151,14 +151,14 @@ protected Object[] getResultRow(Object[] row, ResultSet rs, SharedSessionContrac Type[] types = translator.getProjectedTypes(); result = new Object[types.length]; String[] columnAliases = translator.getProjectedColumnAliases(); - for ( int i = 0, pos = 0; i < result.length; i++ ) { + for ( int i=0, pos=0; i 1 ) { String[] typeColumnAliases = ArrayHelper.slice( columnAliases, pos, numColumns ); - result[i] = types[i].nullSafeGet( rs, typeColumnAliases, session, null ); + result[i] = types[i].nullSafeGet(rs, typeColumnAliases, session, null); } else { - result[i] = types[i].nullSafeGet( rs, columnAliases[pos], session, null ); + result[i] = types[i].nullSafeGet(rs, columnAliases[pos], session, null); } pos += numColumns; } @@ -174,7 +174,7 @@ private Object[] toResultRow(Object[] row) { return row; } else { - Object[] result = new Object[resultRowLength]; + Object[] result = new Object[ resultRowLength ]; int j = 0; for ( int i = 0; i < row.length; i++ ) { if ( includeInResultRow[i] ) { @@ -218,27 +218,27 @@ protected String applyLocks( afterLoadActions.add( new AfterLoadAction() { - @Override + @Override public void afterLoad(SharedSessionContractImplementor session, Object entity, Loadable persister) { - ( (Session) session ).buildLockRequest( lockOptionsToUse ) + ( (Session) session ).buildLockRequest( lockOptionsToUse ) .lock( persister.getEntityName(), entity ); - } + } } ); parameters.setLockOptions( new LockOptions() ); return sql; } } - final LockOptions locks = new LockOptions( lockOptions.getLockMode() ); - locks.setScope( lockOptions.getScope() ); - locks.setTimeOut( lockOptions.getTimeOut() ); + final LockOptions locks = new LockOptions(lockOptions.getLockMode()); + locks.setScope( lockOptions.getScope()); + locks.setTimeOut( lockOptions.getTimeOut()); final Map keyColumnNames = dialect.forUpdateOfColumns() ? new HashMap() : null; final String[] drivingSqlAliases = getAliases(); for ( int i = 0; i < drivingSqlAliases.length; i++ ) { final LockMode lockMode = lockOptions.getAliasSpecificLockMode( drivingSqlAliases[i] ); if ( lockMode != null ) { - final Lockable drivingPersister = (Lockable) getEntityPersisters()[i]; + final Lockable drivingPersister = ( Lockable ) getEntityPersisters()[i]; final String rootSqlAlias = drivingPersister.getRootTableAlias( drivingSqlAliases[i] ); locks.setAliasSpecificLockMode( rootSqlAlias, lockMode ); if ( keyColumnNames != null ) { @@ -270,9 +270,9 @@ protected LockMode[] getLockModes(LockOptions lockOptions) { } final int size = entityAliases.length; LockMode[] lockModesArray = new LockMode[size]; - for ( int i = 0; i < size; i++ ) { + for ( int i=0; i criteriaSQLAliasMap = new HashMap(); private final Map aliasCriteriaMap = new HashMap(); private final Map associationPathCriteriaMap = new LinkedHashMap(); - private final Map associationPathJoinTypesMap = new LinkedHashMap(); + private final Map associationPathJoinTypesMap = new LinkedHashMap(); private final Map withClauseMap = new HashMap(); + private Set associations; private final SessionFactoryImplementor sessionFactory; private final SessionFactoryHelper helper; @@ -88,13 +94,17 @@ public CriteriaQueryTranslator( this.rootEntityName = rootEntityName; this.sessionFactory = factory; this.rootSQLAlias = rootSQLAlias; - this.helper = new SessionFactoryHelper( factory ); + this.helper = new SessionFactoryHelper(factory); createAliasCriteriaMap(); createAssociationPathCriteriaMap(); createCriteriaEntityNameMap(); createCriteriaSQLAliasMap(); } + public void setAssociations(Set associations) { + this.associations = associations; + } + @Override public String generateSQLAlias() { int aliasCount = 0; @@ -123,10 +133,23 @@ public Criteria getCriteria(String path) { } public Set getQuerySpaces() { - Set result = new HashSet(); + Set result = new HashSet<>(); for ( CriteriaInfoProvider info : criteriaInfoMap.values() ) { result.addAll( Arrays.asList( info.getSpaces() ) ); } + for ( final Map.Entry entry : associationPathCriteriaMap.entrySet() ) { + String path = entry.getKey(); + CriteriaImpl.Subcriteria crit = (CriteriaImpl.Subcriteria) entry.getValue(); + int index = path.lastIndexOf( '.' ); + if ( index > 0 ) { + path = path.substring( index + 1, path.length() ); + } + CriteriaInfoProvider info = criteriaInfoMap.get( crit.getParent() ); + CollectionPersister persister = getFactory().getMetamodel().collectionPersisters().get( info.getName() + "." + path ); + if ( persister != null ) { + result.addAll( Arrays.asList( persister.getCollectionSpaces() ) ); + } + } return result; } @@ -176,8 +199,8 @@ private String getWholeAssociationPath(CriteriaImpl.Subcriteria subcriteria) { String testAlias = StringHelper.root( path ); if ( !testAlias.equals( subcriteria.getAlias() ) ) { // and the qualifier is not the alias of this criteria - // -> check to see if we belong to some criteria other - // than the one that created us + // -> check to see if we belong to some criteria other + // than the one that created us parent = aliasCriteriaMap.get( testAlias ); } } @@ -195,7 +218,7 @@ private String getWholeAssociationPath(CriteriaImpl.Subcriteria subcriteria) { } else { // otherwise, recurse - return getWholeAssociationPath( (CriteriaImpl.Subcriteria) parent ) + '.' + path; + return getWholeAssociationPath( ( CriteriaImpl.Subcriteria ) parent ) + '.' + path; } } @@ -204,7 +227,7 @@ private void createCriteriaEntityNameMap() { final CriteriaInfoProvider rootProvider = new EntityCriteriaInfoProvider( (Queryable) sessionFactory.getEntityPersister( rootEntityName ) ); - criteriaInfoMap.put( rootCriteria, rootProvider ); + criteriaInfoMap.put( rootCriteria, rootProvider); nameCriteriaInfoMap.put( rootProvider.getName(), rootProvider ); for ( final String key : associationPathCriteriaMap.keySet() ) { @@ -229,11 +252,11 @@ private CriteriaInfoProvider getPathInfo(String path) { if ( type.isAssociationType() ) { // CollectionTypes are always also AssociationTypes - but there's not always an associated entity... final AssociationType atype = (AssociationType) type; - final CollectionType ctype = type.isCollectionType() ? (CollectionType) type : null; - final Type elementType = ( ctype != null ) ? ctype.getElementType( sessionFactory ) : null; + final CollectionType ctype = type.isCollectionType() ? (CollectionType)type : null; + final Type elementType = (ctype != null) ? ctype.getElementType( sessionFactory ) : null; // is the association a collection of components or value-types? (i.e a colloction of valued types?) - if ( ctype != null && elementType.isComponentType() ) { - provider = new ComponentCollectionCriteriaInfoProvider( helper.getCollectionPersister( ctype.getRole() ) ); + if ( ctype != null && elementType.isComponentType() ) { + provider = new ComponentCollectionCriteriaInfoProvider( helper.getCollectionPersister(ctype.getRole()) ); } else if ( ctype != null && !elementType.isEntityType() ) { provider = new ScalarCollectionCriteriaInfoProvider( helper, ctype.getRole() ); @@ -247,7 +270,7 @@ else if ( ctype != null && !elementType.isEntityType() ) { componentPath = ""; } else if ( type.isComponentType() ) { - if ( !tokens.hasMoreTokens() ) { + if (!tokens.hasMoreTokens()) { throw new QueryException( "Criteria objects cannot be created directly on components. Create a criteria on " + "owning entity and use a dotted property to access component property: " + path @@ -271,7 +294,7 @@ public int getSQLAliasCount() { private void createCriteriaSQLAliasMap() { int i = 0; - for ( final Criteria crit : criteriaInfoMap.keySet() ) { + for(final Criteria crit : criteriaInfoMap.keySet()){ final CriteriaInfoProvider value = criteriaInfoMap.get( crit ); String alias = crit.getAlias(); if ( alias == null ) { @@ -304,6 +327,7 @@ public QueryParameters getQueryParameters() { final List values = new ArrayList(); final List types = new ArrayList(); + final Iterator subcriteriaIterator = rootCriteria.iterateSubcriteria(); while ( subcriteriaIterator.hasNext() ) { final CriteriaImpl.Subcriteria subcriteria = subcriteriaIterator.next(); @@ -384,7 +408,7 @@ public Type[] getProjectedTypes() { public String[] getProjectedColumnAliases() { return rootCriteria.getProjection() instanceof EnhancedProjection ? - ( (EnhancedProjection) rootCriteria.getProjection() ).getColumnAliases( 0, rootCriteria, this ) : + ( ( EnhancedProjection ) rootCriteria.getProjection() ).getColumnAliases( 0, rootCriteria, this ) : rootCriteria.getProjection().getColumnAliases( 0 ); } @@ -458,7 +482,7 @@ public String[] getColumnsUsingProjection( String[] projectionColumns = null; if ( projection != null ) { projectionColumns = ( projection instanceof EnhancedProjection ? - ( (EnhancedProjection) projection ).getColumnAliases( propertyName, 0, rootCriteria, this ) : + ( ( EnhancedProjection ) projection ).getColumnAliases( propertyName, 0, rootCriteria, this ) : projection.getColumnAliases( propertyName, 0 ) ); } @@ -468,7 +492,7 @@ public String[] getColumnsUsingProjection( try { return getColumns( propertyName, subcriteria ); } - catch (HibernateException he) { + catch ( HibernateException he ) { //not found in inner query , try the outer query if ( outerQueryTranslator != null ) { return outerQueryTranslator.getColumnsUsingProjection( subcriteria, propertyName ); @@ -487,18 +511,18 @@ public String[] getColumnsUsingProjection( @Override public String[] getIdentifierColumns(Criteria criteria) { String[] idcols = - ( (Loadable) getPropertyMapping( getEntityName( criteria ) ) ).getIdentifierColumnNames(); + ( ( Loadable ) getPropertyMapping( getEntityName( criteria ) ) ).getIdentifierColumnNames(); return StringHelper.qualify( getSQLAlias( criteria ), idcols ); } @Override public Type getIdentifierType(Criteria criteria) { - return ( (Loadable) getPropertyMapping( getEntityName( criteria ) ) ).getIdentifierType(); + return ( ( Loadable ) getPropertyMapping( getEntityName( criteria ) ) ).getIdentifierType(); } @Override public TypedValue getTypedIdentifierValue(Criteria criteria, Object value) { - final Loadable loadable = (Loadable) getPropertyMapping( getEntityName( criteria ) ); + final Loadable loadable = ( Loadable ) getPropertyMapping( getEntityName( criteria ) ); return new TypedValue( loadable.getIdentifierType(), value ); } @@ -519,12 +543,12 @@ public String[] getColumns( * Projection aliases are ignored. */ @Override - public String[] findColumns(String propertyName, Criteria subcriteria) - throws HibernateException { + public String[] findColumns(String propertyName, Criteria subcriteria ) + throws HibernateException { try { return getColumns( propertyName, subcriteria ); } - catch (HibernateException he) { + catch ( HibernateException he ) { //not found in inner query, try the outer query if ( outerQueryTranslator != null ) { return outerQueryTranslator.findColumns( propertyName, subcriteria ); @@ -551,7 +575,7 @@ public Type getTypeUsingProjection(Criteria subcriteria, String propertyName) //look for a property return getType( subcriteria, propertyName ); } - catch (HibernateException he) { + catch ( HibernateException he ) { //not found in inner query , try the outer query if ( outerQueryTranslator != null ) { return outerQueryTranslator.getType( subcriteria, propertyName ); @@ -666,5 +690,4 @@ public boolean hasRestriction(String path) { final CriteriaImpl.Subcriteria subcriteria = (CriteriaImpl.Subcriteria) getCriteria( path ); return subcriteria != null && subcriteria.hasRestriction(); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java index 4858ef968884..13c10f880510 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java @@ -8,7 +8,6 @@ import java.util.Map; -import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.SQLLoadableCollection; @@ -111,7 +110,7 @@ private String join(String[] aliases) { return null; } - return StringHelper.join( ", ", aliases ); + return String.join( ", ", aliases ); } private String[] getUserProvidedAliases(String propertyPath, String[] defaultAliases) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java old mode 100755 new mode 100644 index fe53fa1ba20e..cdde43aa9e87 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java @@ -7,12 +7,12 @@ package org.hibernate.loader.custom; import java.io.Serializable; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.hibernate.HibernateException; @@ -20,8 +20,8 @@ import org.hibernate.LockOptions; import org.hibernate.QueryException; import org.hibernate.Session; -import org.hibernate.cache.spi.QueryCache; import org.hibernate.cache.spi.QueryKey; +import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -33,6 +33,7 @@ import org.hibernate.loader.EntityAliases; import org.hibernate.loader.Loader; import org.hibernate.loader.spi.AfterLoadAction; +import org.hibernate.param.ParameterBinder; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.Loadable; @@ -55,7 +56,8 @@ public class CustomLoader extends Loader { private final String sql; private final Set querySpaces = new HashSet<>(); - private final Map namedParameterBindPoints; + + private final List paramValueBinders; private final Queryable[] entityPersisters; private final int[] entiytOwners; @@ -85,7 +87,8 @@ public CustomLoader(CustomQuery customQuery, SessionFactoryImplementor factory) this.sql = customQuery.getSQL(); this.querySpaces.addAll( customQuery.getQuerySpaces() ); - this.namedParameterBindPoints = customQuery.getNamedParameterBindPoints(); + + this.paramValueBinders = customQuery.getParameterValueBinders(); List entityPersisters = new ArrayList<>(); List entityOwners = new ArrayList<>(); @@ -370,25 +373,21 @@ public void afterLoad(SharedSessionContractImplementor session, Object entity, L public ScrollableResultsImplementor scroll(final QueryParameters queryParameters, final SharedSessionContractImplementor session) throws HibernateException { + + ResultTransformer resultTransformer = queryParameters.getResultTransformer(); + + HolderInstantiator holderInstantiator = ( resultTransformer == null ) ? + HolderInstantiator.NOOP_INSTANTIATOR : + new HolderInstantiator( resultTransformer, this::getReturnAliasesForTransformer ); + return scroll( queryParameters, resultTypes, - getHolderInstantiator( queryParameters.getResultTransformer(), getReturnAliasesForTransformer() ), + holderInstantiator, session ); } - static private HolderInstantiator getHolderInstantiator( - ResultTransformer resultTransformer, - String[] queryReturnAliases) { - if ( resultTransformer == null ) { - return HolderInstantiator.NOOP_INSTANTIATOR; - } - else { - return new HolderInstantiator( resultTransformer, queryReturnAliases ); - } - } - @Override protected String[] getResultRowAliases() { return transformerAliases; @@ -457,23 +456,32 @@ protected CollectionAliases[] getCollectionAliases() { } @Override - public int[] getNamedParameterLocs(String name) throws QueryException { - Object loc = namedParameterBindPoints.get( name ); - if ( loc == null ) { - throw new QueryException( - "Named parameter does not appear in Query: " + name, - sql - ); - } - if ( loc instanceof Integer ) { - return new int[] {(Integer) loc}; + protected int bindParameterValues( + PreparedStatement statement, + QueryParameters queryParameters, + int startIndex, + SharedSessionContractImplementor session) throws SQLException { + final Serializable optionalId = queryParameters.getOptionalId(); + if ( optionalId != null ) { + paramValueBinders.get( 0 ).bind( statement, queryParameters, session, startIndex ); + return session.getFactory().getMetamodel() + .entityPersister( queryParameters.getOptionalEntityName() ) + .getIdentifierType() + .getColumnSpan( session.getFactory() ); } - else { - return ArrayHelper.toIntArray( (List) loc ); + + int span = 0; + for ( ParameterBinder paramValueBinder : paramValueBinders ) { + span += paramValueBinder.bind( + statement, + queryParameters, + session, + startIndex + span + ); } + return span; } - @Override protected void autoDiscoverTypes(ResultSet rs) { try { @@ -522,7 +530,7 @@ protected void validateAlias(String alias) { /** * {@link #resultTypes} can be overridden by {@link #autoDiscoverTypes(ResultSet)}, - * *afterQuery* {@link #list(SharedSessionContractImplementor, QueryParameters)} has already been called. It's a bit of a + * *after* {@link #list(SharedSessionContractImplementor, QueryParameters)} has already been called. It's a bit of a * chicken-and-the-egg issue since {@link #autoDiscoverTypes(ResultSet)} needs the {@link ResultSet}. *

    * As a hacky workaround, overriden here to provide the {@link #resultTypes}. @@ -534,7 +542,7 @@ protected void putResultInQueryCache( final SharedSessionContractImplementor session, final QueryParameters queryParameters, final Type[] resultTypes, - final QueryCache queryCache, + final QueryResultsCache queryCache, final QueryKey key, final List result) { super.putResultInQueryCache( session, queryParameters, this.resultTypes, queryCache, key, result ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java index 9bcd8f14f601..a17625fb8d46 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java @@ -5,10 +5,12 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.loader.custom; + import java.util.List; -import java.util.Map; import java.util.Set; +import org.hibernate.param.ParameterBinder; + /** * Extension point allowing any SQL query with named and positional parameters * to be executed by Hibernate, returning managed entities, collections and @@ -23,7 +25,7 @@ public interface CustomQuery { * * @return The SQL statement string. */ - public String getSQL(); + String getSQL(); /** * Any query spaces to apply to the query execution. Query spaces are @@ -32,22 +34,9 @@ public interface CustomQuery { * * @return The query spaces */ - public Set getQuerySpaces(); + Set getQuerySpaces(); - /** - * A map representing positions within the supplied {@link #getSQL query} to - * which we need to bind named parameters. - *

    - * Optional, may return null if no named parameters. - *

    - * The structure of the returned map (if one) as follows:

      - *
    1. The keys into the map are the named parameter names
    2. - *
    3. The corresponding value is either an {@link Integer} if the - * parameter occurs only once in the query; or a List of Integers if the - * parameter occurs more than once
    4. - *
    - */ - public Map getNamedParameterBindPoints(); + List getParameterValueBinders(); /** * A collection of {@link Return descriptors} describing the @@ -55,5 +44,6 @@ public interface CustomQuery { * * @return List of return descriptors. */ - public List getCustomQueryReturns(); + List getCustomQueryReturns(); + } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/NamedParamBinder.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/NamedParamBinder.java new file mode 100644 index 000000000000..696154228397 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/NamedParamBinder.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 . + */ +package org.hibernate.loader.custom.sql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.param.ParameterBinder; + +/** + * @author Steve Ebersole + */ +public class NamedParamBinder implements ParameterBinder { + private final String name; + + public NamedParamBinder(String name) { + this.name = name; + } + + @Override + public int bind( + PreparedStatement statement, + QueryParameters qp, + SharedSessionContractImplementor session, + int position) throws SQLException { + final TypedValue typedValue = qp.getNamedParameters().get( name ); + typedValue.getType().nullSafeSet( statement, typedValue.getValue(), position, session ); + return typedValue.getType().getColumnSpan( session.getFactory() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/PositionalParamBinder.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/PositionalParamBinder.java new file mode 100644 index 000000000000..f3eb23edca61 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/PositionalParamBinder.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 . + */ +package org.hibernate.loader.custom.sql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.param.ParameterBinder; + +/** + * @author Steve Ebersole + */ +public class PositionalParamBinder implements ParameterBinder { + private final int label; + + public PositionalParamBinder(int label) { + this.label = label; + } + + @Override + public int bind( + PreparedStatement statement, + QueryParameters qp, + SharedSessionContractImplementor session, + int position) throws SQLException { + final TypedValue typedValue = qp.getNamedParameters().get( Integer.toString( label ) ); + typedValue.getType().nullSafeSet( statement, typedValue.getValue(), position, session ); + return typedValue.getType().getColumnSpan( session.getFactory() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLCustomQuery.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLCustomQuery.java index 3e9f380c96c7..57d163c62d20 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLCustomQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLCustomQuery.java @@ -9,7 +9,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -20,6 +19,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.loader.custom.CustomQuery; +import org.hibernate.param.ParameterBinder; import org.hibernate.persister.collection.SQLLoadableCollection; import org.hibernate.persister.entity.SQLLoadable; @@ -40,7 +40,9 @@ public class SQLCustomQuery implements CustomQuery, Serializable { private final String sql; private final Set querySpaces = new HashSet(); - private final Map namedParameterBindPoints = new HashMap(); + + private final List paramValueBinders; + private final List customQueryReturns = new ArrayList(); @@ -52,8 +54,9 @@ public Set getQuerySpaces() { return querySpaces; } - public Map getNamedParameterBindPoints() { - return namedParameterBindPoints; + @Override + public List getParameterValueBinders() { + return paramValueBinders; } public List getCustomQueryReturns() { @@ -115,7 +118,8 @@ public SQLCustomQuery( SQLQueryParser parser = new SQLQueryParser( sqlQuery, new ParserContext( aliasContext ), factory ); this.sql = parser.process(); - this.namedParameterBindPoints.putAll( parser.getNamedParameters() ); + + this.paramValueBinders = parser.getParameterValueBinders(); // SQLQueryParser parser = new SQLQueryParser( // sqlQuery, 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 aae7ad4ffbec..823753360c04 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 @@ -7,13 +7,15 @@ package org.hibernate.loader.custom.sql; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import org.hibernate.QueryException; import org.hibernate.engine.query.spi.ParameterParser; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.param.ParameterBinder; import org.hibernate.persister.collection.SQLLoadableCollection; import org.hibernate.persister.entity.SQLLoadable; @@ -24,6 +26,7 @@ * @author Paul Benedict */ public class SQLQueryParser { + private static final Pattern PREPARED_STATEMENT_PATTERN = Pattern.compile( "^\\{.*?\\}$" ); private static final String HIBERNATE_PLACEHOLDER_PREFIX = "h-"; private static final String DOMAIN_PLACEHOLDER = "h-domain"; private static final String CATALOG_PLACEHOLDER = "h-catalog"; @@ -33,9 +36,10 @@ public class SQLQueryParser { private final String originalQueryString; private final ParserContext context; - private final Map namedParameters = new HashMap(); private long aliasesFound; + private List paramValueBinders; + interface ParserContext { boolean isEntityAlias(String aliasName); SQLLoadable getEntityPersisterByAlias(String alias); @@ -52,14 +56,18 @@ public SQLQueryParser(String queryString, ParserContext context, SessionFactoryI this.factory = factory; } - public Map getNamedParameters() { - return namedParameters; + public List getParameterValueBinders() { + return paramValueBinders == null ? Collections.emptyList() : paramValueBinders; } public boolean queryHasAliases() { return aliasesFound>0; } + protected String getOriginalQueryString() { + return originalQueryString; + } + public String process() { String processedSql = substituteBrackets( originalQueryString ); processedSql = substituteParams( processedSql ); @@ -68,7 +76,11 @@ public String process() { // TODO: should "record" how many properties we have reffered to - and if we // don't get'em'all we throw an exception! Way better than trial and error ;) - private String substituteBrackets(String sqlQuery) throws QueryException { + protected String substituteBrackets(String sqlQuery) throws QueryException { + + if ( PREPARED_STATEMENT_PATTERN.matcher( sqlQuery.trim() ).matches() ) { + return sqlQuery; + } StringBuilder result = new StringBuilder( sqlQuery.length() + 20 ); int left, right; @@ -77,7 +89,7 @@ private String substituteBrackets(String sqlQuery) throws QueryException { for ( int curr = 0; curr < sqlQuery.length(); curr = right + 1 ) { if ( ( left = sqlQuery.indexOf( '{', curr ) ) < 0 ) { // No additional open braces found in the string, append the - // rest of the string in its entirty and quit this loop + // rest of the string in its entirety and quit this loop result.append( sqlQuery.substring( curr ) ); break; } @@ -126,7 +138,7 @@ else if ( CATALOG_PLACEHOLDER.equals( aliasPath ) ) { throw new QueryException( "Unknown placeholder ", aliasPath ); } } - else { + else if (context != null) { int firstDot = aliasPath.indexOf( '.' ); if ( firstDot == -1 ) { if ( context.isEntityAlias( aliasPath ) ) { @@ -159,6 +171,9 @@ else if ( context.isEntityAlias( aliasName ) ) { } } } + else { + result.append( '{' ).append(aliasPath).append( '}' ); + } } // Possibly handle :something parameters for the query ? @@ -266,19 +281,25 @@ private String resolveProperties(String aliasName, String propertyName) { * @return The SQL query with parameter substitution complete. */ private String substituteParams(String sqlString) { - ParameterSubstitutionRecognizer recognizer = new ParameterSubstitutionRecognizer(); + final ParameterSubstitutionRecognizer recognizer = new ParameterSubstitutionRecognizer( factory ); ParameterParser.parse( sqlString, recognizer ); - namedParameters.clear(); - namedParameters.putAll( recognizer.namedParameterBindPoints ); + paramValueBinders = recognizer.getParameterValueBinders(); return recognizer.result.toString(); } public static class ParameterSubstitutionRecognizer implements ParameterParser.Recognizer { StringBuilder result = new StringBuilder(); - Map namedParameterBindPoints = new HashMap(); - int parameterCount; + + int jdbcPositionalParamCount; + private List paramValueBinders; + + public ParameterSubstitutionRecognizer(SessionFactoryImplementor factory) { + this.jdbcPositionalParamCount = factory.getSessionFactoryOptions().jdbcStyleParamsZeroBased() + ? 0 + : 1; + } @Override public void outParameter(int position) { @@ -288,17 +309,35 @@ public void outParameter(int position) { @Override public void ordinalParameter(int position) { result.append( '?' ); + registerPositionParamBinder( jdbcPositionalParamCount++ ); + } + + private void registerPositionParamBinder(int label) { + if ( paramValueBinders == null ) { + paramValueBinders = new ArrayList<>(); + } + + paramValueBinders.add( new PositionalParamBinder( label ) ); } @Override - public void namedParameter(String name, int position) { - addNamedParameter( name ); + public void jpaPositionalParameter(int name, int position) { result.append( '?' ); + registerPositionParamBinder( name ); } @Override - public void jpaPositionalParameter(String name, int position) { - namedParameter( name, position ); + public void namedParameter(String name, int position) { + result.append( '?' ); + registerNamedParamBinder( name ); + } + + private void registerNamedParamBinder(String name) { + if ( paramValueBinders == null ) { + paramValueBinders = new ArrayList<>(); + } + + paramValueBinders.add( new NamedParamBinder( name ) ); } @Override @@ -306,21 +345,12 @@ public void other(char character) { result.append( character ); } - private void addNamedParameter(String name) { - Integer loc = parameterCount++; - Object o = namedParameterBindPoints.get( name ); - if ( o == null ) { - namedParameterBindPoints.put( name, loc ); - } - else if ( o instanceof Integer ) { - ArrayList list = new ArrayList( 4 ); - list.add( o ); - list.add( loc ); - namedParameterBindPoints.put( name, list ); - } - else { - ( ( List ) o ).add( loc ); - } + public List getParameterValueBinders() { + return paramValueBinders; + } + + @Override + public void complete() { } } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java index 34828dad6bc2..fdafd4203bc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java @@ -183,6 +183,41 @@ public ResultAliasContext process() { return new ResultAliasContext(); } + private interface QueryReturnVisitor { + void visitScalarReturn(NativeSQLQueryScalarReturn rtn); + void visitRootReturn(NativeSQLQueryRootReturn rtn); + void visitCollectionReturn(NativeSQLQueryCollectionReturn rtn); + + void visitFetch(NativeSQLQueryJoinReturn rtn); + + void visitDynamicInstantiation(NativeSQLQueryConstructorReturn rtn); + } + + public void visitReturns(QueryReturnVisitor visitor) { + for ( NativeSQLQueryReturn queryReturn : queryReturns ) { + if ( NativeSQLQueryScalarReturn.class.isInstance( queryReturn ) ) { + visitor.visitScalarReturn( (NativeSQLQueryScalarReturn) queryReturn ); + } + else if ( NativeSQLQueryRootReturn.class.isInstance( queryReturn ) ) { + visitor.visitRootReturn( (NativeSQLQueryRootReturn) queryReturn ); + } + else if ( NativeSQLQueryCollectionReturn.class.isInstance( queryReturn ) ) { + visitor.visitCollectionReturn( (NativeSQLQueryCollectionReturn) queryReturn ); + } + else if ( NativeSQLQueryJoinReturn.class.isInstance( queryReturn ) ) { + visitor.visitFetch( (NativeSQLQueryJoinReturn) queryReturn ); + } + else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) { + visitor.visitDynamicInstantiation( (NativeSQLQueryConstructorReturn) queryReturn ); + } + else { + throw new IllegalStateException( + "Unrecognized NativeSQLQueryReturn concrete type : " + queryReturn + ); + } + } + } + public List generateCustomReturns(boolean queryHadAliases) { List customReturns = new ArrayList(); Map customReturnsByAlias = new HashMap(); @@ -354,6 +389,58 @@ else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) { return customReturns; } + public List generateCallableReturns() { + final List customReturns = new ArrayList<>(); + + visitReturns( + new QueryReturnVisitor() { + @Override + public void visitScalarReturn(NativeSQLQueryScalarReturn rtn) { + customReturns.add( new ScalarReturn( rtn.getType(), rtn.getColumnAlias() ) ); + } + + @Override + public void visitRootReturn(NativeSQLQueryRootReturn rtn) { + customReturns.add( + new RootReturn( + rtn.getAlias(), + rtn.getReturnEntityName(), + new ColumnEntityAliases( + (Map) entityPropertyResultMaps.get( rtn.getAlias() ), + (SQLLoadable) alias2Persister.get( rtn.getAlias() ), + (String) alias2Suffix.get( rtn.getAlias() ) + ), + rtn.getLockMode() + ) + ); + } + + @Override + public void visitCollectionReturn(NativeSQLQueryCollectionReturn rtn) { + throw new UnsupportedOperationException( "Collection returns not supported for stored procedure mapping" ); + } + + @Override + public void visitFetch(NativeSQLQueryJoinReturn rtn) { + throw new UnsupportedOperationException( "Collection returns not supported for stored procedure mapping" ); + } + + @Override + public void visitDynamicInstantiation(NativeSQLQueryConstructorReturn rtn) { + final ScalarReturn[] scalars = new ScalarReturn[ rtn.getColumnReturns().length ]; + int i = 0; + for ( NativeSQLQueryScalarReturn scalarReturn : rtn.getColumnReturns() ) { + scalars[i++] = new ScalarReturn( scalarReturn.getType(), scalarReturn.getColumnAlias() ); + } + + customReturns.add( new ConstructorReturn( rtn.getTargetClass(), scalars ) ); + } + } + ); + + return customReturns; + } + private SQLLoadable getSQLLoadable(String entityName) throws MappingException { EntityPersister persister = factory.getEntityPersister( entityName ); if ( !(persister instanceof SQLLoadable) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java index 3bc5330fcab5..a552eb0157b9 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java @@ -9,14 +9,17 @@ import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collections; import java.util.List; import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.OuterJoinLoader; +import org.hibernate.param.ParameterBinder; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java index 47682525c199..fd4573d85a85 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java @@ -12,6 +12,7 @@ import java.util.List; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.Loader; @@ -97,6 +98,15 @@ protected Object doBatchLoad( try { final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false ); log.debug( "Done entity batch load" ); + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + ids, + results, + persister(), + session + ); return getObjectFromList(results, id, session); } catch ( SQLException sqle ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java index 4da683818786..6e03a58079bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java @@ -18,6 +18,9 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.dialect.pagination.LimitHelper; +import org.hibernate.engine.internal.BatchFetchQueueHelper; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -344,7 +347,13 @@ public Object load( final int numberOfIds = ArrayHelper.countNonNull( batch ); if ( numberOfIds <= 1 ) { - return singleKeyLoader.load( id, optionalObject, session ); + final Object result = singleKeyLoader.load( id, optionalObject, session ); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } final Serializable[] idsToLoad = new Serializable[numberOfIds]; @@ -356,6 +365,12 @@ public Object load( QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions ); List results = dynamicLoader.doEntityBatchFetch( session, qp, idsToLoad ); + + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( idsToLoad, results, persister(), session ); + return getObjectFromList( results, id, session ); } } @@ -421,16 +436,22 @@ protected boolean isSingleRowLoader() { return false; } + @Override + protected boolean isSubselectLoadingEnabled() { + return persister.hasSubselectLoadableCollections(); + } + public List doEntityBatchFetch( SharedSessionContractImplementor session, QueryParameters queryParameters, Serializable[] ids) { + final JdbcServices jdbcServices = session.getJdbcServices(); final String sql = StringHelper.expandBatchIdPlaceholder( sqlTemplate, ids, alias, persister.getKeyColumnNames(), - session.getJdbcServices().getJdbcEnvironment().getDialect() + jdbcServices.getJdbcEnvironment().getDialect() ); try { @@ -465,7 +486,7 @@ public List doEntityBatchFetch( } } catch ( SQLException sqle ) { - throw session.getJdbcServices().getSqlExceptionHelper().convert( + throw jdbcServices.getSqlExceptionHelper().convert( sqle, "could not load an entity batch: " + MessageHelper.infoString( getEntityPersisters()[0], @@ -491,8 +512,9 @@ private List doTheLoad(String sql, QueryParameters queryParameters, SharedSessio return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions ); } finally { - session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st ); - session.getJdbcCoordinator().afterStatementExecution(); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st ); + jdbcCoordinator.afterStatementExecution(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java index 0430cd7af474..8599f626c7f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java @@ -11,6 +11,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -100,10 +101,25 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract persister(), lockOptions ); + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + smallBatch, + results, + persister(), + session + ); return getObjectFromList(results, id, session); //EARLY EXIT } } - return ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load(id, optionalObject, session); + final Object result = ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load(id, optionalObject, session); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java index 1d95fd31adfa..0f4baa91bd78 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java @@ -11,6 +11,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -96,7 +97,13 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract final int numberOfIds = ArrayHelper.countNonNull( batch ); if ( numberOfIds <= 1 ) { - return ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load( id, optionalObject, session ); + final Object result = ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load( id, optionalObject, session ); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } // Uses the first batch-size bigger than the number of actual ids in the batch diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java index 05d469d36724..578d2b814a59 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java @@ -28,6 +28,7 @@ import org.hibernate.loader.plan.build.spi.MetamodelDrivenLoadPlanBuilder; import org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader; import org.hibernate.loader.plan.exec.internal.BatchingLoadQueryDetailsFactory; +import org.hibernate.loader.plan.exec.internal.EntityLoadQueryDetails; import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; import org.hibernate.loader.plan.spi.LoadPlan; @@ -48,7 +49,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan private final Type uniqueKeyType; private final String entityName; - private final LoadQueryDetails staticLoadQuery; + private final EntityLoadQueryDetails staticLoadQuery; public AbstractLoadPlanBasedEntityLoader( OuterJoinLoadable entityPersister, @@ -64,17 +65,20 @@ public AbstractLoadPlanBasedEntityLoader( final LoadPlanBuildingAssociationVisitationStrategy strategy; if ( buildingParameters.getQueryInfluencers().getFetchGraph() != null ) { strategy = new FetchGraphLoadPlanBuildingStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } else if ( buildingParameters.getQueryInfluencers().getLoadGraph() != null ) { strategy = new LoadGraphLoadPlanBuildingStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } else { strategy = new FetchStyleLoadPlanBuildingAssociationVisitationStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } @@ -87,6 +91,23 @@ else if ( buildingParameters.getQueryInfluencers().getLoadGraph() != null ) { ); } + protected AbstractLoadPlanBasedEntityLoader( + OuterJoinLoadable entityPersister, + SessionFactoryImplementor factory, + EntityLoadQueryDetails entityLoaderQueryDetailsTemplate, + Type uniqueKeyType, + QueryBuildingParameters buildingParameters) { + super( factory ); + this.entityPersister = entityPersister; + this.uniqueKeyType = uniqueKeyType; + this.entityName = entityPersister.getEntityName(); + + this.staticLoadQuery = BatchingLoadQueryDetailsFactory.INSTANCE.makeEntityLoadQueryDetails( + entityLoaderQueryDetailsTemplate, + buildingParameters + ); + } + @Override protected LoadQueryDetails getStaticLoadQuery() { return staticLoadQuery; @@ -122,6 +143,9 @@ public final List loadEntityBatch( final QueryParameters qp = new QueryParameters(); qp.setPositionalParameterTypes( types ); qp.setPositionalParameterValues( ids ); + qp.setOptionalObject( optionalObject ); + qp.setOptionalEntityName( optionalEntityName ); + qp.setOptionalId( optionalId ); qp.setLockOptions( lockOptions ); result = executeLoad( @@ -171,7 +195,7 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract false, null ); - result = extractEntityResult( results ); + result = extractEntityResult( results, id ); } catch ( SQLException sqle ) { throw session.getJdbcServices().getSqlExceptionHelper().convert( @@ -190,14 +214,22 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract return result; } + /** + * @deprecated {@link #extractEntityResult(List, Serializable)} should be used instead. + */ + @Deprecated protected Object extractEntityResult(List results) { + return extractEntityResult( results, null ); + } + + protected Object extractEntityResult(List results, Serializable id) { if ( results.size() == 0 ) { return null; } else if ( results.size() == 1 ) { return results.get( 0 ); } - else { + else if ( staticLoadQuery.hasCollectionInitializers() ) { final Object row = results.get( 0 ); if ( row.getClass().isArray() ) { // the logical type of the result list is List. See if the contained @@ -212,7 +244,20 @@ else if ( results.size() == 1 ) { } } - throw new HibernateException( "Unable to interpret given query results in terms of a load-entity query" ); + if ( id == null ) { + throw new HibernateException( + "Unable to interpret given query results in terms of a load-entity query for " + + entityName + ); + } + else { + throw new HibernateException( + "More than one row with the given identifier was found: " + + id + + ", for class: " + + entityName + ); + } } protected int[] getNamedParameterLocs(String name) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java index fccdca4dd324..4447e31ca5c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java @@ -12,11 +12,11 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.loader.plan.exec.internal.EntityLoadQueryDetails; import org.hibernate.loader.plan.exec.query.internal.QueryBuildingParametersImpl; import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.type.Type; - import org.jboss.logging.Logger; /** @@ -44,6 +44,7 @@ public static Builder forEntity(OuterJoinLoadable persister) { public static class Builder { private final OuterJoinLoadable persister; + private EntityLoader entityLoaderTemplate; private int batchSize = 1; private LoadQueryInfluencers influencers = LoadQueryInfluencers.NONE; private LockMode lockMode = LockMode.NONE; @@ -53,6 +54,11 @@ public Builder(OuterJoinLoadable persister) { this.persister = persister; } + public Builder withEntityLoaderTemplate(EntityLoader entityLoaderTemplate) { + this.entityLoaderTemplate = entityLoaderTemplate; + return this; + } + public Builder withBatchSize(int batchSize) { this.batchSize = batchSize; return this; @@ -79,18 +85,34 @@ public EntityLoader byPrimaryKey() { public EntityLoader byUniqueKey(String[] keyColumnNames, Type keyType) { // capture current values in a new instance of QueryBuildingParametersImpl - return new EntityLoader( - persister.getFactory(), - persister, - keyColumnNames, - keyType, - new QueryBuildingParametersImpl( - influencers, - batchSize, - lockMode, - lockOptions - ) - ); + if ( entityLoaderTemplate == null ) { + return new EntityLoader( + persister.getFactory(), + persister, + keyColumnNames, + keyType, + new QueryBuildingParametersImpl( + influencers, + batchSize, + lockMode, + lockOptions + ) + ); + } + else { + return new EntityLoader( + persister.getFactory(), + persister, + entityLoaderTemplate, + keyType, + new QueryBuildingParametersImpl( + influencers, + batchSize, + lockMode, + lockOptions + ) + ); + } } } @@ -121,4 +143,37 @@ else if ( buildingParameters.getLockMode() != null ) { } } } + + private EntityLoader( + SessionFactoryImplementor factory, + OuterJoinLoadable persister, + EntityLoader entityLoaderTemplate, + Type uniqueKeyType, + QueryBuildingParameters buildingParameters) throws MappingException { + super( persister, factory, entityLoaderTemplate.getStaticLoadQuery(), uniqueKeyType, buildingParameters ); + if ( log.isDebugEnabled() ) { + if ( buildingParameters.getLockOptions() != null ) { + log.debugf( + "Static select for entity %s [%s:%s]: %s", + getEntityName(), + buildingParameters.getLockOptions().getLockMode(), + buildingParameters.getLockOptions().getTimeOut(), + getStaticLoadQuery().getSqlStatement() + ); + } + else if ( buildingParameters.getLockMode() != null ) { + log.debugf( + "Static select for entity %s [%s]: %s", + getEntityName(), + buildingParameters.getLockMode(), + getStaticLoadQuery().getSqlStatement() + ); + } + } + } + + @Override + protected EntityLoadQueryDetails getStaticLoadQuery() { + return (EntityLoadQueryDetails) super.getStaticLoadQuery(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java index 832a19063295..d8cf9850d700 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java @@ -11,6 +11,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -19,7 +20,7 @@ import org.hibernate.persister.entity.OuterJoinLoadable; /** - * LoadPlan-based implementation of the the legacy batch loading strategy + * LoadPlan-based implementation of the legacy batch loading strategy * * @author Steve Ebersole */ @@ -56,15 +57,7 @@ public LegacyBatchingEntityLoader( LockMode lockMode, SessionFactoryImplementor factory, LoadQueryInfluencers loadQueryInfluencers) { - super( persister ); - this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); - this.loaders = new EntityLoader[ batchSizes.length ]; - final EntityLoader.Builder entityLoaderBuilder = EntityLoader.forEntity( persister ) - .withInfluencers( loadQueryInfluencers ) - .withLockMode( lockMode ); - for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = entityLoaderBuilder.withBatchSize( batchSizes[i] ).byPrimaryKey(); - } + this( persister, maxBatchSize, lockMode, null, factory, loadQueryInfluencers ); } public LegacyBatchingEntityLoader( @@ -73,14 +66,29 @@ public LegacyBatchingEntityLoader( LockOptions lockOptions, SessionFactoryImplementor factory, LoadQueryInfluencers loadQueryInfluencers) { + this( persister, maxBatchSize, null, lockOptions, factory, loadQueryInfluencers ); + } + + protected LegacyBatchingEntityLoader( + OuterJoinLoadable persister, + int maxBatchSize, + LockMode lockMode, + LockOptions lockOptions, + SessionFactoryImplementor factory, + LoadQueryInfluencers loadQueryInfluencers) { super( persister ); this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); this.loaders = new EntityLoader[ batchSizes.length ]; final EntityLoader.Builder entityLoaderBuilder = EntityLoader.forEntity( persister ) .withInfluencers( loadQueryInfluencers ) + .withLockMode( lockMode ) .withLockOptions( lockOptions ); - for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = entityLoaderBuilder.withBatchSize( batchSizes[i] ).byPrimaryKey(); + + // we create a first entity loader to use it as a template for the others + this.loaders[0] = entityLoaderBuilder.withBatchSize( batchSizes[0] ).byPrimaryKey(); + + for ( int i = 1; i < batchSizes.length; i++ ) { + this.loaders[i] = entityLoaderBuilder.withEntityLoaderTemplate( this.loaders[0] ).withBatchSize( batchSizes[i] ).byPrimaryKey(); } } @@ -106,12 +114,27 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract persister(), lockOptions ); + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + smallBatch, + results, + persister(), + session + ); + //EARLY EXIT return getObjectFromList( results, id, session ); } } - return ( loaders[batchSizes.length-1] ).load( id, optionalObject, session, lockOptions ); + final Object result = ( loaders[batchSizes.length-1] ).load( id, optionalObject, session, lockOptions ); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } } - } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java index e57e4893fef1..f103f413776b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java @@ -32,9 +32,12 @@ import org.hibernate.hql.internal.ast.tree.FromElement; import org.hibernate.hql.internal.ast.tree.QueryNode; import org.hibernate.hql.internal.ast.tree.SelectClause; +import org.hibernate.hql.spi.NamedParameterInformation; +import org.hibernate.hql.spi.ParameterInformation; import org.hibernate.internal.IteratorImpl; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.BasicLoader; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.loader.spi.AfterLoadAction; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.CollectionPersister; @@ -157,7 +160,7 @@ private void initialize(SelectClause selectClause) { entityAliases[i] = element.getClassAlias(); sqlAliasByEntityAlias.put( entityAliases[i], sqlAliases[i] ); // TODO should we just collect these like with the collections above? - sqlAliasSuffixes[i] = ( size == 1 ) ? "" : Integer.toString( i ) + "_"; + sqlAliasSuffixes[i] = ( size == 1 ) ? "" : AliasConstantsHelper.get( i ); // sqlAliasSuffixes[i] = element.getColumnAliasSuffix(); includeInSelect[i] = !element.isFetch(); if ( includeInSelect[i] ) { @@ -333,7 +336,7 @@ protected String applyLocks( } // there are other conditions we might want to add here, such as checking the result types etc - // but those are better served afterQuery we have redone the SQL generation to use ASTs. + // but those are better served after we have redone the SQL generation to use ASTs. // we need both the set of locks and the columns to reference in locks @@ -599,7 +602,22 @@ private Object[] toResultRow(Object[] row) { */ @Override public int[] getNamedParameterLocs(String name) throws QueryException { - return queryTranslator.getParameterTranslations().getNamedParameterSqlLocations( name ); + ParameterInformation info = queryTranslator.getParameterTranslations().getNamedParameterInformation( name ); + if ( info == null ) { + try { + info = queryTranslator.getParameterTranslations().getPositionalParameterInformation( + Integer.parseInt( name ) + ); + } + catch (Exception ignore) { + } + } + + if ( info == null ) { + throw new QueryException( "Unrecognized parameter label : " + name ); + } + + return info.getSourceLocations(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/AliasConstantsHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/AliasConstantsHelper.java new file mode 100644 index 000000000000..7fe126ae2ada --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/AliasConstantsHelper.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.loader.internal; + +/** + * @author Sanne Grinovero + */ +public final class AliasConstantsHelper { + + private static final int MAX_POOL_SIZE = 40; + private static final String[] pool = initPool( MAX_POOL_SIZE ); + + /** + * Returns the same as Integer.toString( i ) + '_' + * Strings might be returned from a pool of constants, when i + * is within the range of expected most commonly requested elements. + * + * @param i + * @return + */ + public static String get(final int i) { + if ( i < MAX_POOL_SIZE && i >= 0 ) { + return pool[i]; + } + else { + return internalAlias( i ); + } + } + + private static String[] initPool(final int maxPoolSize) { + String[] pool = new String[maxPoolSize]; + for ( int i = 0; i < maxPoolSize; i++ ) { + pool[i] = internalAlias( i ); + } + return pool; + } + + private static String internalAlias(final int i) { + return Integer.toString( i ) + '_'; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java index 8c60516e7e30..f5e2014dd90b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java @@ -132,15 +132,16 @@ public void finishingEntity(final EntityDefinition entityDefinition) { } /** - * I'm using NULL-OBJECT pattern here, for attributes that not existing in the EntityGraph, + * I'm using the NULL-OBJECT pattern here. + * For attributes that don't exist in the EntityGraph, * a predefined NULL-ATTRIBUTE-NODE is pushed to the stack. * - * and for an not existing sub graph, a predefined NULL-SUBGRAPH is pushed to the stack. + * And for a nonexistent subgraph, a predefined NULL-SUBGRAPH is pushed to the stack. * - * So, whenever we're start visiting an attribute, there will be a attribute node pushed to the attribute stack, + * So, whenever we start visiting an attribute, there will be an attribute node pushed to the attribute stack, * and a subgraph node pushed to the graph stack. * - * when we're finish visiting an attribute, these two will be poped from each stack. + * when we finish visiting an attribute, these two will be popped from each stack. */ @Override public boolean startingAttribute(AttributeDefinition attributeDefinition) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java index 815e8b7522ec..367d3fff4bc0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java @@ -61,7 +61,7 @@ * LoadPlanBuildingAssociationVisitationStrategy is also a AssociationVisitationStrategy, which is used in * conjunction with visiting associations via walking metamodel definitions. *

    - * So this strategy defines a AssociationVisitationStrategy that walks the metamodel-defined associations afterQuery + * So this strategy defines a AssociationVisitationStrategy that walks the metamodel-defined associations after * which is can then build a LoadPlan based on the visited associations. {@link #determineFetchStrategy} is the * main decision point that determines if an association is walked. * @@ -286,7 +286,7 @@ public void finishingEntityIdentifier(EntityIdentifierDefinition entityIdentifie final ExpandingEntityIdentifierDescription identifierDescription = (ExpandingEntityIdentifierDescription) popFromStack(); - // and then on the node beforeQuery it (which should be the entity that owns the identifier being described) + // and then on the node before it (which should be the entity that owns the identifier being described) final ExpandingFetchSource entitySource = currentSource(); if ( ! EntityReference.class.isInstance( entitySource ) ) { throw new WalkingException( "Unexpected state in FetchSource stack" ); @@ -662,31 +662,34 @@ public FetchSource registeredFetchSource(AssociationKey associationKey) { @Override public void foundCircularAssociation(AssociationAttributeDefinition attributeDefinition) { final FetchStrategy fetchStrategy = determineFetchStrategy( attributeDefinition ); - if ( fetchStrategy.getStyle() != FetchStyle.JOIN ) { - return; // nothing to do - } - final AssociationKey associationKey = attributeDefinition.getAssociationKey(); // go ahead and build the bidirectional fetch if ( attributeDefinition.getAssociationNature() == AssociationAttributeDefinition.AssociationNature.ENTITY ) { - final Joinable currentEntityPersister = (Joinable) currentSource().resolveEntityReference().getEntityPersister(); - final AssociationKey currentEntityReferenceAssociationKey = - new AssociationKey( currentEntityPersister.getTableName(), currentEntityPersister.getKeyColumnNames() ); - // if associationKey is equal to currentEntityReferenceAssociationKey - // that means that the current EntityPersister has a single primary key attribute - // (i.e., derived attribute) which is mapped by attributeDefinition. - // This is not a bidirectional association. - // TODO: AFAICT, to avoid an overflow, the associated entity must already be loaded into the session, or - // it must be loaded when the ID for the dependent entity is resolved. Is there some other way to - // deal with this??? final FetchSource registeredFetchSource = registeredFetchSource( associationKey ); - if ( registeredFetchSource != null && ! associationKey.equals( currentEntityReferenceAssociationKey ) ) { - currentSource().buildBidirectionalEntityReference( - attributeDefinition, - fetchStrategy, - registeredFetchSource( associationKey ).resolveEntityReference() - ); + if ( registeredFetchSource != null ) { + final ExpandingFetchSource currentSource = currentSource(); + final Joinable currentEntityPersister = (Joinable) currentSource.resolveEntityReference().getEntityPersister(); + + final AssociationKey currentEntityReferenceAssociationKey = + new AssociationKey( currentEntityPersister.getTableName(), currentEntityPersister.getKeyColumnNames() ); + // if associationKey is equal to currentEntityReferenceAssociationKey + // that means that the current EntityPersister has a single primary key attribute + // (i.e., derived attribute) which is mapped by attributeDefinition. + // This is not a bidirectional association. + // TODO: AFAICT, to avoid an overflow, the associated entity must already be loaded into the session, or + // it must be loaded when the ID for the dependent entity is resolved. Is there some other way to + // deal with this??? + if ( !associationKey.equals( currentEntityReferenceAssociationKey ) ) { + currentSource.buildBidirectionalEntityReference( + attributeDefinition, + fetchStrategy, + registeredFetchSource.resolveEntityReference() + ); + } + } + else { + // Do nothing, no bi-directionality } } else { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/returns/AbstractCollectionReference.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/returns/AbstractCollectionReference.java index b77f3e433e2d..5077a7fc4630 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/returns/AbstractCollectionReference.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/returns/AbstractCollectionReference.java @@ -64,7 +64,7 @@ protected AbstractCollectionReference( this.allowIndexJoin = false; } - // All other fields must be initialized beforeQuery building this.index and this.element. + // All other fields must be initialized before building this.index and this.element. this.index = buildIndexGraph(); this.element = buildElementGraph(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/QuerySpaceTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/QuerySpaceTreePrinter.java index 547066a4ad6a..5f0038515d59 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/QuerySpaceTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/QuerySpaceTreePrinter.java @@ -10,7 +10,6 @@ import java.io.PrintStream; import java.io.PrintWriter; -import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.EntityAliases; import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.exec.spi.CollectionReferenceAliases; @@ -180,7 +179,7 @@ private void generateDetailLines( printWriter.println( TreePrinterHelper.INSTANCE.generateNodePrefix( depth + detailDepthOffset ) + "suffixed key columns - {" - + StringHelper.join( ", ", entityAliases.getColumnAliases().getSuffixedKeyAliases() ) + + String.join( ", ", entityAliases.getColumnAliases().getSuffixedKeyAliases() ) + "}" ); } @@ -193,7 +192,7 @@ private void generateDetailLines( printWriter.println( TreePrinterHelper.INSTANCE.generateNodePrefix( depth + detailDepthOffset ) + "suffixed key columns - {" - + StringHelper.join( ", ", collectionReferenceAliases.getCollectionColumnAliases().getSuffixedKeyAliases() ) + + String.join( ", ", collectionReferenceAliases.getCollectionColumnAliases().getSuffixedKeyAliases() ) + "}" ); final EntityAliases elementAliases = @@ -209,7 +208,7 @@ private void generateDetailLines( TreePrinterHelper.INSTANCE.generateNodePrefix( depth + detailDepthOffset ) + elementAliases.getSuffix() + "entity-element suffixed key columns - " - + StringHelper.join( ", ", elementAliases.getSuffixedKeyAliases() ) + + String.join( ", ", elementAliases.getSuffixedKeyAliases() ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java index c13577754e68..3181c70632ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -24,6 +25,7 @@ import org.hibernate.dialect.pagination.LimitHelper; import org.hibernate.dialect.pagination.NoopLimitHandler; import org.hibernate.engine.jdbc.ColumnNameCache; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.ResultSetWrapper; import org.hibernate.engine.spi.PersistenceContext; @@ -37,6 +39,7 @@ import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; import org.hibernate.loader.spi.AfterLoadAction; +import org.hibernate.resource.jdbc.ResourceRegistry; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; @@ -82,24 +85,6 @@ protected List executeLoad( LoadQueryDetails loadQueryDetails, boolean returnProxies, ResultTransformer forcedResultTransformer) throws SQLException { - final List afterLoadActions = new ArrayList(); - return executeLoad( - session, - queryParameters, - loadQueryDetails, - returnProxies, - forcedResultTransformer, - afterLoadActions - ); - } - - protected List executeLoad( - SharedSessionContractImplementor session, - QueryParameters queryParameters, - LoadQueryDetails loadQueryDetails, - boolean returnProxies, - ResultTransformer forcedResultTransformer, - List afterLoadActions) throws SQLException { final PersistenceContext persistenceContext = session.getPersistenceContext(); final boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); if ( queryParameters.isReadOnlyInitialized() ) { @@ -114,11 +99,11 @@ protected List executeLoad( } persistenceContext.beforeLoad(); try { - List results = null; + final List results; final String sql = loadQueryDetails.getSqlStatement(); SqlStatementWrapper wrapper = null; try { - wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session ); + wrapper = executeQueryStatement( sql, queryParameters, false, session ); results = loadQueryDetails.getResultSetProcessor().extractResults( wrapper.getResultSet(), session, @@ -132,17 +117,15 @@ public int[] getNamedParameterLocations(String name) { returnProxies, queryParameters.isReadOnly(), forcedResultTransformer, - afterLoadActions + Collections.EMPTY_LIST ); } finally { if ( wrapper != null ) { - session.getJdbcCoordinator().getResourceRegistry().release( - wrapper.getResultSet(), - wrapper.getStatement() - ); - session.getJdbcCoordinator().getResourceRegistry().release( wrapper.getStatement() ); - session.getJdbcCoordinator().afterStatementExecution(); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + final ResourceRegistry resourceRegistry = jdbcCoordinator.getResourceRegistry(); + resourceRegistry.release( wrapper.getStatement() ); + jdbcCoordinator.afterStatementExecution(); } persistenceContext.afterLoad(); } @@ -158,16 +141,14 @@ public int[] getNamedParameterLocations(String name) { protected SqlStatementWrapper executeQueryStatement( final QueryParameters queryParameters, final boolean scroll, - List afterLoadActions, final SharedSessionContractImplementor session) throws SQLException { - return executeQueryStatement( getStaticLoadQuery().getSqlStatement(), queryParameters, scroll, afterLoadActions, session ); + return executeQueryStatement( getStaticLoadQuery().getSqlStatement(), queryParameters, scroll, session ); } protected SqlStatementWrapper executeQueryStatement( String sqlStatement, QueryParameters queryParameters, boolean scroll, - List afterLoadActions, SharedSessionContractImplementor session) throws SQLException { // Processing query filters. @@ -180,7 +161,12 @@ protected SqlStatementWrapper executeQueryStatement( String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); // Adding locks and comments. - sql = preprocessSQL( sql, queryParameters, session.getJdbcServices().getJdbcEnvironment().getDialect(), afterLoadActions ); + sql = session.getJdbcServices().getJdbcEnvironment().getDialect() + .addSqlHintOrComment( + sql, + queryParameters, + session.getFactory().getSessionFactoryOptions().isCommentsEnabled() + ); final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session ); return new SqlStatementWrapper( st, getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session ) ); @@ -198,26 +184,6 @@ protected LimitHandler getLimitHandler(RowSelection selection) { return LimitHelper.useLimit( limitHandler, selection ) ? limitHandler : NoopLimitHandler.INSTANCE; } - private String preprocessSQL( - String sql, - QueryParameters queryParameters, - Dialect dialect, - List afterLoadActions) { - return getFactory().getSettings().isCommentsEnabled() - ? prependComment( sql, queryParameters ) - : sql; - } - - private String prependComment(String sql, QueryParameters parameters) { - final String comment = parameters.getComment(); - if ( comment == null ) { - return sql; - } - else { - return "/* " + comment + " */ " + sql; - } - } - /** * Obtain a PreparedStatement with all parameters pre-bound. * Bind JDBC-style ? parameters, named parameters, and @@ -341,7 +307,7 @@ protected int bindParameterValues( *

    * Positional parameters are those specified by JDBC-style ? parameters * in the source query. It is (currently) expected that these come - * beforeQuery any named parameters in the source query. + * before any named parameters in the source query. * * @param statement The JDBC prepared statement * @param queryParameters The encapsulation of the parameter values to be bound. @@ -443,10 +409,10 @@ protected final ResultSet getResultSet( } return rs; } - catch ( SQLException sqle ) { + catch (SQLException | HibernateException ex) { session.getJdbcCoordinator().getResourceRegistry().release( st ); session.getJdbcCoordinator().afterStatementExecution(); - throw sqle; + throw ex; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java index 07fdd2421cad..ee0e6482082e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java @@ -90,6 +90,15 @@ protected final QueryBuildingParameters getQueryBuildingParameters() { protected final SessionFactoryImplementor getSessionFactory() { return queryProcessor.getSessionFactory(); } + + protected LoadPlan getLoadPlan() { + return loadPlan; + } + + protected String[] getKeyColumnNames() { + return keyColumnNames; + } + /** * Main entry point for properly handling the FROM clause and and joins and restrictions * diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java index 13e30760176c..08137d34331d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java @@ -19,6 +19,7 @@ import org.hibernate.loader.DefaultEntityAliases; import org.hibernate.loader.EntityAliases; import org.hibernate.loader.GeneratedCollectionAliases; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.loader.plan.build.spi.QuerySpaceTreePrinter; import org.hibernate.loader.plan.build.spi.TreePrinterHelper; import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; @@ -34,6 +35,8 @@ import org.jboss.logging.Logger; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * Provides aliases that are used by load queries and ResultSet processors. * @@ -140,7 +143,7 @@ private EntityAliases createEntityAliases(EntityPersister entityPersister) { } private String createSuffix() { - return Integer.toString( currentAliasSuffix++ ) + '_'; + return AliasConstantsHelper.get( currentAliasSuffix++ ); } /** @@ -237,7 +240,7 @@ private void registerSqlTableAliasMapping(String querySpaceUid, String sqlTableA if ( querySpaceUidToSqlTableAliasMap == null ) { querySpaceUidToSqlTableAliasMap = new HashMap(); } - String old = querySpaceUidToSqlTableAliasMap.put( querySpaceUid, sqlTableAlias ); + String old = querySpaceUidToSqlTableAliasMap.put( safeInterning( querySpaceUid ), safeInterning( sqlTableAlias ) ); if ( old != null ) { if ( old.equals( sqlTableAlias ) ) { // silently ignore... @@ -346,7 +349,7 @@ private void generateDetailLines(QuerySpace querySpace, int depth, PrintWriter p printWriter.println( TreePrinterHelper.INSTANCE.generateNodePrefix( depth+3 ) + "suffixed key columns - " - + StringHelper.join( ", ", entityAliases.getColumnAliases().getSuffixedKeyAliases() ) + + String.join( ", ", entityAliases.getColumnAliases().getSuffixedKeyAliases() ) ); } @@ -358,7 +361,7 @@ private void generateDetailLines(QuerySpace querySpace, int depth, PrintWriter p printWriter.println( TreePrinterHelper.INSTANCE.generateNodePrefix( depth+3 ) + "suffixed key columns - " - + StringHelper.join( ", ", collectionReferenceAliases.getCollectionColumnAliases().getSuffixedKeyAliases() ) + + String.join( ", ", collectionReferenceAliases.getCollectionColumnAliases().getSuffixedKeyAliases() ) ); final EntityReferenceAliases elementAliases = collectionReferenceAliases.getEntityElementAliases(); if ( elementAliases != null ) { @@ -370,7 +373,7 @@ private void generateDetailLines(QuerySpace querySpace, int depth, PrintWriter p TreePrinterHelper.INSTANCE.generateNodePrefix( depth+3 ) + elementAliases.getColumnAliases().getSuffix() + "entity-element suffixed key columns - " - + StringHelper.join( ", ", elementAliases.getColumnAliases().getSuffixedKeyAliases() ) + + String.join( ", ", elementAliases.getColumnAliases().getSuffixedKeyAliases() ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java index 7f5a1b4a2627..3b03f72d9ce2 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java @@ -41,7 +41,7 @@ private BatchingLoadQueryDetailsFactory() { * * @return The EntityLoadQueryDetails */ - public LoadQueryDetails makeEntityLoadQueryDetails( + public EntityLoadQueryDetails makeEntityLoadQueryDetails( LoadPlan loadPlan, String[] keyColumnNames, QueryBuildingParameters buildingParameters, @@ -68,6 +68,23 @@ public LoadQueryDetails makeEntityLoadQueryDetails( ); } + /** + * Returns a EntityLoadQueryDetails object based on an existing one and additional elements specific to this one. + * + * @param entityLoadQueryDetailsTemplate the template + * @param buildingParameters And influencers that would affect the generated SQL (mostly we are concerned with those + * that add additional joins here) + * @return The EntityLoadQueryDetails + */ + public EntityLoadQueryDetails makeEntityLoadQueryDetails( + EntityLoadQueryDetails entityLoadQueryDetailsTemplate, + QueryBuildingParameters buildingParameters) { + return new EntityLoadQueryDetails( + entityLoadQueryDetailsTemplate, + buildingParameters + ); + } + /** * Constructs a BasicCollectionLoadQueryDetails object from the given inputs. * diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java index 4dc4a8c6bd6e..7c78004c5112 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java @@ -10,17 +10,15 @@ import java.sql.SQLException; import java.util.Collections; -import org.hibernate.LockOptions; -import org.hibernate.Session; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.plan.exec.process.internal.AbstractRowReader; import org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl; import org.hibernate.loader.plan.exec.process.internal.EntityReturnReader; import org.hibernate.loader.plan.exec.process.internal.ResultSetProcessingContextImpl; -import org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorHelper; import org.hibernate.loader.plan.exec.process.spi.EntityReferenceInitializer; import org.hibernate.loader.plan.exec.process.spi.ReaderCollector; import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; @@ -31,12 +29,9 @@ import org.hibernate.loader.plan.spi.EntityReturn; import org.hibernate.loader.plan.spi.LoadPlan; import org.hibernate.loader.plan.spi.QuerySpace; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.entity.Queryable; -import org.hibernate.type.CompositeType; -import org.hibernate.type.Type; import org.jboss.logging.Logger; @@ -90,6 +85,24 @@ protected EntityLoadQueryDetails( generate(); } + protected EntityLoadQueryDetails( + EntityLoadQueryDetails initialEntityLoadQueryDetails, + QueryBuildingParameters buildingParameters) { + this( + initialEntityLoadQueryDetails.getLoadPlan(), + initialEntityLoadQueryDetails.getKeyColumnNames(), + new AliasResolutionContextImpl( initialEntityLoadQueryDetails.getSessionFactory() ), + (EntityReturn) initialEntityLoadQueryDetails.getRootReturn(), + buildingParameters, + initialEntityLoadQueryDetails.getSessionFactory() + ); + } + + public boolean hasCollectionInitializers() { + return CollectionHelper.isNotEmpty( readerCollector.getArrayReferenceInitializers() ) || + CollectionHelper.isNotEmpty( readerCollector.getNonArrayCollectionReferenceInitializers() ); + } + private EntityReturn getRootEntityReturn() { return (EntityReturn) getRootReturn(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java index 16df015cec23..2d4d87c74e27 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java @@ -9,6 +9,8 @@ import org.hibernate.loader.EntityAliases; import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * @author Gail Badner * @author Steve Ebersole @@ -18,7 +20,7 @@ public class EntityReferenceAliasesImpl implements EntityReferenceAliases { private final EntityAliases columnAliases; public EntityReferenceAliasesImpl(String tableAlias, EntityAliases columnAliases) { - this.tableAlias = tableAlias; + this.tableAlias = safeInterning( tableAlias ); this.columnAliases = columnAliases; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java index 59d4362c3f02..333f3384d01e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java @@ -38,6 +38,7 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPropertyNames; import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.OuterJoinLoadable; @@ -70,7 +71,7 @@ public class LoadQueryJoinAndFetchProcessor { private final SessionFactoryImplementor factory; /** - * Instantiates a LoadQueryBuilderHelper with the given information + * Instantiates a LoadQueryJoinAndFetchProcessor with the given information * * @param aliasResolutionContext * @param buildingParameters @@ -257,14 +258,37 @@ else if ( !StringHelper.isEmpty( joinConditions ) ) { getJoinedAssociationTypeOrNull( join ) ); - joinFragment.addJoin( - joinable.getTableName(), - rhsTableAlias, - join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ), - join.resolveNonAliasedRightHandSideJoinConditionColumns(), - join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN, - additionalJoinConditions - ); + String[] joinColumns = join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ); + QuerySpace lhsQuerySpace = join.getLeftHandSide(); + if ( joinColumns.length == 0 && lhsQuerySpace instanceof EntityQuerySpace ) { + // When no columns are available, this is a special join that involves multiple subtypes + EntityQuerySpace entityQuerySpace = (EntityQuerySpace) lhsQuerySpace; + AbstractEntityPersister persister = (AbstractEntityPersister) entityQuerySpace.getEntityPersister(); + + String[][] polyJoinColumns = persister.getPolymorphicJoinColumns( + lhsTableAlias, + ( (JoinDefinedByMetadata) join ).getJoinedPropertyName() + ); + + joinFragment.addJoin( + joinable.getTableName(), + rhsTableAlias, + polyJoinColumns, + join.resolveNonAliasedRightHandSideJoinConditionColumns(), + join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN, + additionalJoinConditions + ); + } + else { + joinFragment.addJoin( + joinable.getTableName(), + rhsTableAlias, + joinColumns, + join.resolveNonAliasedRightHandSideJoinConditionColumns(), + join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN, + additionalJoinConditions + ); + } joinFragment.addJoins( joinable.fromJoinFragment( rhsTableAlias, false, true ), joinable.whereJoinFragment( rhsTableAlias, false, true ) 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 e8369a418867..2083474b9a86 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java @@ -15,9 +15,15 @@ import java.util.Map; import org.hibernate.engine.internal.TwoPhaseLoad; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.service.spi.EventListenerGroup; +import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.PostLoadEvent; +import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.event.spi.PreLoadEvent; +import org.hibernate.event.spi.PreLoadEventListener; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.plan.exec.process.spi.CollectionReferenceInitializer; @@ -110,7 +116,7 @@ public Object readRow(ResultSet resultSet, ResultSetProcessingContextImpl contex Object logicalRow = readLogicalRow( resultSet, context ); - // 4) allow arrays, entities and collections afterQuery row callbacks + // 4) allow arrays, entities and collections after row callbacks if ( hasEntityReferenceInitializers ) { for ( EntityReferenceInitializer entityReferenceInitializer : entityReferenceInitializers ) { entityReferenceInitializer.finishUpRow( resultSet, context ); @@ -188,7 +194,7 @@ else if ( CompositeFetch.class.isInstance( fetch ) ) { public void finishUp(ResultSetProcessingContextImpl context, List afterLoadActionList) { final List hydratedEntityRegistrations = context.getHydratedEntityRegistrationList(); - // for arrays, we should end the collection load beforeQuery resolving the entities, since the + // for arrays, we should end the collection load before resolving the entities, since the // actual array instances are not instantiated during loading finishLoadingArrays( context ); @@ -230,16 +236,25 @@ private void performTwoPhaseLoad( : hydratedEntityRegistrations.size(); log.tracev( "Total objects hydrated: {0}", numberOfHydratedObjects ); - if ( hydratedEntityRegistrations == null ) { + if ( numberOfHydratedObjects == 0 ) { return; } + final SharedSessionContractImplementor session = context.getSession(); + final Iterable listeners = session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.PRE_LOAD ) + .listeners(); + for ( HydratedEntityRegistration registration : hydratedEntityRegistrations ) { TwoPhaseLoad.initializeEntity( registration.getInstance(), context.isReadOnly(), - context.getSession(), - preLoadEvent + session, + preLoadEvent, + listeners ); } } @@ -256,19 +271,32 @@ private void postLoad( List hydratedEntityRegistrations, List afterLoadActionList) { // Until this entire method is refactored w/ polymorphism, postLoad was - // split off from initializeEntity. It *must* occur afterQuery + // split off from initializeEntity. It *must* occur after // endCollectionLoad to ensure the collection is in the // persistence context. - if ( hydratedEntityRegistrations == null ) { + if ( hydratedEntityRegistrations == null || hydratedEntityRegistrations.size() == 0 ) { return; } + final SharedSessionContractImplementor session = context.getSession(); + final Iterable postLoadEventListeners; + if ( session.isEventSource() ) { + final EventListenerGroup listenerGroup = session.getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.POST_LOAD ); + postLoadEventListeners = listenerGroup.listeners(); + } + else { + postLoadEventListeners = Collections.emptyList(); + } + for ( HydratedEntityRegistration registration : hydratedEntityRegistrations ) { - TwoPhaseLoad.postLoad( registration.getInstance(), context.getSession(), postLoadEvent ); + TwoPhaseLoad.postLoad( registration.getInstance(), session, postLoadEvent, postLoadEventListeners ); if ( afterLoadActionList != null ) { for ( AfterLoadAction afterLoadAction : afterLoadActionList ) { afterLoadAction.afterLoad( - context.getSession(), + session, registration.getInstance(), (Loadable) registration.getEntityReference().getEntityPersister() ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReferenceInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReferenceInitializerImpl.java index 1208525d0977..65548e1971ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReferenceInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReferenceInitializerImpl.java @@ -14,7 +14,6 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.internal.CoreLogging; import org.hibernate.loader.plan.exec.process.spi.CollectionReferenceInitializer; -import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; import org.hibernate.loader.plan.exec.spi.CollectionReferenceAliases; import org.hibernate.loader.plan.spi.CollectionReference; import org.hibernate.loader.plan.spi.Fetch; @@ -85,6 +84,7 @@ public void finishUpRow(ResultSet resultSet, ResultSetProcessingContextImpl cont } else { final Serializable optionalKey = findCollectionOwnerKey( context ); + if ( optionalKey != null ) { // we did not find a collection element in the result set, so we // ensure that a collection is created with the owner's identifier, @@ -138,12 +138,12 @@ protected Object findCollectionOwner( } protected Serializable findCollectionOwnerKey(ResultSetProcessingContextImpl context) { - ResultSetProcessingContext.EntityReferenceProcessingState ownerState = context.getOwnerProcessingState( (Fetch) collectionReference ); + Object owner = context.getOwnerProcessingState( (Fetch) collectionReference ).getEntityInstance(); - if(ownerState == null || ownerState.getEntityKey()==null){ - return null; - } - return ownerState.getEntityKey().getIdentifier(); + return collectionReference.getCollectionPersister().getCollectionType().getKeyOfOwner( + owner, + context.getSession() + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java index 62ee596e406f..094000b664cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java @@ -14,6 +14,9 @@ import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityKey; @@ -133,7 +136,7 @@ public void resolveEntityKey(ResultSet resultSet, ResultSetProcessingContextImpl // Look for the hydrated form final Object identifierHydratedForm = processingState.getIdentifierHydratedForm(); if ( identifierHydratedForm == null ) { - // we need to register the missing identifier, but that happens later afterQuery all readers have had a chance + // we need to register the missing identifier, but that happens later after all readers have had a chance // to resolve its EntityKey return; } @@ -195,6 +198,31 @@ public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContextIm // use the existing association as the hydrated state processingState.registerEntityInstance( existing ); //context.registerHydratedEntity( entityReference, entityKey, existing ); + + // see if the entity is enhanced and is being used as a "proxy" (is fully uninitialized) + final BytecodeEnhancementMetadata enhancementMetadata = entityReference.getEntityPersister() + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + + if ( enhancementMetadata.isEnhancedForLazyLoading() ) { + final BytecodeLazyAttributeInterceptor interceptor = enhancementMetadata.extractLazyInterceptor( existing ); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final LockMode requestedLockMode = context.resolveLockMode( entityReference ); + final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE + ? LockMode.READ + : requestedLockMode; + + loadFromResultSet( + resultSet, + context, + existing, + getConcreteEntityTypeName( resultSet, context, entityKey ), + entityKey, + lockModeToAcquire + ); + } + } + return; } @@ -202,9 +230,9 @@ public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContextIm // determine which entity instance to use. Either the supplied one, or instantiate one Object entityInstance = null; - if ( isReturn && - context.shouldUseOptionalEntityInformation() && - context.getQueryParameters().getOptionalObject() != null ) { + // If an "optional" instance with an EntityKey equal to entityKey is available + // in the context, then use that instance. + if ( isReturn && context.getQueryParameters().getOptionalObject() != null ) { final EntityKey optionalEntityKey = ResultSetProcessorHelper.getOptionalObjectKey( context.getQueryParameters(), context.getSession() @@ -328,6 +356,13 @@ private void loadFromResultSet( rowId = concreteEntityPersister.hasRowId() ? resultSet.getObject( entityReferenceAliases.getColumnAliases().getRowIdAlias() ) : null; + + if ( rowId != null && log.isTraceEnabled() ) { + log.tracev( + "extracted ROWID value: {0}", + rowId + ); + } } catch (SQLException e) { throw context.getSession().getFactory().getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().convert( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java index dbee13153d31..ae3f37b64f61 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java @@ -15,8 +15,6 @@ import java.util.Map; import java.util.Set; -import org.jboss.logging.Logger; - import org.hibernate.LockMode; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.QueryParameters; @@ -30,10 +28,11 @@ import org.hibernate.loader.plan.spi.EntityReference; import org.hibernate.loader.plan.spi.Fetch; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; import org.hibernate.type.EntityType; +import org.jboss.logging.Logger; + /** * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessor.java index 29180cc84faf..b18219858b5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessor.java @@ -45,7 +45,7 @@ public interface ResultSetProcessor { * @param queryParameters The "parameters" used to build the query * @param returnProxies Can proxies be returned (not the same as can they be created!) * @param forcedResultTransformer My old "friend" ResultTransformer... - * @param afterLoadActions Actions to be performed afterQuery loading an entity. + * @param afterLoadActions Actions to be performed after loading an entity. * * @return The extracted results list. * diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ScrollableResultSetProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ScrollableResultSetProcessor.java index 2bb4ad0ce115..a594c576f4ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ScrollableResultSetProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ScrollableResultSetProcessor.java @@ -76,7 +76,7 @@ public Object extractLogicalRowForward( * @param resultSet The result set being processed. * @param session The originating session * @param queryParameters The "parameters" used to build the query - * @param isLogicallyAfterLast Is the result set currently positioned afterQuery the last row; again, is this really needed? How is it any diff + * @param isLogicallyAfterLast Is the result set currently positioned after the last row; again, is this really needed? How is it any diff * * @return The extracted result row * diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java index 7902cd6c1d8d..0a1f4df0ec7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java @@ -82,7 +82,7 @@ public void appendFromClauseFragment(String tableName, String alias) { } /** - * Appends the specified restrictions afterQuery "cleaning" the specified value + * Appends the specified restrictions after "cleaning" the specified value * (by trimming and removing 'and ' from beginning and ' and' from the end). * If the where clause already exists, this method ensure that ' and ' * prefixes the cleaned restrictions. @@ -187,7 +187,7 @@ public String toStatementString() { StringBuilder buf = new StringBuilder( guesstimatedBufferSize ); if ( StringHelper.isNotEmpty( comment ) ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "select " ) @@ -201,7 +201,7 @@ public String toStatementString() { if ( isNotEmpty( whereClause ) || isNotEmpty( outerJoinsAfterWhere ) ) { buf.append( " where " ); - // the outerJoinsAfterWhere needs to come beforeQuery where clause to properly + // the outerJoinsAfterWhere needs to come before where clause to properly // handle dynamic filters if ( StringHelper.isNotEmpty( outerJoinsAfterWhere ) ) { buf.append( outerJoinsAfterWhere ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIdentifierDescription.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIdentifierDescription.java index 03fb0c60895c..a04c3a3be05a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIdentifierDescription.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIdentifierDescription.java @@ -16,7 +16,7 @@ public interface EntityIdentifierDescription { * Can this EntityIdentifierDescription be treated as a FetchSource and if so does it have any * fetches? * - * @return {@code true} iff {@code this} can be cast to {@link FetchSource} and (afterQuery casting) it returns + * @return {@code true} iff {@code this} can be cast to {@link FetchSource} and (after casting) it returns * non-empty results for {@link FetchSource#getFetches()} */ public boolean hasFetches(); @@ -25,7 +25,7 @@ public interface EntityIdentifierDescription { * Can this EntityIdentifierDescription be treated as a FetchSource and if so does it have any * bidirectional entity references? * - * @return {@code true} iff {@code this} can be cast to {@link FetchSource} and (afterQuery casting) it returns + * @return {@code true} iff {@code this} can be cast to {@link FetchSource} and (after casting) it returns * non-empty results for {@link FetchSource#getBidirectionalEntityReferences()} */ public boolean hasBidirectionalEntityReferences(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java index 1fda4d0298b7..297277e72e03 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java @@ -7,8 +7,10 @@ package org.hibernate.mapping; import java.util.Map; +import java.util.Objects; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.MetaType; import org.hibernate.type.Type; @@ -23,10 +25,18 @@ public class Any extends SimpleValue { private String metaTypeName = "string"; private Map metaValues; + /** + * @deprecated Use {@link Any#Any(MetadataBuildingContext, Table)} instead. + */ + @Deprecated public Any(MetadataImplementor metadata, Table table) { super( metadata, table ); } + public Any(MetadataBuildingContext buildingContext, Table table) { + super( buildingContext, table ); + } + public String getIdentifierType() { return identifierTypeName; } @@ -69,4 +79,16 @@ public void setTypeUsingReflection(String className, String propertyName) public Object accept(ValueVisitor visitor) { return visitor.accept(this); } + + @Override + public boolean isSame(SimpleValue other) { + return other instanceof Any && isSame( (Any) other ); + } + + public boolean isSame(Any other) { + return super.isSame( other ) + && Objects.equals( identifierTypeName, other.identifierTypeName ) + && Objects.equals( metaTypeName, other.metaTypeName ) + && Objects.equals( metaValues, other.metaValues ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Array.java b/hibernate-core/src/main/java/org/hibernate/mapping/Array.java index 2521f7b5dfc7..766dad92e699 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Array.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Array.java @@ -9,6 +9,7 @@ import org.hibernate.MappingException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.CollectionType; import org.hibernate.type.PrimitiveType; @@ -21,10 +22,18 @@ public class Array extends List { private String elementClassName; + /** + * @deprecated Use {@link Array#Array(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public Array(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public Array(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public Class getElementClass() throws MappingException { if ( elementClassName == null ) { org.hibernate.type.Type elementType = getElement().getType(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java b/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java index 6e817d3809fd..17b89a432054 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java @@ -6,6 +6,7 @@ */ package org.hibernate.mapping; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.CollectionType; @@ -15,10 +16,19 @@ * @author Gavin King */ public class Bag extends Collection { + + /** + * @deprecated Use {@link Bag#Bag(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public Bag(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public Bag(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public CollectionType getDefaultCollectionType() { return getMetadata().getTypeResolver() .getTypeFactory() diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index 69d5ce3b7aec..eec8dfd22100 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -12,14 +12,17 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Properties; +import java.util.Objects; import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.FilterConfiguration; +import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.CollectionType; @@ -36,6 +39,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { public static final String DEFAULT_KEY_COLUMN_NAME = "id"; private final MetadataImplementor metadata; + private MetadataBuildingContext buildingContext; private PersistentClass owner; private KeyValue key; @@ -85,6 +89,15 @@ public abstract class Collection implements Fetchable, Value, Filterable { private String loaderName; + protected Collection(MetadataBuildingContext buildingContext, PersistentClass owner) { + this(buildingContext.getMetadataCollector(), owner); + this.buildingContext = buildingContext; + } + + /** + * @deprecated Use {@link Collection#Collection(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated protected Collection(MetadataImplementor metadata, PersistentClass owner) { this.metadata = metadata; this.owner = owner; @@ -293,12 +306,6 @@ public void validate(Mapping mapping) throws MappingException { assert getKey() != null : "Collection key not bound : " + getRole(); assert getElement() != null : "Collection element not bound : " + getRole(); - if ( getKey().isCascadeDeleteEnabled() && ( !isInverse() || !isOneToMany() ) ) { - throw new MappingException( - "only inverse one-to-many associations may use on-delete=\"cascade\": " - + getRole() - ); - } if ( !getKey().isValid( mapping ) ) { throw new MappingException( "collection foreign key mapping has wrong number of columns: " @@ -381,7 +388,7 @@ public CollectionType getCollectionType() { return getDefaultCollectionType(); } else { - return metadata.getTypeResolver() + return getMetadata().getTypeConfiguration().getTypeResolver() .getTypeFactory() .customCollection( typeName, typeParameters, role, referencedPropertyName ); } @@ -410,6 +417,27 @@ public boolean isValid(Mapping mapping) throws MappingException { return true; } + @Override + public boolean isSame(Value other) { + return this == other || other instanceof Collection && isSame( (Collection) other ); + } + + protected static boolean isSame(Value v1, Value v2) { + return v1 == v2 || v1 != null && v2 != null && v1.isSame( v2 ); + } + + public boolean isSame(Collection other) { + return this == other || isSame( key, other.key ) + && isSame( element, other.element ) + && Objects.equals( collectionTable, other.collectionTable ) + && Objects.equals( where, other.where ) + && Objects.equals( manyToManyWhere, other.manyToManyWhere ) + && Objects.equals( referencedPropertyName, other.referencedPropertyName ) + && Objects.equals( mappedByProperty, other.mappedByProperty ) + && Objects.equals( typeName, other.typeName ) + && Objects.equals( typeParameters, other.typeParameters ); + } + private void createForeignKeys() throws MappingException { // if ( !isInverse() ) { // for inverse collections, let the "other end" handle it if ( referencedPropertyName == null ) { @@ -444,7 +472,7 @@ public String getCacheRegionName() { } public void setCacheRegionName(String cacheRegionName) { - this.cacheRegionName = cacheRegionName; + this.cacheRegionName = StringHelper.nullIfEmpty( cacheRegionName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index 9faeba5dae16..d2dcc5aab60a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -15,8 +15,11 @@ import org.hibernate.dialect.function.SQLFunctionRegistry; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.util.StringHelper; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * A column of a relational database table * @@ -90,21 +93,25 @@ public void setName(String name) { * returns quoted name as it would be in the mapping file. */ public String getQuotedName() { - return quoted ? + return safeInterning( + quoted ? "`" + name + "`" : - name; + name + ); } public String getQuotedName(Dialect d) { - return quoted ? + return safeInterning( + quoted ? d.openQuote() + name + d.closeQuote() : - name; + name + ); } @Override public String getAlias(Dialect dialect) { final int lastLetter = StringHelper.lastIndexOfLetter( name ); - final String suffix = Integer.toString( uniqueInteger ) + '_'; + final String suffix = AliasConstantsHelper.get( uniqueInteger ); String alias = name; if ( lastLetter == -1 ) { @@ -137,7 +144,7 @@ else if ( name.length() > lastLetter + 1 ) { */ @Override public String getAlias(Dialect dialect, Table table) { - return getAlias( dialect ) + table.getUniqueInteger() + '_'; + return safeInterning( getAlias( dialect ) + AliasConstantsHelper.get( table.getUniqueInteger() ) ); } public boolean isNullable() { @@ -268,13 +275,16 @@ public boolean hasCheckConstraint() { @Override public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) { - return hasCustomRead() - ? Template.renderWhereStringTemplate( customRead, dialect, functionRegistry ) - : Template.TEMPLATE + '.' + getQuotedName( dialect ); + return safeInterning( + hasCustomRead() + // see note in renderTransformerReadFragment wrt access to SessionFactory + ? Template.renderTransformerReadFragment( customRead, getQuotedName( dialect ) ) + : Template.TEMPLATE + '.' + getQuotedName( dialect ) + ); } public boolean hasCustomRead() { - return ( customRead != null && customRead.length() > 0 ); + return customRead != null; } public String getReadExpr(Dialect dialect) { @@ -337,7 +347,7 @@ public String getCustomWrite() { } public void setCustomWrite(String customWrite) { - this.customWrite = customWrite; + this.customWrite = safeInterning( customWrite ); } public String getCustomRead() { @@ -345,7 +355,7 @@ public String getCustomRead() { } public void setCustomRead(String customRead) { - this.customRead = customRead; + this.customRead = safeInterning( StringHelper.nullIfEmpty( customRead ) ); } public String getCanonicalName() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index 53ed74ff6c95..f988a8ba4e7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.Objects; import java.util.Map; import org.hibernate.EntityMode; @@ -18,6 +19,7 @@ import org.hibernate.boot.model.relational.ExportableProducer; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -38,7 +40,7 @@ * @author Steve Ebersole */ public class Component extends SimpleValue implements MetaAttributable { - private ArrayList properties = new ArrayList(); + private ArrayList properties = new ArrayList<>(); private String componentClassName; private boolean embedded; private String parentProperty; @@ -50,27 +52,71 @@ public class Component extends SimpleValue implements MetaAttributable { private java.util.Map tuplizerImpls; + // cache the status of the type + private volatile Type type; + + /** + * @deprecated User {@link Component#Component(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public Component(MetadataImplementor metadata, PersistentClass owner) throws MappingException { this( metadata, owner.getTable(), owner ); } + /** + * @deprecated User {@link Component#Component(MetadataBuildingContext, Component)} instead. + */ + @Deprecated public Component(MetadataImplementor metadata, Component component) throws MappingException { this( metadata, component.getTable(), component.getOwner() ); } + /** + * @deprecated User {@link Component#Component(MetadataBuildingContext, Join)} instead. + */ + @Deprecated public Component(MetadataImplementor metadata, Join join) throws MappingException { this( metadata, join.getTable(), join.getPersistentClass() ); } + /** + * @deprecated User {@link Component#Component(MetadataBuildingContext, Collection)} instead. + */ + @Deprecated public Component(MetadataImplementor metadata, Collection collection) throws MappingException { this( metadata, collection.getCollectionTable(), collection.getOwner() ); } + /** + * @deprecated User {@link Component#Component(MetadataBuildingContext, Table, PersistentClass)} instead. + */ + @Deprecated public Component(MetadataImplementor metadata, Table table, PersistentClass owner) throws MappingException { super( metadata, table ); this.owner = owner; } + public Component(MetadataBuildingContext metadata, PersistentClass owner) throws MappingException { + this( metadata, owner.getTable(), owner ); + } + + public Component(MetadataBuildingContext metadata, Component component) throws MappingException { + this( metadata, component.getTable(), component.getOwner() ); + } + + public Component(MetadataBuildingContext metadata, Join join) throws MappingException { + this( metadata, join.getTable(), join.getPersistentClass() ); + } + + public Component(MetadataBuildingContext metadata, Collection collection) throws MappingException { + this( metadata, collection.getCollectionTable(), collection.getOwner() ); + } + + public Component(MetadataBuildingContext metadata, Table table, PersistentClass owner) throws MappingException { + super( metadata, table ); + this.owner = owner; + } + public int getPropertySpan() { return properties.size(); } @@ -120,7 +166,8 @@ public String getComponentClassName() { } public Class getComponentClass() throws MappingException { - final ClassLoaderService classLoaderService = getMetadata().getMetadataBuildingOptions() + final ClassLoaderService classLoaderService = getMetadata() + .getMetadataBuildingOptions() .getServiceRegistry() .getService( ClassLoaderService.class ); try { @@ -165,10 +212,28 @@ public void setDynamic(boolean dynamic) { @Override public Type getType() throws MappingException { - // TODO : temporary initial step towards HHH-1907 - final ComponentMetamodel metamodel = new ComponentMetamodel( this, getMetadata().getMetadataBuildingOptions() ); - final TypeFactory factory = getMetadata().getTypeResolver().getTypeFactory(); - return isEmbedded() ? factory.embeddedComponent( metamodel ) : factory.component( metamodel ); + // Resolve the type of the value once and for all as this operation generates a proxy class + // for each invocation. + // Unfortunately, there's no better way of doing that as none of the classes are immutable and + // we can't know for sure the current state of the property or the value. + Type localType = type; + + if ( localType == null ) { + synchronized ( this ) { + if ( type == null ) { + // TODO : temporary initial step towards HHH-1907 + final ComponentMetamodel metamodel = new ComponentMetamodel( + this, + getMetadata().getMetadataBuildingOptions() + ); + final TypeFactory factory = getMetadata().getTypeConfiguration().getTypeResolver().getTypeFactory(); + localType = isEmbedded() ? factory.embeddedComponent( metamodel ) : factory.component( metamodel ); + type = localType; + } + } + } + + return localType; } @Override @@ -196,6 +261,20 @@ public Object accept(ValueVisitor visitor) { return visitor.accept(this); } + @Override + public boolean isSame(SimpleValue other) { + return other instanceof Component && isSame( (Component) other ); + } + + public boolean isSame(Component other) { + return super.isSame( other ) + && Objects.equals( properties, other.properties ) + && Objects.equals( componentClassName, other.componentClassName ) + && embedded == other.embedded + && Objects.equals( parentProperty, other.parentProperty ) + && Objects.equals( metaAttributes, other.metaAttributes ); + } + @Override public boolean[] getColumnInsertability() { boolean[] result = new boolean[ getColumnSpan() ]; @@ -227,22 +306,22 @@ public boolean[] getColumnUpdateability() { } return result; } - + public boolean isKey() { return isKey; } - + public void setKey(boolean isKey) { this.isKey = isKey; } - + public boolean hasPojoRepresentation() { return componentClassName!=null; } public void addTuplizer(EntityMode entityMode, String implClassName) { if ( tuplizerImpls == null ) { - tuplizerImpls = new HashMap(); + tuplizerImpls = new HashMap<>(); } tuplizerImpls.put( entityMode, implClassName ); } 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 33c0a46f364f..cbaefbfb260e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -165,10 +165,11 @@ public boolean isGenerated(Dialect dialect) { public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { if ( isGenerated( dialect ) ) { + final String tableName = getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ); return String.format( Locale.ROOT, - "alter table %s drop constraint %s", - getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ), + "%s evictData constraint %s", + dialect.getAlterTableString( tableName ), dialect.quote( getName() ) ); } @@ -184,8 +185,8 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, // empty string. Prevent blank "alter table" statements. String constraintString = sqlConstraintString( dialect, getName(), defaultCatalog, defaultSchema ); if ( !StringHelper.isEmpty( constraintString ) ) { - return "alter table " + getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ) - + constraintString; + final String tableName = getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ); + return dialect.getAlterTableString( tableName ) + " " + constraintString; } } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java index 59c23a50076b..56e9c39712de 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java @@ -7,6 +7,7 @@ package org.hibernate.mapping; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.Type; @@ -22,11 +23,20 @@ public class DependantValue extends SimpleValue { private boolean nullable; private boolean updateable; + /** + * @deprecated Use {@link DependantValue#DependantValue(MetadataBuildingContext, Table, KeyValue)} instead. + */ + @Deprecated public DependantValue(MetadataImplementor metadata, Table table, KeyValue prototype) { super( metadata, table ); this.wrappedValue = prototype; } + public DependantValue(MetadataBuildingContext buildingContext, Table table, KeyValue prototype) { + super( buildingContext, table ); + this.wrappedValue = prototype; + } + public Type getType() throws MappingException { return wrappedValue.getType(); } @@ -53,4 +63,15 @@ public boolean isUpdateable() { public void setUpdateable(boolean updateable) { this.updateable = updateable; } + + @Override + public boolean isSame(SimpleValue other) { + return other instanceof DependantValue && isSame( (DependantValue) other ); + } + + public boolean isSame(DependantValue other) { + return super.isSame( other ) + && isSame( wrappedValue, other.wrappedValue ); + } + } 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 f0da8b0fe1e3..44c5db907b32 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java @@ -33,7 +33,7 @@ public ForeignKey() { @Override public String getExportIdentifier() { // NOt sure name is always set. Might need some implicit naming - return StringHelper.qualify( getTable().getName(), "FK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "FK-" + getName() ); } public void disableCreation() { @@ -173,8 +173,8 @@ public void setKeyDefinition(String keyDefinition) { } public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { - final StringBuilder buf = new StringBuilder( "alter table " ); - buf.append( getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ) ); + String tableName = getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ); + final StringBuilder buf = new StringBuilder( dialect.getAlterTableString( tableName ) ); buf.append( dialect.getDropForeignKeyString() ); if ( dialect.supportsIfExistsBeforeConstraintName() ) { buf.append( "if exists " ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java index d8ecc0c93456..b32eae836425 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java @@ -10,8 +10,12 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.SQLFunctionRegistry; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * A formula is a derived column value * @author Gavin King @@ -33,7 +37,8 @@ public Formula(String formula) { @Override public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) { - return Template.renderWhereStringTemplate(formula, dialect, functionRegistry); + String template = Template.renderWhereStringTemplate(formula, dialect, functionRegistry); + return safeInterning( StringHelper.replace( template, "{alias}", Template.TEMPLATE ) ); } @Override @@ -48,7 +53,7 @@ public String getText() { @Override public String getAlias(Dialect dialect) { - return "formula" + Integer.toString(uniqueInteger) + '_'; + return "formula" + AliasConstantsHelper.get( uniqueInteger ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java index 6172f39f0aed..6ead3e03ec1a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java @@ -6,6 +6,7 @@ */ package org.hibernate.mapping; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.CollectionType; @@ -14,10 +15,18 @@ * just the identifier column */ public class IdentifierBag extends IdentifierCollection { + /** + * @deprecated User {@link IdentifierBag#IdentifierBag(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public IdentifierBag(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public IdentifierBag(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public CollectionType getDefaultCollectionType() { return getMetadata().getTypeResolver() .getTypeFactory() diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java index 064c4f6cda7d..53ef8b07ba7c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java @@ -7,6 +7,7 @@ package org.hibernate.mapping; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; @@ -19,10 +20,18 @@ public abstract class IdentifierCollection extends Collection { private KeyValue identifier; + /** + * @deprecated Use {@link IdentifierCollection#IdentifierCollection(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public IdentifierCollection(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public IdentifierCollection(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public KeyValue getIdentifier() { return identifier; } @@ -33,18 +42,23 @@ public final boolean isIdentified() { return true; } + @Override + public boolean isSame(Collection other) { + return other instanceof IdentifierCollection + && isSame( (IdentifierCollection) other ); + } + + public boolean isSame(IdentifierCollection other) { + return super.isSame( other ) + && isSame( identifier, other.identifier ); + } + void createPrimaryKey() { if ( !isOneToMany() ) { PrimaryKey pk = new PrimaryKey( getCollectionTable() ); pk.addColumns( getIdentifier().getColumnIterator() ); getCollectionTable().setPrimaryKey(pk); } - else { - // don't create a unique key, 'cos some - // databases don't like a UK on nullable - // columns - //getCollectionTable().createUniqueKey( getIdentifier().getConstraintColumns() ); - } // create an index on the key columns?? } 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 7781ccceb317..4d7da83ec7cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java @@ -238,6 +238,6 @@ public String toString() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "IDX-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "IDX-" + getName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java b/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java index bb0c3251f7b6..b24875492c35 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java @@ -9,6 +9,7 @@ import java.util.Iterator; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; @@ -23,10 +24,18 @@ public abstract class IndexedCollection extends Collection { private Value index; + /** + * @deprecated Use {@link IndexedCollection#IndexedCollection(MetadataBuildingContext, PersistentClass)} insetad. + */ + @Deprecated public IndexedCollection(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public IndexedCollection(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public Value getIndex() { return index; } @@ -37,11 +46,22 @@ public final boolean isIndexed() { return true; } + @Override + public boolean isSame(Collection other) { + return other instanceof IndexedCollection + && isSame( (IndexedCollection) other ); + } + + public boolean isSame(IndexedCollection other) { + return super.isSame( other ) + && isSame( index, other.index ); + } + void createPrimaryKey() { if ( !isOneToMany() ) { PrimaryKey pk = new PrimaryKey( getCollectionTable() ); pk.addColumns( getKey().getColumnIterator() ); - + // index should be last column listed boolean isFormula = false; Iterator iter = getIndex().getColumnIterator(); @@ -55,7 +75,7 @@ void createPrimaryKey() { pk.addColumns( getElement().getColumnIterator() ); } else { - pk.addColumns( getIndex().getColumnIterator() ); + pk.addColumns( getIndex().getColumnIterator() ); } getCollectionTable().setPrimaryKey(pk); } @@ -84,7 +104,7 @@ public void validate(Mapping mapping) throws MappingException { ); } } - + public boolean isList() { return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/List.java b/hibernate-core/src/main/java/org/hibernate/mapping/List.java index fb170d8235ea..d2315d14429b 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/List.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/List.java @@ -7,6 +7,7 @@ package org.hibernate.mapping; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.CollectionType; @@ -23,10 +24,18 @@ public boolean isList() { return true; } + /** + * @deprecated Use {@link List#List(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public List(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public List(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public CollectionType getDefaultCollectionType() throws MappingException { return getMetadata().getTypeResolver() .getTypeFactory() 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 8c41ae4a01e8..22ebebf3e21b 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.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -22,16 +23,25 @@ public class ManyToOne extends ToOne { private boolean ignoreNotFound; private boolean isLogicalOneToOne; - + + /** + * @deprecated Use {@link ManyToOne#ManyToOne(MetadataBuildingContext, Table)} instead. + */ + @Deprecated public ManyToOne(MetadataImplementor metadata, Table table) { super( metadata, table ); } + public ManyToOne(MetadataBuildingContext buildingContext, Table table) { + super( buildingContext, table ); + } + public Type getType() throws MappingException { return getMetadata().getTypeResolver().getTypeFactory().manyToOne( getReferencedEntityName(), referenceToPrimaryKey, getReferencedPropertyName(), + getPropertyName(), isLazy(), isUnwrapProxy(), isIgnoreNotFound(), @@ -67,7 +77,7 @@ public void createPropertyRefConstraints(Map persistentClasses) { Iterator iter = property.getColumnIterator(); while ( iter.hasNext() ) { Column col = (Column) iter.next(); - refColumns.add( col ); + refColumns.add( col ); } ForeignKey fk = getTable().createForeignKey( diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Map.java b/hibernate-core/src/main/java/org/hibernate/mapping/Map.java index c496f1c27f2f..80f40a926afb 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Map.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Map.java @@ -7,6 +7,7 @@ package org.hibernate.mapping; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.CollectionType; @@ -16,9 +17,17 @@ */ public class Map extends IndexedCollection { + /** + * @deprecated Use {@link Map#Map(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public Map(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + + public Map(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } public boolean isMap() { return true; 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 a462df9795f1..a4497a9b19e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java @@ -7,9 +7,11 @@ package org.hibernate.mapping; import java.util.Iterator; +import java.util.Objects; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; import org.hibernate.service.ServiceRegistry; @@ -27,14 +29,22 @@ public class OneToMany implements Value { private String referencedEntityName; private PersistentClass associatedClass; - private boolean embedded; private boolean ignoreNotFound; + /** + * @deprecated Use {@link OneToMany#OneToMany(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public OneToMany(MetadataImplementor metadata, PersistentClass owner) throws MappingException { this.metadata = metadata; this.referencingTable = ( owner == null ) ? null : owner.getTable(); } + public OneToMany(MetadataBuildingContext buildingContext, PersistentClass owner) throws MappingException { + this.metadata = buildingContext.getMetadataCollector(); + this.referencingTable = ( owner == null ) ? null : owner.getTable(); + } + @Override public ServiceRegistry getServiceRegistry() { return metadata.getMetadataBuildingOptions().getServiceRegistry(); @@ -131,6 +141,16 @@ public Object accept(ValueVisitor visitor) { return visitor.accept( this ); } + @Override + public boolean isSame(Value other) { + return this == other || other instanceof OneToMany && isSame( (OneToMany) other ); + } + + public boolean isSame(OneToMany other) { + return Objects.equals( referencingTable, other.referencingTable ) + && Objects.equals( referencedEntityName, other.referencedEntityName ) + && Objects.equals( associatedClass, other.associatedClass ); + } public boolean[] getColumnInsertability() { //TODO: we could just return all false... diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java index 429e1cacf21f..51a37b4016fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java @@ -8,8 +8,10 @@ import java.util.ArrayList; import java.util.Iterator; +import java.util.Objects; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.EntityType; import org.hibernate.type.ForeignKeyDirection; @@ -27,12 +29,22 @@ public class OneToOne extends ToOne { private String propertyName; private String entityName; + /** + * @deprecated Use {@link OneToOne#OneToOne(MetadataBuildingContext, Table, PersistentClass)} instead. + */ + @Deprecated public OneToOne(MetadataImplementor metadata, Table table, PersistentClass owner) throws MappingException { super( metadata, table ); this.identifier = owner.getKey(); this.entityName = owner.getEntityName(); } + public OneToOne(MetadataBuildingContext buildingContext, Table table, PersistentClass owner) throws MappingException { + super( buildingContext, table ); + this.identifier = owner.getKey(); + this.entityName = owner.getEntityName(); + } + public String getPropertyName() { return propertyName; } @@ -146,5 +158,19 @@ public boolean isNullable() { public Object accept(ValueVisitor visitor) { return visitor.accept(this); } + + @Override + public boolean isSame(ToOne other) { + return other instanceof OneToOne && isSame( (OneToOne) other ); + } + + public boolean isSame(OneToOne other) { + return super.isSame( other ) + && Objects.equals( foreignKeyType, other.foreignKeyType ) + && isSame( identifier, other.identifier ) + && Objects.equals( propertyName, other.propertyName ) + && Objects.equals( entityName, other.entityName ) + && constrained == other.constrained; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index 38dc218108a3..e39f074652a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -23,7 +24,6 @@ import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.FilterConfiguration; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.EmptyIterator; import org.hibernate.internal.util.collections.JoinedIterator; import org.hibernate.internal.util.collections.SingletonIterator; import org.hibernate.service.ServiceRegistry; @@ -54,20 +54,20 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl private String discriminatorValue; private boolean lazy; - private ArrayList properties = new ArrayList(); - private ArrayList declaredProperties = new ArrayList(); - private final ArrayList subclasses = new ArrayList(); - private final ArrayList subclassProperties = new ArrayList(); - private final ArrayList subclassTables = new ArrayList(); + private java.util.List properties = new ArrayList<>(); + private java.util.List declaredProperties = new ArrayList<>(); + private final java.util.List subclasses = new ArrayList<>(); + private final java.util.List subclassProperties = new ArrayList<>(); + private final java.util.List

    subclassTables = new ArrayList<>(); private boolean dynamicInsert; private boolean dynamicUpdate; private int batchSize = -1; private boolean selectBeforeUpdate; private java.util.Map metaAttributes; - private ArrayList joins = new ArrayList(); - private final ArrayList subclassJoins = new ArrayList(); - private final java.util.List filters = new ArrayList(); - protected final java.util.Set synchronizedTables = new HashSet(); + private java.util.List joins = new ArrayList<>(); + private final java.util.List subclassJoins = new ArrayList<>(); + private final java.util.List filters = new ArrayList<>(); + protected final java.util.Set synchronizedTables = new HashSet<>(); private String loaderName; private Boolean isAbstract; private boolean hasSubselectLoadableCollections; @@ -90,6 +90,8 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl private Component declaredIdentifierMapper; private OptimisticLockStyle optimisticLockStyle; + private boolean isCached; + public PersistentClass(MetadataBuildingContext metadataBuildingContext) { this.metadataBuildingContext = metadataBuildingContext; } @@ -123,7 +125,7 @@ public Class getMappedClass() throws MappingException { try { if ( mappedClass == null ) { - mappedClass = metadataBuildingContext.getClassLoaderAccess().classForName( className ); + mappedClass = metadataBuildingContext.getBootstrapContext().getClassLoaderAccess().classForName( className ); } return mappedClass; } @@ -138,7 +140,7 @@ public Class getProxyInterface() { } try { if ( proxyInterface == null ) { - proxyInterface = metadataBuildingContext.getClassLoaderAccess().classForName( proxyInterfaceName ); + proxyInterface = metadataBuildingContext.getBootstrapContext().getClassLoaderAccess().classForName( proxyInterfaceName ); } return proxyInterface; } @@ -270,10 +272,35 @@ public String getEntityName() { public abstract boolean isVersioned(); - public abstract String getNaturalIdCacheRegionName(); + + public boolean isCached() { + return isCached; + } + + public void setCached(boolean cached) { + isCached = cached; + } + + /** + * @deprecated Use {@link #isCached} instead + */ + @Deprecated + public boolean isCachingExplicitlyRequested() { + return isCached(); + } + + /** + * @deprecated Use {@link #setCached} instead + */ + @Deprecated + public void setCachingExplicitlyRequested(boolean cached) { + setCached( cached ); + } public abstract String getCacheConcurrencyStrategy(); + public abstract String getNaturalIdCacheRegionName(); + public abstract PersistentClass getSuperclass(); public abstract boolean isExplicitPolymorphism(); @@ -303,7 +330,7 @@ public Iterator getSubclassPropertyClosureIterator() { iters.add( getPropertyClosureIterator() ); iters.add( subclassProperties.iterator() ); for ( int i = 0; i < subclassJoins.size(); i++ ) { - Join join = (Join) subclassJoins.get( i ); + Join join = subclassJoins.get( i ); iters.add( join.getPropertyIterator() ); } return new JoinedIterator( iters ); @@ -419,7 +446,7 @@ public Property getReferencedProperty(String propertyPath) throws MappingExcepti public Property getRecursiveProperty(String propertyPath) throws MappingException { try { - return getRecursiveProperty( propertyPath, getPropertyIterator() ); + return getRecursiveProperty( propertyPath, getPropertyClosureIterator() ); } catch (MappingException e) { throw new MappingException( @@ -608,7 +635,7 @@ public void validate(Mapping mapping) throws MappingException { } private void checkPropertyDuplication() throws MappingException { - HashSet names = new HashSet(); + HashSet names = new HashSet<>(); Iterator iter = getPropertyIterator(); while ( iter.hasNext() ) { Property prop = (Property) iter.next(); @@ -700,7 +727,7 @@ public Iterator getPropertyIterator() { ArrayList iterators = new ArrayList(); iterators.add( properties.iterator() ); for ( int i = 0; i < joins.size(); i++ ) { - Join join = (Join) joins.get( i ); + Join join = joins.get( i ); iterators.add( join.getPropertyIterator() ); } return new JoinedIterator( iterators ); @@ -861,7 +888,7 @@ protected Iterator getNonDuplicatedPropertyIterator() { } protected Iterator getDiscriminatorColumnIterator() { - return EmptyIterator.INSTANCE; + return Collections.emptyIterator(); } protected void checkColumnDuplication() { @@ -955,14 +982,12 @@ public boolean hasNaturalId() { return false; } - public abstract boolean isLazyPropertiesCacheable(); - // The following methods are added to support @MappedSuperclass in the metamodel public Iterator getDeclaredPropertyIterator() { ArrayList iterators = new ArrayList(); iterators.add( declaredProperties.iterator() ); for ( int i = 0; i < joins.size(); i++ ) { - Join join = (Join) joins.get( i ); + Join join = joins.get( i ); iterators.add( join.getDeclaredPropertyIterator() ); } return new JoinedIterator( iterators ); 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 bcc9cd24486b..3c5e9a03b127 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java @@ -89,6 +89,6 @@ public String generatedConstraintNamePrefix() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "PK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "PK-" + getName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java index 4d45c9dd1c32..2ec526bbf342 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java @@ -6,16 +6,25 @@ */ package org.hibernate.mapping; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; /** * A primitive array has a primary key consisting of the key columns + index column. */ public class PrimitiveArray extends Array { + /** + * @deprecated Use {@link PrimitiveArray#PrimitiveArray(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public PrimitiveArray(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public PrimitiveArray(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public boolean isPrimitiveArray() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index ebf86b44e977..f18d27046b63 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -14,6 +14,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.PropertyNotFoundException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.Mapping; @@ -233,29 +234,31 @@ public String toString() { public void setLazy(boolean lazy) { this.lazy=lazy; } - + + /** + * Is this property lazy in the "bytecode" sense? + * + * Lazy here means whether we should push *something* to the entity + * instance for this field in its "base fetch group". Mainly it affects + * whether we should list this property's columns in the SQL select + * for the owning entity when we load its "base fetch group". + * + * The "something" we push varies based on the nature (basic, etc) of + * the property. + * + * @apiNote This form reports whether the property is considered part of the + * base fetch group based solely on the mapping information. However, + * {@link EnhancementHelper#includeInBaseFetchGroup} is used internally to make that + * decision to account for {@link org.hibernate.cfg.AvailableSettings#ALLOW_ENHANCEMENT_AS_PROXY} + */ public boolean isLazy() { if ( value instanceof ToOne ) { - // both many-to-one and one-to-one are represented as a - // Property. EntityPersister is relying on this value to - // determine "lazy fetch groups" in terms of field-level - // interception. So we need to make sure that we return - // true here for the case of many-to-one and one-to-one - // with lazy="no-proxy" - // - // * impl note - lazy="no-proxy" currently forces both - // lazy and unwrap to be set to true. The other case we - // are extremely interested in here is that of lazy="proxy" - // where lazy is set to true, but unwrap is set to false. - // thus we use both here under the assumption that this - // return is really only ever used during persister - // construction to determine the lazy property/field fetch - // groupings. If that assertion changes then this check - // needs to change as well. Partially, this is an issue with - // the overloading of the term "lazy" here... - ToOne toOneValue = ( ToOne ) value; - return toOneValue.isLazy() && toOneValue.isUnwrapProxy(); + // For a many-to-one, this is always false. Whether the + // association is EAGER, PROXY or NO-PROXY we want the fk + // selected + return false; } + return lazy; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java b/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java index eeeceec7dc29..167a28bc1a72 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java @@ -12,8 +12,11 @@ /** * A relational object which may be created using DDL * @author Gavin King + * + * @deprecated (since 5.2) not needed anymore. */ +@Deprecated public interface RelationalModel { - public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) throws HibernateException; - public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema); + String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) throws HibernateException; + String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java index 8f21e41f9ffa..63b019a866f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java @@ -17,6 +17,7 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.SingletonIterator; /** @@ -34,10 +35,12 @@ public class RootClass extends PersistentClass implements TableOwner { private KeyValue identifier; private Property version; private boolean polymorphic; + private String cacheConcurrencyStrategy; private String cacheRegionName; - private String naturalIdCacheRegionName; private boolean lazyPropertiesCacheable = true; + private String naturalIdCacheRegionName; + private Value discriminator; private boolean mutable = true; private boolean embeddedIdentifier; @@ -50,7 +53,6 @@ public class RootClass extends PersistentClass implements TableOwner { private int nextSubclassId; private Property declaredIdentifierProperty; private Property declaredVersion; - private boolean cachingExplicitlyRequested; public RootClass(MetadataBuildingContext metadataBuildingContext) { super( metadataBuildingContext ); @@ -311,25 +313,24 @@ public String getCacheRegionName() { } public void setCacheRegionName(String cacheRegionName) { - this.cacheRegionName = cacheRegionName; + this.cacheRegionName = StringHelper.nullIfEmpty( cacheRegionName ); } - @Override - public String getNaturalIdCacheRegionName() { - return naturalIdCacheRegionName; + public boolean isLazyPropertiesCacheable() { + return lazyPropertiesCacheable; } - public void setNaturalIdCacheRegionName(String naturalIdCacheRegionName) { - this.naturalIdCacheRegionName = naturalIdCacheRegionName; + public void setLazyPropertiesCacheable(boolean lazyPropertiesCacheable) { + this.lazyPropertiesCacheable = lazyPropertiesCacheable; } @Override - public boolean isLazyPropertiesCacheable() { - return lazyPropertiesCacheable; + public String getNaturalIdCacheRegionName() { + return naturalIdCacheRegionName; } - public void setLazyPropertiesCacheable(boolean lazyPropertiesCacheable) { - this.lazyPropertiesCacheable = lazyPropertiesCacheable; + public void setNaturalIdCacheRegionName(String naturalIdCacheRegionName) { + this.naturalIdCacheRegionName = naturalIdCacheRegionName; } @Override @@ -360,11 +361,4 @@ public Object accept(PersistentClassVisitor mv) { return mv.accept( this ); } - public void setCachingExplicitlyRequested(boolean explicitlyRequested) { - this.cachingExplicitlyRequested = explicitlyRequested; - } - - public boolean isCachingExplicitlyRequested() { - return cachingExplicitlyRequested; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Set.java b/hibernate-core/src/main/java/org/hibernate/mapping/Set.java index 3852d269b9df..1b891a502dd6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Set.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Set.java @@ -9,6 +9,7 @@ import java.util.Iterator; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; import org.hibernate.type.CollectionType; @@ -19,10 +20,18 @@ * @author Gavin King */ public class Set extends Collection { + /** + * @deprecated Use {@link Set#Set(MetadataBuildingContext, PersistentClass)} instead. + */ + @Deprecated public Set(MetadataImplementor metadata, PersistentClass owner) { super( metadata, owner ); } + public Set(MetadataBuildingContext buildingContext, PersistentClass owner) { + super( buildingContext, owner ); + } + public void validate(Mapping mapping) throws MappingException { super.validate( mapping ); //for backward compatibility, disable this: @@ -68,17 +77,20 @@ void createPrimaryKey() { if ( selectable instanceof Column ) { Column col = (Column) selectable; if ( !col.isNullable() ) { - pk.addColumn(col); + pk.addColumn( col ); + } + else { + return; } } } - if ( pk.getColumnSpan()==getKey().getColumnSpan() ) { - //for backward compatibility, allow a set with no not-null + if ( pk.getColumnSpan() == getKey().getColumnSpan() ) { + //for backward compatibility, allow a set with no not-null //element columns, using all columns in the row locater SQL //TODO: create an implicit not null constraint on all cols? } else { - getCollectionTable().setPrimaryKey(pk); + getCollectionTable().setPrimaryKey( pk ); } } else { 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 7ebc7ab0cfd3..0e40a4eedd2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -14,15 +14,19 @@ import java.util.List; import java.util.Locale; import java.util.Properties; +import java.util.Objects; import javax.persistence.AttributeConverter; import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.annotations.common.reflection.XProperty; -import org.hibernate.boot.internal.AttributeConverterDescriptorNonAutoApplicableImpl; +import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; -import org.hibernate.boot.spi.AttributeConverterDescriptor; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; @@ -37,6 +41,8 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.BinaryType; import org.hibernate.type.RowVersionType; @@ -44,13 +50,14 @@ import org.hibernate.type.descriptor.JdbcTypeNameMapper; 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.java.JavaTypeDescriptorRegistry; +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.descriptor.sql.SqlTypeDescriptorRegistry; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.DynamicParameterizedType; /** @@ -62,11 +69,12 @@ public class SimpleValue implements KeyValue { public static final String DEFAULT_ID_GEN_STRATEGY = "assigned"; + private MetadataBuildingContext buildingContext; private final MetadataImplementor metadata; - private final List columns = new ArrayList(); - private final List insertability = new ArrayList(); - private final List updatability = new ArrayList(); + private final List columns = new ArrayList<>(); + private final List insertability = new ArrayList<>(); + private final List updatability = new ArrayList<>(); private String typeName; private Properties typeParameters; @@ -83,18 +91,36 @@ public class SimpleValue implements KeyValue { private boolean alternateUniqueKey; private boolean cascadeDeleteEnabled; - private AttributeConverterDescriptor attributeConverterDescriptor; + private ConverterDescriptor attributeConverterDescriptor; private Type type; + /** + * @deprecated Use {@link SimpleValue#SimpleValue(MetadataBuildingContext)} instead. + */ + @Deprecated public SimpleValue(MetadataImplementor metadata) { this.metadata = metadata; } + /** + * @deprecated Use {@link SimpleValue#SimpleValue(MetadataBuildingContext, Table)} instead. + */ + @Deprecated public SimpleValue(MetadataImplementor metadata, Table table) { this( metadata ); this.table = table; } + public SimpleValue(MetadataBuildingContext buildingContext) { + this(buildingContext.getMetadataCollector()); + this.buildingContext = buildingContext; + } + + public SimpleValue(MetadataBuildingContext buildingContext, Table table) { + this( buildingContext ); + this.table = table; + } + public MetadataImplementor getMetadata() { return metadata; } @@ -175,12 +201,17 @@ public String getTypeName() { public void setTypeName(String typeName) { if ( typeName != null && typeName.startsWith( AttributeConverterTypeAdapter.NAME_PREFIX ) ) { final String converterClassName = typeName.substring( AttributeConverterTypeAdapter.NAME_PREFIX.length() ); - final ClassLoaderService cls = getMetadata().getMetadataBuildingOptions() + final ClassLoaderService cls = getMetadata() + .getMetadataBuildingOptions() .getServiceRegistry() .getService( ClassLoaderService.class ); try { - final Class converterClass = cls.classForName( converterClassName ); - attributeConverterDescriptor = new AttributeConverterDescriptorNonAutoApplicableImpl( converterClass.newInstance() ); + final Class converterClass = cls.classForName( converterClassName ); + this.attributeConverterDescriptor = new ClassBasedConverterDescriptor( + converterClass, + false, + ( (InFlightMetadataCollector) getMetadata() ).getClassmateContext() + ); return; } catch (Exception e) { @@ -437,7 +468,7 @@ public Type getType() throws MappingException { createParameterImpl(); } - Type result = metadata.getTypeResolver().heuristicType( typeName, typeParameters ); + Type result = getMetadata().getTypeConfiguration().getTypeResolver().heuristicType( typeName, typeParameters ); // if this is a byte[] version/timestamp, then we need to use RowVersionType // instead of BinaryType (HHH-10413) if ( isVersion && BinaryType.class.isInstance( result ) ) { @@ -458,6 +489,7 @@ public Type getType() throws MappingException { return result; } + @Override public void setTypeUsingReflection(String className, String propertyName) throws MappingException { // NOTE : this is called as the last piece in setting SimpleValue type information, and implementations // rely on that fact, using it as a signal that all information it is going to get is defined at this point... @@ -478,7 +510,14 @@ public void setTypeUsingReflection(String className, String propertyName) throws if ( className == null ) { throw new MappingException( "Attribute types for a dynamic entity must be explicitly specified: " + propertyName ); } - typeName = ReflectHelper.reflectedPropertyClass( className, propertyName, metadata.getMetadataBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class ) ).getName(); + typeName = ReflectHelper.reflectedPropertyClass( + className, + propertyName, + getMetadata() + .getMetadataBuildingOptions() + .getServiceRegistry() + .getService( ClassLoaderService.class ) + ).getName(); // todo : to fully support isNationalized here we need do the process hinted at above // essentially, much of the logic from #buildAttributeConverterTypeAdapter wrt resolving // a (1) SqlTypeDescriptor, a (2) JavaTypeDescriptor and dynamically building a BasicType @@ -526,14 +565,24 @@ public void setTypeUsingReflection(String className, String propertyName) throws private Type buildAttributeConverterTypeAdapter() { // todo : validate the number of columns present here? - final Class entityAttributeJavaType = attributeConverterDescriptor.getDomainType(); - final Class databaseColumnJavaType = attributeConverterDescriptor.getJdbcType(); - + final JpaAttributeConverter jpaAttributeConverter = attributeConverterDescriptor.createJpaAttributeConverter( + new JpaAttributeConverterCreationContext() { + @Override + public ManagedBeanRegistry getManagedBeanRegistry() { + return getMetadata() + .getMetadataBuildingOptions() + .getServiceRegistry() + .getService( ManagedBeanRegistry.class ); + } + + @Override + public org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry getJavaTypeDescriptorRegistry() { + return metadata.getTypeConfiguration().getJavaTypeDescriptorRegistry(); + } + } + ); - // resolve the JavaTypeDescriptor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // For the JavaTypeDescriptor portion we simply resolve the "entity attribute representation" part of - // the AttributeConverter to resolve the corresponding descriptor. - final JavaTypeDescriptor entityAttributeJavaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor( entityAttributeJavaType ); + final BasicJavaDescriptor entityAttributeJavaTypeDescriptor = jpaAttributeConverter.getDomainJavaTypeDescriptor(); // build the SqlTypeDescriptor adapter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -542,13 +591,17 @@ private Type buildAttributeConverterTypeAdapter() { // corresponding to the AttributeConverter's declared "databaseColumnJavaType" (how we read that value out // of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this should return // VARCHAR/CHAR - int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType ); + final SqlTypeDescriptor recommendedSqlType = jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJdbcRecommendedSqlType( + // todo (6.0) : handle the other JdbcRecommendedSqlTypeMappingContext methods + metadata::getTypeConfiguration + ); + int jdbcTypeCode = recommendedSqlType.getSqlType(); if ( isLob() ) { - if ( LobTypeMappings.INSTANCE.hasCorrespondingLobCode( jdbcTypeCode ) ) { - jdbcTypeCode = LobTypeMappings.INSTANCE.getCorrespondingLobCode( jdbcTypeCode ); + if ( LobTypeMappings.isMappedToKnownLobCode( jdbcTypeCode ) ) { + jdbcTypeCode = LobTypeMappings.getLobCodeTypeMapping( jdbcTypeCode ); } else { - if ( Serializable.class.isAssignableFrom( entityAttributeJavaType ) ) { + if ( Serializable.class.isAssignableFrom( entityAttributeJavaTypeDescriptor.getJavaType() ) ) { jdbcTypeCode = Types.BLOB; } else { @@ -564,43 +617,44 @@ private Type buildAttributeConverterTypeAdapter() { } } if ( isNationalized() ) { - jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode ); + jdbcTypeCode = NationalizedTypeMappings.toNationalizedTypeCode( jdbcTypeCode ); } // find the standard SqlTypeDescriptor for that JDBC type code (allow itr to be remapped if needed!) - final SqlTypeDescriptor sqlTypeDescriptor = metadata.getMetadataBuildingOptions().getServiceRegistry() + final SqlTypeDescriptor sqlTypeDescriptor = getMetadata() + .getMetadataBuildingOptions() + .getServiceRegistry() .getService( JdbcServices.class ) .getJdbcEnvironment() .getDialect() - .remapSqlTypeDescriptor( SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode ) ); - - // find the JavaTypeDescriptor representing the "intermediate database type representation". Back to the - // illustration, this should be the type descriptor for Strings - final JavaTypeDescriptor intermediateJavaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor( databaseColumnJavaType ); + .remapSqlTypeDescriptor( + metadata.getTypeConfiguration() + .getSqlTypeDescriptorRegistry() + .getDescriptor( jdbcTypeCode ) ); // and finally construct the adapter, which injects the AttributeConverter calls into the binding/extraction // process... final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter( - attributeConverterDescriptor.getAttributeConverter(), + jpaAttributeConverter, sqlTypeDescriptor, - intermediateJavaTypeDescriptor + jpaAttributeConverter.getRelationalJavaTypeDescriptor() ); // todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied multiple times. - final String name = AttributeConverterTypeAdapter.NAME_PREFIX + attributeConverterDescriptor.getAttributeConverter().getClass().getName(); + final String name = AttributeConverterTypeAdapter.NAME_PREFIX + jpaAttributeConverter.getConverterJavaTypeDescriptor().getJavaType().getName(); final String description = String.format( "BasicType adapter for AttributeConverter<%s,%s>", - entityAttributeJavaType.getSimpleName(), - databaseColumnJavaType.getSimpleName() + jpaAttributeConverter.getDomainJavaTypeDescriptor().getJavaType().getSimpleName(), + jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJavaType().getSimpleName() ); return new AttributeConverterTypeAdapter( name, description, - attributeConverterDescriptor.getAttributeConverter(), + jpaAttributeConverter, sqlTypeDescriptorAdapter, - entityAttributeJavaType, - databaseColumnJavaType, + jpaAttributeConverter.getDomainJavaTypeDescriptor().getJavaType(), + jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJavaType(), entityAttributeJavaTypeDescriptor ); } @@ -625,6 +679,24 @@ public void copyTypeFrom( SimpleValue sourceValue ) { attributeConverterDescriptor = sourceValue.attributeConverterDescriptor; } + @Override + public boolean isSame(Value other) { + return this == other || other instanceof SimpleValue && isSame( (SimpleValue) other ); + } + + protected static boolean isSame(Value v1, Value v2) { + return v1 == v2 || v1 != null && v2 != null && v1.isSame( v2 ); + } + + public boolean isSame(SimpleValue other) { + return Objects.equals( columns, other.columns ) + && Objects.equals( typeName, other.typeName ) + && Objects.equals( typeParameters, other.typeParameters ) + && Objects.equals( table, other.table ) + && Objects.equals( foreignKeyName, other.foreignKeyName ) + && Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition ); + } + @Override public String toString() { return getClass().getName() + '(' + columns.toString() + ')'; @@ -651,8 +723,8 @@ private static boolean[] extractBooleansFromList(List list) { return array; } - public void setJpaAttributeConverterDescriptor(AttributeConverterDescriptor attributeConverterDescriptor) { - this.attributeConverterDescriptor = attributeConverterDescriptor; + public void setJpaAttributeConverterDescriptor(ConverterDescriptor descriptor) { + this.attributeConverterDescriptor = descriptor; } private void createParameterImpl() { @@ -671,7 +743,8 @@ private void createParameterImpl() { ? null : xProperty.getAnnotations(); - final ClassLoaderService classLoaderService = getMetadata().getMetadataBuildingOptions() + final ClassLoaderService classLoaderService = getMetadata() + .getMetadataBuildingOptions() .getServiceRegistry() .getService( ClassLoaderService.class ); typeParameters.put( diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java b/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java index 89eb399f812a..4b367f7908a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java @@ -47,7 +47,7 @@ public String getNaturalIdCacheRegionName() { } public String getCacheConcurrencyStrategy() { - return getSuperclass().getCacheConcurrencyStrategy(); + return getRootClass().getCacheConcurrencyStrategy(); } public RootClass getRootClass() { @@ -190,10 +190,6 @@ public void setEntityPersisterClass(Class classPersisterClass) { this.classPersisterClass = classPersisterClass; } - public boolean isLazyPropertiesCacheable() { - return getSuperclass().isLazyPropertiesCacheable(); - } - public int getJoinClosureSpan() { return getSuperclass().getJoinClosureSpan() + super.getJoinClosureSpan(); } 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 d32651725b24..f9a9d868faac 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -27,7 +27,6 @@ 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.internal.util.StringHelper; import org.hibernate.tool.hbm2ddl.ColumnMetadata; import org.hibernate.tool.hbm2ddl.TableMetadata; import org.hibernate.tool.schema.extract.spi.ColumnInformation; @@ -409,7 +408,7 @@ public boolean equals(Table table) { && Identifier.areEqual( schema, table.schema ) && Identifier.areEqual( catalog, table.catalog ); } - + public void validateColumns(Dialect dialect, Mapping mapping, TableMetadata tableInfo) { Iterator iter = getColumnIterator(); while ( iter.hasNext() ) { @@ -442,24 +441,27 @@ public Iterator sqlAlterStrings( Dialect dialect, Metadata metadata, TableInformation tableInfo, - String defaultCatalog, - String defaultSchema) throws HibernateException { - + Identifier defaultCatalog, + Identifier defaultSchema) throws HibernateException { + final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - StringBuilder root = new StringBuilder( "alter table " ) - .append( - jdbcEnvironment.getQualifiedObjectNameFormatter().format( - tableInfo.getName(), - dialect - ) - ) + final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + new QualifiedTableName( + catalog != null ? catalog : defaultCatalog, + schema != null ? schema : defaultSchema, + name + ), + dialect + ); + + StringBuilder root = new StringBuilder( dialect.getAlterTableString( tableName ) ) .append( ' ' ) .append( dialect.getAddColumnString() ); Iterator iter = getColumnIterator(); List results = new ArrayList(); - + while ( iter.hasNext() ) { final Column column = (Column) iter.next(); final ColumnInformation columnInfo = tableInfo.getColumn( Identifier.toIdentifier( column.getName(), column.isQuoted() ) ); @@ -567,7 +569,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, } } - + if ( col.isUnique() ) { String keyName = Constraint.generateName( "UK_", this, col ); UniqueKey uk = getOrCreateUniqueKey( keyName ); @@ -575,7 +577,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, buf.append( dialect.getUniqueDelegate() .getColumnDefinitionUniquenessFragment( col ) ); } - + if ( col.hasCheckConstraint() && dialect.supportsColumnCheck() ) { buf.append( " check (" ) .append( col.getCheckConstraint() ) @@ -714,7 +716,7 @@ public ForeignKey createForeignKey( } // NOTE : if the name is null, we will generate an implicit name during second pass processing - // afterQuery we know the referenced table name (which might not be resolved yet). + // after we know the referenced table name (which might not be resolved yet). fk.setName( keyName ); foreignKeys.put( key, fk ); @@ -886,9 +888,9 @@ public boolean equals(Object other) { @Override public String toString() { return "ForeignKeyKey{" + - "columns=" + StringHelper.join( ",", columns ) + + "columns=" + String.join( ",", columns ) + ", referencedClassName='" + referencedClassName + '\'' + - ", referencedColumns=" + StringHelper.join( ",", referencedColumns ) + + ", referencedColumns=" + String.join( ",", referencedColumns ) + '}'; } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java index 2351c4e15728..3c4f62ee10d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java @@ -9,11 +9,14 @@ import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.type.Type; +import java.util.Objects; + /** * A simple-point association (ie. a reference to another entity). * @author Gavin King @@ -22,15 +25,24 @@ public abstract class ToOne extends SimpleValue implements Fetchable { private FetchMode fetchMode; protected String referencedPropertyName; private String referencedEntityName; + private String propertyName; private boolean embedded; private boolean lazy = true; protected boolean unwrapProxy; protected boolean referenceToPrimaryKey = true; + /** + * @deprecated Use {@link ToOne#ToOne(MetadataBuildingContext, Table)} instead. + */ + @Deprecated protected ToOne(MetadataImplementor metadata, Table table) { super( metadata, table ); } + protected ToOne(MetadataBuildingContext buildingContext, Table table) { + super( buildingContext, table ); + } + public FetchMode getFetchMode() { return fetchMode; } @@ -59,6 +71,16 @@ public void setReferencedEntityName(String referencedEntityName) { null : referencedEntityName.intern(); } + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String propertyName) { + this.propertyName = propertyName==null ? + null : propertyName.intern(); + } + + @Override public void setTypeUsingReflection(String className, String propertyName) throws MappingException { if (referencedEntityName == null) { final ClassLoaderService cls = getMetadata().getMetadataBuildingOptions() @@ -76,6 +98,18 @@ public Object accept(ValueVisitor visitor) { return visitor.accept(this); } + @Override + public boolean isSame(SimpleValue other) { + return other instanceof ToOne && isSame( (ToOne) other ); + } + + public boolean isSame(ToOne other) { + return super.isSame( other ) + && Objects.equals( referencedPropertyName, other.referencedPropertyName ) + && Objects.equals( referencedEntityName, other.referencedEntityName ) + && embedded == other.embedded; + } + public boolean isValid(Mapping mapping) throws MappingException { if (referencedEntityName==null) { throw new MappingException("association must specify the referenced entity"); 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 c85a6847f74d..b3ff9fdf5d0c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java @@ -72,6 +72,6 @@ public String generatedConstraintNamePrefix() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "UK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "UK-" + getName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java index 6a7be09bc67f..e1c095834158 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java @@ -10,6 +10,7 @@ import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.spi.Mapping; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; @@ -39,6 +40,7 @@ public interface Value extends Serializable { public boolean isValid(Mapping mapping) throws MappingException; public void setTypeUsingReflection(String className, String propertyName) throws MappingException; public Object accept(ValueVisitor visitor); + public boolean isSame(Value other); ServiceRegistry getServiceRegistry(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metadata/ClassMetadata.java b/hibernate-core/src/main/java/org/hibernate/metadata/ClassMetadata.java index 1203dd8db0fc..1bee6c91b67e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metadata/ClassMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/metadata/ClassMetadata.java @@ -10,6 +10,7 @@ import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.Type; @@ -116,12 +117,24 @@ public interface ClassMetadata { // stuff that is tuplizer-centric, but is passed a session ~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + /** + * Return the values of the mapped properties of the object + * + * @deprecated (since 5.3) Use the form accepting SharedSessionContractImplementor + * instead + */ + @Deprecated + @SuppressWarnings({"UnusedDeclaration"}) + default Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SessionImplementor session) + throws HibernateException { + return getPropertyValuesToInsert( entity, mergeMap, (SharedSessionContractImplementor) session ); + } + /** * Return the values of the mapped properties of the object */ @SuppressWarnings( {"UnusedDeclaration"}) - Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionContractImplementor session) - throws HibernateException; + Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionContractImplementor session) throws HibernateException; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -133,6 +146,22 @@ Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionCon */ Class getMappedClass(); + /** + * Create a class instance initialized with the given identifier + * + * @param id The identifier value to use (may be null to represent no value) + * @param session The session from which the request originated. + * + * @return The instantiated entity. + * + * @deprecated (since 5.3) Use the form accepting SharedSessionContractImplementor + * instead + */ + @Deprecated + default Object instantiate(Serializable id, SessionImplementor session) { + return instantiate( id, (SharedSessionContractImplementor) session ); + } + /** * Create a class instance initialized with the given identifier * @@ -176,6 +205,21 @@ Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionCon @SuppressWarnings( {"JavaDoc"}) Serializable getIdentifier(Object object) throws HibernateException; + /** + * Get the identifier of an instance (throw an exception if no identifier property) + * + * @param entity The entity for which to get the identifier + * @param session The session from which the request originated + * + * @return The identifier + * + * @deprecated Use {@link #getIdentifier(Object, SharedSessionContractImplementor)} instead + */ + @Deprecated + default Serializable getIdentifier(Object entity, SessionImplementor session) { + return getIdentifier( entity, (SharedSessionContractImplementor) session ); + } + /** * Get the identifier of an instance (throw an exception if no identifier property) * @@ -186,6 +230,20 @@ Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionCon */ Serializable getIdentifier(Object entity, SharedSessionContractImplementor session); + /** + * Inject the identifier value into the given entity. + * + * @param entity The entity to inject with the identifier value. + * @param id The value to be injected as the identifier. + * @param session The session from which is requests originates + * + * @deprecated Use {@link #setIdentifier(Object, Serializable, SharedSessionContractImplementor)} instead + */ + @Deprecated + default void setIdentifier(Object entity, Serializable id, SessionImplementor session) { + setIdentifier( entity, id, (SharedSessionContractImplementor) session ); + } + /** * Inject the identifier value into the given entity. * diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java index 1b8c060a412d..823ddfac2e0f 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java @@ -11,6 +11,7 @@ import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.Iterator; import javax.persistence.ManyToMany; import javax.persistence.OneToOne; @@ -212,8 +213,15 @@ private Type getMetaModelType(ValueContext typeContext) { } case EMBEDDABLE: { final Component component = (Component) typeContext.getValue(); + Class javaType; + if ( component.getComponentClassName() == null ) { + javaType = typeContext.getBindableType(); + } + else { + javaType = component.getComponentClass(); + } final EmbeddableTypeImpl embeddableType = new EmbeddableTypeImpl( - typeContext.getBindableType(), + javaType, typeContext.getAttributeMetadata().getOwnerType(), (ComponentType) typeContext.getValue().getType() ); @@ -871,6 +879,10 @@ else if ( type instanceof ParameterizedType ) { final java.lang.reflect.Type rawType = ( (ParameterizedType) type ).getRawType(); return getClassFromGenericArgument( rawType ); } + else if ( type instanceof WildcardType ) { + final java.lang.reflect.Type upperBound = ( (WildcardType) type ).getUpperBounds()[0]; + return getClassFromGenericArgument( upperBound ); + } else { throw new AssertionFailure( "Fail to process type argument in a generic declaration. Member : " + getMemberDescription() diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index bfaaf5f5110c..e97fdad69112 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -25,6 +25,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.EntityManagerMessageLogger; import org.hibernate.internal.HEMLogging; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.KeyValue; @@ -56,7 +57,7 @@ class MetadataContext { private Map, EntityTypeImpl> entityTypes = new HashMap<>(); private Map> entityTypesByEntityName = new HashMap<>(); private Map> entityTypesByPersistentClass = new HashMap<>(); - private Map, EmbeddableTypeImpl> embeddables = new HashMap<>(); + private Set> embeddables = new HashSet<>(); private Map> mappedSuperclassByMappedSuperclassMapping = new HashMap<>(); //this list contains MappedSuperclass and EntityTypes ordered by superclass first private List orderedMappings = new ArrayList<>(); @@ -92,8 +93,8 @@ public Map, EntityTypeImpl> getEntityTypeMap() { return Collections.unmodifiableMap( entityTypes ); } - public Map, EmbeddableTypeImpl> getEmbeddableTypeMap() { - return Collections.unmodifiableMap( embeddables ); + public Set> getEmbeddableTypeMap() { + return Collections.unmodifiableSet( embeddables ); } public Map, MappedSuperclassType> getMappedSuperclassTypeMap() { @@ -113,16 +114,23 @@ public Map, MappedSuperclassType> getMappedSuperclassTypeMap() { } /*package*/ void registerEntityType(PersistentClass persistentClass, EntityTypeImpl entityType) { + if ( ignoreUnsupported && entityType.getBindableJavaType() == null ) { + return; + } + if ( entityType.getBindableJavaType() != null ) { entityTypes.put( entityType.getBindableJavaType(), entityType ); } + entityTypesByEntityName.put( persistentClass.getEntityName(), entityType ); entityTypesByPersistentClass.put( persistentClass, entityType ); orderedMappings.add( persistentClass ); } /*package*/ void registerEmbeddedableType(EmbeddableTypeImpl embeddableType) { - embeddables.put( embeddableType.getJavaType(), embeddableType ); + if ( !( ignoreUnsupported && embeddableType.getParent().getJavaType() == null ) ) { + embeddables.add( embeddableType ); + } } /*package*/ void registerMappedSuperclassType( @@ -267,7 +275,7 @@ else if ( MappedSuperclass.class.isAssignableFrom( mapping.getClass() ) ) { } if ( staticMetamodelScanEnabled ) { - for ( EmbeddableTypeImpl embeddable : embeddables.values() ) { + for ( EmbeddableTypeImpl embeddable : embeddables ) { populateStaticMetamodel( embeddable ); } } @@ -318,7 +326,7 @@ private void applyIdMetadata(MappedSuperclass mappingType, MappedSuperclassT ); } } - //an MappedSuperclass can have no identifier if the id is set below in the hierarchy + //a MappedSuperclass can have no identifier if the id is set below in the hierarchy else if ( mappingType.getIdentifierMapper() != null ) { @SuppressWarnings("unchecked") Iterator propertyIterator = mappingType.getIdentifierMapper().getPropertyIterator(); @@ -437,7 +445,7 @@ private void registerAttribute(Class metamodelClass, Attribute attribu : metamodelClass.getDeclaredField( name ); try { // should be public anyway, but to be sure... - field.setAccessible( true ); + ReflectHelper.ensureAccessibility( field ); field.set( null, attribute ); } catch (IllegalAccessException e) { 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 c85793970841..fb499da06d6c 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 @@ -8,6 +8,8 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -15,6 +17,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; import javax.persistence.EntityGraph; import javax.persistence.NamedAttributeNode; import javax.persistence.NamedEntityGraph; @@ -32,9 +35,12 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.cfg.internal.DomainDataRegionConfigImpl; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cfg.annotations.NamedEntityGraphDefinition; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.graph.spi.EntityGraphImplementor; @@ -50,6 +56,8 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.RootClass; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -59,6 +67,7 @@ import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.type.AssociationType; import org.hibernate.type.Type; +import org.hibernate.type.spi.TypeConfiguration; /** * Hibernate implementation of the JPA {@link javax.persistence.metamodel.Metamodel} contract. @@ -70,6 +79,8 @@ public class MetamodelImpl implements MetamodelImplementor, Serializable { // todo : Integrate EntityManagerLogger into CoreMessageLogger private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( MetamodelImpl.class ); private static final Object ENTITY_NAME_RESOLVER_MAP_VALUE = new Object(); + private static final String INVALID_IMPORT = ""; + private static final String[] EMPTY_IMPLEMENTORS = new String[0]; private final SessionFactoryImplementor sessionFactory; @@ -82,14 +93,31 @@ public class MetamodelImpl implements MetamodelImplementor, Serializable { private final Map, EntityTypeImpl> jpaEntityTypeMap = new ConcurrentHashMap<>(); + /** + * There can be multiple instances of an Embeddable type, each one being relative to its parent entity. + */ + private final Set> jpaEmbeddableTypes = new CopyOnWriteArraySet<>(); + /** + * That's not strictly correct in the JPA standard since for a given Java type we could have + * multiple instances of an embeddable type. Some embeddable might override attributes, but we + * can only return a single EmbeddableTypeImpl for a given Java object class. + * + * A better approach would be if the parent class and attribute name would be included as well + * when trying to locate the embeddable type. + */ private final Map, EmbeddableTypeImpl> jpaEmbeddableTypeMap = new ConcurrentHashMap<>(); private final Map, MappedSuperclassType> jpaMappedSuperclassTypeMap = new ConcurrentHashMap<>(); private final Map> jpaEntityTypesByEntityName = new ConcurrentHashMap<>(); private final transient Map entityGraphMap = new ConcurrentHashMap<>(); - public MetamodelImpl(SessionFactoryImplementor sessionFactory) { + private final TypeConfiguration typeConfiguration; + + private final Map implementorsCache = new ConcurrentHashMap<>(); + + public MetamodelImpl(SessionFactoryImplementor sessionFactory, TypeConfiguration typeConfiguration) { this.sessionFactory = sessionFactory; + this.typeConfiguration = typeConfiguration; } /** @@ -102,6 +130,8 @@ public MetamodelImpl(SessionFactoryImplementor sessionFactory) { public void initialize(MetadataImplementor mappingMetadata, JpaMetaModelPopulationSetting jpaMetaModelPopulationSetting) { this.imports.putAll( mappingMetadata.getImports() ); + primeSecondLevelCacheRegions( mappingMetadata ); + final PersisterCreationContext persisterCreationContext = new PersisterCreationContext() { @Override public SessionFactoryImplementor getSessionFactory() { @@ -117,13 +147,9 @@ public MetadataImplementor getMetadata() { final PersisterFactory persisterFactory = sessionFactory.getServiceRegistry().getService( PersisterFactory.class ); for ( final PersistentClass model : mappingMetadata.getEntityBindings() ) { - final EntityRegionAccessStrategy accessStrategy = sessionFactory.getCache().determineEntityRegionAccessStrategy( - model - ); - - final NaturalIdRegionAccessStrategy naturalIdAccessStrategy = sessionFactory.getCache().determineNaturalIdRegionAccessStrategy( - model - ); + final NavigableRole rootEntityRole = new NavigableRole( model.getRootClass().getEntityName() ); + final EntityDataAccess accessStrategy = sessionFactory.getCache().getEntityRegionAccess( rootEntityRole ); + final NaturalIdDataAccess naturalIdAccessStrategy = sessionFactory.getCache().getNaturalIdCacheRegionAccessStrategy( rootEntityRole ); final EntityPersister cp = persisterFactory.createEntityPersister( model, @@ -164,9 +190,10 @@ public MetadataImplementor getMetadata() { } for ( final Collection model : mappingMetadata.getCollectionBindings() ) { - final CollectionRegionAccessStrategy accessStrategy = sessionFactory.getCache().determineCollectionRegionAccessStrategy( - model - ); + final NavigableRole navigableRole = new NavigableRole( model.getRole() ); + + final CollectionDataAccess accessStrategy = sessionFactory.getCache().getCollectionRegionAccess( + navigableRole ); final CollectionPersister persister = persisterFactory.createCollectionPersister( model, @@ -220,7 +247,10 @@ public MetadataImplementor getMetadata() { context.wrapUp(); this.jpaEntityTypeMap.putAll( context.getEntityTypeMap() ); - this.jpaEmbeddableTypeMap.putAll( context.getEmbeddableTypeMap() ); + this.jpaEmbeddableTypes.addAll( context.getEmbeddableTypeMap() ); + for ( EmbeddableTypeImpl embeddable: jpaEmbeddableTypes ) { + this.jpaEmbeddableTypeMap.put( embeddable.getJavaType(), embeddable ); + } this.jpaMappedSuperclassTypeMap.putAll( context.getMappedSuperclassTypeMap() ); this.jpaEntityTypesByEntityName.putAll( context.getEntityTypesByEntityName() ); @@ -229,6 +259,51 @@ public MetadataImplementor getMetadata() { } + private void primeSecondLevelCacheRegions(MetadataImplementor mappingMetadata) { + final Map regionConfigBuilders = new ConcurrentHashMap<>(); + + // todo : ultimately this code can be made more efficient when we have a better intrinsic understanding of the hierarchy as a whole + + for ( PersistentClass bootEntityDescriptor : mappingMetadata.getEntityBindings() ) { + final AccessType accessType = AccessType.fromExternalName( bootEntityDescriptor.getCacheConcurrencyStrategy() ); + + if ( accessType != null ) { + if ( bootEntityDescriptor.isCached() ) { + regionConfigBuilders.computeIfAbsent( bootEntityDescriptor.getRootClass().getCacheRegionName(), DomainDataRegionConfigImpl.Builder::new ) + .addEntityConfig( bootEntityDescriptor, accessType ); + } + + if ( RootClass.class.isInstance( bootEntityDescriptor ) + && bootEntityDescriptor.hasNaturalId() + && bootEntityDescriptor.getNaturalIdCacheRegionName() != null ) { + regionConfigBuilders.computeIfAbsent( bootEntityDescriptor.getNaturalIdCacheRegionName(), DomainDataRegionConfigImpl.Builder::new ) + .addNaturalIdConfig( (RootClass) bootEntityDescriptor, accessType ); + } + } + } + + for ( Collection collection : mappingMetadata.getCollectionBindings() ) { + final AccessType accessType = AccessType.fromExternalName( collection.getCacheConcurrencyStrategy() ); + if ( accessType != null ) { + regionConfigBuilders.computeIfAbsent( collection.getCacheRegionName(), DomainDataRegionConfigImpl.Builder::new ) + .addCollectionConfig( collection, accessType ); + } + } + + final Set regionConfigs; + if ( regionConfigBuilders.isEmpty() ) { + regionConfigs = Collections.emptySet(); + } + else { + regionConfigs = new HashSet<>(); + for ( DomainDataRegionConfigImpl.Builder builder : regionConfigBuilders.values() ) { + regionConfigs.add( builder.build() ); + } + } + + getSessionFactory().getCache().prime( regionConfigs ); + } + @SuppressWarnings("unchecked") private void applyNamedEntityGraphs(java.util.Collection namedEntityGraphs) { for ( NamedEntityGraphDefinition definition : namedEntityGraphs ) { @@ -443,6 +518,11 @@ private static MappedSuperclassTypeImpl buildMappedSuperclassType( // this.entityTypesByEntityName = entityTypesByEntityName; // } + @Override + public TypeConfiguration getTypeConfiguration() { + return typeConfiguration; + } + @Override public SessionFactoryImplementor getSessionFactory() { return sessionFactory; @@ -487,12 +567,12 @@ public EmbeddableType embeddable(Class cls) { @Override public Set> getManagedTypes() { final int setSize = CollectionHelper.determineProperSizing( - jpaEntityTypeMap.size() + jpaMappedSuperclassTypeMap.size() + jpaEmbeddableTypeMap.size() + jpaEntityTypeMap.size() + jpaMappedSuperclassTypeMap.size() + jpaEmbeddableTypes.size() ); final Set> managedTypes = new HashSet>( setSize ); - managedTypes.addAll( jpaEntityTypeMap.values() ); + managedTypes.addAll( jpaEntityTypesByEntityName.values() ); managedTypes.addAll( jpaMappedSuperclassTypeMap.values() ); - managedTypes.addAll( jpaEmbeddableTypeMap.values() ); + managedTypes.addAll( jpaEmbeddableTypes ); return managedTypes; } @@ -503,7 +583,7 @@ public Set> getEntities() { @Override public Set> getEmbeddables() { - return new HashSet<>( jpaEmbeddableTypeMap.values() ); + return new HashSet<>( jpaEmbeddableTypes ); } @Override @@ -522,71 +602,41 @@ public String getImportedClassName(String className) { return className; } catch ( ClassLoadingException cnfe ) { + imports.put( className, INVALID_IMPORT ); return null; } } + else if ( result == INVALID_IMPORT ) { + return null; + } else { return result; } } - /** - * Given the name of an entity class, determine all the class and interface names by which it can be - * referenced in an HQL query. - * - * @param className The name of the entity class - * - * @return the names of all persistent (mapped) classes that extend or implement the - * given class or interface, accounting for implicit/explicit polymorphism settings - * and excluding mapped subclasses/joined-subclasses of other classes in the result. - * @throws MappingException - */ + @Override public String[] getImplementors(String className) throws MappingException { - - final Class clazz; - try { - clazz = getSessionFactory().getServiceRegistry().getService( ClassLoaderService.class ).classForName( className ); - } - catch (ClassLoadingException e) { - return new String[] { className }; //for a dynamic-class + // computeIfAbsent() can be a contention point and we expect all the values to be in the map at some point so + // let's do an optimistic check first + String[] implementors = implementorsCache.get( className ); + if ( implementors != null ) { + return Arrays.copyOf( implementors, implementors.length ); } - ArrayList results = new ArrayList<>(); - for ( EntityPersister checkPersister : entityPersisters().values() ) { - if ( ! Queryable.class.isInstance( checkPersister ) ) { - continue; - } - final Queryable checkQueryable = Queryable.class.cast( checkPersister ); - final String checkQueryableEntityName = checkQueryable.getEntityName(); - final boolean isMappedClass = className.equals( checkQueryableEntityName ); - if ( checkQueryable.isExplicitPolymorphism() ) { - if ( isMappedClass ) { - return new String[] { className }; //NOTE EARLY EXIT - } + try { + final Class clazz = getSessionFactory().getServiceRegistry().getService( ClassLoaderService.class ).classForName( className ); + implementors = doGetImplementors( clazz ); + if ( implementors.length > 0 ) { + implementorsCache.putIfAbsent( className, implementors ); + return Arrays.copyOf( implementors, implementors.length ); } else { - if ( isMappedClass ) { - results.add( checkQueryableEntityName ); - } - else { - final Class mappedClass = checkQueryable.getMappedClass(); - if ( mappedClass != null && clazz.isAssignableFrom( mappedClass ) ) { - final boolean assignableSuperclass; - if ( checkQueryable.isInherited() ) { - Class mappedSuperclass = entityPersister( checkQueryable.getMappedSuperclass() ).getMappedClass(); - assignableSuperclass = clazz.isAssignableFrom( mappedSuperclass ); - } - else { - assignableSuperclass = false; - } - if ( !assignableSuperclass ) { - results.add( checkQueryableEntityName ); - } - } - } + return EMPTY_IMPLEMENTORS; } } - return results.toArray( new String[results.size()] ); + catch (ClassLoadingException e) { + return new String[]{ className }; // we don't cache anything for dynamic classes + } } @Override @@ -661,7 +711,7 @@ public String[] getAllEntityNames() { @Override public String[] getAllCollectionRoles() { - return ArrayHelper.toStringArray( entityPersisterMap.keySet() ); + return ArrayHelper.toStringArray( collectionPersisterMap.keySet() ); } @Override @@ -709,4 +759,44 @@ public List> findEntityGraphsByType(Class entityCl public void close() { // anything to do ? } + + private String[] doGetImplementors(Class clazz) throws MappingException { + ArrayList results = new ArrayList<>(); + for ( EntityPersister checkPersister : entityPersisters().values() ) { + if ( !Queryable.class.isInstance( checkPersister ) ) { + continue; + } + final Queryable checkQueryable = Queryable.class.cast( checkPersister ); + final String checkQueryableEntityName = checkQueryable.getEntityName(); + final boolean isMappedClass = clazz.getName().equals( checkQueryableEntityName ); + if ( checkQueryable.isExplicitPolymorphism() ) { + if ( isMappedClass ) { + return new String[]{ clazz.getName() }; // NOTE EARLY EXIT + } + } + else { + if ( isMappedClass ) { + results.add( checkQueryableEntityName ); + } + else { + final Class mappedClass = checkQueryable.getMappedClass(); + if ( mappedClass != null && clazz.isAssignableFrom( mappedClass ) ) { + final boolean assignableSuperclass; + if ( checkQueryable.isInherited() ) { + Class mappedSuperclass = entityPersister( checkQueryable.getMappedSuperclass() ).getMappedClass(); + assignableSuperclass = clazz.isAssignableFrom( mappedSuperclass ); + } + else { + assignableSuperclass = false; + } + if ( !assignableSuperclass ) { + results.add( checkQueryableEntityName ); + } + } + } + } + } + + return results.toArray( new String[results.size()] ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java new file mode 100644 index 000000000000..6be89acaf17e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.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.metamodel.model.convert.internal; + +import javax.persistence.AttributeConverter; + +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; +import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * Standard implementation of JpaAttributeConverter + * + * @author Steve Ebersole + */ +public class JpaAttributeConverterImpl implements JpaAttributeConverter { + private final ManagedBean> attributeConverterBean; + private final JavaTypeDescriptor> converterJavaTypeDescriptor; + private final BasicJavaDescriptor domainJavaTypeDescriptor; + private final BasicJavaDescriptor relationalJavaTypeDescriptor; + + public JpaAttributeConverterImpl( + ManagedBean> attributeConverterBean, + JavaTypeDescriptor> converterJavaTypeDescriptor, + JavaTypeDescriptor domainJavaTypeDescriptor, + JavaTypeDescriptor relationalJavaTypeDescriptor) { + this.attributeConverterBean = attributeConverterBean; + this.converterJavaTypeDescriptor = converterJavaTypeDescriptor; + this.domainJavaTypeDescriptor = (BasicJavaDescriptor) domainJavaTypeDescriptor; + this.relationalJavaTypeDescriptor = (BasicJavaDescriptor) relationalJavaTypeDescriptor; + } + + @Override + public ManagedBean> getConverterBean() { + return attributeConverterBean; + } + + @Override + public O toDomainValue(R relationalForm) { + return attributeConverterBean.getBeanInstance().convertToEntityAttribute( relationalForm ); + } + + @Override + public R toRelationalValue(O domainForm) { + return attributeConverterBean.getBeanInstance().convertToDatabaseColumn( domainForm ); + } + + @Override + public JavaTypeDescriptor> getConverterJavaTypeDescriptor() { + return converterJavaTypeDescriptor; + } + + @Override + public BasicJavaDescriptor getDomainJavaTypeDescriptor() { + return domainJavaTypeDescriptor; + } + + @Override + public BasicJavaDescriptor getRelationalJavaTypeDescriptor() { + return relationalJavaTypeDescriptor; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java new file mode 100644 index 000000000000..c077c82e5429 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.metamodel.model.convert.internal; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Locale; + +import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; +import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor; + +import org.jboss.logging.Logger; + +/** + * BasicValueConverter handling the conversion of an enum based on + * JPA {@link javax.persistence.EnumType#STRING} strategy (storing the name) + * + * @author Steve Ebersole + */ +public class NamedEnumValueConverter implements EnumValueConverter, Serializable { + private static final Logger log = Logger.getLogger( NamedEnumValueConverter.class ); + + private final EnumJavaTypeDescriptor enumJavaDescriptor; + + public NamedEnumValueConverter(EnumJavaTypeDescriptor enumJavaDescriptor) { + this.enumJavaDescriptor = enumJavaDescriptor; + } + + @Override + @SuppressWarnings("unchecked") + public E toDomainValue(String relationalForm) { + return enumJavaDescriptor.fromName( relationalForm ); + } + + @Override + public String toRelationalValue(E domainForm) { + return enumJavaDescriptor.toName( domainForm ); + } + + @Override + public int getJdbcTypeCode() { + return Types.VARCHAR; + } + + @Override + public EnumJavaTypeDescriptor getJavaDescriptor() { + return enumJavaDescriptor; + } + + @Override + public E readValue(ResultSet resultSet, String name) throws SQLException { + final String value = resultSet.getString( name ); + + final boolean traceEnabled = log.isTraceEnabled(); + if ( resultSet.wasNull() ) { + if ( traceEnabled ) { + log.trace( String.format( "Returning null as column [%s]", name ) ); + } + return null; + } + + final E enumValue = toDomainValue( value ); + if ( traceEnabled ) { + log.trace( String.format( "Returning [%s] as column [%s]", enumValue, name ) ); + } + + return enumValue; + } + + @Override + public void writeValue(PreparedStatement statement, E value, int position) throws SQLException { + final String jdbcValue = value == null ? null : toRelationalValue( value ); + + final boolean traceEnabled = log.isTraceEnabled(); + if ( jdbcValue == null ) { + if ( traceEnabled ) { + log.tracef( "Binding null to parameter: [%s]", position ); + } + statement.setNull( position, getJdbcTypeCode() ); + return; + } + + if ( traceEnabled ) { + log.tracef( "Binding [%s] to parameter: [%s]", jdbcValue, position ); + } + + statement.setString( position, jdbcValue ); + } + + @Override + @SuppressWarnings("unchecked") + public String toSqlLiteral(Object value) { + return String.format( Locale.ROOT, "'%s'", ( (E) value ).name() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/OrdinalEnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/OrdinalEnumValueConverter.java new file mode 100644 index 000000000000..4f3fa4d78df8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/OrdinalEnumValueConverter.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.metamodel.model.convert.internal; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; +import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor; + +import org.jboss.logging.Logger; + +/** + * BasicValueConverter handling the conversion of an enum based on + * JPA {@link javax.persistence.EnumType#ORDINAL} strategy (storing the ordinal) + * + * @author Steve Ebersole + */ +public class OrdinalEnumValueConverter implements EnumValueConverter, Serializable { + private static final Logger log = Logger.getLogger( OrdinalEnumValueConverter.class ); + + private final EnumJavaTypeDescriptor enumJavaDescriptor; + + public OrdinalEnumValueConverter(EnumJavaTypeDescriptor enumJavaDescriptor) { + this.enumJavaDescriptor = enumJavaDescriptor; + } + + @Override + @SuppressWarnings("unchecked") + public E toDomainValue(Integer relationalForm) { + return enumJavaDescriptor.fromOrdinal( relationalForm ); + } + + @Override + public Integer toRelationalValue(E domainForm) { + return enumJavaDescriptor.toOrdinal( domainForm ); + } + + @Override + public int getJdbcTypeCode() { + return Types.INTEGER; + } + + @Override + public EnumJavaTypeDescriptor getJavaDescriptor() { + return enumJavaDescriptor; + } + + @Override + public E readValue(ResultSet resultSet, String name) throws SQLException { + final int ordinal = resultSet.getInt( name ); + final boolean traceEnabled = log.isTraceEnabled(); + if ( resultSet.wasNull() ) { + if ( traceEnabled ) { + log.trace(String.format("Returning null as column [%s]", name)); + } + return null; + } + + final E enumValue = toDomainValue( ordinal ); + if ( traceEnabled ) { + log.trace(String.format("Returning [%s] as column [%s]", enumValue, name)); + } + + return enumValue; + } + + @Override + public void writeValue(PreparedStatement statement, E value, int position) throws SQLException { + final Integer jdbcValue = value == null ? null : toRelationalValue( value ); + + final boolean traceEnabled = log.isTraceEnabled(); + if ( jdbcValue == null ) { + if ( traceEnabled ) { + log.tracef( "Binding null to parameter: [%s]", position ); + } + statement.setNull( position, getJdbcTypeCode() ); + return; + } + + if ( traceEnabled ) { + log.tracef( "Binding [%s] to parameter: [%s]", jdbcValue.intValue(), position ); + } + + statement.setInt( position, jdbcValue ); + } + + @Override + @SuppressWarnings("unchecked") + public String toSqlLiteral(Object value) { + return Integer.toString( ( (E) value ).ordinal() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/StandardBasicValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/StandardBasicValueConverter.java new file mode 100644 index 000000000000..b74a1663bbc4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/StandardBasicValueConverter.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.metamodel.model.convert.internal; + +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; + +/** + * A no-op / pass-through conversion + * + * @author Steve Ebersole + */ +public class StandardBasicValueConverter implements BasicValueConverter { + /** + * Singleton access + */ + public static final StandardBasicValueConverter INSTANCE = new StandardBasicValueConverter(); + + private StandardBasicValueConverter() { + } + + @Override + @SuppressWarnings("unchecked") + public O toDomainValue(R relationalForm) { + return (O) relationalForm; + } + + @Override + @SuppressWarnings("unchecked") + public R toRelationalValue(O domainForm) { + return (R) domainForm; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/package-info.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/package-info.java new file mode 100644 index 000000000000..5ab404bccce9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/package-info.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ + +/** + * Support for basic-value conversions. The main contract is + * {@link org.hibernate.metamodel.model.convert.spi.BasicValueConverter}. + * + * All basic value conversions are defined by this package including: + * * Enum conversions - {@link org.hibernate.metamodel.model.convert.spi.EnumValueConverter} + * * AttributeConverter - {@link org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter} + */ +package org.hibernate.metamodel.model.convert; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/BasicValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/BasicValueConverter.java new file mode 100644 index 000000000000..2ffc0ccf7928 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/BasicValueConverter.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.metamodel.model.convert.spi; + +/** + * Support for basic-value conversions. + * + * Conversions might be defined by: + * + * * a custom JPA {@link javax.persistence.AttributeConverter}, + * * implicitly, based on the Java type (e.g., enums) + * * etc + * + * @author Steve Ebersole + */ +public interface BasicValueConverter { + /** + * Convert the relational form just retrieved from JDBC ResultSet into + * the domain form. + */ + O toDomainValue(R relationalForm); + + /** + * Convert the domain form into the relational form in preparation for + * storage into JDBC + */ + R toRelationalValue(O domainForm); +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/EnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/EnumValueConverter.java new file mode 100644 index 000000000000..dc384c4540fc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/EnumValueConverter.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.metamodel.model.convert.spi; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor; + +/** + * BasicValueConverter extension for enum-specific support + * + * @author Steve Ebersole + */ +public interface EnumValueConverter extends BasicValueConverter { + EnumJavaTypeDescriptor getJavaDescriptor(); + int getJdbcTypeCode(); + + O readValue(ResultSet resultSet, String name) throws SQLException; + void writeValue(PreparedStatement statement, O value, int position) throws SQLException; + + String toSqlLiteral(Object value); +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/JpaAttributeConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/JpaAttributeConverter.java new file mode 100644 index 000000000000..a5f5d4f4b7a1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/JpaAttributeConverter.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.metamodel.model.convert.spi; + +import javax.persistence.AttributeConverter; + +import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * BasicValueConverter extension for AttributeConverter-specific support + * + * @author Steve Ebersole + */ +public interface JpaAttributeConverter extends BasicValueConverter { + JavaTypeDescriptor> getConverterJavaTypeDescriptor(); + + ManagedBean> getConverterBean(); + + BasicJavaDescriptor getDomainJavaTypeDescriptor(); + BasicJavaDescriptor getRelationalJavaTypeDescriptor(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/NavigableRole.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/NavigableRole.java new file mode 100644 index 000000000000..f9b766411a3a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/NavigableRole.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.metamodel.model.domain; + +import java.io.Serializable; +import java.util.Objects; + +import org.hibernate.internal.util.StringHelper; + +/** + * A representation of the static "Navigable" path relative to some "root entity". + * + * @author Steve Ebersole + */ +public class NavigableRole implements Serializable { + public static final String IDENTIFIER_MAPPER_PROPERTY = "_identifierMapper"; + + private final NavigableRole parent; + private final String navigableName; + private final String fullPath; + + public NavigableRole(NavigableRole parent, String navigableName) { + this.parent = parent; + this.navigableName = navigableName; + + // the _identifierMapper is a "hidden" property on entities with composite keys. + // concatenating it will prevent the path from correctly being used to look up + // various things such as criteria paths and fetch profile association paths + if ( IDENTIFIER_MAPPER_PROPERTY.equals( navigableName ) ) { + this.fullPath = parent != null ? parent.getFullPath() : ""; + } + else { + final String prefix; + if ( parent != null ) { + final String resolvedParent = parent.getFullPath(); + if ( StringHelper.isEmpty( resolvedParent ) ) { + prefix = ""; + } + else { + prefix = resolvedParent + '.'; + } + } + else { + prefix = ""; + } + + this.fullPath = prefix + navigableName; + } + } + + public NavigableRole(String navigableName) { + this( null, navigableName ); + } + + public NavigableRole() { + this( "" ); + } + + public NavigableRole append(String property) { + return new NavigableRole( this, property ); + } + + public NavigableRole getParent() { + return parent; + } + + public String getNavigableName() { + return navigableName; + } + + public String getFullPath() { + return fullPath; + } + + public boolean isRoot() { + return parent == null && StringHelper.isEmpty( navigableName ); + } + + @Override + public String toString() { + return getClass().getSimpleName() + '[' + fullPath + ']'; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NavigableRole that = (NavigableRole) o; + return Objects.equals( getFullPath(), that.getFullPath() ); + } + + @Override + public int hashCode() { + return Objects.hash( getFullPath() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/MetamodelImplementor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/MetamodelImplementor.java index 443df5cf6edd..2d049d73c64e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/MetamodelImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/MetamodelImplementor.java @@ -18,11 +18,19 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.spi.TypeConfiguration; /** * @author Steve Ebersole */ public interface MetamodelImplementor extends Metamodel { + /** + * Access to the TypeConfiguration in effect for this SessionFactory/Metamodel + * + * @return Access to the TypeConfiguration + */ + TypeConfiguration getTypeConfiguration(); + @Override SessionFactoryImplementor getSessionFactory(); diff --git a/hibernate-core/src/main/java/org/hibernate/param/CollectionFilterKeyParameterSpecification.java b/hibernate-core/src/main/java/org/hibernate/param/CollectionFilterKeyParameterSpecification.java index b54e59efc657..2d7642480ebd 100644 --- a/hibernate-core/src/main/java/org/hibernate/param/CollectionFilterKeyParameterSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/param/CollectionFilterKeyParameterSpecification.java @@ -6,9 +6,11 @@ */ package org.hibernate.param; +import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.SQLException; +import org.hibernate.QueryException; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.Type; @@ -20,22 +22,20 @@ * @author Steve Ebersole */ public class CollectionFilterKeyParameterSpecification implements ParameterSpecification { + public static final String PARAM_KEY = "{collection_key}"; + private final String collectionRole; private final Type keyType; - private final int queryParameterPosition; /** * Creates a specialized collection-filter collection-key parameter spec. * * @param collectionRole The collection role being filtered. * @param keyType The mapped collection-key type. - * @param queryParameterPosition The position within {@link org.hibernate.engine.spi.QueryParameters} where - * we can find the appropriate param value to bind. */ - public CollectionFilterKeyParameterSpecification(String collectionRole, Type keyType, int queryParameterPosition) { + public CollectionFilterKeyParameterSpecification(String collectionRole, Type keyType) { this.collectionRole = collectionRole; this.keyType = keyType; - this.queryParameterPosition = queryParameterPosition; } @Override @@ -44,7 +44,7 @@ public int bind( QueryParameters qp, SharedSessionContractImplementor session, int position) throws SQLException { - Object value = qp.getPositionalParameterValues()[queryParameterPosition]; + final Object value = qp.getNamedParameters().get( PARAM_KEY ).getValue(); keyType.nullSafeSet( statement, value, position, session ); return keyType.getColumnSpan( session.getFactory() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/param/DynamicFilterParameterSpecification.java b/hibernate-core/src/main/java/org/hibernate/param/DynamicFilterParameterSpecification.java index 916d45efd3a6..def45bdf997b 100644 --- a/hibernate-core/src/main/java/org/hibernate/param/DynamicFilterParameterSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/param/DynamicFilterParameterSpecification.java @@ -50,17 +50,22 @@ public int bind( SharedSessionContractImplementor session, int start) throws SQLException { final int columnSpan = definedParameterType.getColumnSpan( session.getFactory() ); - final Object value = session.getLoadQueryInfluencers().getFilterParameterValue( filterName + '.' + parameterName ); + final String fullParamName = filterName + '.' + parameterName; + final Object value = session.getLoadQueryInfluencers().getFilterParameterValue(fullParamName); + final Type type = session.getLoadQueryInfluencers().getFilterParameterType(fullParamName); if ( Collection.class.isInstance( value ) ) { int positions = 0; Iterator itr = ( ( Collection ) value ).iterator(); while ( itr.hasNext() ) { - definedParameterType.nullSafeSet( statement, itr.next(), start + positions, session ); + Object next = itr.next(); + qp.bindDynamicParameter( type, next ); + definedParameterType.nullSafeSet( statement, next, start + positions, session ); positions += columnSpan; } return positions; } else { + qp.bindDynamicParameter(type, value); definedParameterType.nullSafeSet( statement, value, start, session ); return columnSpan; } diff --git a/hibernate-core/src/main/java/org/hibernate/param/ParameterBinder.java b/hibernate-core/src/main/java/org/hibernate/param/ParameterBinder.java new file mode 100644 index 000000000000..6a88b02fe834 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/param/ParameterBinder.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.param; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public interface ParameterBinder { + /** + * Bind the appropriate value into the given statement at the specified position. + * + * @param statement The statement into which the value should be bound. + * @param qp The defined values for the current query execution. + * @param session The session against which the current execution is occuring. + * @param position The position from which to start binding value(s). + * + * @return The number of sql bind positions "eaten" by this bind operation. + * @throws java.sql.SQLException Indicates problems performing the JDBC biind operation. + */ + int bind(PreparedStatement statement, QueryParameters qp, SharedSessionContractImplementor session, int position) throws SQLException; + +} diff --git a/hibernate-core/src/main/java/org/hibernate/param/ParameterSpecification.java b/hibernate-core/src/main/java/org/hibernate/param/ParameterSpecification.java index b78aec881cc8..2c1d64a9d19f 100644 --- a/hibernate-core/src/main/java/org/hibernate/param/ParameterSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/param/ParameterSpecification.java @@ -7,10 +7,7 @@ package org.hibernate.param; import java.sql.PreparedStatement; -import java.sql.SQLException; -import org.hibernate.engine.spi.QueryParameters; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.Type; /** @@ -19,20 +16,7 @@ * * @author Steve Ebersole */ -public interface ParameterSpecification { - /** - * Bind the appropriate value into the given statement at the specified position. - * - * @param statement The statement into which the value should be bound. - * @param qp The defined values for the current query execution. - * @param session The session against which the current execution is occuring. - * @param position The position from which to start binding value(s). - * - * @return The number of sql bind positions "eaten" by this bind operation. - * @throws java.sql.SQLException Indicates problems performing the JDBC biind operation. - */ - int bind(PreparedStatement statement, QueryParameters qp, SharedSessionContractImplementor session, int position) throws SQLException; - +public interface ParameterSpecification extends ParameterBinder { /** * Get the type which we are expeting for a bind into this parameter based * on translated contextual information. diff --git a/hibernate-core/src/main/java/org/hibernate/param/PositionalParameterSpecification.java b/hibernate-core/src/main/java/org/hibernate/param/PositionalParameterSpecification.java index 713d6afc4582..178fb5c5f34d 100644 --- a/hibernate-core/src/main/java/org/hibernate/param/PositionalParameterSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/param/PositionalParameterSpecification.java @@ -11,7 +11,7 @@ import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.type.Type; +import org.hibernate.engine.spi.TypedValue; /** * Parameter bind specification for an explicit positional (or ordinal) parameter. @@ -19,18 +19,24 @@ * @author Steve Ebersole */ public class PositionalParameterSpecification extends AbstractExplicitParameterSpecification { - private final int hqlPosition; + private final int label; + private final int bindingPosition; /** * Constructs a position/ordinal parameter bind specification. * * @param sourceLine See {@link #getSourceLine()} * @param sourceColumn See {@link #getSourceColumn()} - * @param hqlPosition The position in the source query, relative to the other source positional parameters. + * @param label The position in the source query, relative to the other source positional parameters. */ - public PositionalParameterSpecification(int sourceLine, int sourceColumn, int hqlPosition) { + public PositionalParameterSpecification( + int sourceLine, + int sourceColumn, + int label, + int bindingPosition) { super( sourceLine, sourceColumn ); - this.hqlPosition = hqlPosition; + this.label = label; + this.bindingPosition = bindingPosition; } /** @@ -45,24 +51,17 @@ public PositionalParameterSpecification(int sourceLine, int sourceColumn, int hq */ @Override public int bind(PreparedStatement statement, QueryParameters qp, SharedSessionContractImplementor session, int position) throws SQLException { - Type type = qp.getPositionalParameterTypes()[hqlPosition]; - Object value = qp.getPositionalParameterValues()[hqlPosition]; - - type.nullSafeSet( statement, value, position, session ); - return type.getColumnSpan( session.getFactory() ); + final TypedValue typedValue = qp.getNamedParameters().get( Integer.toString( label ) ); + typedValue.getType().nullSafeSet( statement, typedValue.getValue(), position, session ); + return typedValue.getType().getColumnSpan( session.getFactory() ); } @Override public String renderDisplayInfo() { - return "ordinal=" + hqlPosition + ", expectedType=" + getExpectedType(); + return "label=" + label + ", expectedType=" + getExpectedType(); } - /** - * Getter for property 'hqlPosition'. - * - * @return Value for property 'hqlPosition'. - */ - public int getHqlPosition() { - return hqlPosition; + public int getLabel() { + return label; } } 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 ce917153336b..f282eab74eb2 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -18,13 +18,14 @@ import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; +import org.hibernate.Filter; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.TransientObjectException; import org.hibernate.boot.model.relational.Database; import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.cache.spi.entry.StructuredCollectionCacheEntry; import org.hibernate.cache.spi.entry.StructuredMapCacheEntry; @@ -60,6 +61,7 @@ import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Table; import org.hibernate.metadata.CollectionMetadata; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.PropertyMapping; @@ -111,7 +113,7 @@ public abstract class AbstractCollectionPersister // TODO: encapsulate the protected instance variables! - private final String role; + private final NavigableRole navigableRole; // SQL statements private final String sqlDeleteString; @@ -198,7 +200,7 @@ public abstract class AbstractCollectionPersister private final IdentifierGenerator identifierGenerator; private final PropertyMapping elementPropertyMapping; private final EntityPersister elementPersister; - private final CollectionRegionAccessStrategy cacheAccessStrategy; + private final CollectionDataAccess cacheAccessStrategy; private final CollectionType collectionType; private CollectionInitializer initializer; @@ -230,7 +232,7 @@ public abstract class AbstractCollectionPersister public AbstractCollectionPersister( Collection collectionBinding, - CollectionRegionAccessStrategy cacheAccessStrategy, + CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) throws MappingException, CacheException { final Database database = creationContext.getMetadata().getDatabase(); @@ -250,7 +252,7 @@ public AbstractCollectionPersister( dialect = factory.getDialect(); sqlExceptionHelper = factory.getSQLExceptionHelper(); collectionType = collectionBinding.getCollectionType(); - role = collectionBinding.getRole(); + navigableRole = new NavigableRole( collectionBinding.getRole() ); entityName = collectionBinding.getOwnerEntityName(); ownerPersister = factory.getEntityPersister( entityName ); queryLoaderName = collectionBinding.getLoaderName(); @@ -551,6 +553,7 @@ else if ( !elementType.isEntityType() ) { hasOrder = collectionBinding.getOrderBy() != null; if ( hasOrder ) { + LOG.debugf( "Translating order-by fragment [%s] for collection role : %s", collectionBinding.getOrderBy(), getRole() ); orderByTranslation = Template.translateOrderBy( collectionBinding.getOrderBy(), new ColumnMapperImpl(), @@ -577,6 +580,7 @@ else if ( !elementType.isEntityType() ) { hasManyToManyOrder = collectionBinding.getManyToManyOrdering() != null; if ( hasManyToManyOrder ) { + LOG.debugf( "Translating many-to-many order-by fragment [%s] for collection role : %s", collectionBinding.getOrderBy(), getRole() ); manyToManyOrderByTranslation = Template.translateOrderBy( collectionBinding.getManyToManyOrdering(), new ColumnMapperImpl(), @@ -698,7 +702,7 @@ protected CollectionInitializer getAppropriateInitializer(Serializable key, Shar if ( subselectInitializer != null ) { return subselectInitializer; } - else if ( session.getLoadQueryInfluencers().getEnabledFilters().isEmpty() ) { + else if ( ! session.getLoadQueryInfluencers().hasEnabledFilters() ) { return initializer; } else { @@ -742,7 +746,12 @@ protected abstract CollectionInitializer createCollectionInitializer(LoadQueryIn throws MappingException; @Override - public CollectionRegionAccessStrategy getCacheAccessStrategy() { + public NavigableRole getNavigableRole() { + return navigableRole; + } + + @Override + public CollectionDataAccess getCacheAccessStrategy() { return cacheAccessStrategy; } @@ -844,7 +853,7 @@ public Object readIndex(ResultSet rs, String[] aliases, SharedSessionContractImp throws HibernateException, SQLException { Object index = getIndexType().nullSafeGet( rs, aliases, session, null ); if ( index == null ) { - throw new HibernateException( "null index column for collection: " + role ); + throw new HibernateException( "null index column for collection: " + navigableRole.getFullPath() ); } index = decrementIndexByBase( index ); return index; @@ -862,7 +871,7 @@ public Object readIdentifier(ResultSet rs, String alias, SharedSessionContractIm throws HibernateException, SQLException { Object id = getIdentifierType().nullSafeGet( rs, alias, session, null ); if ( id == null ) { - throw new HibernateException( "null identifier column for collection: " + role ); + throw new HibernateException( "null identifier column for collection: " + navigableRole.getFullPath() ); } return id; } @@ -870,7 +879,16 @@ public Object readIdentifier(ResultSet rs, String alias, SharedSessionContractIm @Override public Object readKey(ResultSet rs, String[] aliases, SharedSessionContractImplementor session) throws HibernateException, SQLException { - return getKeyType().nullSafeGet( rs, aliases, session, null ); + // First hydrate the collection key to check if it is null. + // Don't bother resolving the collection key if the hydrated value is null. + + // Implementation note: if collection key is a composite value, then resolving a null value will + // result in instantiating an empty composite if AvailableSettings#CREATE_EMPTY_COMPOSITES_ENABLED + // is true. By not resolving a null value for a composite key, we avoid the overhead of instantiating + // an empty composite, checking if it is equivalent to null (it should be), then ultimately throwing + // out the empty value. + final Object hydratedKey = getKeyType().hydrate( rs, aliases, session, null ); + return hydratedKey == null ? null : getKeyType().resolve( hydratedKey, session, null ); } /** @@ -880,7 +898,7 @@ protected int writeKey(PreparedStatement st, Serializable key, int i, SharedSess throws HibernateException, SQLException { if ( key == null ) { - throw new NullPointerException( "null key for collection: " + role ); // an assertion + throw new NullPointerException( "null key for collection: " + navigableRole.getFullPath() ); // an assertion } getKeyType().nullSafeSet( st, key, i, session ); return i + keyColumnAliases.length; @@ -1592,7 +1610,7 @@ public void insertRows(PersistentCollection collection, Serializable id, SharedS @Override public String getRole() { - return role; + return navigableRole.getFullPath(); } public String getOwnerEntityName() { @@ -1713,7 +1731,7 @@ public void updateRows(PersistentCollection collection, Serializable id, SharedS if ( !isInverse && collection.isRowUpdatePossible() ) { - LOG.debugf( "Updating rows of collection: %s#%s", role, id ); + LOG.debugf( "Updating rows of collection: %s#%s", navigableRole.getFullPath(), id ); // update all the modified entries int count = doUpdateRows( id, collection, session ); @@ -1835,7 +1853,7 @@ protected ExecuteUpdateResultCheckStyle getDeleteAllCheckStyle() { @Override public String toString() { - return StringHelper.unqualify( getClass().getName() ) + '(' + role + ')'; + return StringHelper.unqualify( getClass().getName() ) + '(' + navigableRole.getFullPath() + ')'; } @Override @@ -1860,8 +1878,9 @@ public CacheEntryStructure getCacheEntryStructure() { @Override public boolean isAffectedByEnabledFilters(SharedSessionContractImplementor session) { - return filterHelper.isAffectedBy( session.getLoadQueryInfluencers().getEnabledFilters() ) || - ( isManyToMany() && manyToManyFilterHelper.isAffectedBy( session.getLoadQueryInfluencers().getEnabledFilters() ) ); + final Map enabledFilters = session.getLoadQueryInfluencers().getEnabledFilters(); + return filterHelper.isAffectedBy( enabledFilters ) || + ( isManyToMany() && manyToManyFilterHelper.isAffectedBy( enabledFilters ) ); } public boolean isSubselectLoadable() { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java index 31d9ed3941c1..d271051a69cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java @@ -9,13 +9,15 @@ import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -52,7 +54,7 @@ public boolean isCascadeDeleteEnabled() { public BasicCollectionPersister( Collection collectionBinding, - CollectionRegionAccessStrategy cacheAccessStrategy, + CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) throws MappingException, CacheException { super( collectionBinding, cacheAccessStrategy, creationContext ); } @@ -193,82 +195,51 @@ protected int doUpdateRows(Serializable id, PersistentCollection collection, Sha } try { - PreparedStatement st = null; - Expectation expectation = Expectations.appropriateExpectation( getUpdateCheckStyle() ); - boolean callable = isUpdateCallable(); - boolean useBatch = expectation.canBeBatched(); - Iterator entries = collection.entries( this ); - String sql = getSQLUpdateRowString(); - int i = 0; - int count = 0; + final Expectation expectation = Expectations.appropriateExpectation( getUpdateCheckStyle() ); + final boolean callable = isUpdateCallable(); + final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize(); + boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1; + final Iterator entries = collection.entries( this ); + + final List elements = new ArrayList(); while ( entries.hasNext() ) { - Object entry = entries.next(); - if ( collection.needsUpdating( entry, i, elementType ) ) { - int offset = 1; - - if ( useBatch ) { - if ( updateBatchKey == null ) { - updateBatchKey = new BasicBatchKey( - getRole() + "#UPDATE", - expectation - ); - } - st = session - .getJdbcCoordinator() - .getBatch( updateBatchKey ) - .getBatchStatement( sql, callable ); - } - else { - st = session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql, callable ); - } + elements.add( entries.next() ); + } - try { - offset += expectation.prepare( st ); - int loc = writeElement( st, collection.getElement( entry ), offset, session ); - if ( hasIdentifier ) { - writeIdentifier( st, collection.getIdentifier( entry, i ), loc, session ); - } - else { - loc = writeKey( st, id, loc, session ); - if ( hasIndex && !indexContainsFormula ) { - writeIndexToWhere( st, collection.getIndex( entry, i, this ), loc, session ); - } - else { - writeElementToWhere( st, collection.getSnapshotElement( entry, i ), loc, session ); - } - } - - if ( useBatch ) { - session.getJdbcCoordinator() - .getBatch( updateBatchKey ) - .addToBatch(); - } - else { - expectation.verifyOutcome( - session.getJdbcCoordinator().getResultSetReturn().executeUpdate( - st - ), st, -1 - ); - } - } - catch (SQLException sqle) { - if ( useBatch ) { - session.getJdbcCoordinator().abortBatch(); - } - throw sqle; - } - finally { - if ( !useBatch ) { - session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st ); - session.getJdbcCoordinator().afterStatementExecution(); - } - } - count++; + final String sql = getSQLUpdateRowString(); + int count = 0; + if ( collection.isElementRemoved() ) { + // the update should be done starting from the end to the list + for ( int i = elements.size() - 1; i >= 0; i-- ) { + count = doUpdateRow( + id, + collection, + session, + expectation, + callable, + useBatch, + elements, + sql, + count, + i + ); + } + } + else { + for ( int i = 0; i < elements.size(); i++ ) { + count = doUpdateRow( + id, + collection, + session, + expectation, + callable, + useBatch, + elements, + sql, + count, + i + ); } - i++; } return count; } @@ -286,6 +257,82 @@ protected int doUpdateRows(Serializable id, PersistentCollection collection, Sha } } + private int doUpdateRow( + Serializable id, + PersistentCollection collection, + SharedSessionContractImplementor session, + Expectation expectation, boolean callable, boolean useBatch, List elements, String sql, int count, int i) + throws SQLException { + PreparedStatement st; + Object entry = elements.get( i ); + if ( collection.needsUpdating( entry, i, elementType ) ) { + int offset = 1; + + if ( useBatch ) { + if ( updateBatchKey == null ) { + updateBatchKey = new BasicBatchKey( + getRole() + "#UPDATE", + expectation + ); + } + st = session + .getJdbcCoordinator() + .getBatch( updateBatchKey ) + .getBatchStatement( sql, callable ); + } + else { + st = session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql, callable ); + } + + try { + offset += expectation.prepare( st ); + int loc = writeElement( st, collection.getElement( entry ), offset, session ); + if ( hasIdentifier ) { + writeIdentifier( st, collection.getIdentifier( entry, i ), loc, session ); + } + else { + loc = writeKey( st, id, loc, session ); + if ( hasIndex && !indexContainsFormula ) { + writeIndexToWhere( st, collection.getIndex( entry, i, this ), loc, session ); + } + else { + writeElementToWhere( st, collection.getSnapshotElement( entry, i ), loc, session ); + } + } + + if ( useBatch ) { + session.getJdbcCoordinator() + .getBatch( updateBatchKey ) + .addToBatch(); + } + else { + expectation.verifyOutcome( + session.getJdbcCoordinator().getResultSetReturn().executeUpdate( + st + ), st, -1 + ); + } + } + catch (SQLException sqle) { + if ( useBatch ) { + session.getJdbcCoordinator().abortBatch(); + } + throw sqle; + } + finally { + if ( !useBatch ) { + session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st ); + session.getJdbcCoordinator().afterStatementExecution(); + } + } + count++; + } + return count; + } + public String selectFragment( Joinable rhs, String rhsAlias, diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java index 7cb382873ec3..6e21f10ced45 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java @@ -13,13 +13,14 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; import org.hibernate.metadata.CollectionMetadata; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.walking.spi.CollectionDefinition; import org.hibernate.type.CollectionType; @@ -47,7 +48,7 @@ * by the persister * *
  • - * {@link CollectionRegionAccessStrategy} - the second level caching strategy for this collection + * {@link CollectionDataAccess} - the second level caching strategy for this collection *
  • *
  • * {@link org.hibernate.persister.spi.PersisterCreationContext} - access to additional @@ -72,7 +73,10 @@ public interface CollectionPersister extends CollectionDefinition { /** * Get the cache */ - CollectionRegionAccessStrategy getCacheAccessStrategy(); + CollectionDataAccess getCacheAccessStrategy(); + + NavigableRole getNavigableRole(); + /** * Get the cache structure */ diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyMapping.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyMapping.java index 0283794ade62..8dbe52ecbda6 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyMapping.java @@ -27,7 +27,7 @@ public Type toType(String propertyName) throws QueryException { } else if ( propertyName.equals(CollectionPropertyNames.COLLECTION_INDICES) ) { if ( !memberPersister.hasIndex() ) { - throw new QueryException("unindexed collection beforeQuery indices()"); + throw new QueryException("unindexed collection before indices()"); } return memberPersister.getIndexType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/NamedQueryCollectionInitializer.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/NamedQueryCollectionInitializer.java index a28ecf6e7983..053fbfd92ac8 100755 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/NamedQueryCollectionInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/NamedQueryCollectionInitializer.java @@ -46,7 +46,7 @@ public void initialize(Serializable key, SharedSessionContractImplementor sessio ); } else { - nativeQuery.setParameter( 0, key, persister.getKeyType() ); + nativeQuery.setParameter( 1, key, persister.getKeyType() ); } nativeQuery.setCollectionKey( key ).setFlushMode( FlushMode.MANUAL ).list(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java index 4afd3436b418..3c4cec89d54e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java @@ -15,7 +15,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -64,7 +64,7 @@ public boolean isCascadeDeleteEnabled() { public OneToManyPersister( Collection collectionBinding, - CollectionRegionAccessStrategy cacheAccessStrategy, + CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) throws MappingException, CacheException { super( collectionBinding, cacheAccessStrategy, creationContext ); cascadeDeleteEnabled = collectionBinding.getKey().isCascadeDeleteEnabled() 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 e0198baf2bf1..44eba39291e4 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 @@ -14,18 +14,22 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.hibernate.AssertionFailure; import org.hibernate.EntityMode; import org.hibernate.FetchMode; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; @@ -34,12 +38,15 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl; @@ -68,9 +75,10 @@ import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.Mapping; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper; import org.hibernate.engine.spi.PersistentAttributeInterceptable; -import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.ValueInclusion; @@ -87,6 +95,7 @@ import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectations; import org.hibernate.jdbc.TooManyRowsAffectedException; +import org.hibernate.loader.custom.sql.SQLQueryParser; import org.hibernate.loader.entity.BatchingEntityLoaderBuilder; import org.hibernate.loader.entity.CascadeEntityLoader; import org.hibernate.loader.entity.DynamicBatchingEntityLoaderBuilder; @@ -94,11 +103,14 @@ import org.hibernate.loader.entity.UniqueEntityLoader; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; +import org.hibernate.mapping.Formula; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.Subclass; import org.hibernate.mapping.Table; import org.hibernate.metadata.ClassMetadata; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.spi.PersisterCreationContext; import org.hibernate.persister.walking.internal.EntityIdentifierDefinitionHelper; @@ -140,16 +152,21 @@ */ public abstract class AbstractEntityPersister implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable, - SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { + SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractEntityPersister.class ); public static final String ENTITY_CLASS = "class"; + private final NavigableRole navigableRole; + // moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private final SessionFactoryImplementor factory; - private final EntityRegionAccessStrategy cacheAccessStrategy; - private final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy; + private final boolean canReadFromCache; + private final boolean canWriteToCache; + private final boolean invalidateCache; + private final EntityDataAccess cacheAccessStrategy; + private final NaturalIdDataAccess naturalIdRegionAccessStrategy; private final boolean isLazyPropertiesCacheable; private final CacheEntryHelper cacheEntryHelper; private final EntityMetamodel entityMetamodel; @@ -187,7 +204,7 @@ public abstract class AbstractEntityPersister private final boolean[] propertyUniqueness; private final boolean[] propertySelectable; - private final List lobProperties = new ArrayList(); + private final List lobProperties = new ArrayList<>(); //information about lazy properties of this class private final String[] lazyPropertyNames; @@ -224,11 +241,13 @@ public abstract class AbstractEntityPersister // dynamic filters attached to the class-level private final FilterHelper filterHelper; - private final Set affectingFetchProfileNames = new HashSet(); + private final Set affectingFetchProfileNames = new HashSet<>(); private final Map uniqueKeyLoaders = new HashMap(); private final Map lockers = new HashMap(); - private final Map loaders = new HashMap(); + private UniqueEntityLoader noneLockLoader; + private UniqueEntityLoader readLockLoader; + private final Map loaders = new ConcurrentHashMap<>(); // SQL strings private String sqlVersionSelectString; @@ -330,7 +349,12 @@ public String getDiscriminatorColumnReaders() { } public String getDiscriminatorColumnReaderTemplate() { - return DISCRIMINATOR_ALIAS; + if ( getEntityMetamodel().getSubclassEntityNames().size() == 1 ) { + return getDiscriminatorSQLValue(); + } + else { + return Template.TEMPLATE + "." + DISCRIMINATOR_ALIAS; + } } protected String getDiscriminatorAlias() { @@ -506,15 +530,29 @@ protected boolean[] getPropertySelectable() { @SuppressWarnings("UnnecessaryBoxing") public AbstractEntityPersister( final PersistentClass persistentClass, - final EntityRegionAccessStrategy cacheAccessStrategy, - final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy, + final EntityDataAccess cacheAccessStrategy, + final NaturalIdDataAccess naturalIdRegionAccessStrategy, final PersisterCreationContext creationContext) throws HibernateException { // moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ this.factory = creationContext.getSessionFactory(); - this.cacheAccessStrategy = cacheAccessStrategy; - this.naturalIdRegionAccessStrategy = naturalIdRegionAccessStrategy; - isLazyPropertiesCacheable = persistentClass.isLazyPropertiesCacheable(); + + this.navigableRole = new NavigableRole( persistentClass.getEntityName() ); + + if ( creationContext.getSessionFactory().getSessionFactoryOptions().isSecondLevelCacheEnabled() ) { + this.canWriteToCache = determineCanWriteToCache( persistentClass, cacheAccessStrategy ); + this.canReadFromCache = determineCanReadFromCache( persistentClass, cacheAccessStrategy ); + this.cacheAccessStrategy = cacheAccessStrategy; + this.isLazyPropertiesCacheable = persistentClass.getRootClass().isLazyPropertiesCacheable(); + this.naturalIdRegionAccessStrategy = naturalIdRegionAccessStrategy; + } + else { + this.canWriteToCache = false; + this.canReadFromCache = false; + this.cacheAccessStrategy = null; + this.isLazyPropertiesCacheable = true; + this.naturalIdRegionAccessStrategy = null; + } this.entityMetamodel = new EntityMetamodel( persistentClass, this, factory ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); @@ -632,6 +670,7 @@ public AbstractEntityPersister( colAliases[k] = thing.getAlias( dialect, prop.getValue().getTable() ); if ( thing.isFormula() ) { foundFormula = true; + ( (Formula) thing ).setFormula( substituteBrackets( ( (Formula) thing ).getFormula() ) ); formulaTemplates[k] = thing.getTemplate( dialect, factory.getSqlFunctionRegistry() ); } else { @@ -648,7 +687,13 @@ public AbstractEntityPersister( propertyColumnWriters[i] = colWriters; propertyColumnAliases[i] = colAliases; - if ( lazyAvailable && prop.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); + + if ( lazy ) { lazyNames.add( prop.getName() ); lazyNumbers.add( i ); lazyTypes.add( prop.getValue().getType() ); @@ -718,7 +763,11 @@ public AbstractEntityPersister( int[] colnos = new int[prop.getColumnSpan()]; int[] formnos = new int[prop.getColumnSpan()]; int l = 0; - Boolean lazy = Boolean.valueOf( prop.isLazy() && lazyAvailable ); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); while ( colIter.hasNext() ) { Selectable thing = (Selectable) colIter.next(); if ( thing.isFormula() ) { @@ -734,7 +783,7 @@ public AbstractEntityPersister( else { Column col = (Column) thing; String colName = col.getQuotedName( dialect ); - colnos[l] = columns.size(); //beforeQuery add :-) + colnos[l] = columns.size(); //before add :-) formnos[l] = -1; columns.add( colName ); cols[l] = colName; @@ -828,6 +877,78 @@ public AbstractEntityPersister( this.cacheEntryHelper = buildCacheEntryHelper(); + if ( creationContext.getSessionFactory().getSessionFactoryOptions().isSecondLevelCacheEnabled() ) { + this.invalidateCache = canWriteToCache && determineWhetherToInvalidateCache( persistentClass, creationContext ); + } + else { + this.invalidateCache = false; + } + + } + + @SuppressWarnings("RedundantIfStatement") + private boolean determineWhetherToInvalidateCache( + PersistentClass persistentClass, + PersisterCreationContext creationContext) { + if ( hasFormulaProperties() ) { + return true; + } + + if ( isVersioned() ) { + return false; + } + + if ( entityMetamodel.isDynamicUpdate() ) { + return false; + } + + // We need to check whether the user may have circumvented this logic (JPA TCK) + final boolean complianceEnabled = creationContext.getSessionFactory() + .getSessionFactoryOptions() + .getJpaCompliance() + .isJpaCacheComplianceEnabled(); + if ( complianceEnabled ) { + // The JPA TCK (inadvertently, but still...) requires that we cache + // entities with secondary tables even though Hibernate historically + // invalidated them + return false; + } + + if ( persistentClass.getJoinClosureSpan() >= 1 ) { + // todo : this should really consider optionality of the secondary tables in count + // non-optional tables do not cause this bypass + return true; + } + + return false; + } + + private boolean determineCanWriteToCache(PersistentClass persistentClass, EntityDataAccess cacheAccessStrategy) { + if ( cacheAccessStrategy == null ) { + return false; + } + + return persistentClass.isCached(); + } + + @SuppressWarnings("unchecked") + private boolean determineCanReadFromCache(PersistentClass persistentClass, EntityDataAccess cacheAccessStrategy) { + if ( cacheAccessStrategy == null ) { + return false; + } + + if ( persistentClass.isCached() ) { + return true; + } + + final Iterator subclassIterator = persistentClass.getSubclassIterator(); + while ( subclassIterator.hasNext() ) { + final Subclass subclass = subclassIterator.next(); + if ( subclass.isCached() ) { + return true; + } + } + return false; } protected CacheEntryHelper buildCacheEntryHelper() { @@ -917,7 +1038,7 @@ protected Map generateLazySelectStringsByFetchGroup() { public Object initializeLazyProperty(String fieldName, Object entity, SharedSessionContractImplementor session) { final EntityEntry entry = session.getPersistenceContext().getEntry( entity ); - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; if ( hasCollections() ) { @@ -944,10 +1065,10 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess session.getPersistenceContext().addUninitializedCollection( persister, collection, key ); } - // HHH-11161 Initialize, if the collection is not extra lazy - if ( !persister.isExtraLazy() ) { - session.initializeCollection( collection, false ); - } +// // HHH-11161 Initialize, if the collection is not extra lazy +// if ( !persister.isExtraLazy() ) { +// session.initializeCollection( collection, false ); +// } interceptor.attributeInitialized( fieldName ); if ( collectionType.isArrayType() ) { @@ -985,17 +1106,20 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess ); } - if ( session.getCacheMode().isGetEnabled() && hasCache() && isLazyPropertiesCacheable() ) { - final EntityRegionAccessStrategy cache = getCacheAccessStrategy(); - final Object cacheKey = cache.generateCacheKey(id, this, session.getFactory(), session.getTenantIdentifier() ); - final Object ce = CacheHelper.fromSharedCache( session, cacheKey, cache ); + if ( session.getCacheMode().isGetEnabled() && canReadFromCache() && isLazyPropertiesCacheable() ) { + final EntityDataAccess cacheAccess = getCacheAccessStrategy(); + final Object cacheKey = cacheAccess.generateCacheKey(id, this, session.getFactory(), session.getTenantIdentifier() ); + final Object ce = CacheHelper.fromSharedCache( session, cacheKey, cacheAccess ); if ( ce != null ) { final CacheEntry cacheEntry = (CacheEntry) getCacheEntryStructure().destructure( ce, factory ); final Object initializedValue = initializeLazyPropertiesFromCache( fieldName, entity, session, entry, cacheEntry ); - interceptor.attributeInitialized( fieldName ); + if (initializedValue != LazyPropertyInitializer.UNFETCHED_PROPERTY) { + // The following should be redundant, since the setter should have set this already. + // interceptor.attributeInitialized(fieldName); - // NOTE EARLY EXIT!!! - return initializedValue; + // NOTE EARLY EXIT!!! + return initializedValue; + } } } @@ -1035,10 +1159,10 @@ private Object initializeLazyPropertiesFromDatastore( throw new AssertionFailure( "no lazy properties" ); } - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; - LOG.trace( "Initializing lazy properties from datastore" ); + LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() @@ -1068,7 +1192,6 @@ private Object initializeLazyPropertiesFromDatastore( rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps ); rs.next(); } - final Object[] snapshot = entry.getLoadedState(); for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) { final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() ); @@ -1099,7 +1222,7 @@ private Object initializeLazyPropertiesFromDatastore( fieldName, entity, session, - snapshot, + entry, fetchGroupAttributeDescriptor.getLazyIndex(), selectedValue ); @@ -1148,15 +1271,25 @@ private Object initializeLazyPropertiesFromCache( Object result = null; Serializable[] disassembledValues = cacheEntry.getDisassembledState(); - final Object[] snapshot = entry.getLoadedState(); for ( int j = 0; j < lazyPropertyNames.length; j++ ) { - final Object propValue = lazyPropertyTypes[j].assemble( - disassembledValues[lazyPropertyNumbers[j]], - session, - entity - ); - if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) { - result = propValue; + final Serializable cachedValue = disassembledValues[lazyPropertyNumbers[j]]; + final Type lazyPropertyType = lazyPropertyTypes[j]; + final String propertyName = lazyPropertyNames[j]; + if (cachedValue == LazyPropertyInitializer.UNFETCHED_PROPERTY) { + if (fieldName.equals(propertyName)) { + result = LazyPropertyInitializer.UNFETCHED_PROPERTY; + } + // don't try to initialize the unfetched property + } + else { + final Object propValue = lazyPropertyType.assemble( + cachedValue, + session, + entity + ); + if ( initializeLazyProperty( fieldName, entity, session, entry, j, propValue ) ) { + result = propValue; + } } } @@ -1169,13 +1302,17 @@ private boolean initializeLazyProperty( final String fieldName, final Object entity, final SharedSessionContractImplementor session, - final Object[] snapshot, + final EntityEntry entry, final int j, final Object propValue) { setPropertyValue( entity, lazyPropertyNumbers[j], propValue ); - if ( snapshot != null ) { + if ( entry.getLoadedState() != null ) { // object have been loaded with setReadOnly(true); HHH-2236 - snapshot[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); + entry.getLoadedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); + } + // If the entity has deleted state, then update that as well + if ( entry.getDeletedState() != null ) { + entry.getDeletedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); } return fieldName.equals( lazyPropertyNames[j] ); } @@ -1186,6 +1323,11 @@ public boolean isBatchable() { || getFactory().getSessionFactoryOptions().isJdbcBatchVersionedData(); } + @Override + public NavigableRole getNavigableRole() { + return navigableRole; + } + public Serializable[] getQuerySpaces() { return getPropertySpaces(); } @@ -1240,8 +1382,7 @@ protected boolean[] getSubclassFormulaLazyiness() { * item. */ public boolean isCacheInvalidationRequired() { - return hasFormulaProperties() || - ( !isVersioned() && ( entityMetamodel.isDynamicUpdate() || getTableSpan() > 1 ) ); + return invalidateCache; } public boolean isLazyPropertiesCacheable() { @@ -1255,7 +1396,7 @@ public String selectFragment(String alias, String suffix) { public String[] getIdentifierAliases(String suffix) { // NOTE: this assumes something about how propertySelectFragment is implemented by the subclass! - // was toUnqotedAliasStrings( getIdentifierColumnNames() ) beforeQuery - now tried + // was toUnqotedAliasStrings( getIdentifierColumnNames() ) before - now tried // to remove that unqoting and missing aliases.. return new Alias( suffix ).toAliasStrings( getIdentifierAliases() ); } @@ -1267,7 +1408,7 @@ public String[] getPropertyAliases(String suffix, int i) { public String getDiscriminatorAlias(String suffix) { // NOTE: this assumes something about how propertySelectFragment is implemented by the subclass! - // was toUnqotedAliasStrings( getdiscriminatorColumnName() ) beforeQuery - now tried + // was toUnqotedAliasStrings( getdiscriminatorColumnName() ) before - now tried // to remove that unqoting and missing aliases.. return entityMetamodel.hasSubclasses() ? new Alias( suffix ).toAliasString( getDiscriminatorAlias() ) : @@ -1564,7 +1705,7 @@ public boolean includeProperty(int propertyNumber) { fromJoinFragment( getRootAlias(), true, false ); String whereClause = new StringBuilder() - .append( StringHelper.join( "=? and ", aliasedIdColumns ) ) + .append( String.join( "=? and ", aliasedIdColumns ) ) .append( "=?" ) .append( whereJoinFragment( getRootAlias(), true, false ) ) .toString(); @@ -1576,8 +1717,8 @@ public boolean includeProperty(int propertyNumber) { .toStatementString(); } - protected static interface InclusionChecker { - public boolean includeProperty(int propertyNumber); + protected interface InclusionChecker { + boolean includeProperty(int propertyNumber); } protected String concretePropertySelectFragment(String alias, final boolean[] includeProperty) { @@ -1623,7 +1764,7 @@ protected String generateSnapshotSelectString() { } String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() ); - String selectClause = StringHelper.join( ", ", aliasedIdColumns ) + + String selectClause = String.join( ", ", aliasedIdColumns ) + concretePropertySelectFragment( getRootAlias(), getPropertyUpdateability() ); String fromClause = fromTableFragment( getRootAlias() ) + @@ -1631,7 +1772,7 @@ protected String generateSnapshotSelectString() { String whereClause = new StringBuilder() .append( - StringHelper.join( + String.join( "=? and ", aliasedIdColumns ) @@ -1675,7 +1816,7 @@ public Object forceVersionIncrement(Serializable id, Object currentVersion, Shar // todo : cache this sql... String versionIncrementString = generateVersionIncrementUpdateString(); - PreparedStatement st = null; + PreparedStatement st; try { st = session .getJdbcCoordinator() @@ -1714,7 +1855,7 @@ private String generateVersionIncrementUpdateString() { update.setComment( "forced version increment" ); } update.addColumn( getVersionColumnName() ); - update.addPrimaryKeyColumns( getIdentifierColumnNames() ); + update.addPrimaryKeyColumns( rootTableKeyColumnNames ); update.setVersionColumnName( getVersionColumnName() ); return update.toStatementString(); } @@ -2166,7 +2307,7 @@ protected void initSubclassPropertyAliasesMap(PersistentClass model) throws Mapp new String[] {idColumnNames[i]} ); } -// if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) { +// if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyNames() ) ) { if ( hasIdentifierProperty() ) { subclassPropertyAliases.put( getIdentifierPropertyName() + "." + idPropertyNames[i], @@ -2394,8 +2535,7 @@ protected boolean check( catch (StaleStateException e) { if ( !isNullableTable( tableNumber ) ) { if ( getFactory().getStatistics().isStatisticsEnabled() ) { - getFactory().getStatisticsImplementor() - .optimisticFailure( getEntityName() ); + getFactory().getStatistics().optimisticFailure( getEntityName() ); } throw new StaleObjectStateException( getEntityName(), id ); } @@ -2514,7 +2654,7 @@ else if ( isAllOrDirtyOptLocking() && oldFields != null ) { return hasColumns ? update.toStatementString() : null; } - private boolean checkVersion(final boolean[] includeProperty) { + protected final boolean checkVersion(final boolean[] includeProperty) { return includeProperty[getVersionProperty()] || entityMetamodel.isVersionGenerated(); } @@ -2635,9 +2775,40 @@ protected String generateIdentityInsertString(boolean[] includeProperty) { // add normal properties except lobs for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { - if ( includeProperty[i] && isPropertyOfTable( i, 0 ) && !lobProperties.contains( i ) ) { - // this property belongs on the table and is to be inserted - insert.addColumns( getPropertyColumnNames( i ), propertyColumnInsertable[i], propertyColumnWriters[i] ); + if ( isPropertyOfTable( i, 0 ) && !lobProperties.contains( i ) ) { + final InDatabaseValueGenerationStrategy generationStrategy = entityMetamodel.getInDatabaseValueGenerationStrategies()[i]; + + if ( includeProperty[i] ) { + insert.addColumns( + getPropertyColumnNames( i ), + propertyColumnInsertable[i], + propertyColumnWriters[i] + ); + } + else if ( generationStrategy != null && + generationStrategy.getGenerationTiming().includesInsert() && + generationStrategy.referenceColumnsInSql() ) { + + final String[] values; + + if ( generationStrategy.getReferencedColumnValues() == null ) { + values = propertyColumnWriters[i]; + } + else { + values = new String[propertyColumnWriters[i].length]; + + for ( int j = 0; j < values.length; j++ ) { + values[j] = ( generationStrategy.getReferencedColumnValues()[j] != null ) ? + generationStrategy.getReferencedColumnValues()[j] : + propertyColumnWriters[i][j]; + } + } + insert.addColumns( + getPropertyColumnNames( i ), + propertyColumnInsertable[i], + values + ); + } } } @@ -2746,6 +2917,16 @@ private int dehydrateId( final SharedSessionContractImplementor session, int index) throws SQLException { if ( rowId != null ) { + if ( LOG.isTraceEnabled() ) { + LOG.tracev( + String.format( + "binding parameter [%s] as ROWID - [%s]", + index, + rowId + ) + ); + } + ps.setObject( index, rowId ); return 1; } @@ -2933,7 +3114,7 @@ public String getSelectByUniqueKeyString(String propertyName) { * Perform an SQL INSERT. *

    * This for is used for all non-root tables as well as the root table - * in cases where the identifier value is known beforeQuery the insert occurs. + * in cases where the identifier value is known before the insert occurs. */ protected void insert( final Serializable id, @@ -2963,9 +3144,11 @@ protected void insert( // TODO : shouldn't inserts be Expectations.NONE? final Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] ); - // we can't batch joined inserts, *especially* not if it is an identity insert; - // nor can we batch statements where the expectation is based on an output param - final boolean useBatch = j == 0 && expectation.canBeBatched(); + final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize(); + final boolean useBatch = expectation.canBeBatched() && + jdbcBatchSizeToUse > 1 && + getIdentifierGenerator().supportsJdbcBatchInserts(); + if ( useBatch && inserBatchKey == null ) { inserBatchKey = new BasicBatchKey( getEntityName() + "#INSERT", @@ -3009,9 +3192,8 @@ protected void insert( .executeUpdate( insert ), insert, -1 ); } - } - catch (SQLException e) { + catch (SQLException | JDBCException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } @@ -3104,7 +3286,8 @@ protected boolean update( final SharedSessionContractImplementor session) throws HibernateException { final Expectation expectation = Expectations.appropriateExpectation( updateResultCheckStyles[j] ); - final boolean useBatch = j == 0 && expectation.canBeBatched() && isBatchable(); //note: updates to joined tables can't be batched... + final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize(); + final boolean useBatch = expectation.canBeBatched() && isBatchable() && jdbcBatchSizeToUse > 1; if ( useBatch && updateBatchKey == null ) { updateBatchKey = new BasicBatchKey( getEntityName() + "#UPDATE", @@ -3292,7 +3475,7 @@ protected void delete( getIdentifierType().nullSafeSet( delete, id, index, session ); index += getIdentifierColumnSpan(); - // We should use the _current_ object state (ie. afterQuery any updates that occurred during flush) + // We should use the _current_ object state (ie. after any updates that occurred during flush) if ( useVersion ) { getVersionType().nullSafeSet( delete, version, index, session ); @@ -3366,7 +3549,7 @@ private String[] getUpdateStrings(boolean byRowId, boolean lazy) { public void update( final Serializable id, final Object[] fields, - final int[] dirtyFields, + int[] dirtyFields, final boolean hasDirtyCollection, final Object[] oldFields, final Object oldVersion, @@ -3376,18 +3559,38 @@ public void update( // apply any pre-update in-memory value generation if ( getEntityMetamodel().hasPreUpdateGeneratedValues() ) { - final InMemoryValueGenerationStrategy[] strategies = getEntityMetamodel().getInMemoryValueGenerationStrategies(); - for ( int i = 0; i < strategies.length; i++ ) { - if ( strategies[i] != null && strategies[i].getGenerationTiming().includesUpdate() ) { - fields[i] = strategies[i].getValueGenerator().generateValue( (Session) session, object ); - setPropertyValue( object, i, fields[i] ); - // todo : probably best to add to dirtyFields if not-null + final InMemoryValueGenerationStrategy[] valueGenerationStrategies = getEntityMetamodel().getInMemoryValueGenerationStrategies(); + int valueGenerationStrategiesSize = valueGenerationStrategies.length; + if ( valueGenerationStrategiesSize != 0 ) { + int[] fieldsPreUpdateNeeded = new int[valueGenerationStrategiesSize]; + for ( int i = 0; i < valueGenerationStrategiesSize; i++ ) { + if ( valueGenerationStrategies[i] != null && valueGenerationStrategies[i].getGenerationTiming() + .includesUpdate() ) { + fields[i] = valueGenerationStrategies[i].getValueGenerator().generateValue( + (Session) session, + object + ); + setPropertyValue( object, i, fields[i] ); + fieldsPreUpdateNeeded[i] = i; + } + } +// if ( fieldsPreUpdateNeeded.length != 0 ) { +// if ( dirtyFields != null ) { +// dirtyFields = ArrayHelper.join( fieldsPreUpdateNeeded, dirtyFields ); +// } +// else if ( hasDirtyCollection ) { +// dirtyFields = fieldsPreUpdateNeeded; +// } +// // no dirty fields and no dirty collections so no update needed ??? +// } + if ( fieldsPreUpdateNeeded.length != 0 && dirtyFields != null ) { + dirtyFields = ArrayHelper.join( fieldsPreUpdateNeeded, dirtyFields ); } } } - //note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-beforeQuery-update - // oldFields==null just means we had no snapshot to begin with (we might have used select-beforeQuery-update to get the dirtyFields) + //note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-before-update + // oldFields==null just means we had no snapshot to begin with (we might have used select-before-update to get the dirtyFields) final boolean[] tableUpdateNeeded = getTableUpdateNeeded( dirtyFields, hasDirtyCollection ); final int span = getTableSpan(); @@ -3415,7 +3618,7 @@ public void update( else if ( !isModifiableEntity( entry ) ) { // We need to generate UPDATE SQL when a non-modifiable entity (e.g., read-only or immutable) // needs: - // - to have references to transient entities set to null beforeQuery being deleted + // - to have references to transient entities set to null before being deleted // - to have version incremented do to a "dirty" association // If dirtyFields == null, then that means that there are no dirty properties to // to be updated; an empty array for the dirty fields needs to be passed to @@ -3664,7 +3867,7 @@ public String fromJoinFragment(String alias, boolean innerJoin, boolean includeS alias, innerJoin, includeSubclasses, - Collections.emptySet() + Collections.emptySet() ).toFromFragmentString(); } @@ -3689,7 +3892,7 @@ public String whereJoinFragment(String alias, boolean innerJoin, boolean include alias, innerJoin, includeSubclasses, - Collections.emptySet() + Collections.emptySet() ).toWhereFragmentString(); } @@ -3839,7 +4042,7 @@ protected String createFrom(int tableNumber, String alias) { protected String createWhereByKey(int tableNumber, String alias) { //TODO: move to .sql package, and refactor with similar things! - return StringHelper.join( + return String.join( "=? and ", StringHelper.qualify( alias, getSubclassTableKeyColumns( tableNumber ) ) ) + "=?"; @@ -3884,8 +4087,8 @@ private String getRootAlias() { } /** - * Post-construct is a callback for AbstractEntityPersister subclasses to call afterQuery they are all done with their - * constructor processing. It allows AbstractEntityPersister to extend its construction afterQuery all subclass-specific + * Post-construct is a callback for AbstractEntityPersister subclasses to call after they are all done with their + * constructor processing. It allows AbstractEntityPersister to extend its construction after all subclass-specific * details have been handled. * * @param mapping The mapping @@ -3917,16 +4120,16 @@ private void doLateInit() { for ( int j = 0; j < joinSpan; j++ ) { sqlInsertStrings[j] = customSQLInsert[j] == null ? generateInsertString( getPropertyInsertability(), j ) : - customSQLInsert[j]; + substituteBrackets( customSQLInsert[j]); sqlUpdateStrings[j] = customSQLUpdate[j] == null ? generateUpdateString( getPropertyUpdateability(), j, false ) : - customSQLUpdate[j]; + substituteBrackets( customSQLUpdate[j]); sqlLazyUpdateStrings[j] = customSQLUpdate[j] == null ? generateUpdateString( getNonLazyPropertyUpdateability(), j, false ) : - customSQLUpdate[j]; + substituteBrackets( customSQLUpdate[j]); sqlDeleteStrings[j] = customSQLDelete[j] == null ? generateDeleteString( j ) : - customSQLDelete[j]; + substituteBrackets( customSQLDelete[j]); } tableHasColumns = new boolean[joinSpan]; @@ -3949,7 +4152,7 @@ private void doLateInit() { .getInsertGeneratedIdentifierDelegate( this, getFactory().getDialect(), useGetGeneratedKeys() ); sqlIdentityInsertString = customSQLInsert[0] == null ? generateIdentityInsertString( getPropertyInsertability() ) - : customSQLInsert[0]; + : substituteBrackets( customSQLInsert[0] ); } else { sqlIdentityInsertString = null; @@ -3958,6 +4161,10 @@ private void doLateInit() { logStaticSQL(); } + private String substituteBrackets(String sql) { + return new SubstituteBracketSQLQueryParser( sql, getFactory() ).process(); + } + public final void postInstantiate() throws MappingException { doLateInit(); @@ -3971,68 +4178,34 @@ public final void postInstantiate() throws MappingException { protected void doPostInstantiate() { } - //needed by subclasses to override the createLoader strategy + /** + * "Needed" by subclasses to override the createLoader strategy + * + * @deprecated Because there are better patterns for this + */ + @Deprecated protected Map getLoaders() { return loaders; } //Relational based Persisters should be content with this implementation protected void createLoaders() { - final Map loaders = getLoaders(); - loaders.put( LockMode.NONE, createEntityLoader( LockMode.NONE ) ); + // We load the entity loaders for the most common lock modes. - UniqueEntityLoader readLoader = createEntityLoader( LockMode.READ ); - loaders.put( LockMode.READ, readLoader ); + noneLockLoader = createEntityLoader( LockMode.NONE ); + readLockLoader = createEntityLoader( LockMode.READ ); - //TODO: inexact, what we really need to know is: are any outer joins used? - boolean disableForUpdate = getSubclassTableSpan() > 1 && - hasSubclasses() && - !getFactory().getDialect().supportsOuterJoinForUpdate(); - loaders.put( - LockMode.UPGRADE, - disableForUpdate ? - readLoader : - createEntityLoader( LockMode.UPGRADE ) - ); - loaders.put( - LockMode.UPGRADE_NOWAIT, - disableForUpdate ? - readLoader : - createEntityLoader( LockMode.UPGRADE_NOWAIT ) - ); - loaders.put( - LockMode.UPGRADE_SKIPLOCKED, - disableForUpdate ? - readLoader : - createEntityLoader( LockMode.UPGRADE_SKIPLOCKED ) - ); - loaders.put( - LockMode.FORCE, - disableForUpdate ? - readLoader : - createEntityLoader( LockMode.FORCE ) - ); - loaders.put( - LockMode.PESSIMISTIC_READ, - disableForUpdate ? - readLoader : - createEntityLoader( LockMode.PESSIMISTIC_READ ) - ); - loaders.put( - LockMode.PESSIMISTIC_WRITE, - disableForUpdate ? - readLoader : - createEntityLoader( LockMode.PESSIMISTIC_WRITE ) - ); - loaders.put( - LockMode.PESSIMISTIC_FORCE_INCREMENT, - disableForUpdate ? - readLoader : - createEntityLoader( LockMode.PESSIMISTIC_FORCE_INCREMENT ) - ); - loaders.put( LockMode.OPTIMISTIC, createEntityLoader( LockMode.OPTIMISTIC ) ); - loaders.put( LockMode.OPTIMISTIC_FORCE_INCREMENT, createEntityLoader( LockMode.OPTIMISTIC_FORCE_INCREMENT ) ); + // The loaders for the other lock modes are lazily loaded and will later be stored in this map, + // unless this setting is disabled + if ( ! factory.getSessionFactoryOptions().isDelayBatchFetchLoaderCreationsEnabled() ) { + for ( LockMode lockMode : EnumSet.complementOf( EnumSet.of( LockMode.NONE, LockMode.READ, LockMode.WRITE ) ) ) { + loaders.put( lockMode, createEntityLoader( lockMode ) ); + } + } + + + // And finally, create the internal merge and refresh load plans loaders.put( "merge", @@ -4044,6 +4217,49 @@ protected void createLoaders() { ); } + protected final UniqueEntityLoader getLoaderByLockMode(LockMode lockMode) { + if ( LockMode.NONE == lockMode ) { + return noneLockLoader; + } + else if ( LockMode.READ == lockMode ) { + return readLockLoader; + } + + return loaders.computeIfAbsent( lockMode, this::generateDelayedEntityLoader ); + } + + private UniqueEntityLoader generateDelayedEntityLoader(Object lockModeObject) { + // Unfortunately, the loaders map mixes LockModes and Strings as keys so we need to accept an Object. + // The cast is safe as we will always call this method with a LockMode. + LockMode lockMode = (LockMode) lockModeObject; + + switch ( lockMode ) { + case NONE: + case READ: + case OPTIMISTIC: + case OPTIMISTIC_FORCE_INCREMENT: { + return createEntityLoader( lockMode ); + } + case UPGRADE: + case UPGRADE_NOWAIT: + case UPGRADE_SKIPLOCKED: + case FORCE: + case PESSIMISTIC_READ: + case PESSIMISTIC_WRITE: + case PESSIMISTIC_FORCE_INCREMENT: { + //TODO: inexact, what we really need to know is: are any outer joins used? + boolean disableForUpdate = getSubclassTableSpan() > 1 + && hasSubclasses() + && !getFactory().getDialect().supportsOuterJoinForUpdate(); + + return disableForUpdate ? readLockLoader : createEntityLoader( lockMode ); + } + default: { + throw new IllegalStateException( String.format( Locale.ROOT, "Lock mode %1$s not supported by entity loaders.", lockMode ) ); + } + } + } + protected void createQueryLoader() { if ( loaderName != null ) { queryLoader = new NamedQueryLoader( loaderName, this ); @@ -4073,6 +4289,62 @@ public Object load(Serializable id, Object optionalObject, LockOptions lockOptio return loader.load( id, optionalObject, session, lockOptions ); } + @Override + public Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + final BytecodeEnhancementMetadata enhancementMetadata = getEntityMetamodel().getBytecodeEnhancementMetadata(); + final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) currentInterceptor; + + final EntityKey entityKey = proxyInterceptor.getEntityKey(); + final Serializable identifier = entityKey.getIdentifier(); + final Object loaded = readLockLoader.load( + identifier, + entity, + session, + LockOptions.READ + ); + + if ( loaded == null ) { + final PersistenceContext persistenceContext = session.getPersistenceContext(); + persistenceContext.removeEntry( entity ); + persistenceContext.removeEntity( entityKey ); + session.getFactory().getEntityNotFoundDelegate().handleEntityNotFound( + entityKey.getEntityName(), + identifier + ); + } + + final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata.injectInterceptor( + entity, + identifier, + session + ); + + final Object value; + if ( nameOfAttributeBeingAccessed == null ) { + return null; + } + else if ( interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) ) { + value = getEntityTuplizer().getPropertyValue( entity, nameOfAttributeBeingAccessed ); + } + else { + value = ( (LazyPropertyInitializer) this ).initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ); + } + + return interceptor.readObject( + entity, + nameOfAttributeBeingAccessed, + value + ); + } + + throw new IllegalStateException( ); + } + @Override public List multiLoad(Serializable[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) { return DynamicBatchingEntityLoaderBuilder.INSTANCE.multiLoad( @@ -4123,7 +4395,7 @@ else if ( session.getLoadQueryInfluencers().getInternalFetchProfile() != null && // Next, we consider whether an 'internal' fetch profile has been set. // This indicates a special fetch profile Hibernate needs applied // (for its merge loading process e.g.). - return (UniqueEntityLoader) getLoaders().get( session.getLoadQueryInfluencers().getInternalFetchProfile() ); + return loaders.get( session.getLoadQueryInfluencers().getInternalFetchProfile() ); } else if ( isAffectedByEnabledFetchProfiles( session ) ) { // If the session has associated influencers we need to adjust the @@ -4137,11 +4409,11 @@ else if ( lockOptions.getTimeOut() != LockOptions.WAIT_FOREVER ) { return createEntityLoader( lockOptions, session.getLoadQueryInfluencers() ); } else { - return (UniqueEntityLoader) getLoaders().get( lockOptions.getLockMode() ); + return getLoaderByLockMode( lockOptions.getLockMode() ); } } - private boolean isAllNull(Object[] array, int tableNumber) { + protected final boolean isAllNull(Object[] array, int tableNumber) { for ( int i = 0; i < array.length; i++ ) { if ( isPropertyOfTable( i, tableNumber ) && array[i] != null ) { return false; @@ -4210,7 +4482,6 @@ public int[] findDirty(Object[] currentState, Object[] previousState, Object ent currentState, previousState, propertyColumnUpdateable, - hasUninitializedLazyProperties( entity ), session ); if ( props == null ) { @@ -4242,7 +4513,6 @@ public int[] findModified(Object[] old, Object[] current, Object entity, SharedS old, propertyColumnUpdateable, getPropertyUpdateability(), - hasUninitializedLazyProperties( entity ), session ); if ( props == null ) { @@ -4281,11 +4551,21 @@ public EntityMetamodel getEntityMetamodel() { return entityMetamodel; } + @Override + public boolean canReadFromCache() { + return canReadFromCache; + } + + @Override + public boolean canWriteToCache() { + return canWriteToCache; + } + public boolean hasCache() { - return cacheAccessStrategy != null; + return canWriteToCache; } - public EntityRegionAccessStrategy getCacheAccessStrategy() { + public EntityDataAccess getCacheAccessStrategy() { return cacheAccessStrategy; } @@ -4303,7 +4583,7 @@ public boolean hasNaturalIdCache() { return naturalIdRegionAccessStrategy != null; } - public NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy() { + public NaturalIdDataAccess getNaturalIdCacheAccessStrategy() { return naturalIdRegionAccessStrategy; } @@ -4374,9 +4654,14 @@ public boolean hasLazyProperties() { public void afterReassociate(Object entity, SharedSessionContractImplementor session) { if ( getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { - LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata() + .extractLazyInterceptor( entity ); if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { interceptor.setSession( session ); @@ -4455,8 +4740,8 @@ public Boolean isTransient(Object entity, SharedSessionContractImplementor sessi } // check to see if it is in the second-level cache - if ( session.getCacheMode().isGetEnabled() && hasCache() ) { - final EntityRegionAccessStrategy cache = getCacheAccessStrategy(); + if ( session.getCacheMode().isGetEnabled() && canReadFromCache() ) { + final EntityDataAccess cache = getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( id, this, session.getFactory(), session.getTenantIdentifier() ); final Object ce = CacheHelper.fromSharedCache( session, ck, getCacheAccessStrategy() ); if ( ce != null ) { @@ -4479,7 +4764,7 @@ public boolean isMutable() { return entityMetamodel.isMutable(); } - private boolean isModifiableEntity(EntityEntry entry) { + protected final boolean isModifiableEntity(EntityEntry entry) { return ( entry == null ? isMutable() : entry.isModifiableEntity() ); } @@ -4984,7 +5269,7 @@ public Object[] getNaturalIdentifierSnapshot(Serializable id, SharedSessionContr String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() ); String whereClause = new StringBuilder() .append( - StringHelper.join( + String.join( "=? and ", aliasedIdColumns ) @@ -5195,10 +5480,10 @@ private String generateEntityIdByNaturalIdSql(boolean[] valueNullness) { final String[] aliasedPropertyColumns = StringHelper.qualify( tableAlias, propertyColumnNames ); if ( valueNullness != null && valueNullness[valuesIndex] ) { - whereClause.append( StringHelper.join( " is null and ", aliasedPropertyColumns ) ).append( " is null" ); + whereClause.append( String.join( " is null and ", aliasedPropertyColumns ) ).append( " is null" ); } else { - whereClause.append( StringHelper.join( "=? and ", aliasedPropertyColumns ) ).append( "=?" ); + whereClause.append( String.join( "=? and ", aliasedPropertyColumns ) ).append( "=?" ); } } @@ -5245,6 +5530,11 @@ public EntityTuplizer getEntityTuplizer() { @Override public BytecodeEnhancementMetadata getInstrumentationMetadata() { + return getBytecodeEnhancementMetadata(); + } + + @Override + public BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { return entityMetamodel.getBytecodeEnhancementMetadata(); } @@ -5368,7 +5658,6 @@ public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, // EntityDefinition impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private EntityIdentifierDefinition entityIdentifierDefinition; - private Iterable embeddedCompositeIdentifierAttributes; private Iterable attributeDefinitions; @Override @@ -5392,6 +5681,36 @@ public Iterable getAttributes() { return attributeDefinitions; } + public String[][] getPolymorphicJoinColumns(String lhsTableAlias, String propertyPath) { + Set subclassEntityNames = getEntityMetamodel().getSubclassEntityNames(); + // We will collect all the join columns from the LHS subtypes here + List polymorphicJoinColumns = new ArrayList<>( subclassEntityNames.size() ); + + String[] joinColumns; + + OUTER: + for ( String subclassEntityName : subclassEntityNames ) { + AbstractEntityPersister subclassPersister = (AbstractEntityPersister) getFactory() + .getMetamodel() + .entityPersister( subclassEntityName ); + joinColumns = subclassPersister.toColumns( lhsTableAlias, propertyPath ); + + if ( joinColumns.length == 0 ) { + // The subtype does not have a "concrete" mapping for the property path + continue; + } + + // Check for duplicates like this since we will mostly have just a few candidates + for ( String[] existingColumns : polymorphicJoinColumns ) { + if ( Arrays.deepEquals( existingColumns, joinColumns ) ) { + continue OUTER; + } + } + polymorphicJoinColumns.add( joinColumns ); + } + + return ArrayHelper.to2DStringArray( polymorphicJoinColumns ); + } private void prepareEntityIdentifierDefinition() { if ( entityIdentifierDefinition != null ) { @@ -5479,7 +5798,7 @@ private void collectAttributeDefinitions() { // to try and drive SQL generation on these (which we do ultimately). A possible solution there // would be to delay all SQL generation until postInstantiate - Map attributeDefinitionsByName = new LinkedHashMap(); + Map attributeDefinitionsByName = new LinkedHashMap<>(); collectAttributeDefinitions( attributeDefinitionsByName, getEntityMetamodel() ); @@ -5499,7 +5818,7 @@ private void collectAttributeDefinitions() { // } this.attributeDefinitions = Collections.unmodifiableList( - new ArrayList( attributeDefinitionsByName.values() ) + new ArrayList<>( attributeDefinitionsByName.values() ) ); // // todo : leverage the attribute definitions housed on EntityMetamodel // // for that to work, we'd have to be able to walk our super entity persister(s) @@ -5551,5 +5870,15 @@ private void collectAttributeDefinitions() { // }; } + private static class SubstituteBracketSQLQueryParser extends SQLQueryParser { + SubstituteBracketSQLQueryParser(String queryString, SessionFactoryImplementor factory) { + super( queryString, null, factory ); + } + + @Override + public String process() { + return substituteBrackets( getOriginalQueryString() ); + } + } } 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 13bac0dd2773..59c9f3cbf2b8 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 @@ -7,19 +7,29 @@ package org.hibernate.persister.entity; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.hibernate.MappingException; import org.hibernate.QueryException; +import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.PersistentClass; import org.hibernate.sql.Template; +import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; +import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; +import org.hibernate.type.SpecialOneToOneType; import org.hibernate.type.Type; /** @@ -30,11 +40,12 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractPropertyMapping.class ); - private final Map typesByPropertyPath = new HashMap(); - private final Map columnsByPropertyPath = new HashMap(); - private final Map columnReadersByPropertyPath = new HashMap(); - private final Map columnReaderTemplatesByPropertyPath = new HashMap(); - private final Map formulaTemplatesByPropertyPath = new HashMap(); + private final Map typesByPropertyPath = new HashMap<>(); + private final Set duplicateIncompatiblePaths = new HashSet<>(); + private final Map columnsByPropertyPath = new HashMap<>(); + private final Map columnReadersByPropertyPath = new HashMap<>(); + private final Map columnReaderTemplatesByPropertyPath = new HashMap<>(); + private final Map formulaTemplatesByPropertyPath = new HashMap<>(); public String[] getIdentifierColumnNames() { throw new UnsupportedOperationException( "one-to-one is not supported here" ); @@ -109,6 +120,35 @@ public String[] toColumns(String propertyName) throws QueryException { return result; } + private void logDuplicateRegistration(String path, Type existingType, Type type) { + if ( LOG.isTraceEnabled() ) { + LOG.tracev( + "Skipping duplicate registration of path [{0}], existing type = [{1}], incoming type = [{2}]", + path, + existingType, + type + ); + } + } + + private void logIncompatibleRegistration(String path, Type existingType, Type type) { + if ( LOG.isTraceEnabled() ) { + LOG.tracev( + "Skipped adding attribute [{1}] to base-type [{0}] as more than one sub-type defined the attribute using incompatible types (strictly speaking the attributes are not inherited); existing type = [{2}], incoming type = [{3}]", + getEntityName(), + path, + existingType, + type + ); + } + } + + /** + * Only kept around for compatibility reasons since this seems to be API. + * + * @deprecated Use {@link #addPropertyPath(String, Type, String[], String[], String[], String[], Mapping)} instead + */ + @Deprecated protected void addPropertyPath( String path, Type type, @@ -116,25 +156,124 @@ protected void addPropertyPath( String[] columnReaders, String[] columnReaderTemplates, String[] formulaTemplates) { - // TODO : not quite sure yet of the difference, but this is only needed from annotations for @Id @ManyToOne support - if ( typesByPropertyPath.containsKey( path ) ) { - if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Skipping duplicate registration of path [{0}], existing type = [{1}], incoming type = [{2}]", - path, - typesByPropertyPath.get( path ), - type - ); + addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates, null ); + } + + protected void addPropertyPath( + String path, + Type type, + String[] columns, + String[] columnReaders, + String[] columnReaderTemplates, + String[] formulaTemplates, + Mapping factory) { + Type existingType = typesByPropertyPath.get( path ); + if ( existingType != null || duplicateIncompatiblePaths.contains( path ) ) { + // If types match or the new type is not an association type, there is nothing for us to do + if ( type == existingType || existingType == null || !( type instanceof AssociationType ) ) { + logDuplicateRegistration( path, existingType, type ); + } + else if ( !( existingType instanceof AssociationType ) ) { + // Workaround for org.hibernate.cfg.annotations.PropertyBinder.bind() adding a component for *ToOne ids + logDuplicateRegistration( path, existingType, type ); + } + else { + if ( type instanceof AnyType && existingType instanceof AnyType ) { + // TODO: not sure how to handle any types. For now we just return and let the first type dictate what type the property has... + } + else { + Type commonType = null; + MetadataImplementor metadata = (MetadataImplementor) factory; + if ( type instanceof CollectionType && existingType instanceof CollectionType ) { + Collection thisCollection = metadata.getCollectionBinding( ( (CollectionType) existingType ).getRole() ); + Collection otherCollection = metadata.getCollectionBinding( ( (CollectionType) type ).getRole() ); + + if ( thisCollection.isSame( otherCollection ) ) { + logDuplicateRegistration( path, existingType, type ); + return; + } + else { + logIncompatibleRegistration( path, existingType, type ); + } + } + else if ( type instanceof EntityType && existingType instanceof EntityType ) { + EntityType entityType1 = (EntityType) existingType; + EntityType entityType2 = (EntityType) type; + + if ( entityType1.getAssociatedEntityName().equals( entityType2.getAssociatedEntityName() ) ) { + logDuplicateRegistration( path, existingType, type ); + return; + } + else { + commonType = getCommonType( metadata, entityType1, entityType2 ); + } + } + else { + logIncompatibleRegistration( path, existingType, type ); + } + if ( commonType == null ) { + duplicateIncompatiblePaths.add( path ); + typesByPropertyPath.remove( path ); + // Set everything to empty to signal action has to be taken! + // org.hibernate.hql.internal.ast.tree.DotNode.dereferenceEntityJoin() is reacting to this + String[] empty = new String[0]; + columnsByPropertyPath.put( path, empty ); + columnReadersByPropertyPath.put( path, empty ); + columnReaderTemplatesByPropertyPath.put( path, empty ); + if ( formulaTemplates != null ) { + formulaTemplatesByPropertyPath.put( path, empty ); + } + } + else { + typesByPropertyPath.put( path, commonType ); + } + } + } + } + else { + typesByPropertyPath.put( path, type ); + columnsByPropertyPath.put( path, columns ); + columnReadersByPropertyPath.put( path, columnReaders ); + columnReaderTemplatesByPropertyPath.put( path, columnReaderTemplates ); + if ( formulaTemplates != null ) { + formulaTemplatesByPropertyPath.put( path, formulaTemplates ); } - return; } - typesByPropertyPath.put( path, type ); - columnsByPropertyPath.put( path, columns ); - columnReadersByPropertyPath.put( path, columnReaders ); - columnReaderTemplatesByPropertyPath.put( path, columnReaderTemplates ); - if ( formulaTemplates != null ) { - formulaTemplatesByPropertyPath.put( path, formulaTemplates ); + } + + private Type getCommonType(MetadataImplementor metadata, EntityType entityType1, EntityType entityType2) { + PersistentClass thisClass = metadata.getEntityBinding( entityType1.getAssociatedEntityName() ); + PersistentClass otherClass = metadata.getEntityBinding( entityType2.getAssociatedEntityName() ); + PersistentClass commonClass = getCommonPersistentClass( thisClass, otherClass ); + + if ( commonClass == null ) { + return null; + } + + // Create a copy of the type but with the common class + if ( entityType1 instanceof ManyToOneType ) { + ManyToOneType t = (ManyToOneType) entityType1; + return new ManyToOneType( t, commonClass.getEntityName() ); + } + else if ( entityType1 instanceof SpecialOneToOneType ) { + SpecialOneToOneType t = (SpecialOneToOneType) entityType1; + return new SpecialOneToOneType( t, commonClass.getEntityName() ); + } + else if ( entityType1 instanceof OneToOneType ) { + OneToOneType t = (OneToOneType) entityType1; + return new OneToOneType( t, commonClass.getEntityName() ); + } + else { + throw new IllegalStateException( "Unexpected entity type: " + entityType1 ); + } + } + + private PersistentClass getCommonPersistentClass(PersistentClass clazz1, PersistentClass clazz2) { + while ( clazz2 != null && clazz2.getMappedClass() != null && clazz1.getMappedClass() != null && !clazz2.getMappedClass() + .isAssignableFrom( clazz1.getMappedClass() ) ) { + clazz2 = clazz2.getSuperclass(); } + return clazz2; } /*protected void initPropertyPaths( @@ -176,7 +315,7 @@ protected void initPropertyPaths( else { String foreignKeyProperty = actype.getLHSPropertyName(); if ( foreignKeyProperty != null && !path.equals( foreignKeyProperty ) ) { - //TODO: this requires that the collection is defined afterQuery the + //TODO: this requires that the collection is defined after the // referenced property in the mapping file (ok?) columns = columnsByPropertyPath.get( foreignKeyProperty ); if ( columns == null ) { @@ -189,7 +328,7 @@ protected void initPropertyPaths( } if ( path != null ) { - addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates ); + addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); } if ( type.isComponentType() ) { @@ -242,14 +381,14 @@ protected void initIdentifierPropertyPaths( if ( etype.isReferenceToPrimaryKey() ) { if ( !hasNonIdentifierPropertyNamedId ) { String idpath1 = extendPath( path, EntityPersister.ENTITY_ID ); - addPropertyPath( idpath1, idtype, columns, columnReaders, columnReaderTemplates, null ); + addPropertyPath( idpath1, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); initPropertyPaths( idpath1, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); } } if ( idPropName != null ) { String idpath2 = extendPath( path, idPropName ); - addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null ); + addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); initPropertyPaths( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java index 8ad219484b5e..2bb0367175c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java @@ -10,6 +10,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; +import java.util.Objects; import org.hibernate.EntityMode; import org.hibernate.HibernateException; @@ -19,7 +20,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.internal.util.compare.EqualsHelper; import org.hibernate.type.AbstractType; import org.hibernate.type.Type; @@ -37,18 +37,22 @@ public DiscriminatorType(Type underlyingType, Loadable persister) { this.persister = persister; } + @Override public Class getReturnedClass() { return Class.class; } + @Override public String getName() { return getClass().getName(); } + @Override public boolean isMutable() { return false; } + @Override public Object nullSafeGet( ResultSet rs, String[] names, @@ -57,6 +61,7 @@ public Object nullSafeGet( return nullSafeGet( rs, names[0], session, owner ); } + @Override public Object nullSafeGet( ResultSet rs, String name, @@ -71,6 +76,7 @@ public Object nullSafeGet( return ( EntityMode.POJO == entityPersister.getEntityMode() ) ? entityPersister.getMappedClass() : entityName; } + @Override public void nullSafeSet( PreparedStatement st, Object value, @@ -80,6 +86,7 @@ public void nullSafeSet( nullSafeSet( st, value, index, session ); } + @Override public void nullSafeSet( PreparedStatement st, Object value, @@ -90,34 +97,40 @@ public void nullSafeSet( underlyingType.nullSafeSet(st, entityPersister.getDiscriminatorValue(), index, session); } + @Override public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException { return value == null ? "[null]" : value.toString(); } + @Override public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException { return value; } + @Override public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException { return original; } + @Override public boolean[] toColumnNullness(Object value, Mapping mapping) { return value == null ? ArrayHelper.FALSE : ArrayHelper.TRUE; } + @Override public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException { - return EqualsHelper.equals( old, current ); + return Objects.equals( old, current ); } // simple delegation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @Override public int[] sqlTypes(Mapping mapping) throws MappingException { return underlyingType.sqlTypes( mapping ); } @@ -132,6 +145,7 @@ public Size[] defaultSizes(Mapping mapping) throws MappingException { return underlyingType.defaultSizes( mapping ); } + @Override public int getColumnSpan(Mapping mapping) throws MappingException { return underlyingType.getColumnSpan( mapping ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index 88c958efb4b6..dac30cd60e57 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -15,10 +15,11 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; -import org.hibernate.cache.spi.OptimisticCacheSource; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.engine.spi.CascadeStyle; @@ -29,6 +30,7 @@ import org.hibernate.id.IdentifierGenerator; import org.hibernate.internal.FilterAliasGenerator; import org.hibernate.metadata.ClassMetadata; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.walking.spi.EntityDefinition; import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.tuple.entity.EntityTuplizer; @@ -48,10 +50,10 @@ * to be handled by the persister *

  • *
  • - * {@link EntityRegionAccessStrategy} - the second level caching strategy for this entity + * {@link org.hibernate.cache.spi.access.EntityDataAccess} - the second level caching strategy for this entity *
  • *
  • - * {@link NaturalIdRegionAccessStrategy} - the second level caching strategy for the natural-id + * {@link org.hibernate.cache.spi.access.NaturalIdDataAccess} - the second level caching strategy for the natural-id * defined for this entity, if one *
  • *
  • @@ -66,7 +68,7 @@ * @see org.hibernate.persister.spi.PersisterFactory * @see org.hibernate.persister.spi.PersisterClassResolver */ -public interface EntityPersister extends OptimisticCacheSource, EntityDefinition { +public interface EntityPersister extends EntityDefinition { /** * The property name of the "special" identifier property in HQL @@ -75,16 +77,16 @@ public interface EntityPersister extends OptimisticCacheSource, EntityDefinition /** * Generate the entity definition for this object. This must be done for all - * entity persisters beforeQuery calling {@link #postInstantiate()}. + * entity persisters before calling {@link #postInstantiate()}. */ void generateEntityDefinition(); /** * Finish the initialization of this object. {@link #generateEntityDefinition()} - * must be called for all entity persisters beforeQuery calling this method. + * must be called for all entity persisters before calling this method. *

    * Called only once per {@link org.hibernate.SessionFactory} lifecycle, - * afterQuery all entity persisters have been instantiated. + * after all entity persisters have been instantiated. * * @throws org.hibernate.MappingException Indicates an issue in the metadata. */ @@ -97,6 +99,7 @@ public interface EntityPersister extends OptimisticCacheSource, EntityDefinition */ SessionFactoryImplementor getFactory(); + NavigableRole getNavigableRole(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // stuff that is persister-centric and/or EntityInfo-centric ~~~~~~~~~~~~~~ @@ -131,6 +134,20 @@ public interface EntityPersister extends OptimisticCacheSource, EntityDefinition */ EntityMetamodel getEntityMetamodel(); + /** + * Called from {@link EnhancementAsProxyLazinessInterceptor} to trigger load of + * the entity's non-lazy state as well as the named attribute we are accessing + * if it is still uninitialized after fetching non-lazy state + */ + default Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + throw new UnsupportedOperationException( + "Initialization of entity enhancement used to act like a proxy is not supported by this EntityPersister : " + getClass().getName() + ); + } + /** * Determine whether the given name represents a subclass entity * (or this entity itself) of the entity mapped by this persister. @@ -218,7 +235,7 @@ public interface EntityPersister extends OptimisticCacheSource, EntityDefinition boolean isInherited(); /** - * Are identifiers of this entity assigned known beforeQuery the insert execution? + * Are identifiers of this entity assigned known before the insert execution? * Or, are they generated (in the database) by the insert execution. * * @return True if identifiers for this entity are generated by the insert @@ -503,14 +520,21 @@ void update( * Should lazy properties of this entity be cached? */ boolean isLazyPropertiesCacheable(); + + boolean canReadFromCache(); + boolean canWriteToCache(); + /** * Does this class have a cache. + * + * @deprecated Use {@link #canReadFromCache()} and/or {@link #canWriteToCache()} depending on need */ + @Deprecated boolean hasCache(); /** * Get the cache (optional operation) */ - EntityRegionAccessStrategy getCacheAccessStrategy(); + EntityDataAccess getCacheAccessStrategy(); /** * Get the cache structure */ @@ -526,7 +550,7 @@ void update( /** * Get the NaturalId cache (optional operation) */ - NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy(); + NaturalIdDataAccess getNaturalIdCacheAccessStrategy(); /** * Get the user-visible metadata for the class (optional operation) @@ -539,7 +563,7 @@ void update( boolean isBatchLoadable(); /** - * Is select snapshot beforeQuery update enabled? + * Is select snapshot before update enabled? */ boolean isSelectBeforeUpdateRequired(); @@ -597,12 +621,12 @@ void update( // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** - * Called just afterQuery the entities properties have been initialized + * Called just after the entities properties have been initialized */ void afterInitialize(Object entity, SharedSessionContractImplementor session); /** - * Called just afterQuery the entity has been reassociated with the session + * Called just after the entity has been reassociated with the session */ void afterReassociate(Object entity, SharedSessionContractImplementor session); @@ -630,7 +654,7 @@ Object createProxy(Serializable id, SharedSessionContractImplementor session) *

    * Note, that because we update the PersistenceContext here, callers * need to take care that they have already written the initial snapshot - * to the PersistenceContext beforeQuery calling this method. + * to the PersistenceContext before calling this method. * * @param id The entity's id value. * @param entity The entity for which to get the state. @@ -646,7 +670,7 @@ Object createProxy(Serializable id, SharedSessionContractImplementor session) *

    * Note, that because we update the PersistenceContext here, callers * need to take care that they have already written the initial snapshot - * to the PersistenceContext beforeQuery calling this method. + * to the PersistenceContext before calling this method. * * @param id The entity's id value. * @param entity The entity for which to get the state. @@ -790,7 +814,11 @@ Object createProxy(Serializable id, SharedSessionContractImplementor session) EntityTuplizer getEntityTuplizer(); BytecodeEnhancementMetadata getInstrumentationMetadata(); - + + default BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { + return getInstrumentationMetadata(); + } + FilterAliasGenerator getFilterAliasGenerator(final String rootAlias); /** diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index 40167bdba8d8..a936c776f71e 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 @@ -11,8 +11,8 @@ import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.boot.model.relational.Database; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; @@ -128,8 +128,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { public JoinedSubclassEntityPersister( final PersistentClass persistentClass, - final EntityRegionAccessStrategy cacheAccessStrategy, - final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy, + final EntityDataAccess cacheAccessStrategy, + final NaturalIdDataAccess naturalIdRegionAccessStrategy, final PersisterCreationContext creationContext) throws HibernateException { super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); @@ -243,7 +243,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { while ( joinItr.hasNext() ) { Join join = (Join) joinItr.next(); - isNullableTable[tableIndex++] = join.isOptional(); + isNullableTable[tableIndex] = join.isOptional(); Table table = join.getTable(); final String tableName = determineTableName( table, jdbcEnvironment ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java index 9fe1e9f1ef0a..612f6a12ab46 100755 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java @@ -29,6 +29,8 @@ public final class NamedQueryLoader implements UniqueEntityLoader { private final String queryName; private final EntityPersister persister; + private final int position; + /** * Constructs the NamedQueryLoader * @@ -39,6 +41,9 @@ public NamedQueryLoader(String queryName, EntityPersister persister) { super(); this.queryName = queryName; this.persister = persister; + this.position = persister.getFactory().getSessionFactoryOptions().jdbcStyleParamsZeroBased() + ? 0 + : 1; } @Override @@ -61,7 +66,7 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract query.setParameter( query.getNamedParameters()[0], id, persister.getIdentifierType() ); } else { - query.setParameter( 0, id, persister.getIdentifierType() ); + query.setParameter( position, id, persister.getIdentifierType() ); } query.setOptionalId( id ); 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 ea079ed35419..a5c574e4530e 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 @@ -18,8 +18,8 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.boot.model.relational.Database; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +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; @@ -117,8 +117,8 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { public SingleTableEntityPersister( final PersistentClass persistentClass, - final EntityRegionAccessStrategy cacheAccessStrategy, - final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy, + final EntityDataAccess cacheAccessStrategy, + final NaturalIdDataAccess naturalIdRegionAccessStrategy, final PersisterCreationContext creationContext) throws HibernateException { super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); @@ -563,7 +563,7 @@ private String discriminatorFilterFragment(String alias) throws MappingException } public String oneToManyFilterFragment(String alias) throws MappingException { - return needsDiscriminator() + return forceDiscriminator ? discriminatorFilterFragment( alias, null ) : ""; } 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 47c6912af2f9..04f72dad9d1c 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,8 +20,8 @@ import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.boot.model.relational.Database; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +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; @@ -70,8 +70,8 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister { public UnionSubclassEntityPersister( final PersistentClass persistentClass, - final EntityRegionAccessStrategy cacheAccessStrategy, - final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy, + final EntityDataAccess cacheAccessStrategy, + final NaturalIdDataAccess naturalIdRegionAccessStrategy, final PersisterCreationContext creationContext) throws HibernateException { super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java index 6ea4fa634d25..acf9cbb334b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java @@ -11,9 +11,9 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.persister.collection.CollectionPersister; @@ -39,8 +39,8 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi */ public static final Class[] ENTITY_PERSISTER_CONSTRUCTOR_ARGS = new Class[] { PersistentClass.class, - EntityRegionAccessStrategy.class, - NaturalIdRegionAccessStrategy.class, + EntityDataAccess.class, + NaturalIdDataAccess.class, PersisterCreationContext.class }; @@ -49,7 +49,7 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi */ public static final Class[] COLLECTION_PERSISTER_CONSTRUCTOR_ARGS = new Class[] { Collection.class, - CollectionRegionAccessStrategy.class, + CollectionDataAccess.class, PersisterCreationContext.class }; @@ -64,8 +64,8 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { @SuppressWarnings( {"unchecked"}) public EntityPersister createEntityPersister( PersistentClass entityBinding, - EntityRegionAccessStrategy entityCacheAccessStrategy, - NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy, + EntityDataAccess entityCacheAccessStrategy, + NaturalIdDataAccess naturalIdCacheAccessStrategy, PersisterCreationContext creationContext) throws HibernateException { // If the metadata for the entity specified an explicit persister class, use it... Class persisterClass = entityBinding.getEntityPersisterClass(); @@ -87,8 +87,8 @@ public EntityPersister createEntityPersister( private EntityPersister createEntityPersister( Class persisterClass, PersistentClass entityBinding, - EntityRegionAccessStrategy entityCacheAccessStrategy, - NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy, + EntityDataAccess entityCacheAccessStrategy, + NaturalIdDataAccess naturalIdCacheAccessStrategy, PersisterCreationContext creationContext) { try { final Constructor constructor = persisterClass.getConstructor( ENTITY_PERSISTER_CONSTRUCTOR_ARGS ); @@ -128,7 +128,7 @@ private EntityPersister createEntityPersister( @SuppressWarnings( {"unchecked"}) public CollectionPersister createCollectionPersister( Collection collectionBinding, - CollectionRegionAccessStrategy cacheAccessStrategy, + CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) throws HibernateException { // If the metadata for the collection specified an explicit persister class, use it Class persisterClass = collectionBinding.getCollectionPersisterClass(); @@ -144,7 +144,7 @@ public CollectionPersister createCollectionPersister( private CollectionPersister createCollectionPersister( Class persisterClass, Collection collectionBinding, - CollectionRegionAccessStrategy cacheAccessStrategy, + CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) { try { Constructor constructor = persisterClass.getConstructor( COLLECTION_PERSISTER_CONSTRUCTOR_ARGS ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterFactory.java b/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterFactory.java index c30db259b038..d4d044c7b95f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterFactory.java @@ -7,9 +7,9 @@ package org.hibernate.persister.spi; import org.hibernate.HibernateException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.persister.collection.CollectionPersister; @@ -34,10 +34,10 @@ public interface PersisterFactory extends Service { * * @throws HibernateException Indicates a problem building the persister. */ - public EntityPersister createEntityPersister( + EntityPersister createEntityPersister( PersistentClass entityBinding, - EntityRegionAccessStrategy entityCacheAccessStrategy, - NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy, + EntityDataAccess entityCacheAccessStrategy, + NaturalIdDataAccess naturalIdCacheAccessStrategy, PersisterCreationContext creationContext) throws HibernateException; /** @@ -51,9 +51,9 @@ public EntityPersister createEntityPersister( * * @throws HibernateException Indicates a problem building the persister. */ - public CollectionPersister createCollectionPersister( + CollectionPersister createCollectionPersister( Collection collectionBinding, - CollectionRegionAccessStrategy cacheAccessStrategy, + CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) throws HibernateException; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java index fa6ab39bd046..3ca5bf195d6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java @@ -8,9 +8,9 @@ import java.util.Iterator; +import org.hibernate.FetchMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; -import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -122,6 +122,7 @@ public AttributeDefinition next() { final String name = compositeType.getPropertyNames()[subAttributeNumber]; final Type type = compositeType.getSubtypes()[subAttributeNumber]; + final FetchMode fetchMode = compositeType.getFetchMode( subAttributeNumber ); final int columnPosition = currentColumnPosition; final int columnSpan = type.getColumnSpan( ownerEntityPersister.getFactory() ); @@ -181,7 +182,19 @@ public CollectionDefinition toCollectionDefinition() { @Override public FetchStrategy determineFetchPlan(LoadQueryInfluencers loadQueryInfluencers, PropertyPath propertyPath) { - return new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.JOIN ); + final FetchStyle style = FetchStrategyHelper.determineFetchStyleByMetadata( + fetchMode, + (AssociationType) type, + ownerEntityPersister.getFactory() + ); + return new FetchStrategy( + FetchStrategyHelper.determineFetchTiming( + style, + getType(), + ownerEntityPersister.getFactory() + ), + style + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java index afbe99fad86f..70d46f62ab05 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java @@ -58,7 +58,7 @@ public static FetchStyle determineFetchStyleByProfile( final String relativePropertyPath = pos >= 0 ? fullPath.substring( pos ) : rootPropertyName; - final String fetchRole = persister.getEntityName() + "." + relativePropertyPath; + final String fetchRole = persister.getEntityName() + '.' + relativePropertyPath; for ( String profileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) { final FetchProfile profile = loadQueryInfluencers.getSessionFactory().getFetchProfile( profileName ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationKey.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationKey.java index 77efa0e7b0a2..4179c107d95a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationKey.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationKey.java @@ -8,7 +8,6 @@ import java.util.Arrays; -import org.hibernate.internal.util.StringHelper; /** * Used to uniquely identify a foreign key, so that we don't join it more than once creating circularities. Note @@ -63,7 +62,7 @@ public int hashCode() { @Override public String toString() { if ( str == null ) { - str = "AssociationKey(table=" + table + ", columns={" + StringHelper.join( ",", columns ) + "})"; + str = "AssociationKey(table=" + table + ", columns={" + String.join( ",", columns ) + "})"; } return str; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetamodelGraphWalker.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetamodelGraphWalker.java index 46d8a3e13193..48312a3c1859 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetamodelGraphWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetamodelGraphWalker.java @@ -13,6 +13,7 @@ import org.hibernate.loader.PropertyPath; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.Type; @@ -90,8 +91,9 @@ public MetamodelGraphWalker(AssociationVisitationStrategy strategy, SessionFacto private void visitEntityDefinition(EntityDefinition entityDefinition) { strategy.startingEntity( entityDefinition ); + AbstractEntityPersister persister = (AbstractEntityPersister) entityDefinition.getEntityPersister(); visitIdentifierDefinition( entityDefinition.getEntityKeyDefinition() ); - visitAttributes( entityDefinition ); + visitAttributes( entityDefinition, persister); strategy.finishingEntity( entityDefinition ); } @@ -122,17 +124,17 @@ private void visitIdentifierDefinition(EntityIdentifierDefinition identifierDefi strategy.finishingEntityIdentifier( identifierDefinition ); } - private void visitAttributes(AttributeSource attributeSource) { + private void visitAttributes(AttributeSource attributeSource, AbstractEntityPersister sourcePersister) { final Iterable attributeDefinitions = attributeSource.getAttributes(); if ( attributeDefinitions == null ) { return; } for ( AttributeDefinition attributeDefinition : attributeDefinitions ) { - visitAttributeDefinition( attributeDefinition ); + visitAttributeDefinition( attributeDefinition, sourcePersister); } } - private void visitAttributeDefinition(AttributeDefinition attributeDefinition) { + private void visitAttributeDefinition(AttributeDefinition attributeDefinition, AbstractEntityPersister sourcePersister) { final PropertyPath subPath = currentPropertyPath.append( attributeDefinition.getName() ); log.debug( "Visiting attribute path : " + subPath.getFullPath() ); @@ -147,6 +149,14 @@ private void visitAttributeDefinition(AttributeDefinition attributeDefinition) { // EARLY EXIT!!! return; } + + if ( sourcePersister != null ) { + String[] columns = sourcePersister.toColumns(attributeDefinition.getName()); + // Empty columns means that the attribute is not resolvable on this persister + if ( columns.length == 0 ) { + return; + } + } } @@ -196,7 +206,7 @@ private void visitAnyDefinition(AnyMappingDefinition anyDefinition) { private void visitCompositeDefinition(CompositionDefinition compositionDefinition) { strategy.startingComposite( compositionDefinition ); - visitAttributes( compositionDefinition ); + visitAttributes( compositionDefinition, null ); strategy.finishingComposite( compositionDefinition ); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java index a4df1a7bd9d2..08eca3af7737 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java @@ -8,10 +8,12 @@ import javax.persistence.TemporalType; +import org.hibernate.query.spi.QueryParameterBinding; + /** * Describes an input value binding for any IN/INOUT parameters. */ -public interface ParameterBind { +public interface ParameterBind extends QueryParameterBinding { /** * Retrieves the bound value. * diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java index b17fa02d5d1b..b36f656052f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java @@ -9,6 +9,8 @@ import javax.persistence.ParameterMode; import javax.persistence.TemporalType; +import org.hibernate.query.QueryParameter; +import org.hibernate.query.procedure.ProcedureParameter; import org.hibernate.type.Type; /** @@ -16,13 +18,14 @@ * * @author Steve Ebersole */ -public interface ParameterRegistration { +public interface ParameterRegistration extends ProcedureParameter { /** * The name under which this parameter was registered. Can be {@code null} which should indicate that * positional registration was used (and therefore {@link #getPosition()} should return non-null. * * @return The name; */ + @Override String getName(); /** @@ -31,15 +34,19 @@ public interface ParameterRegistration { * * @return The name; */ + @Override Integer getPosition(); /** - * Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType} - * is called explicitly). + * Return the Java type of the parameter. * - * @return The parameter Java type. + * @return The Java type of the parameter. + * @deprecated Call {@link #getParameterType()} instead. */ - Class getType(); + @Deprecated + default Class getType() { + return getParameterType(); + } /** * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure @@ -47,6 +54,7 @@ public interface ParameterRegistration { * * @return The parameter mode. */ + @Override ParameterMode getMode(); /** @@ -65,6 +73,7 @@ public interface ParameterRegistration { * * @param enabled {@code true} indicates that the NULL should be passed; {@code false} indicates it should not. */ + @Override void enablePassingNulls(boolean enabled); /** diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java index 5cbe53e09fda..10d2653339e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java @@ -14,13 +14,14 @@ import org.hibernate.BasicQueryContract; import org.hibernate.MappingException; import org.hibernate.SynchronizeableQuery; +import org.hibernate.query.CommonQueryContract; /** * Defines support for executing database stored procedures and functions * * @author Steve Ebersole */ -public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery, StoredProcedureQuery { +public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery, StoredProcedureQuery { @Override ProcedureCall addSynchronizedQuerySpace(String querySpace); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java index 24bd793c5b72..d6d941378241 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java @@ -8,6 +8,8 @@ import java.util.Map; +import org.hibernate.Session; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -16,6 +18,28 @@ * @author Steve Ebersole */ public interface ProcedureCallMemento { + /** + * Convert the memento back into an executable (connected) form. + * + * @param session The session to connect the procedure call to + * + * @return The executable call + */ + default ProcedureCall makeProcedureCall(Session session) { + return makeProcedureCall( (SharedSessionContractImplementor) session ); + } + + /** + * Convert the memento back into an executable (connected) form. + * + * @param session The session to connect the procedure call to + * + * @return The executable call + */ + default ProcedureCall makeProcedureCall(SessionImplementor session) { + return makeProcedureCall( (SharedSessionContractImplementor) session ); + } + /** * Convert the memento back into an executable (connected) form. * diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java deleted file mode 100644 index 457185f8433c..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java +++ /dev/null @@ -1,432 +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.procedure.internal; - -import java.sql.CallableStatement; -import java.sql.SQLException; -import java.util.Calendar; -import java.util.Date; -import javax.persistence.ParameterMode; -import javax.persistence.TemporalType; - -import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; -import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.procedure.ParameterBind; -import org.hibernate.procedure.ParameterMisuseException; -import org.hibernate.procedure.spi.ParameterRegistrationImplementor; -import org.hibernate.procedure.spi.ParameterStrategy; -import org.hibernate.type.CalendarDateType; -import org.hibernate.type.CalendarTimeType; -import org.hibernate.type.CalendarType; -import org.hibernate.type.ProcedureParameterExtractionAware; -import org.hibernate.type.ProcedureParameterNamedBinder; -import org.hibernate.type.Type; - -import org.jboss.logging.Logger; - -/** - * Abstract implementation of ParameterRegistration/ParameterRegistrationImplementor - * - * @author Steve Ebersole - */ -public abstract class AbstractParameterRegistrationImpl implements ParameterRegistrationImplementor { - private static final Logger log = Logger.getLogger( AbstractParameterRegistrationImpl.class ); - - private final ProcedureCallImpl procedureCall; - - private final Integer position; - private final String name; - - private final ParameterMode mode; - private final Class type; - - private ParameterBindImpl bind; - private boolean passNulls; - - private int startIndex; - private Type hibernateType; - private int[] sqlTypes; - - - // positional constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - this( procedureCall, position, null, mode, type, initialPassNullsSetting ); - } - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - this( procedureCall, position, null, mode, type, hibernateType, initialPassNullsSetting ); - } - - - // named constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - this( procedureCall, null, name, mode, type, initialPassNullsSetting ); - } - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - this( procedureCall, null, name, mode, type, hibernateType, initialPassNullsSetting ); - } - - - // full constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - private AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - String name, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - this.procedureCall = procedureCall; - - this.position = position; - this.name = name; - - this.mode = mode; - this.type = type; - - if ( mode == ParameterMode.REF_CURSOR ) { - return; - } - - this.passNulls = initialPassNullsSetting; - setHibernateType( hibernateType ); - } - - private AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - String name, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - this( - procedureCall, - position, - name, - mode, - type, - procedureCall.getSession().getFactory().getTypeResolver().heuristicType( type.getName() ), - initialPassNullsSetting - ); - } - - protected SharedSessionContractImplementor session() { - return procedureCall.getSession(); - } - - @Override - public String getName() { - return name; - } - - @Override - public Integer getPosition() { - return position; - } - - @Override - public Class getType() { - return type; - } - - @Override - public ParameterMode getMode() { - return mode; - } - - @Override - public boolean isPassNullsEnabled() { - return passNulls; - } - - @Override - public void enablePassingNulls(boolean enabled) { - this.passNulls = enabled; - } - - @Override - public Type getHibernateType() { - return hibernateType; - } - - @Override - public void setHibernateType(Type type) { - if ( type == null ) { - throw new IllegalArgumentException( "Type cannot be null" ); - } - this.hibernateType = type; - this.sqlTypes = hibernateType.sqlTypes( session().getFactory() ); - } - - @Override - @SuppressWarnings("unchecked") - public ParameterBind getBind() { - return bind; - } - - @Override - public void bindValue(T value) { - validateBindability(); - this.bind = new ParameterBindImpl( value ); - } - - private void validateBindability() { - if ( ! canBind() ) { - throw new ParameterMisuseException( "Cannot bind value to non-input parameter : " + this ); - } - } - - private boolean canBind() { - return mode == ParameterMode.IN || mode == ParameterMode.INOUT; - } - - @Override - public void bindValue(T value, TemporalType explicitTemporalType) { - validateBindability(); - if ( explicitTemporalType != null ) { - if ( ! isDateTimeType() ) { - throw new IllegalArgumentException( "TemporalType should not be specified for non date/time type" ); - } - } - this.bind = new ParameterBindImpl( value, explicitTemporalType ); - } - - private boolean isDateTimeType() { - return Date.class.isAssignableFrom( type ) - || Calendar.class.isAssignableFrom( type ); - } - - @Override - public void prepare(CallableStatement statement, int startIndex) throws SQLException { - // initially set up the Type we will use for binding as the explicit type. - Type typeToUse = hibernateType; - int[] sqlTypesToUse = sqlTypes; - - // however, for Calendar binding with an explicit TemporalType we may need to adjust this... - if ( bind != null && bind.getExplicitTemporalType() != null ) { - if ( Calendar.class.isInstance( bind.getValue() ) ) { - switch ( bind.getExplicitTemporalType() ) { - case TIMESTAMP: { - typeToUse = CalendarType.INSTANCE; - sqlTypesToUse = typeToUse.sqlTypes( session().getFactory() ); - break; - } - case DATE: { - typeToUse = CalendarDateType.INSTANCE; - sqlTypesToUse = typeToUse.sqlTypes( session().getFactory() ); - break; - } - case TIME: { - typeToUse = CalendarTimeType.INSTANCE; - sqlTypesToUse = typeToUse.sqlTypes( session().getFactory() ); - break; - } - } - } - } - - this.startIndex = startIndex; - if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - if ( sqlTypesToUse.length > 1 ) { - // there is more than one column involved; see if the Hibernate Type can handle - // multi-param extraction... - final boolean canHandleMultiParamExtraction = - ProcedureParameterExtractionAware.class.isInstance( hibernateType ) - && ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction(); - if ( ! canHandleMultiParamExtraction ) { - // it cannot... - throw new UnsupportedOperationException( - "Type [" + hibernateType + "] does support multi-parameter value extraction" - ); - } - } - // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). - // The idea is that an embeddable/custom type can have more than one column values - // that correspond with embeddable/custom attribute value. This does not seem to - // be working yet. For now, if sqlTypesToUse.length > 1, then register - // the out parameters by position (since we only have one name). - // This will cause a failure if there are other parameters bound by - // name and the dialect does not support "mixed" named/positional parameters; - // e.g., Oracle. - if ( sqlTypesToUse.length == 1 && - procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && - canDoNameParameterBinding() ) { - statement.registerOutParameter( getName(), sqlTypesToUse[0] ); - } - else { - for ( int i = 0; i < sqlTypesToUse.length; i++ ) { - statement.registerOutParameter( startIndex + i, sqlTypesToUse[i] ); - } - } - } - - if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { - if ( bind == null || bind.getValue() == null ) { - // the user did not bind a value to the parameter being processed. This is the condition - // defined by `passNulls` and that value controls what happens here. If `passNulls` is - // {@code true} we will bind the NULL value into the statement; if `passNulls` is - // {@code false} we will not. - // - // Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure - // parameter defines a default value. Deferring to that information would be the best option - if ( passNulls ) { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", - procedureCall.getProcedureName(), - this - ); - if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding() ) { - ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( - statement, - null, - this.getName(), - session() - ); - } - else { - typeToUse.nullSafeSet( statement, null, startIndex, session() ); - } - } - else { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value", - procedureCall.getProcedureName(), - this - ); - } - } - else { - if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding()) { - ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( - statement, - bind.getValue(), - this.getName(), - session() - ); - } - else { - typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() ); - } - } - } - } - else { - // we have a REF_CURSOR type param - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { - session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, getName() ); - } - else { - session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, startIndex ); - } - } - } - - private boolean canDoNameParameterBinding() { - final ExtractedDatabaseMetaData databaseMetaData = session() - .getJdbcCoordinator() - .getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry().getService( JdbcEnvironment.class ) - .getExtractedDatabaseMetaData(); - return - databaseMetaData.supportsNamedParameters() && - ProcedureParameterNamedBinder.class.isInstance( hibernateType ) - && ((ProcedureParameterNamedBinder) hibernateType).canDoSetting(); - } - - public int[] getSqlTypes() { - if ( mode == ParameterMode.REF_CURSOR ) { - // we could use the Types#REF_CURSOR added in Java 8, but that would require requiring Java 8... - throw new IllegalStateException( "REF_CURSOR parameters do not have a SQL/JDBC type" ); - } - return sqlTypes; - } - - @Override - @SuppressWarnings("unchecked") - public T extract(CallableStatement statement) { - if ( mode == ParameterMode.IN ) { - throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); - } - else if ( mode == ParameterMode.REF_CURSOR ) { - throw new ParameterMisuseException( "REF_CURSOR parameters should be accessed via results" ); - } - - // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). - // For now, if sqlTypes.length > 1 with a named parameter, then extract - // parameter values by position (since we only have one name). - final boolean useNamed = sqlTypes.length == 1 && - procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && - canDoNameParameterBinding(); - - try { - if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { - if ( useNamed ) { - return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( - statement, - new String[] { getName() }, - session() - ); - } - else { - return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( - statement, - startIndex, - session() - ); - } - } - else { - if ( useNamed ) { - return (T) statement.getObject( name ); - } - else { - return (T) statement.getObject( startIndex ); - } - } - } - catch (SQLException e) { - throw procedureCall.getSession().getFactory().getSQLExceptionHelper().convert( - e, - "Unable to extract OUT/INOUT parameter value" - ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java deleted file mode 100644 index a12b6ba38a6c..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java +++ /dev/null @@ -1,37 +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.procedure.internal; - -import javax.persistence.ParameterMode; - -import org.hibernate.type.Type; - -/** - * Represents a registered named parameter - * - * @author Steve Ebersole - */ -public class NamedParameterRegistration extends AbstractParameterRegistrationImpl { - NamedParameterRegistration( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - super( procedureCall, name, mode, type, initialPassNullsSetting ); - } - - NamedParameterRegistration( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - super( procedureCall, name, mode, type, hibernateType, initialPassNullsSetting ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java index 6ffda13a767c..e5536efd3215 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java @@ -6,9 +6,16 @@ */ package org.hibernate.procedure.internal; +import javax.persistence.ParameterMode; import javax.persistence.TemporalType; import org.hibernate.procedure.ParameterBind; +import org.hibernate.query.internal.BindingTypeHelper; +import org.hibernate.query.procedure.internal.ProcedureParamBindings; +import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.type.Type; + +import org.jboss.logging.Logger; /** * Implementation of the {@link ParameterBind} contract. @@ -16,16 +23,25 @@ * @author Steve Ebersole */ public class ParameterBindImpl implements ParameterBind { - private final T value; - private final TemporalType explicitTemporalType; + private static final Logger log = Logger.getLogger( ParameterBindImpl.class ); - ParameterBindImpl(T value) { - this( value, null ); - } + private final ProcedureParameterImplementor procedureParameter; + private final ProcedureParamBindings procedureParamBindings; - ParameterBindImpl(T value, TemporalType explicitTemporalType) { - this.value = value; - this.explicitTemporalType = explicitTemporalType; + private boolean isBound; + + private T value; + private Type hibernateType; + + private TemporalType explicitTemporalType; + + public ParameterBindImpl( + ProcedureParameterImplementor procedureParameter, + ProcedureParamBindings procedureParamBindings) { + this.procedureParameter = procedureParameter; + this.procedureParamBindings = procedureParamBindings; + + this.hibernateType = procedureParameter.getHibernateType(); } @Override @@ -37,4 +53,77 @@ public T getValue() { public TemporalType getExplicitTemporalType() { return explicitTemporalType; } + + @Override + public boolean isBound() { + return isBound; + } + + @Override + public void setBindValue(T value) { + internalSetValue( value ); + + if ( value != null && hibernateType == null ) { + hibernateType = procedureParamBindings.getProcedureCall() + .getSession() + .getFactory() + .getTypeResolver() + .heuristicType( value.getClass().getName() ); + log.debugf( "Using heuristic type [%s] based on bind value [%s] as `bindType`", hibernateType, value ); + } + } + + private void internalSetValue(T value) { + if ( procedureParameter.getMode() != ParameterMode.IN && procedureParameter.getMode() != ParameterMode.INOUT ) { + throw new IllegalStateException( "Can only bind values for IN/INOUT parameters : " + procedureParameter ); + } + + if ( procedureParameter.getParameterType() != null ) { + if ( value == null ) { + if ( !procedureParameter.isPassNullsEnabled() ) { + throw new IllegalArgumentException( "The parameter " + + ( procedureParameter.getName() != null + ? "named [" + procedureParameter.getName() + "]" + : "at position [" + procedureParameter.getPosition() + "]" ) + + " was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters." ); + } + } + else if ( !procedureParameter.getParameterType().isInstance( value ) && + !procedureParameter.getHibernateType().getReturnedClass().isInstance( value ) ) { + throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter + .getParameterType() ); + } + } + + this.value = value; + this.isBound = true; + } + + @Override + public void setBindValue(T value, Type clarifiedType) { + internalSetValue( value ); + this.hibernateType = clarifiedType; + log.debugf( "Using explicit type [%s] as `bindType`", hibernateType, value ); + } + + @Override + public void setBindValue(T value, TemporalType clarifiedTemporalType) { + internalSetValue( value ); + this.hibernateType = BindingTypeHelper.INSTANCE.determineTypeForTemporalType( clarifiedTemporalType, hibernateType, value ); + this.explicitTemporalType = clarifiedTemporalType; + log.debugf( "Using type [%s] (based on TemporalType [%s] as `bindType`", hibernateType, clarifiedTemporalType ); + } + + @Override + public T getBindValue() { + if ( !isBound ) { + throw new IllegalStateException( "Value not yet bound" ); + } + return value; + } + + @Override + public Type getBindType() { + return hibernateType; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java deleted file mode 100644 index 515c3f169b75..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java +++ /dev/null @@ -1,37 +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.procedure.internal; - -import javax.persistence.ParameterMode; - -import org.hibernate.type.Type; - -/** - * Represents a registered positional parameter - * - * @author Steve Ebersole - */ -public class PositionalParameterRegistration extends AbstractParameterRegistrationImpl { - PositionalParameterRegistration( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - super( procedureCall, position, mode, type, initialPassNullsSetting ); - } - - PositionalParameterRegistration( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - super( procedureCall, position, mode, type, hibernateType, initialPassNullsSetting ); - } -} 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 56b8bb775416..f80cb13dfa9f 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 @@ -11,13 +11,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.NoResultException; @@ -25,20 +25,15 @@ import javax.persistence.Parameter; import javax.persistence.ParameterMode; import javax.persistence.TemporalType; -import javax.persistence.TransactionRequiredException; import org.hibernate.HibernateException; -import org.hibernate.QueryException; import org.hibernate.engine.ResultSetMappingDefinition; -import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.procedure.NoSuchParameterException; import org.hibernate.procedure.ParameterRegistration; @@ -51,8 +46,12 @@ import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.query.QueryParameter; import org.hibernate.query.internal.AbstractProducedQuery; +import org.hibernate.query.procedure.internal.ProcedureParamBindings; import org.hibernate.query.procedure.internal.ProcedureParameterImpl; import org.hibernate.query.procedure.internal.ProcedureParameterMetadata; +import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.result.NoMoreReturnsException; import org.hibernate.result.Output; import org.hibernate.result.ResultSetOutput; @@ -82,8 +81,8 @@ public class ProcedureCallImpl private final boolean globalParameterPassNullsSetting; - private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN; - private List> registeredParameters = new ArrayList<>(); + private final ProcedureParameterMetadata parameterMetadata; + private final ProcedureParamBindings paramBindings; private Set synchronizedQuerySpaces; @@ -96,11 +95,14 @@ public class ProcedureCallImpl * @param procedureName The name of the procedure to call */ public ProcedureCallImpl(SharedSessionContractImplementor session, String procedureName) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = procedureName; this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); this.queryReturns = NO_RETURNS; + + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); } /** @@ -111,7 +113,7 @@ public ProcedureCallImpl(SharedSessionContractImplementor session, String proced * @param resultClasses The classes making up the result */ public ProcedureCallImpl(final SharedSessionContractImplementor session, String procedureName, Class... resultClasses) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = procedureName; this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); @@ -140,6 +142,9 @@ public void addQuerySpaces(String... spaces) { this.queryReturns = collectedQueryReturns.toArray( new NativeSQLQueryReturn[ collectedQueryReturns.size() ] ); this.synchronizedQuerySpaces = collectedQuerySpaces; + + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); } /** @@ -150,7 +155,7 @@ public void addQuerySpaces(String... spaces) { * @param resultSetMappings The names of the result set mappings making up the result */ public ProcedureCallImpl(final SharedSessionContractImplementor session, String procedureName, String... resultSetMappings) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = procedureName; this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); @@ -184,6 +189,9 @@ public void addQuerySpaces(String... spaces) { this.queryReturns = collectedQueryReturns.toArray( new NativeSQLQueryReturn[ collectedQueryReturns.size() ] ); this.synchronizedQuerySpaces = collectedQuerySpaces; + + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); } /** @@ -194,41 +202,21 @@ public void addQuerySpaces(String... spaces) { */ @SuppressWarnings("unchecked") ProcedureCallImpl(SharedSessionContractImplementor session, ProcedureCallMementoImpl memento) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = memento.getProcedureName(); this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); this.queryReturns = memento.getQueryReturns(); this.synchronizedQuerySpaces = Util.copy( memento.getSynchronizedQuerySpaces() ); - this.parameterStrategy = memento.getParameterStrategy(); - if ( parameterStrategy == ParameterStrategy.UNKNOWN ) { - // nothing else to do in this case - return; - } - - final List storedRegistrations = memento.getParameterDeclarations(); - if ( storedRegistrations == null ) { - // most likely a problem if ParameterStrategy is not UNKNOWN... - LOG.debugf( - "ParameterStrategy was [%s] on named copy [%s], but no parameters stored", - parameterStrategy, - procedureName - ); - return; - } - final List> parameterRegistrations = - CollectionHelper.arrayList( storedRegistrations.size() ); + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); + + for ( ProcedureCallMementoImpl.ParameterMemento storedRegistration : memento.getParameterDeclarations() ) { + final ProcedureParameterImplementor registration; - for ( ProcedureCallMementoImpl.ParameterMemento storedRegistration : storedRegistrations ) { - final ParameterRegistrationImplementor registration; if ( StringHelper.isNotEmpty( storedRegistration.getName() ) ) { - if ( parameterStrategy != ParameterStrategy.NAMED ) { - throw new IllegalStateException( - "Found named stored procedure parameter associated with positional parameters" - ); - } - registration = new NamedParameterRegistration( + registration = new ProcedureParameterImpl( this, storedRegistration.getName(), storedRegistration.getMode(), @@ -238,12 +226,7 @@ public void addQuerySpaces(String... spaces) { ); } else { - if ( parameterStrategy != ParameterStrategy.POSITIONAL ) { - throw new IllegalStateException( - "Found named stored procedure parameter associated with positional parameters" - ); - } - registration = new PositionalParameterRegistration( + registration = new ProcedureParameterImpl( this, storedRegistration.getPosition(), storedRegistration.getMode(), @@ -252,10 +235,9 @@ public void addQuerySpaces(String... spaces) { storedRegistration.isPassNullsEnabled() ); } - getParameterMetadata().registerParameter( new ProcedureParameterImpl( registration ) ); - parameterRegistrations.add( registration ); + + getParameterMetadata().registerParameter( registration ); } - this.registeredParameters = parameterRegistrations; for ( Map.Entry entry : memento.getHintsMap().entrySet() ) { setHint( entry.getKey(), entry.getValue() ); @@ -264,7 +246,12 @@ public void addQuerySpaces(String... spaces) { @Override public ProcedureParameterMetadata getParameterMetadata() { - return (ProcedureParameterMetadata) super.getParameterMetadata(); + return parameterMetadata; + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return paramBindings; } @Override @@ -273,7 +260,7 @@ public SharedSessionContractImplementor getSession() { } public ParameterStrategy getParameterStrategy() { - return parameterStrategy; + return getParameterMetadata().getParameterStrategy(); } @Override @@ -294,11 +281,17 @@ public NativeSQLQueryReturn[] getQueryReturns() { @Override @SuppressWarnings("unchecked") public ParameterRegistration registerParameter(int position, Class type, ParameterMode mode) { + final ProcedureParameterImpl procedureParameter = new ProcedureParameterImpl( + this, + position, + mode, + type, + getSession().getFactory().getTypeResolver().heuristicType( type.getName() ), + globalParameterPassNullsSetting + ); - final PositionalParameterRegistration parameterRegistration = - new PositionalParameterRegistration( this, position, mode, type, globalParameterPassNullsSetting ); - registerParameter( parameterRegistration ); - return parameterRegistration; + registerParameter( procedureParameter ); + return procedureParameter; } @Override @@ -308,68 +301,30 @@ public ProcedureCall registerParameter0(int position, Class type, ParameterMode return this; } - private void registerParameter(ParameterRegistrationImplementor parameter) { - if ( StringHelper.isNotEmpty( parameter.getName() ) ) { - prepareForNamedParameters(); - } - else if ( parameter.getPosition() != null ) { - prepareForPositionalParameters(); - } - else { - throw new IllegalArgumentException( "Given parameter did not define name or position [" + parameter + "]" ); - } - ((ProcedureParameterMetadata)getParameterMetadata()).registerParameter( new ProcedureParameterImpl( parameter ) ); - - registeredParameters.add( parameter ); - } - - private void prepareForPositionalParameters() { - if ( parameterStrategy == ParameterStrategy.NAMED ) { - throw new QueryException( "Cannot mix named and positional parameters" ); - } - parameterStrategy = ParameterStrategy.POSITIONAL; - } - - private void prepareForNamedParameters() { - if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { - throw new QueryException( "Cannot mix named and positional parameters" ); - } - if ( parameterStrategy == ParameterStrategy.UNKNOWN ) { - // protect to only do this check once - final ExtractedDatabaseMetaData databaseMetaData = getSession() - .getJdbcCoordinator() - .getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry().getService( JdbcEnvironment.class ) - .getExtractedDatabaseMetaData(); - if ( ! databaseMetaData.supportsNamedParameters() ) { - LOG.unsupportedNamedParameters(); - } - parameterStrategy = ParameterStrategy.NAMED; - } + private void registerParameter(ProcedureParameterImplementor parameter) { + getParameterMetadata().registerParameter( parameter ); } @Override public ParameterRegistrationImplementor getParameterRegistration(int position) { - if ( parameterStrategy != ParameterStrategy.POSITIONAL ) { - throw new ParameterStrategyException( - "Attempt to access positional parameter [" + position + "] but ProcedureCall using named parameters" - ); - } - for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - if ( position == parameter.getPosition() ) { - return parameter; - } - } - throw new NoSuchParameterException( "Could not locate parameter registered using that position [" + position + "]" ); + return getParameterMetadata().getQueryParameter( position ); } @Override @SuppressWarnings("unchecked") public ParameterRegistration registerParameter(String name, Class type, ParameterMode mode) { - final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, mode, type, globalParameterPassNullsSetting ); - registerParameter( parameterRegistration ); - return parameterRegistration; + final ProcedureParameterImpl parameter = new ProcedureParameterImpl( + this, + name, + mode, + type, + getSession().getFactory().getTypeResolver().heuristicType( type.getName() ), + globalParameterPassNullsSetting + ); + + registerParameter( parameter ); + + return parameter; } @Override @@ -381,21 +336,13 @@ public ProcedureCall registerParameter0(String name, Class type, ParameterMode m @Override public ParameterRegistrationImplementor getParameterRegistration(String name) { - if ( parameterStrategy != ParameterStrategy.NAMED ) { - throw new ParameterStrategyException( "Names were not used to register parameters with this stored procedure call" ); - } - for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - if ( name.equals( parameter.getName() ) ) { - return parameter; - } - } - throw new NoSuchParameterException( "Could not locate parameter registered under that name [" + name + "]" ); + return getParameterMetadata().getQueryParameter( name ); } @Override @SuppressWarnings("unchecked") - public List getRegisteredParameters() { - return new ArrayList<>( registeredParameters ); + public List getRegisteredParameters() { + return new ArrayList( getParameterMetadata().collectAllParameters() ); } @Override @@ -424,41 +371,48 @@ private ProcedureOutputsImpl buildOutputs() { final String call = getProducer().getJdbcServices().getJdbcEnvironment().getDialect().getCallableStatementSupport().renderCallableStatement( procedureName, - parameterStrategy, - registeredParameters, + getParameterMetadata(), + paramBindings, getProducer() ); - try { - LOG.debugf( "Preparing procedure call : %s", call ); - final CallableStatement statement = (CallableStatement) getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( call, true ); + LOG.debugf( "Preparing procedure call : %s", call ); + final CallableStatement statement = (CallableStatement) getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( call, true ); - // prepare parameters - int i = 1; + // prepare parameters - for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - parameter.prepare( statement, i ); - if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { - i++; - } - else { - i += parameter.getSqlTypes().length; + getParameterMetadata().visitRegistrations( + new Consumer() { + int i = 1; + + @Override + public void accept(QueryParameter queryParameter) { + try { + final ParameterRegistrationImplementor registration = (ParameterRegistrationImplementor) queryParameter; + registration.prepare( statement, i ); + if ( registration.getMode() == ParameterMode.REF_CURSOR ) { + i++; + } + else { + i += registration.getSqlTypes().length; + } + } + catch (SQLException e) { + throw getSession().getJdbcServices().getSqlExceptionHelper().convert( + e, + "Error preparing registered callable parameter", + getProcedureName() + ); + } + } } - } + ); - return new ProcedureOutputsImpl( this, statement ); - } - catch (SQLException e) { - throw getSession().getJdbcServices().getSqlExceptionHelper().convert( - e, - "Error preparing CallableStatement", - getProcedureName() - ); - } + return new ProcedureOutputsImpl( this, statement ); } @Override @@ -492,6 +446,7 @@ public ProcedureCallImplementor setEntity(String name, Object val) { * * @return The spaces */ + @SuppressWarnings("WeakerAccess") protected Set synchronizedQuerySpaces() { if ( synchronizedQuerySpaces == null ) { synchronizedQuerySpaces = new HashSet<>(); @@ -522,6 +477,7 @@ public ProcedureCallImplementor addSynchronizedEntityName(String entityName) return this; } + @SuppressWarnings("WeakerAccess") protected void addSynchronizedQuerySpaces(EntityPersister persister) { synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) ); } @@ -553,11 +509,15 @@ public QueryParameters getQueryParameters() { */ public ParameterRegistrationImplementor[] collectRefCursorParameters() { final List refCursorParams = new ArrayList<>(); - for ( ParameterRegistrationImplementor param : registeredParameters ) { - if ( param.getMode() == ParameterMode.REF_CURSOR ) { - refCursorParams.add( param ); - } - } + + getParameterMetadata().visitRegistrations( + queryParameter -> { + final ParameterRegistrationImplementor registration = (ParameterRegistrationImplementor) queryParameter; + if ( registration.getMode() == ParameterMode.REF_CURSOR ) { + refCursorParams.add( registration ); + } + } + ); return refCursorParams.toArray( new ParameterRegistrationImplementor[refCursorParams.size()] ); } @@ -566,8 +526,8 @@ public ProcedureCallMemento extractMemento(Map hints) { return new ProcedureCallMementoImpl( procedureName, Util.copy( queryReturns ), - parameterStrategy, - toParameterMementos( registeredParameters ), + getParameterMetadata().getParameterStrategy(), + toParameterMementos( getParameterMetadata() ), Util.copy( synchronizedQuerySpaces ), Util.copy( hints ) ); @@ -578,22 +538,28 @@ public ProcedureCallMemento extractMemento() { return new ProcedureCallMementoImpl( procedureName, Util.copy( queryReturns ), - parameterStrategy, - toParameterMementos( registeredParameters ), + getParameterMetadata().getParameterStrategy(), + toParameterMementos( getParameterMetadata() ), Util.copy( synchronizedQuerySpaces ), Util.copy( getHints() ) ); } - private static List toParameterMementos(List> registeredParameters) { - if ( registeredParameters == null ) { - return null; + private static List toParameterMementos(ProcedureParameterMetadata parameterMetadata) { + if ( parameterMetadata.getParameterStrategy() == ParameterStrategy.UNKNOWN ) { + // none... + return Collections.emptyList(); } - final List copy = CollectionHelper.arrayList( registeredParameters.size() ); - for ( ParameterRegistrationImplementor registration : registeredParameters ) { - copy.add( ProcedureCallMementoImpl.ParameterMemento.fromRegistration( registration ) ); - } + final List copy = new ArrayList<>(); + + parameterMetadata.visitRegistrations( + queryParameter -> { + final ParameterRegistrationImplementor registration = (ParameterRegistrationImplementor) queryParameter; + copy.add( ProcedureCallMementoImpl.ParameterMemento.fromRegistration( registration ) ); + } + ); + return copy; } @@ -604,6 +570,7 @@ private static List toParameterMement private ProcedureOutputs procedureResult; @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { getProducer().checkOpen( true ); @@ -622,6 +589,7 @@ public ProcedureCallImplementor registerStoredProcedureParameter(int position } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) { getProducer().checkOpen( true ); try { @@ -667,9 +635,8 @@ protected ProcedureOutputs outputs() { @Override public int executeUpdate() { - if ( ! getProducer().isTransactionInProgress() ) { - throw new TransactionRequiredException( "javax.persistence.Query.executeUpdate requires active transaction" ); - } + getProducer().checkTransactionNeededForUpdateOperation( + "javax.persistence.Query.executeUpdate requires active transaction" ); // the expectation is that there is just one Output, of type UpdateCountOutput try { @@ -828,182 +795,127 @@ public ProcedureCallImplementor setFlushMode(FlushModeType flushModeType) { return this; } + // todo (5.3) : all of the parameter stuff here can be done in AbstractProducedQuery + // using #getParameterMetadata and #getQueryParameterBindings for abstraction. + // this "win" is to define these in one place + @Override public

    ProcedureCallImplementor setParameter(QueryParameter

    parameter, P value) { - locateParameterRegistration( parameter ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ).setBindValue( value ); return this; } - @SuppressWarnings("unchecked") - private

    ParameterRegistrationImplementor

    locateParameterRegistration(Parameter

    parameter) { - if ( parameter.getName() != null ) { - return locateParameterRegistration( parameter.getName() ); - } - - if ( parameter.getPosition() != null ) { - return locateParameterRegistration( parameter.getPosition() ); - } - - throw getExceptionConverter().convert( - new IllegalArgumentException( "Could not resolve registration for given parameter reference [" + parameter + "]" ) - ); - } - - @SuppressWarnings("unchecked") - private

    ParameterRegistrationImplementor

    locateParameterRegistration(String name) { - assert name != null; - - if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { - throw new IllegalArgumentException( "Expecting positional parameter" ); - } - - for ( ParameterRegistrationImplementor registeredParameter : registeredParameters ) { - if ( name.equals( registeredParameter.getName() ) ) { - return (ParameterRegistrationImplementor

    ) registeredParameter; - } - } - - throw new IllegalArgumentException( "Unknown parameter registration name [" + name + "]" ); - } - - @SuppressWarnings("unchecked") - private

    ParameterRegistrationImplementor

    locateParameterRegistration(int position) { - if ( parameterStrategy == ParameterStrategy.NAMED ) { - throw new IllegalArgumentException( "Expecting named parameter" ); - } - - for ( ParameterRegistrationImplementor registeredParameter : registeredParameters ) { - if ( registeredParameter.getPosition() != null && registeredParameter.getPosition() == position ) { - return (ParameterRegistrationImplementor

    ) registeredParameter; - } - } - - throw new IllegalArgumentException( "Unknown parameter registration position [" + position + "]" ); - } - @Override public

    ProcedureCallImplementor setParameter(Parameter

    parameter, P value) { - locateParameterRegistration( parameter ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ).setBindValue( value ); return this; } @Override public ProcedureCallImplementor setParameter(String name, Object value) { - locateParameterRegistration( name ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ).setBindValue( value ); return this; } @Override public ProcedureCallImplementor setParameter(int position, Object value) { - locateParameterRegistration( position ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ).setBindValue( value ); return this; } @Override public

    ProcedureCallImplementor setParameter(QueryParameter

    parameter, P value, Type type) { - final ParameterRegistrationImplementor

    reg = locateParameterRegistration( parameter ); - reg.bindValue( value ); - reg.setHibernateType( type ); + final QueryParameterBinding

    binding = paramBindings.getBinding( parameter ); + binding.setBindValue( value, type ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Object value, Type type) { - final ParameterRegistrationImplementor reg = locateParameterRegistration( name ); - reg.bindValue( value ); - reg.setHibernateType( type ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ); + binding.setBindValue( value, type ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Object value, Type type) { - final ParameterRegistrationImplementor reg = locateParameterRegistration( position ); - reg.bindValue( value ); - reg.setHibernateType( type ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ); + binding.setBindValue( value, type ); return this; } @Override + @SuppressWarnings("unchecked") public

    ProcedureCallImplementor setParameter(QueryParameter

    parameter, P value, TemporalType temporalType) { - locateParameterRegistration( parameter ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( parameter ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Object value, TemporalType temporalType) { - locateParameterRegistration( name ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Object value, TemporalType temporalType) { - locateParameterRegistration( position ).bindValue( value, temporalType ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(QueryParameter parameter, Collection values) { - super.setParameterList( parameter, values ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Collection values) { - super.setParameterList( name, values ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Collection values, Type type) { - super.setParameterList( name, values, type ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Object[] values, Type type) { - super.setParameterList( name, values, type ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Object[] values) { - super.setParameterList( name, values ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(Parameter parameter, Calendar value, TemporalType temporalType) { - locateParameterRegistration( parameter ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(Parameter parameter, Date value, TemporalType temporalType) { - locateParameterRegistration( parameter ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Calendar value, TemporalType temporalType) { - locateParameterRegistration( name ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( name ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Date value, TemporalType temporalType) { - locateParameterRegistration( name ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( name ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Calendar value, TemporalType temporalType) { - locateParameterRegistration( position ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( position ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Date value, TemporalType temporalType) { - locateParameterRegistration( position ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( position ); + binding.setBindValue( value, temporalType ); return this; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java index 3ebab8379a47..7817a432b92b 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java @@ -162,7 +162,7 @@ public static ParameterMemento fromRegistration(ParameterRegistrationImplementor registration.getPosition(), registration.getName(), registration.getMode(), - registration.getType(), + registration.getParameterType(), registration.getHibernateType(), registration.isPassNullsEnabled() ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java index 3849161f90fe..5552e1c66c87 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java @@ -8,6 +8,8 @@ import java.sql.CallableStatement; import java.sql.ResultSet; +import java.util.List; +import java.util.function.Supplier; import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; import org.hibernate.procedure.ParameterRegistration; @@ -90,7 +92,7 @@ protected Output buildExtendedReturn() { .getService( RefCursorSupport.class ) .getResultSet( ProcedureOutputsImpl.this.callableStatement, refCursorParam.getPosition() ); } - return buildResultSetOutput( extractResults( resultSet ) ); + return buildResultSetOutput( () -> extractResults( resultSet ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/package-info.java b/hibernate-core/src/main/java/org/hibernate/procedure/package-info.java index a5cffb455f76..b799bc7c21c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/package-info.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/package-info.java @@ -34,7 +34,7 @@ * *

    * Finally output parameters can be accessed using the overloaded {@link ProcedureResult#getOutputParameterValue} methods. - * For portability amongst databases, it is advised to access the output parameters afterQuery all returns have been + * For portability amongst databases, it is advised to access the output parameters after all returns have been * processed. * * @see org.hibernate.Session#createStoredProcedureCall(String) diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java index e0d626bbd4bf..940162d22972 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java @@ -7,24 +7,44 @@ package org.hibernate.procedure.spi; import java.sql.CallableStatement; +import java.util.ArrayList; import java.util.List; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.procedure.internal.ProcedureParamBindings; +import org.hibernate.query.procedure.internal.ProcedureParameterMetadata; /** * @author Steve Ebersole */ public interface CallableStatementSupport { - String renderCallableStatement( + default String renderCallableStatement( String name, ParameterStrategy parameterStrategy, List> parameterRegistrations, - SharedSessionContractImplementor session); + SharedSessionContractImplementor session) { + throw new UnsupportedOperationException( + "Legacy #renderCallableStatement called but implementation does not support that call." + ); + } + + default String renderCallableStatement( + String procedureName, + ProcedureParameterMetadata parameterMetadata, + ProcedureParamBindings paramBindings, + SharedSessionContractImplementor session) { + return renderCallableStatement( + procedureName, + parameterMetadata.getParameterStrategy(), + new ArrayList( parameterMetadata.collectAllParameters() ), + session + ); + } void registerParameters( String procedureName, CallableStatement statement, ParameterStrategy parameterStrategy, List> parameterRegistrations, - SharedSessionContractImplementor session); + SharedSessionContractImplementor session);; } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java index a9b6bb335b19..130cde2d2092 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.query.QueryParameter; import org.hibernate.type.Type; /** @@ -33,6 +34,7 @@ public interface ParameterRegistrationImplementor extends ParameterRegistrati * * @return The Hibernate Type */ + @Override Type getHibernateType(); /** @@ -45,6 +47,7 @@ public interface ParameterRegistrationImplementor extends ParameterRegistrati * that the parameter will simply be ignored, with the assumption that the corresponding argument * defined a default value. */ + @Override boolean isPassNullsEnabled(); /** @@ -55,7 +58,7 @@ public interface ParameterRegistrationImplementor extends ParameterRegistrati int[] getSqlTypes(); /** - * Extract value from the statement afterQuery execution (used for OUT/INOUT parameters). + * Extract value from the statement after execution (used for OUT/INOUT parameters). * * @param statement The callable statement * diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java index c7618617b13c..b6da1205657a 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java @@ -8,6 +8,7 @@ import java.util.Calendar; import java.util.Date; +import java.util.List; import javax.persistence.FlushModeType; import javax.persistence.Parameter; import javax.persistence.ParameterMode; @@ -20,6 +21,16 @@ * @author Steve Ebersole */ public interface ProcedureCallImplementor extends ProcedureCall, QueryImplementor { + @Override + default List getResultList() { + return list(); + } + + @Override + default R getSingleResult() { + return uniqueResult(); + } + @Override ProcedureCallImplementor setHint(String hintName, Object value); diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/AbstractFieldSerialForm.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/AbstractFieldSerialForm.java index 3e185964abc7..ea9bfd5566e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/internal/AbstractFieldSerialForm.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/AbstractFieldSerialForm.java @@ -9,6 +9,7 @@ import java.io.Serializable; import java.lang.reflect.Field; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.access.spi.PropertyAccessSerializationException; /** @@ -32,7 +33,7 @@ protected AbstractFieldSerialForm(Class declaringClass, String fieldName) { protected Field resolveField() { try { final Field field = declaringClass.getDeclaredField( fieldName ); - field.setAccessible( true ); + ReflectHelper.ensureAccessibility( field ); return field; } catch (NoSuchFieldException e) { diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessEnhancedImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessEnhancedImpl.java index 96d2b1abe86a..a8eab526741f 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessEnhancedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessEnhancedImpl.java @@ -6,13 +6,11 @@ */ package org.hibernate.property.access.internal; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.property.access.spi.EnhancedSetterImpl; import org.hibernate.property.access.spi.PropertyAccessStrategy; import org.hibernate.property.access.spi.Setter; -import org.hibernate.property.access.spi.SetterFieldImpl; + +import java.lang.reflect.Field; /** * A PropertyAccess for byte code enhanced entities. Enhanced setter methods ( if available ) are used for @@ -32,20 +30,6 @@ public PropertyAccessEnhancedImpl( @Override protected Setter fieldSetter(Class containerJavaType, String propertyName, Field field) { - return resolveEnhancedSetterForField( containerJavaType, propertyName, field ); + return new EnhancedSetterImpl( containerJavaType, propertyName, field ); } - - private static Setter resolveEnhancedSetterForField(Class containerClass, String propertyName, Field field) { - try { - String enhancedSetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + propertyName; - Method enhancedSetter = containerClass.getDeclaredMethod( enhancedSetterName, field.getType() ); - enhancedSetter.setAccessible( true ); - return new EnhancedSetterImpl( containerClass, propertyName, enhancedSetter ); - } - catch (NoSuchMethodException e) { - // enhancedSetter = null --- field not enhanced: fallback to reflection using the field - return new SetterFieldImpl( containerClass, propertyName, field ); - } - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessMixedImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessMixedImpl.java index f1f03818fc3c..594930b855b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessMixedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessMixedImpl.java @@ -24,6 +24,9 @@ import org.hibernate.property.access.spi.SetterFieldImpl; import org.hibernate.property.access.spi.SetterMethodImpl; +import static org.hibernate.internal.util.ReflectHelper.getterMethodOrNull; +import static org.hibernate.internal.util.ReflectHelper.setterMethodOrNull; + /** * A PropertyAccess based on mix of getter/setter method and/or field. * @@ -85,24 +88,6 @@ protected static Field fieldOrNull(Class containerJavaType, String propertyName) } } - protected static Method getterMethodOrNull(Class containerJavaType, String propertyName) { - try { - return ReflectHelper.findGetterMethod( containerJavaType, propertyName ); - } - catch (PropertyNotFoundException e) { - return null; - } - } - - protected static Method setterMethodOrNull(Class containerJavaType, String propertyName, Class propertyJavaType) { - try { - return ReflectHelper.findSetterMethod( containerJavaType, propertyName, propertyJavaType ); - } - catch (PropertyNotFoundException e) { - return null; - } - } - protected static AccessType getAccessType(Class containerJavaType, String propertyName) { Field field = fieldOrNull( containerJavaType, propertyName ); AccessType fieldAccessType = getAccessTypeOrNull( field ); 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 562df0e3d05a..539e26a0fc55 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,6 +6,7 @@ */ package org.hibernate.property.access.internal; +import org.hibernate.mapping.Map; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.PropertyAccessStrategy; @@ -21,6 +22,19 @@ public class PropertyAccessStrategyMapImpl implements PropertyAccessStrategy { @Override 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)) { + throw new IllegalArgumentException( + String.format( + "Expecting class: [%1$s], but containerJavaType is of type: [%2$s] for propertyName: [%3$s]", + Map.class.getName(), + containerJavaType.getName(), + propertyName + ) + ); + } + return new PropertyAccessMapImpl( this, propertyName ); } } 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 9b4b717624ee..89e90c9d0347 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/spi/EnhancedSetterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/spi/EnhancedSetterImpl.java @@ -6,171 +6,48 @@ */ package org.hibernate.property.access.spi; -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import org.hibernate.PropertyAccessException; -import org.hibernate.PropertySetterAccessException; -import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.engine.spi.CompositeOwner; +import org.hibernate.engine.spi.CompositeTracker; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.CoreMessageLogger; -import static org.hibernate.internal.CoreLogging.messageLogger; +import java.lang.reflect.Field; /** * A specialized Setter implementation for handling setting values into * a into a bytecode-enhanced Class. The reason we need specialized handling - * is to render the fact that the + * is to render the fact that we need to account for certain enhancement features + * during the setting process. * * @author Steve Ebersole * @author Luis Barreiro */ -public class EnhancedSetterImpl implements Setter { - private static final CoreMessageLogger LOG = messageLogger( EnhancedSetterImpl.class ); +public class EnhancedSetterImpl extends SetterFieldImpl { - private final Class containerClass; private final String propertyName; - private final Method setterMethod; - - private final boolean isPrimitive; - public EnhancedSetterImpl(Class containerClass, String propertyName, Method setterMethod) { - this.containerClass = containerClass; + public EnhancedSetterImpl(Class containerClass, String propertyName, Field field) { + super( containerClass, propertyName, field ); this.propertyName = propertyName; - this.setterMethod = setterMethod; - this.isPrimitive = setterMethod.getParameterTypes()[0].isPrimitive(); } @Override public void set(Object target, Object value, SessionFactoryImplementor factory) { - try { - - // for enhanced attribute, don't flag as dirty - if ( target instanceof SelfDirtinessTracker ) { - ( (SelfDirtinessTracker) target ).$$_hibernate_suspendDirtyTracking( true ); - try { - setterMethod.invoke( target, value ); - } - finally { - ( (SelfDirtinessTracker) target ).$$_hibernate_suspendDirtyTracking( false ); - } - } - else { - setterMethod.invoke( target, value ); - } - - } - catch (NullPointerException npe) { - if ( value == null && isPrimitive ) { - throw new PropertyAccessException( - npe, - "Null value was assigned to a property of primitive type", - true, - containerClass, - propertyName - ); - } - else { - throw new PropertyAccessException( - npe, - "NullPointerException occurred while calling", - true, - containerClass, - propertyName - ); - } - } - catch (InvocationTargetException ite) { - throw new PropertyAccessException( - ite, - "Exception occurred inside", - true, - containerClass, - propertyName - ); - } - catch (IllegalAccessException iae) { - throw new PropertyAccessException( - iae, - "IllegalAccessException occurred while calling", - true, - containerClass, - propertyName - ); - //cannot occur - } - catch (IllegalArgumentException iae) { - if ( value == null && isPrimitive ) { - throw new PropertyAccessException( - iae, - "Null value was assigned to a property of primitive type", - true, - containerClass, - propertyName - ); - } - else { - final Class expectedType = setterMethod.getParameterTypes()[0]; - LOG.illegalPropertySetterArgument( containerClass.getName(), propertyName ); - LOG.expectedType( expectedType.getName(), value == null ? null : value.getClass().getName() ); - throw new PropertySetterAccessException( - iae, - containerClass, - propertyName, - expectedType, - target, - value - ); - } - } - } - - @Override - public String getMethodName() { - return setterMethod.getName(); - } - - @Override - public Method getMethod() { - return setterMethod; - } - - private Object writeReplace() throws ObjectStreamException { - return new SerialForm( containerClass, propertyName, setterMethod ); - } - - private static class SerialForm implements Serializable { - private final Class containerClass; - private final String propertyName; - private final Class declaringClass; - private final String methodName; - private final Class argumentType; + super.set( target, value, factory ); - private SerialForm(Class containerClass, String propertyName, Method method) { - this.containerClass = containerClass; - this.propertyName = propertyName; - this.declaringClass = method.getDeclaringClass(); - this.methodName = method.getName(); - this.argumentType = method.getParameterTypes()[0]; + // This sets the component relation for dirty tracking purposes + if ( target instanceof CompositeOwner && value instanceof CompositeTracker ) { + ( (CompositeTracker) value ).$$_hibernate_setOwner( propertyName, (CompositeOwner) target ); } - private Object readResolve() { - return new EnhancedSetterImpl( containerClass, propertyName, resolveMethod() ); - } - - @SuppressWarnings("unchecked") - private Method resolveMethod() { - try { - return declaringClass.getDeclaredMethod( methodName, argumentType ); - } - catch (NoSuchMethodException e) { - throw new PropertyAccessSerializationException( - "Unable to resolve setter method on deserialization : " + declaringClass.getName() + "#" - + methodName + "(" + argumentType.getName() + ")" - ); + // This marks the attribute as initialized, so it doesn't get lazy loaded afterwards + if ( target instanceof PersistentAttributeInterceptable ) { + PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) target ).$$_hibernate_getInterceptor(); + if ( interceptor != null && interceptor instanceof LazyAttributeLoadingInterceptor ) { + interceptor.attributeInitialized( propertyName ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterFieldImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterFieldImpl.java index 404f379a0726..5f0a4a54ef3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterFieldImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterFieldImpl.java @@ -15,6 +15,7 @@ import java.util.Map; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.access.internal.AbstractFieldSerialForm; /** @@ -26,11 +27,14 @@ public class GetterFieldImpl implements Getter { private final Class containerClass; private final String propertyName; private final Field field; + private final Method getterMethod; public GetterFieldImpl(Class containerClass, String propertyName, Field field) { this.containerClass = containerClass; this.propertyName = propertyName; this.field = field; + + this.getterMethod = ReflectHelper.findGetterMethodForFieldAccess( field, propertyName ); } @Override @@ -98,12 +102,12 @@ public Member getMember() { @Override public String getMethodName() { - return null; + return getterMethod != null ? getterMethod.getName() : null; } @Override public Method getMethod() { - return null; + return getterMethod; } private Object writeReplace() throws ObjectStreamException { @@ -123,5 +127,6 @@ private SerialForm(Class containerClass, String propertyName, Field field) { private Object readResolve() { return new GetterFieldImpl( containerClass, propertyName, resolveField() ); } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterMethodImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterMethodImpl.java index 36676303815d..076d9005bc79 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterMethodImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/spi/GetterMethodImpl.java @@ -16,6 +16,7 @@ import org.hibernate.PropertyAccessException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; import static org.hibernate.internal.CoreLogging.messageLogger; @@ -122,7 +123,7 @@ private Object readResolve() { private Method resolveMethod() { try { final Method method = declaringClass.getDeclaredMethod( methodName ); - method.setAccessible( true ); + ReflectHelper.ensureAccessibility( method ); return method; } catch (NoSuchMethodException e) { diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java index 7c1bb6c624b4..58c7d9e47267 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java @@ -14,6 +14,7 @@ import org.hibernate.PropertyAccessException; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.access.internal.AbstractFieldSerialForm; /** @@ -25,11 +26,13 @@ public class SetterFieldImpl implements Setter { private final Class containerClass; private final String propertyName; private final Field field; + private final Method setterMethod; public SetterFieldImpl(Class containerClass, String propertyName, Field field) { this.containerClass = containerClass; this.propertyName = propertyName; this.field = field; + this.setterMethod = ReflectHelper.setterMethodOrNull( containerClass, propertyName, field.getType() ); } @Override @@ -72,12 +75,12 @@ public void set(Object target, Object value, SessionFactoryImplementor factory) @Override public String getMethodName() { - return null; + return setterMethod != null ? setterMethod.getName() : null; } @Override public Method getMethod() { - return null; + return setterMethod; } private Object writeReplace() throws ObjectStreamException { @@ -88,6 +91,7 @@ private static class SerialForm extends AbstractFieldSerialForm implements Seria private final Class containerClass; private final String propertyName; + private SerialForm(Class containerClass, String propertyName, Field field) { super( field ); this.containerClass = containerClass; @@ -97,5 +101,6 @@ private SerialForm(Class containerClass, String propertyName, Field field) { private Object readResolve() { return new SetterFieldImpl( containerClass, propertyName, resolveField() ); } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterMethodImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterMethodImpl.java index 942062878e66..9dce49e7eb5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterMethodImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterMethodImpl.java @@ -15,6 +15,7 @@ import org.hibernate.PropertySetterAccessException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; import static org.hibernate.internal.CoreLogging.messageLogger; @@ -146,7 +147,7 @@ private Object readResolve() { private Method resolveMethod() { try { final Method method = declaringClass.getDeclaredMethod( methodName, argumentType ); - method.setAccessible( true ); + ReflectHelper.ensureAccessibility( method ); return method; } catch (NoSuchMethodException e) { diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java old mode 100755 new mode 100644 index 3afdd902b9e8..be5b30b37173 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -13,14 +13,15 @@ import org.hibernate.LazyInitializationException; import org.hibernate.SessionException; import org.hibernate.TransientObjectException; +import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.persister.entity.EntityPersister; -import org.jboss.logging.Logger; - /** * Convenience base class for lazy initialization handlers. Centralizes the basic plumbing of doing lazy * initialization freeing subclasses to acts as essentially adapters to their intended entity mode and/or @@ -29,7 +30,7 @@ * @author Gavin King */ public abstract class AbstractLazyInitializer implements LazyInitializer { - private static final Logger log = Logger.getLogger( AbstractLazyInitializer.class ); + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractLazyInitializer.class ); private String entityName; private Serializable id; @@ -44,8 +45,17 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { private boolean allowLoadOutsideTransaction; /** - * For serialization from the non-pojo initializers (HHH-3309) + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. + * Subclasses should rather implement Serializable with an {@code Object writeReplace()} method returning + * a subclass of {@link AbstractSerializableProxy}, + * which in turn implements Serializable and an {@code Object readResolve()} method + * instantiating the {@link AbstractLazyInitializer} subclass + * and calling {@link AbstractSerializableProxy#afterDeserialization(AbstractLazyInitializer)} on it. + * See {@link org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor} and + * {@link org.hibernate.proxy.pojo.bytebuddy.SerializableProxy} for examples. */ + @Deprecated protected AbstractLazyInitializer() { } @@ -75,9 +85,18 @@ public final String getEntityName() { @Override public final Serializable getIdentifier() { + if ( isUninitialized() && isInitializeProxyWhenAccessingIdentifier() ) { + initialize(); + } return id; } + private boolean isInitializeProxyWhenAccessingIdentifier() { + return session != null && session.getFactory() + .getSessionFactoryOptions() + .getJpaCompliance().isJpaProxyComplianceEnabled(); + } + @Override public final void setIdentifier(Serializable id) { this.id = id; @@ -102,7 +121,11 @@ public final void setSession(SharedSessionContractImplementor s) throws Hibernat } else if ( isConnectedToSession() ) { //TODO: perhaps this should be some other RuntimeException... - throw new HibernateException( "illegally attempted to associate a proxy with two open Sessions" ); + LOG.attemptToAssociateProxyWithTwoOpenSessions( + entityName, + id + ); + throw new HibernateException( "illegally attempted to associate proxy [" + entityName + "#" + id + "] with two open Sessions" ); } else { // s != null @@ -143,13 +166,13 @@ public final void initialize() throws HibernateException { permissiveInitialization(); } else if ( session == null ) { - throw new LazyInitializationException( "could not initialize proxy - no Session" ); + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" ); } else if ( !session.isOpen() ) { - throw new LazyInitializationException( "could not initialize proxy - the owning Session was closed" ); + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session was closed" ); } else if ( !session.isConnected() ) { - throw new LazyInitializationException( "could not initialize proxy - the owning Session is disconnected" ); + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session is disconnected" ); } else { target = session.immediateLoad( entityName, id ); @@ -166,7 +189,7 @@ protected void permissiveInitialization() { if ( session == null ) { //we have a detached collection thats set to null, reattach if ( sessionFactoryUuid == null ) { - throw new LazyInitializationException( "could not initialize proxy - no Session" ); + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" ); } try { SessionFactoryImplementor sf = (SessionFactoryImplementor) @@ -200,12 +223,12 @@ protected void permissiveInitialization() { session.close(); } catch (Exception e) { - log.warn( "Unable to close temporary session used to load lazy proxy associated to no session" ); + LOG.warn( "Unable to close temporary session used to load lazy proxy associated to no session" ); } } } catch (Exception e) { - log.error( "Initialization failure", e ); + LOG.error( "Initialization failure [" + entityName + "#" + id + "]", e ); throw new LazyInitializationException( e.getMessage() ); } } @@ -215,10 +238,37 @@ else if ( session.isOpen() && session.isConnected() ) { checkTargetState(session); } else { - throw new LazyInitializationException( "could not initialize proxy - Session was closed or disced" ); + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - Session was closed or disced" ); + } + } + + /** + * Attempt to initialize the proxy without loading anything from the database. + * + * This will only have any effect if the proxy is still attached to a session, + * and the entity being proxied has been loaded and added to the persistence context + * of that session since the proxy was created. + */ + public final void initializeWithoutLoadIfPossible() { + if ( !initialized && session != null && session.isOpen() ) { + final EntityKey key = session.generateEntityKey( + getIdentifier(), + session.getFactory().getMetamodel().entityPersister( getEntityName() ) + ); + final Object entity = session.getPersistenceContext().getEntity( key ); + if ( entity != null ) { + setImplementation( entity ); + } } } + /** + * Initialize internal state based on the currently attached session, + * in order to be ready to load data even after the proxy is detached from the session. + * + * This method only has any effect if + * {@link SessionFactoryOptions#isInitializeLazyStateOutsideTransactionsEnabled()} is {@code true}. + */ protected void prepareForPossibleLoadingOutsideTransaction() { if ( session != null ) { allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); @@ -291,12 +341,12 @@ public final boolean isReadOnlySettingAvailable() { private void errorIfReadOnlySettingNotAvailable() { if ( session == null ) { throw new TransientObjectException( - "Proxy is detached (i.e, session is null). The read-only/modifiable setting is only accessible when the proxy is associated with an open session." + "Proxy [" + entityName + "#" + id + "] is detached (i.e, session is null). The read-only/modifiable setting is only accessible when the proxy is associated with an open session." ); } if ( session.isClosed() ) { throw new SessionException( - "Session is closed. The read-only/modifiable setting is only accessible when the proxy is associated with an open session." + "Session is closed. The read-only/modifiable setting is only accessible when the proxy [" + entityName + "#" + id + "] is associated with an open session." ); } } @@ -314,7 +364,7 @@ public final void setReadOnly(boolean readOnly) { if ( this.readOnly != readOnly ) { final EntityPersister persister = session.getFactory().getEntityPersister( entityName ); if ( !persister.isMutable() && !readOnly ) { - throw new IllegalStateException( "cannot make proxies for immutable entities modifiable" ); + throw new IllegalStateException( "cannot make proxies [" + entityName + "#" + id + "] for immutable entities modifiable" ); } this.readOnly = readOnly; if ( initialized ) { @@ -339,35 +389,67 @@ public final void setReadOnly(boolean readOnly) { * * @throws IllegalStateException if isReadOnlySettingAvailable() == true */ - protected final Boolean isReadOnlyBeforeAttachedToSession() { + public final Boolean isReadOnlyBeforeAttachedToSession() { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( - "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true" + "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" ); } return readOnlyBeforeAttachedToSession; } /** - * Set the read-only/modifiable setting that should be put in affect when it is - * attached to a session. - *

    - * This method should only be called during deserialization, beforeQuery associating + * Get whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return {@code true} if out-of-transaction loads are allowed, {@code false} otherwise. + */ + protected boolean isAllowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + /** + * Get the session factory UUID. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return the session factory UUID. + */ + protected String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Restore settings that are not passed to the constructor, + * but are still preserved during serialization. + * + * This method should only be called during deserialization, before associating * the proxy with a session. * * @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when * associated with a session; null indicates that the default should be used. + * @param sessionFactoryUuid the session factory uuid, to be used if {@code allowLoadOutsideTransaction} is {@code true}. + * @param allowLoadOutsideTransaction whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. * * @throws IllegalStateException if isReadOnlySettingAvailable() == true */ /* package-private */ - final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) { + final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession, + String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( - "Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true" + "Cannot call afterDeserialization when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" ); } this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession; + + this.sessionFactoryUuid = sessionFactoryUuid; + this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java index 772e269e8681..95f0a6dfff3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java @@ -5,10 +5,11 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.proxy; + import java.io.Serializable; /** - * Convenience base class for SerializableProxy. + * Convenience base class for the serialized form of {@link AbstractLazyInitializer}. * * @author Gail Badner */ @@ -16,17 +17,32 @@ public abstract class AbstractSerializableProxy implements Serializable { private String entityName; private Serializable id; private Boolean readOnly; + private String sessionFactoryUuid; + private boolean allowLoadOutsideTransaction; /** - * For serialization + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. */ + @Deprecated protected AbstractSerializableProxy() { } + /** + * @deprecated use {@link #AbstractSerializableProxy(String, Serializable, Boolean, String, boolean)} instead. + */ + @Deprecated protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly) { + this( entityName, id, readOnly, null, false ); + } + + protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly, + String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { this.entityName = entityName; this.id = id; this.readOnly = readOnly; + this.sessionFactoryUuid = sessionFactoryUuid; + this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } protected String getEntityName() { @@ -40,14 +56,29 @@ protected Serializable getId() { /** * Set the read-only/modifiable setting from this object in an AbstractLazyInitializer. * - * This method should only be called during deserialization, beforeQuery associating the + * This method should only be called during deserialization, before associating the * AbstractLazyInitializer with a session. * * @param li the read-only/modifiable setting to use when * associated with a session; null indicates that the default should be used. * @throws IllegalStateException if isReadOnlySettingAvailable() == true + * + * @deprecated Use {@link #afterDeserialization(AbstractLazyInitializer)} instead. */ + @Deprecated protected void setReadOnlyBeforeAttachedToSession(AbstractLazyInitializer li) { - li.setReadOnlyBeforeAttachedToSession( readOnly ); + li.afterDeserialization( readOnly, null, false ); + } + + /** + * Initialize an {@link AbstractLazyInitializer} after deserialization. + * + * This method should only be called during deserialization, + * before associating the AbstractLazyInitializer with a session. + * + * @param li the {@link AbstractLazyInitializer} to initialize. + */ + protected void afterDeserialization(AbstractLazyInitializer li) { + li.afterDeserialization( readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/ProxyConfiguration.java b/hibernate-core/src/main/java/org/hibernate/proxy/ProxyConfiguration.java index 50c99dd5c0ac..18c3ebcbdf81 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/ProxyConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/ProxyConfiguration.java @@ -58,7 +58,7 @@ interface Interceptor { } /** - * An static interceptor that guards against method calls before the interceptor is set. + * A static interceptor that guards against method calls before the interceptor is set. */ class InterceptorDispatcher { diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/ProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/ProxyFactory.java index ec4be5ef4c2c..60a796579836 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/ProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/ProxyFactory.java @@ -22,7 +22,7 @@ public interface ProxyFactory { /** - * Called immediately afterQuery instantiation of this factory. + * Called immediately after instantiation of this factory. *

    * Essentially equivalent to constructor injection, but contracted * here via interface. diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java index 7561579352f4..6c08dd18d013 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java @@ -31,4 +31,20 @@ public Class getPersistentClass() { throw new UnsupportedOperationException("dynamic-map entity representation"); } + // Expose the following methods to MapProxy by overriding them (so that classes in this package see them) + + @Override + protected void prepareForPossibleLoadingOutsideTransaction() { + super.prepareForPossibleLoadingOutsideTransaction(); + } + + @Override + protected boolean isAllowLoadOutsideTransaction() { + return super.isAllowLoadOutsideTransaction(); + } + + @Override + protected String getSessionFactoryUuid() { + return super.getSessionFactoryUuid(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java index 7585b2b44723..de6e2a2eb48a 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java @@ -22,64 +22,105 @@ public class MapProxy implements HibernateProxy, Map, Serializable { private MapLazyInitializer li; + private Object replacement; + MapProxy(MapLazyInitializer li) { this.li = li; } - public Object writeReplace() { - return this; - } - + @Override public LazyInitializer getHibernateLazyInitializer() { return li; } + @Override public int size() { return li.getMap().size(); } + @Override public void clear() { li.getMap().clear(); } + @Override public boolean isEmpty() { return li.getMap().isEmpty(); } + @Override public boolean containsKey(Object key) { return li.getMap().containsKey(key); } + @Override public boolean containsValue(Object value) { return li.getMap().containsValue(value); } + @Override public Collection values() { return li.getMap().values(); } + @Override public void putAll(Map t) { li.getMap().putAll(t); } + @Override public Set entrySet() { return li.getMap().entrySet(); } + @Override public Set keySet() { return li.getMap().keySet(); } + @Override public Object get(Object key) { return li.getMap().get(key); } + @Override public Object remove(Object key) { return li.getMap().remove(key); } + @Override public Object put(Object key, Object value) { return li.getMap().put(key, value); } + @Override + public Object writeReplace() { + /* + * If the target has already been loaded somewhere, just not set on the proxy, + * then use it to initialize the proxy so that we will serialize that instead of the proxy. + */ + li.initializeWithoutLoadIfPossible(); + + if ( li.isUninitialized() ) { + if ( replacement == null ) { + li.prepareForPossibleLoadingOutsideTransaction(); + replacement = serializableProxy(); + } + return replacement; + } + else { + return li.getImplementation(); + } + } + + private Object serializableProxy() { + return new SerializableMapProxy( + li.getEntityName(), + li.getIdentifier(), + ( li.isReadOnlySettingAvailable() ? Boolean.valueOf( li.isReadOnly() ) : li.isReadOnlyBeforeAttachedToSession() ), + li.getSessionFactoryUuid(), + li.isAllowLoadOutsideTransaction() + ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java new file mode 100644 index 000000000000..189ae366eb32 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy.map; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.hibernate.proxy.AbstractSerializableProxy; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.type.CompositeType; + +public final class SerializableMapProxy extends AbstractSerializableProxy { + + public SerializableMapProxy( + String entityName, + Serializable id, + Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction) { + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); + } + + private Object readResolve() { + MapLazyInitializer initializer = new MapLazyInitializer( getEntityName(), getId(), null ); + afterDeserialization( initializer ); + return new MapProxy( initializer ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java index 46eae5ff1cf5..a8059fb2d7ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java @@ -9,7 +9,6 @@ import java.io.Serializable; import java.lang.reflect.Method; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.MarkerObject; import org.hibernate.proxy.AbstractLazyInitializer; @@ -91,20 +90,15 @@ else if ( method.equals( setIdentifierMethod ) ) { } private Object getReplacement() { - final SharedSessionContractImplementor session = getSession(); - if ( isUninitialized() && session != null && session.isOpen() ) { - final EntityKey key = session.generateEntityKey( - getIdentifier(), - session.getFactory().getMetamodel().entityPersister( getEntityName() ) - ); - final Object entity = session.getPersistenceContext().getEntity( key ); - if ( entity != null ) { - setImplementation( entity ); - } - } + /* + * If the target has already been loaded somewhere, just not set on the proxy, + * then use it to initialize the proxy so that we will serialize that instead of the proxy. + */ + initializeWithoutLoadIfPossible(); if ( isUninitialized() ) { if ( replacement == null ) { + prepareForPossibleLoadingOutsideTransaction(); replacement = serializableProxy(); } return replacement; diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/ProxyFactoryHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/ProxyFactoryHelper.java new file mode 100644 index 000000000000..4f64fc6bb912 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/ProxyFactoryHelper.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy.pojo; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Iterator; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Subclass; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.property.access.spi.Setter; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.tuple.entity.PojoEntityTuplizer; + +/** + * Most of this code was originally an internal detail of {@link PojoEntityTuplizer}, + * then extracted to make it easier for integrators to initialize a custom + * {@link org.hibernate.proxy.ProxyFactory}. + */ +public final class ProxyFactoryHelper { + + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( ProxyFactoryHelper.class ); + + private ProxyFactoryHelper() { + //not meant to be instantiated + } + + public static Set extractProxyInterfaces(final PersistentClass persistentClass, final String entityName) { + /* + * We need to preserve the order of the interfaces they were put into the set, since javassist will choose the + * first one's class-loader to construct the proxy class with. This is also the reason why HibernateProxy.class + * should be the last one in the order (on JBossAS7 its class-loader will be org.hibernate module's class- + * loader, which will not see the classes inside deployed apps. See HHH-3078 + */ + final Set proxyInterfaces = new java.util.LinkedHashSet(); + final Class mappedClass = persistentClass.getMappedClass(); + final Class proxyInterface = persistentClass.getProxyInterface(); + + if ( proxyInterface != null && !mappedClass.equals( proxyInterface ) ) { + if ( !proxyInterface.isInterface() ) { + throw new MappingException( + "proxy must be either an interface, or the class itself: " + entityName + ); + } + proxyInterfaces.add( proxyInterface ); + } + + if ( mappedClass.isInterface() ) { + proxyInterfaces.add( mappedClass ); + } + + Iterator subclasses = persistentClass.getSubclassIterator(); + while ( subclasses.hasNext() ) { + final Subclass subclass = subclasses.next(); + final Class subclassProxy = subclass.getProxyInterface(); + final Class subclassClass = subclass.getMappedClass(); + if ( subclassProxy != null && !subclassClass.equals( subclassProxy ) ) { + if ( !subclassProxy.isInterface() ) { + throw new MappingException( + "proxy must be either an interface, or the class itself: " + subclass.getEntityName() + ); + } + proxyInterfaces.add( subclassProxy ); + } + } + + proxyInterfaces.add( HibernateProxy.class ); + return proxyInterfaces; + } + + public static void validateProxyability(final PersistentClass persistentClass) { + Iterator properties = persistentClass.getPropertyIterator(); + Class clazz = persistentClass.getMappedClass(); + while ( properties.hasNext() ) { + Property property = (Property) properties.next(); + validateGetterSetterMethodProxyability( "Getter", property.getGetter( clazz ).getMethod() ); + validateGetterSetterMethodProxyability( "Setter", property.getSetter( clazz ).getMethod() ); + } + } + + public static void validateGetterSetterMethodProxyability(String getterOrSetter, Method method ) { + if ( method != null && Modifier.isFinal( method.getModifiers() ) ) { + throw new HibernateException( + String.format( + "%s methods of lazy classes cannot be final: %s#%s", + getterOrSetter, + method.getDeclaringClass().getName(), + method.getName() + ) + ); + } + } + + public static Method extractProxySetIdentifierMethod(final Setter idSetter, final Class proxyInterface) { + Method idSetterMethod = idSetter == null ? null : idSetter.getMethod(); + + Method proxySetIdentifierMethod = idSetterMethod == null || proxyInterface == null ? + null : + ReflectHelper.getMethod( proxyInterface, idSetterMethod ); + return proxySetIdentifierMethod; + } + + public static Method extractProxyGetIdentifierMethod(final Getter idGetter, final Class proxyInterface) { + Method idGetterMethod = idGetter == null ? null : idGetter.getMethod(); + + Method proxyGetIdentifierMethod = idGetterMethod == null || proxyInterface == null ? + null : + ReflectHelper.getMethod( proxyInterface, idGetterMethod ); + return proxyGetIdentifierMethod; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java index a5166fdc98b1..744761d6e5d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java @@ -17,13 +17,6 @@ import org.hibernate.proxy.pojo.BasicLazyInitializer; import org.hibernate.type.CompositeType; -import net.bytebuddy.implementation.bind.annotation.AllArguments; -import net.bytebuddy.implementation.bind.annotation.FieldValue; -import net.bytebuddy.implementation.bind.annotation.Origin; -import net.bytebuddy.implementation.bind.annotation.RuntimeType; -import net.bytebuddy.implementation.bind.annotation.StubValue; -import net.bytebuddy.implementation.bind.annotation.This; - import static org.hibernate.internal.CoreLogging.messageLogger; public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor { @@ -47,13 +40,7 @@ public ByteBuddyInterceptor( @Override public Object intercept(Object proxy, Method thisMethod, Object[] args) throws Throwable { - Object result; - try { - result = this.invoke( thisMethod, args, proxy ); - } - catch (Throwable t) { - throw new Exception( t.getCause() ); - } + Object result = this.invoke( thisMethod, args, proxy ); if ( result == INVOKE_IMPLEMENTATION ) { Object target = getImplementation(); final Object returnValue; @@ -100,6 +87,8 @@ protected Object serializableProxy() { interfaces, getIdentifier(), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), + getSessionFactoryUuid(), + isAllowLoadOutsideTransaction(), getIdentifierMethod, setIdentifierMethod, componentIdType diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java index 360acc864d38..d67b9720c671 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java @@ -6,48 +6,28 @@ */ package org.hibernate.proxy.pojo.bytebuddy; +import static org.hibernate.internal.CoreLogging.messageLogger; + import java.io.Serializable; import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import net.bytebuddy.TypeCache; import org.hibernate.HibernateException; +import org.hibernate.cfg.Environment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.proxy.HibernateProxy; -import org.hibernate.proxy.ProxyFactory; import org.hibernate.proxy.ProxyConfiguration; +import org.hibernate.proxy.ProxyFactory; import org.hibernate.type.CompositeType; -import net.bytebuddy.ByteBuddy; -import net.bytebuddy.NamingStrategy; -import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.dynamic.scaffold.TypeValidation; -import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; -import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.FixedValue; -import net.bytebuddy.implementation.MethodDelegation; -import net.bytebuddy.implementation.SuperMethodCall; -import net.bytebuddy.implementation.bytecode.assign.Assigner; -import net.bytebuddy.matcher.ElementMatchers; - -import static net.bytebuddy.matcher.ElementMatchers.named; -import static org.hibernate.internal.CoreLogging.messageLogger; - public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { + private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); - private static final TypeCache CACHE = - new TypeCache.WithInlineExpunction(TypeCache.Sort.SOFT); + private final ByteBuddyProxyHelper byteBuddyProxyHelper; private Class persistentClass; private String entityName; @@ -59,6 +39,10 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { private Class proxyClass; + public ByteBuddyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) { + this.byteBuddyProxyHelper = byteBuddyProxyHelper; + } + @Override public void postInstantiate( String entityName, @@ -75,7 +59,7 @@ public void postInstantiate( this.componentIdType = componentIdType; this.overridesEquals = ReflectHelper.overridesEquals( persistentClass ); - this.proxyClass = buildProxy( persistentClass, this.interfaces ); + this.proxyClass = byteBuddyProxyHelper.buildProxy( persistentClass, this.interfaces ); } private Class[] toArray(Set interfaces) { @@ -86,37 +70,6 @@ private Class[] toArray(Set interfaces) { return interfaces.toArray( new Class[interfaces.size()] ); } - public static Class buildProxy( - final Class persistentClass, - final Class[] interfaces) { - Set> key = new HashSet>(); - if ( interfaces.length == 1 ) { - key.add( persistentClass ); - } - key.addAll( Arrays.>asList( interfaces ) ); - - return CACHE.findOrInsert(persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), new Callable>() { - @Override - public Class call() throws Exception { - return new ByteBuddy() - .with(TypeValidation.DISABLED) - .with(new NamingStrategy.SuffixingRandom("HibernateProxy")) - .subclass(interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING) - .implement((Type[]) interfaces) - .method(ElementMatchers.isVirtual().and(ElementMatchers.not(ElementMatchers.isFinalizer()))) - .intercept(MethodDelegation.to(ProxyConfiguration.InterceptorDispatcher.class)) - .method(ElementMatchers.nameStartsWith("$$_hibernate_").and(ElementMatchers.isVirtual())) - .intercept(SuperMethodCall.INSTANCE) - .defineField(ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE) - .implement(ProxyConfiguration.class) - .intercept(FieldAccessor.ofField(ProxyConfiguration.INTERCEPTOR_FIELD_NAME).withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)) - .make() - .load(persistentClass.getClassLoader()) - .getLoaded(); - } - }, CACHE); - } - @Override public HibernateProxy getProxy( Serializable id, @@ -144,83 +97,4 @@ public HibernateProxy getProxy( throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t ); } } - - public static HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { - final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor( - serializableProxy.getEntityName(), - serializableProxy.getPersistentClass(), - serializableProxy.getInterfaces(), - serializableProxy.getId(), - resolveIdGetterMethod( serializableProxy ), - resolveIdSetterMethod( serializableProxy ), - serializableProxy.getComponentIdType(), - null, - ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() ) - ); - - // note: interface is assumed to already contain HibernateProxy.class - try { - final Class proxyClass = buildProxy( - serializableProxy.getPersistentClass(), - serializableProxy.getInterfaces() - ); - final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); - ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); - return proxy; - } - catch (Throwable t) { - final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() ); - LOG.error( message, t ); - throw new HibernateException( message, t ); - } - } - - @SuppressWarnings("unchecked") - private static Method resolveIdGetterMethod(SerializableProxy serializableProxy) { - if ( serializableProxy.getIdentifierGetterMethodName() == null ) { - return null; - } - - try { - return serializableProxy.getIdentifierGetterMethodClass().getDeclaredMethod( serializableProxy.getIdentifierGetterMethodName() ); - } - catch (NoSuchMethodException e) { - throw new HibernateException( - String.format( - Locale.ENGLISH, - "Unable to deserialize proxy [%s, %s]; could not locate id getter method [%s] on entity class [%s]", - serializableProxy.getEntityName(), - serializableProxy.getId(), - serializableProxy.getIdentifierGetterMethodName(), - serializableProxy.getIdentifierGetterMethodClass() - ) - ); - } - } - - @SuppressWarnings("unchecked") - private static Method resolveIdSetterMethod(SerializableProxy serializableProxy) { - if ( serializableProxy.getIdentifierSetterMethodName() == null ) { - return null; - } - - try { - return serializableProxy.getIdentifierSetterMethodClass().getDeclaredMethod( - serializableProxy.getIdentifierSetterMethodName(), - serializableProxy.getIdentifierSetterMethodParams() - ); - } - catch (NoSuchMethodException e) { - throw new HibernateException( - String.format( - Locale.ENGLISH, - "Unable to deserialize proxy [%s, %s]; could not locate id setter method [%s] on entity class [%s]", - serializableProxy.getEntityName(), - serializableProxy.getId(), - serializableProxy.getIdentifierSetterMethodName(), - serializableProxy.getIdentifierSetterMethodClass() - ) - ); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java new file mode 100644 index 000000000000..2e3d5fdef541 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -0,0 +1,147 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy.pojo.bytebuddy; + +import static org.hibernate.internal.CoreLogging.messageLogger; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.cfg.Environment; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.ProxyConfiguration; + +import net.bytebuddy.NamingStrategy; +import net.bytebuddy.TypeCache; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.SuperMethodCall; + +public class ByteBuddyProxyHelper implements Serializable { + + private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyHelper.class ); + private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateProxy$" : "HibernateProxy"; + + private final ByteBuddyState byteBuddyState; + + public ByteBuddyProxyHelper(ByteBuddyState byteBuddyState) { + this.byteBuddyState = byteBuddyState; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Class buildProxy( + final Class persistentClass, + final Class[] interfaces) { + Set> key = new HashSet>(); + if ( interfaces.length == 1 ) { + key.add( persistentClass ); + } + key.addAll( Arrays.>asList( interfaces ) ); + + return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey(key), byteBuddy -> byteBuddy + .ignore( byteBuddyState.getProxyDefinitionHelpers().getGroovyGetMetaClassFilter() ) + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) + .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) + .implement( (Type[]) interfaces ) + .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) + .method( byteBuddyState.getProxyDefinitionHelpers().getHibernateGeneratedMethodFilter() ) + .intercept( SuperMethodCall.INSTANCE ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .implement( ProxyConfiguration.class ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) + ); + } + + public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { + final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor( + serializableProxy.getEntityName(), + serializableProxy.getPersistentClass(), + serializableProxy.getInterfaces(), + serializableProxy.getId(), + resolveIdGetterMethod( serializableProxy ), + resolveIdSetterMethod( serializableProxy ), + serializableProxy.getComponentIdType(), + null, + ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() ) + ); + + // note: interface is assumed to already contain HibernateProxy.class + try { + final Class proxyClass = buildProxy( + serializableProxy.getPersistentClass(), + serializableProxy.getInterfaces() + ); + final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); + ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); + return proxy; + } + catch (Throwable t) { + final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() ); + LOG.error( message, t ); + throw new HibernateException( message, t ); + } + } + + @SuppressWarnings("unchecked") + private static Method resolveIdGetterMethod(SerializableProxy serializableProxy) { + if ( serializableProxy.getIdentifierGetterMethodName() == null ) { + return null; + } + + try { + return serializableProxy.getIdentifierGetterMethodClass().getDeclaredMethod( serializableProxy.getIdentifierGetterMethodName() ); + } + catch (NoSuchMethodException e) { + throw new HibernateException( + String.format( + Locale.ENGLISH, + "Unable to deserialize proxy [%s, %s]; could not locate id getter method [%s] on entity class [%s]", + serializableProxy.getEntityName(), + serializableProxy.getId(), + serializableProxy.getIdentifierGetterMethodName(), + serializableProxy.getIdentifierGetterMethodClass() + ) + ); + } + } + + @SuppressWarnings("unchecked") + private static Method resolveIdSetterMethod(SerializableProxy serializableProxy) { + if ( serializableProxy.getIdentifierSetterMethodName() == null ) { + return null; + } + + try { + return serializableProxy.getIdentifierSetterMethodClass().getDeclaredMethod( + serializableProxy.getIdentifierSetterMethodName(), + serializableProxy.getIdentifierSetterMethodParams() + ); + } + catch (NoSuchMethodException e) { + throw new HibernateException( + String.format( + Locale.ENGLISH, + "Unable to deserialize proxy [%s, %s]; could not locate id setter method [%s] on entity class [%s]", + serializableProxy.getEntityName(), + serializableProxy.getId(), + serializableProxy.getIdentifierSetterMethodName(), + serializableProxy.getIdentifierSetterMethodClass() + ) + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java index 493f777b03fa..546247f60649 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java @@ -9,6 +9,11 @@ import java.io.Serializable; import java.lang.reflect.Method; +import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl; +import org.hibernate.bytecode.internal.bytebuddy.ProxyFactoryFactoryImpl; +import org.hibernate.bytecode.spi.BytecodeProvider; +import org.hibernate.bytecode.spi.ProxyFactoryFactory; +import org.hibernate.cfg.Environment; import org.hibernate.proxy.AbstractSerializableProxy; import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.CompositeType; @@ -26,6 +31,10 @@ public final class SerializableProxy extends AbstractSerializableProxy { private final CompositeType componentIdType; + /** + * @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead. + */ + @Deprecated public SerializableProxy( String entityName, Class persistentClass, @@ -35,7 +44,24 @@ public SerializableProxy( Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) { - super( entityName, id, readOnly ); + this( + entityName, persistentClass, interfaces, id, readOnly, null, false, + getIdentifierMethod, setIdentifierMethod, componentIdType + ); + } + + public SerializableProxy( + String entityName, + Class persistentClass, + Class[] interfaces, + Serializable id, + Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction, + Method getIdentifierMethod, + Method setIdentifierMethod, + CompositeType componentIdType) { + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); this.persistentClass = persistentClass; this.interfaces = interfaces; if ( getIdentifierMethod != null ) { @@ -104,8 +130,13 @@ protected CompositeType getComponentIdType() { } private Object readResolve() { - HibernateProxy proxy = ByteBuddyProxyFactory.deserializeProxy( this ); - setReadOnlyBeforeAttachedToSession( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); + BytecodeProvider bytecodeProvider = Environment.getBytecodeProvider(); + if ( !( bytecodeProvider instanceof BytecodeProviderImpl ) ) { + throw new IllegalStateException( "The bytecode provider is not ByteBuddy, unable to deserialize a ByteBuddy proxy." ); + } + + HibernateProxy proxy = ( (BytecodeProviderImpl) bytecodeProvider ).getByteBuddyProxyHelper().deserializeProxy( this ); + afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); return proxy; } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java index 776cd9a9ddc8..ce460ed151d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java @@ -67,7 +67,7 @@ public Object invoke( result = this.invoke( thisMethod, args, proxy ); } catch ( Throwable t ) { - throw new Exception( t.getCause() ); + throw t instanceof RuntimeException ? t : new Exception( t.getCause() ); } if ( result == INVOKE_IMPLEMENTATION ) { Object target = getImplementation(); @@ -125,6 +125,8 @@ protected Object serializableProxy() { interfaces, getIdentifier(), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), + getSessionFactoryUuid(), + isAllowLoadOutsideTransaction(), getIdentifierMethod, setIdentifierMethod, componentIdType diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java index e5b886278ff4..ac76f1cbce58 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java @@ -33,11 +33,14 @@ public class JavassistProxyFactory implements ProxyFactory, Serializable { private static final CoreMessageLogger LOG = messageLogger( JavassistProxyFactory.class ); - private static final MethodFilter FINALIZE_FILTER = new MethodFilter() { - public boolean isHandled(Method m) { - // skip finalize methods - return !( m.getParameterCount() == 0 && m.getName().equals( "finalize" ) ); - } + private static final MethodFilter EXCLUDE_FILTER = m -> { + // skip finalize methods and Groovy getMetaClass + return !( + m.getParameterCount() == 0 && m.getName().equals( "finalize" ) || ( + m.getName().equals( "getMetaClass" ) && + m.getReturnType().getName().equals( "groovy.lang.MetaClass" ) + ) + ); }; private Class persistentClass; @@ -98,7 +101,7 @@ protected ClassLoader getClassLoader() { }; factory.setSuperclass( interfaces.length == 1 ? persistentClass : null ); factory.setInterfaces( interfaces ); - factory.setFilter( FINALIZE_FILTER ); + factory.setFilter( EXCLUDE_FILTER ); return factory; } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java index 9e9785336ebb..3ec55b8182e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java @@ -29,16 +29,37 @@ public final class SerializableProxy extends AbstractSerializableProxy { private final CompositeType componentIdType; + /** + * @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead. + */ + @Deprecated + public SerializableProxy( + String entityName, + Class persistentClass, + Class[] interfaces, + Serializable id, + Boolean readOnly, + Method getIdentifierMethod, + Method setIdentifierMethod, + CompositeType componentIdType) { + this( + entityName, persistentClass, interfaces, id, readOnly, null, false, + getIdentifierMethod, setIdentifierMethod, componentIdType + ); + } + public SerializableProxy( String entityName, Class persistentClass, Class[] interfaces, Serializable id, Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction, Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) { - super( entityName, id, readOnly ); + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); this.persistentClass = persistentClass; this.interfaces = interfaces; if ( getIdentifierMethod != null ) { @@ -114,7 +135,7 @@ protected CompositeType getComponentIdType() { */ private Object readResolve() { HibernateProxy proxy = JavassistProxyFactory.deserializeProxy( this ); - setReadOnlyBeforeAttachedToSession( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() ); + afterDeserialization( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() ); return proxy; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/CommonQueryContract.java b/hibernate-core/src/main/java/org/hibernate/query/CommonQueryContract.java index 5c1ee9bf92dd..1419d78da000 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/CommonQueryContract.java +++ b/hibernate-core/src/main/java/org/hibernate/query/CommonQueryContract.java @@ -14,5 +14,5 @@ * @author Steve Ebersole * @author Gavin King */ -public interface CommonQueryContract extends org.hibernate.BasicQueryContract { +public interface CommonQueryContract extends org.hibernate.BasicQueryContract { } diff --git a/hibernate-core/src/main/java/org/hibernate/query/ImmutableEntityUpdateQueryHandlingMode.java b/hibernate-core/src/main/java/org/hibernate/query/ImmutableEntityUpdateQueryHandlingMode.java new file mode 100644 index 000000000000..aa1343438577 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/ImmutableEntityUpdateQueryHandlingMode.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query; + +import org.hibernate.HibernateException; + +/** + * This enum defines how {@link org.hibernate.annotations.Immutable} entities are handled when executing a + * bulk update statement. + * + * By default, the ({@link ImmutableEntityUpdateQueryHandlingMode#WARNING}) mode is used, meaning that + * a warning log message is issued when an {@link org.hibernate.annotations.Immutable} entity + * is to be updated via a bulk update statement. + * + * If the ({@link ImmutableEntityUpdateQueryHandlingMode#EXCEPTION}) mode is used, then a + * {@link HibernateException} is thrown instead. + * + * @author Vlad Mihalcea + */ +public enum ImmutableEntityUpdateQueryHandlingMode { + + WARNING, + EXCEPTION; + + /** + * Interpret the configured {@link ImmutableEntityUpdateQueryHandlingMode} value. + * Valid values are either a {@link ImmutableEntityUpdateQueryHandlingMode} object or its String representation. + * For string values, the matching is case insensitive, + * so you can use either {@code warning} or {@code exception} (case insensitive). + * + * @param mode configured {@link ImmutableEntityUpdateQueryHandlingMode} representation + * @return associated {@link ImmutableEntityUpdateQueryHandlingMode} object + */ + public static ImmutableEntityUpdateQueryHandlingMode interpret(Object mode) { + if ( mode == null ) { + return WARNING; + } + else if ( mode instanceof ImmutableEntityUpdateQueryHandlingMode ) { + return (ImmutableEntityUpdateQueryHandlingMode) mode; + } + else if ( mode instanceof String ) { + for ( ImmutableEntityUpdateQueryHandlingMode value : values() ) { + if ( value.name().equalsIgnoreCase( (String) mode ) ) { + return value; + } + } + } + throw new HibernateException( + "Unrecognized immutable_entity_update_query_handling_mode value : " + mode + + ". Supported values include 'warning' and 'exception''." + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java index 0cbfe00c9d08..d8144d384673 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java @@ -6,14 +6,24 @@ */ package org.hibernate.query; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Map; + +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; import javax.persistence.Parameter; import javax.persistence.TemporalType; +import org.hibernate.CacheMode; import org.hibernate.FlushMode; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.SQLQuery; import org.hibernate.SynchronizeableQuery; @@ -23,6 +33,7 @@ * @author Steve Ebersole */ public interface NativeQuery extends Query, SQLQuery, SynchronizeableQuery { + @Override NativeQuery setFlushMode(FlushMode flushMode); @@ -77,6 +88,42 @@ public interface NativeQuery extends Query, SQLQuery, SynchronizeableQu @Override NativeQuery setParameter(int position, Date value, TemporalType temporalType); + @Override + NativeQuery setParameter(Parameter param, Instant value, TemporalType temporalType); + + @Override + NativeQuery setParameter(Parameter param, LocalDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(Parameter param, ZonedDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(Parameter param, OffsetDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(String name, Instant value, TemporalType temporalType); + + @Override + NativeQuery setParameter(String name, LocalDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(String name, ZonedDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(String name, OffsetDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(int position, Instant value, TemporalType temporalType); + + @Override + NativeQuery setParameter(int position, LocalDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(int position, ZonedDateTime value, TemporalType temporalType); + + @Override + NativeQuery setParameter(int position, OffsetDateTime value, TemporalType temporalType); + @Override

    NativeQuery setParameterList(QueryParameter

    parameter, Collection

    values); @@ -106,4 +153,97 @@ public interface NativeQuery extends Query, SQLQuery, SynchronizeableQu @Override NativeQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; + + @Override + boolean isCallable(); + + @Override + NativeQuery addScalar(String columnAlias); + + @Override + NativeQuery addScalar(String columnAlias, Type type); + + @Override + RootReturn addRoot(String tableAlias, String entityName); + + @Override + RootReturn addRoot(String tableAlias, Class entityType); + + @Override + NativeQuery addEntity(String entityName); + + @Override + NativeQuery addEntity(String tableAlias, String entityName); + + @Override + NativeQuery addEntity(String tableAlias, String entityName, LockMode lockMode); + + @Override + NativeQuery addEntity(Class entityType); + + @Override + NativeQuery addEntity(String tableAlias, Class entityType); + + @Override + NativeQuery addEntity(String tableAlias, Class entityClass, LockMode lockMode); + + @Override + FetchReturn addFetch(String tableAlias, String ownerTableAlias, String joinPropertyName); + + @Override + NativeQuery addJoin(String tableAlias, String path); + + @Override + NativeQuery addJoin(String tableAlias, String ownerTableAlias, String joinPropertyName); + + @Override + NativeQuery addJoin(String tableAlias, String path, LockMode lockMode); + + @Override + NativeQuery setHibernateFlushMode(FlushMode flushMode); + + @Override + NativeQuery setFlushMode(FlushModeType flushMode); + + @Override + NativeQuery setCacheMode(CacheMode cacheMode); + + @Override + NativeQuery setCacheable(boolean cacheable); + + @Override + NativeQuery setCacheRegion(String cacheRegion); + + @Override + NativeQuery setTimeout(int timeout); + + @Override + NativeQuery setFetchSize(int fetchSize); + + @Override + NativeQuery setReadOnly(boolean readOnly); + + @Override + NativeQuery setLockOptions(LockOptions lockOptions); + + @Override + NativeQuery setLockMode(String alias, LockMode lockMode); + + @Override + NativeQuery setComment(String comment); + + @Override + NativeQuery addQueryHint(String hint); + + @Override + NativeQuery setMaxResults(int maxResult); + + @Override + NativeQuery setFirstResult(int startPosition); + + @Override + NativeQuery setHint(String hintName, Object value); + + @Override + NativeQuery setLockMode(LockModeType lockMode); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java b/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java index cd3a2ded1505..c56f4c722192 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java @@ -6,7 +6,9 @@ */ package org.hibernate.query; +import java.util.Collection; import java.util.Set; +import java.util.function.Consumer; import javax.persistence.Parameter; /** @@ -54,10 +56,13 @@ public interface ParameterMetadata { QueryParameter resolve(Parameter param); - default boolean isOrdinalParametersZeroBased() { - return true; - } + Collection getPositionalParameters(); - default void setOrdinalParametersZeroBased(boolean isZeroBased) { - } + Collection getNamedParameters(); + + int getParameterCount(); + + boolean containsReference(QueryParameter parameter); + + void visitRegistrations(Consumer action); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/Query.java b/hibernate-core/src/main/java/org/hibernate/query/Query.java index 593176e279b8..e522a5b4750e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Query.java @@ -29,12 +29,10 @@ import org.hibernate.CacheMode; import org.hibernate.FlushMode; -import org.hibernate.Incubating; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; -import org.hibernate.engine.spi.RowSelection; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BigDecimalType; import org.hibernate.type.BigIntegerType; @@ -67,7 +65,6 @@ * @author Steve Ebersole * @author Gavin King */ -@Incubating @SuppressWarnings("UnusedDeclaration") public interface Query extends TypedQuery, org.hibernate.Query, CommonQueryContract { /** @@ -75,17 +72,6 @@ public interface Query extends TypedQuery, org.hibernate.Query, CommonQ */ QueryProducer getProducer(); - /** - * "QueryOptions" is a better name, I think, than "RowSelection" -> 6.0 - * - * @todo 6.0 rename RowSelection to QueryOptions - * - * @return Return the encapsulation of this query's options, which includes access to - * firstRow, maxRows, timeout and fetchSize. Important because this gives access to - * those values in their Integer form rather than the primitive form (int) required by JPA. - */ - RowSelection getQueryOptions(); - Optional uniqueResultOptional(); /** @@ -688,7 +674,7 @@ default Query setCalendarDate(int position, Calendar val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -706,7 +692,7 @@ default Query setString(String name, String val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -724,7 +710,7 @@ default Query setCharacter(String name, char val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -742,7 +728,7 @@ default Query setBoolean(String name, boolean val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -760,7 +746,7 @@ default Query setByte(String name, byte val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -778,7 +764,7 @@ default Query setShort(String name, short val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -796,7 +782,7 @@ default Query setInteger(String name, int val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -814,7 +800,7 @@ default Query setLong(String name, long val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -832,7 +818,7 @@ default Query setFloat(String name, float val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -850,7 +836,7 @@ default Query setDouble(String name, double val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -868,7 +854,7 @@ default Query setBinary(String name, byte[] val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -886,7 +872,7 @@ default Query setText(String name, String val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -903,7 +889,7 @@ default Query setSerializable(String name, Serializable val) { * @param val The bind value * * @return {@code this}, for method chaining - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -920,7 +906,7 @@ default Query setLocale(String name, Locale val) { * @param val The bind value * * @return {@code this}, for method chaining - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -937,7 +923,7 @@ default Query setBigDecimal(String name, BigDecimal val) { * @param val The bind value * * @return {@code this}, for method chaining - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -955,7 +941,7 @@ default Query setBigInteger(String name, BigInteger val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -973,7 +959,7 @@ default Query setDate(String name, Date val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -991,7 +977,7 @@ default Query setTime(String name, Date val) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -1009,7 +995,7 @@ default Query setTimestamp(String name, Date value) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -1027,7 +1013,7 @@ default Query setCalendar(String name, Calendar value) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -1047,7 +1033,7 @@ default Query setCalendarDate(String name, Calendar value) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -1063,7 +1049,7 @@ default Query setCalendarDate(String name, Calendar value) { * * @return {@code this}, for method chaining * - * @deprecated (since 5.2) use {@link #setParameter(int, Object)} or {@link #setParameter(int, Object, Type)} + * @deprecated (since 5.2) use {@link #setParameter(String, Object)} or {@link #setParameter(String, Object, Type)} * instead */ @Deprecated @@ -1109,4 +1095,15 @@ default Query setParameters(Object[] values, Type[] types) { return this; } + /** + * JPA 2.2 defines the {@code getResultStream} method so to get a {@link Stream} from the JDBC {@link java.sql.ResultSet}. + * + * Hibernate 5.2 already defines the {@link Query#stream()} method, so {@code getResultStream} can delegate to it. + * + * @return The results Stream + * @since 5.2.11 + */ + default Stream getResultStream() { + return stream(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java b/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java index 2f9b6020d2d8..a947ae7dcea6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java @@ -21,18 +21,11 @@ public interface QueryParameter extends javax.persistence.Parameter { * * @return The Hibernate Type. */ - Type getType(); + Type getHibernateType(); - /** - * JPA has a different definition of positional parameters than what legacy Hibernate HQL had. In JPA, - * the parameter holders are labelled (named :/). At any rate the semantics are different and we often - * need to understand which we are dealing with (and applications might too). - * - * @return {@code true} if this is a JPA-style positional parameter; {@code false} would indicate - * we have either a named parameter ({@link #getName()} would return a non-{@code null} value) or a native - * Hibernate positional parameter. - */ - boolean isJpaPositionalParameter(); + int[] getSourceLocations(); // todo : add a method indicating whether this parameter is valid for use in "parameter list binding" + // actually this already implemented in 6.0 code and I'm not going to mess with + // this in earlier versions } diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java index 9e746fe1f9ac..e170be513985 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java @@ -6,6 +6,8 @@ */ package org.hibernate.query; +import org.hibernate.SQLQuery; + /** * Contract for things that can produce Query instances. Expected implementors include * Session and StatelessSession. @@ -27,7 +29,7 @@ public interface QueryProducer { * defined with the given name or if the query string is * found to be invalid */ - Query getNamedQuery(String queryName); + org.hibernate.Query getNamedQuery(String queryName); /** * Create a {@link Query} instance for the given HQL/JPQL query string. @@ -38,7 +40,7 @@ public interface QueryProducer { * * @see javax.persistence.EntityManager#createQuery(String) */ - Query createQuery(String queryString); + org.hibernate.Query createQuery(String queryString); /** * Create a typed {@link Query} instance for the given HQL/JPQL query string. @@ -95,7 +97,7 @@ public interface QueryProducer { * @deprecated (since 5.2) use {@link #createNativeQuery(String)} instead */ @Deprecated - default NativeQuery createSQLQuery(String queryString) { + default SQLQuery createSQLQuery(String queryString) { NativeQuery query = createNativeQuery( queryString ); query.setComment( "dynamic native SQL query" ); return query; @@ -149,7 +151,7 @@ default NativeQuery createSQLQuery(String queryString) { * @deprecated (since 5.2) use {@link #getNamedNativeQuery(String)} instead */ @Deprecated - default NativeQuery getNamedSQLQuery(String name) { + default org.hibernate.Query getNamedSQLQuery(String name) { return getNamedNativeQuery( name ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/LiteralHandlingMode.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/LiteralHandlingMode.java new file mode 100644 index 000000000000..bc640ce491c5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/LiteralHandlingMode.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.query.criteria; + +import org.hibernate.HibernateException; + +/** + * This enum defines how literals are handled by JPA Criteria. + * + * By default ({@code AUTO}), Criteria queries uses bind parameters for any literal that is not a numeric value. + * + * However, to increase the likelihood of JDBC statement caching, + * you might want to use bind parameters for numeric values too. + * The {@code BIND} mode will use bind variables for any literal value. + * + * The {@code INLINE} mode will inline literal values as-is. + * To prevent SQL injection, never use {@code INLINE} with String variables. + * Always use constants with the {@code INLINE} mode. + * + * @author Vlad Mihalcea + */ +public enum LiteralHandlingMode { + + AUTO, + BIND, + INLINE; + + /** + * Interpret the configured literalHandlingMode value. + * Valid values are either a {@link LiteralHandlingMode} object or its String representation. + * For string values, the matching is case insensitive, so you can use either {@code AUTO} or {@code auto}. + * + * @param literalHandlingMode configured {@link LiteralHandlingMode} representation + * @return associated {@link LiteralHandlingMode} object + */ + public static LiteralHandlingMode interpret(Object literalHandlingMode) { + if ( literalHandlingMode == null ) { + return AUTO; + } + else if ( literalHandlingMode instanceof LiteralHandlingMode ) { + return (LiteralHandlingMode) literalHandlingMode; + } + else if ( literalHandlingMode instanceof String ) { + for ( LiteralHandlingMode value : values() ) { + if ( value.name().equalsIgnoreCase( (String) literalHandlingMode ) ) { + return value; + } + } + } + throw new HibernateException( + "Unrecognized literal_handling_mode value : " + literalHandlingMode + + ". Supported values include 'auto', 'inline', and 'bind'." + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java index 39d35942b398..6d544c56d32b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java @@ -136,11 +136,6 @@ public SubquerySelection(ExpressionImpl wrapped, CriteriaSubqueryImpl subQuer public String render(RenderingContext renderingContext) { return subQuery.render( renderingContext ); } - - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java index 68fb76a00925..736c6c1ea1a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java @@ -253,7 +253,7 @@ public void render(StringBuilder jpaqlQuery, RenderingContext renderingContext) String sep = ""; for ( Expression grouping : getGroupings() ) { jpaqlQuery.append( sep ) - .append( ( (Renderable) grouping ).render( renderingContext ) ); + .append( ( (Renderable) grouping ).renderGroupBy( renderingContext ) ); sep = ", "; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java index c359b3d9b2e0..6a2570db9f9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java @@ -15,6 +15,33 @@ * @author Steve Ebersole */ public interface Renderable { - public String render(RenderingContext renderingContext); - public String renderProjection(RenderingContext renderingContext); + + /** + * Render clause + * + * @param renderingContext context + * @return rendered expression + */ + String render(RenderingContext renderingContext); + + /** + * Render SELECT clause + * + * @param renderingContext context + * @return rendered expression + */ + default String renderProjection(RenderingContext renderingContext) { + return render( renderingContext ); + } + + /** + * Render GROUP BY clause + * + * @param renderingContext context + * + * @return rendered expression + */ + default String renderGroupBy(RenderingContext renderingContext) { + return render( renderingContext ); + } } 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 674658ae92f9..74ea10f60606 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 @@ -14,9 +14,13 @@ import javax.persistence.TypedQuery; import javax.persistence.criteria.ParameterExpression; +import org.hibernate.SessionFactory; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.util.StringHelper; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.type.Type; @@ -47,6 +51,14 @@ public QueryImplementor compile(CompilableCriteria criteria) { final Map, ExplicitParameterInfo> explicitParameterInfoMap = new HashMap<>(); final List implicitParameterBindings = new ArrayList<>(); + final SessionFactoryImplementor sessionFactory = entityManager.getSessionFactory(); + + final LiteralHandlingMode criteriaLiteralHandlingMode = sessionFactory + .getSessionFactoryOptions() + .getCriteriaLiteralHandlingMode(); + + final Dialect dialect = sessionFactory.getServiceRegistry().getService( JdbcServices.class ).getDialect(); + RenderingContext renderingContext = new RenderingContext() { private int aliasCount; private int explicitParameterCount; @@ -122,6 +134,16 @@ public String getCastType(Class javaType) { } return hibernateType.getName(); } + + @Override + public Dialect getDialect() { + return dialect; + } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return criteriaLiteralHandlingMode; + } }; return criteria.interpret( renderingContext ).buildCompiledQuery( diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaQueryTypeQueryAdapter.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaQueryTypeQueryAdapter.java index 3d90dca3263c..555600d62c4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaQueryTypeQueryAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaQueryTypeQueryAdapter.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; + import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.Parameter; @@ -31,6 +32,7 @@ import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.engine.spi.RowSelection; @@ -331,14 +333,22 @@ public Set> getParameters() { @Override public boolean isBound(Parameter param) { entityManager.checkOpen( false ); - return jpqlQuery.isBound( param ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + Parameter jpqlParameter; + if ( parameterInfo.isNamed() ) { + jpqlParameter = jpqlQuery.getParameter( parameterInfo.getName() ); + } + else { + jpqlParameter = jpqlQuery.getParameter( parameterInfo.getPosition() ); + } + return jpqlQuery.isBound( jpqlParameter ); } @Override @SuppressWarnings({ "unchecked" }) public T getParameterValue(Parameter param) { entityManager.checkOpen( false ); - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { return ( T ) jpqlQuery.getParameterValue( parameterInfo.getName() ); } @@ -347,15 +357,15 @@ public T getParameterValue(Parameter param) { } } - private ExplicitParameterInfo resolveParameterInfo(Parameter param) { + private ExplicitParameterInfo resolveParameterInfo(Parameter param) { if ( ExplicitParameterInfo.class.isInstance( param ) ) { - return (ExplicitParameterInfo) param; + return (ExplicitParameterInfo) param; } else if ( ParameterExpression.class.isInstance( param ) ) { - return explicitParameterInfoMap.get( (ParameterExpression) param ); + return explicitParameterInfoMap.get( param ); } else { - for ( ExplicitParameterInfo parameterInfo : explicitParameterInfoMap.values() ) { + for ( ExplicitParameterInfo parameterInfo : explicitParameterInfoMap.values() ) { if ( param.getName() != null && param.getName().equals( parameterInfo.getName() ) ) { return parameterInfo; } @@ -368,10 +378,9 @@ else if ( param.getPosition() != null && param.getPosition().equals( parameterIn } @Override - @SuppressWarnings({ "unchecked" }) public QueryImplementor setParameter(Parameter param, T t) { entityManager.checkOpen( false ); - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), t ); } @@ -382,10 +391,9 @@ public QueryImplementor setParameter(Parameter param, T t) { } @Override - @SuppressWarnings({ "unchecked" }) public QueryImplementor setParameter(Parameter param, Calendar calendar, TemporalType temporalType) { entityManager.checkOpen( false ); - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), calendar, temporalType ); } @@ -396,10 +404,9 @@ public QueryImplementor setParameter(Parameter param, Calendar cale } @Override - @SuppressWarnings({ "unchecked" }) public QueryImplementor setParameter(Parameter param, Date date, TemporalType temporalType) { entityManager.checkOpen( false ); - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), date, temporalType ); } @@ -415,15 +422,14 @@ public T unwrap(Class cls) { } @Override - @SuppressWarnings({ "unchecked" }) public Object getParameterValue(String name) { entityManager.checkOpen( false ); locateParameterByName( name ); return jpqlQuery.getParameterValue( name ); } - private ExplicitParameterInfo locateParameterByName(String name) { - for ( ExplicitParameterInfo parameterInfo : explicitParameterInfoMap.values() ) { + private ExplicitParameterInfo locateParameterByName(String name) { + for ( ExplicitParameterInfo parameterInfo : explicitParameterInfoMap.values() ) { if ( parameterInfo.isNamed() && parameterInfo.getName().equals( name ) ) { return parameterInfo; } @@ -431,8 +437,8 @@ private ExplicitParameterInfo locateParameterByName(String name) { throw new IllegalArgumentException( "Unable to locate parameter registered with that name [" + name + "]" ); } - private ExplicitParameterInfo locateParameterByPosition(int position) { - for ( ExplicitParameterInfo parameterInfo : explicitParameterInfoMap.values() ) { + private ExplicitParameterInfo locateParameterByPosition(int position) { + for ( ExplicitParameterInfo parameterInfo : explicitParameterInfoMap.values() ) { if ( parameterInfo.getPosition() == position ) { return parameterInfo; } @@ -460,30 +466,27 @@ public Parameter getParameter(String name, Class type) { } @Override - @SuppressWarnings({ "unchecked" }) public QueryImplementor setParameter(String name, Object value) { entityManager.checkOpen( true ); - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); + ExplicitParameterInfo parameterInfo = locateParameterByName( name ); parameterInfo.validateBindValue( value ); jpqlQuery.setParameter( name, value ); return this; } @Override - @SuppressWarnings({ "unchecked" }) public QueryImplementor setParameter(String name, Calendar calendar, TemporalType temporalType) { entityManager.checkOpen( true ); - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); + ExplicitParameterInfo parameterInfo = locateParameterByName( name ); parameterInfo.validateCalendarBind(); jpqlQuery.setParameter( name, calendar, temporalType ); return this; } @Override - @SuppressWarnings({ "unchecked" }) public QueryImplementor setParameter(String name, Date date, TemporalType temporalType) { entityManager.checkOpen( true ); - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); + ExplicitParameterInfo parameterInfo = locateParameterByName( name ); parameterInfo.validateDateBind(); jpqlQuery.setParameter( name, date, temporalType ); return this; @@ -491,23 +494,27 @@ public QueryImplementor setParameter(String name, Date date, TemporalType tem @Override public QueryImplementor setEntity(String name, Object val) { - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + ExplicitParameterInfo parameterInfo = locateParameterByName( name ); + parameterInfo.validateBindValue( val ); jpqlQuery.setEntity( name, val ); return this; } @Override public QueryImplementor setParameter(String name, Object val, Type type) { - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + ExplicitParameterInfo parameterInfo = locateParameterByName( name ); + parameterInfo.validateBindValue( val ); jpqlQuery.setParameter( name, val, type ); return this; } @Override public QueryImplementor setParameter(QueryParameter parameter, T val) { - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); + parameterInfo.validateBindValue( val ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), val ); } @@ -520,7 +527,8 @@ public QueryImplementor setParameter(QueryParameter parameter, T val) @Override public

    QueryImplementor setParameter( QueryParameter

    parameter, P val, TemporalType temporalType) { - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), val, temporalType ); } @@ -532,15 +540,16 @@ public

    QueryImplementor setParameter( @Override public

    QueryImplementor setParameter(String name, P val, TemporalType temporalType) { - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, val, temporalType ); return this; } @Override public

    QueryImplementor setParameterList(QueryParameter

    parameter, Collection

    values) { - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), values ); } @@ -552,39 +561,72 @@ public

    QueryImplementor setParameterList(QueryParameter

    parameter, Col @Override public QueryImplementor setParameterList(String name, Collection values) { - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, values ); return this; } + @Override + public Query setParameterList(int position, Collection values) { + entityManager.checkOpen( false ); + locateParameterByPosition( position ); + jpqlQuery.setParameter( position, values ); + return this; + } + @Override public QueryImplementor setParameterList(String name, Collection values, Type type) { - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, values, type ); return this; } + @Override + public Query setParameterList(int position, Collection values, Type type) { + entityManager.checkOpen( false ); + locateParameterByPosition( position ); + jpqlQuery.setParameter( position, values, type ); + return this; + } + @Override public QueryImplementor setParameterList(String name, Object[] values, Type type) { - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, values, type ); return this; } + @Override + public Query setParameterList(int position, Object[] values, Type type) { + entityManager.checkOpen( false ); + locateParameterByPosition( position ); + jpqlQuery.setParameter( position, values, type ); + return this; + } + @Override public QueryImplementor setParameterList(String name, Object[] values) { - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, values ); return this; } + @Override + public Query setParameterList(int position, Object[] values) { + entityManager.checkOpen( false ); + locateParameterByPosition( position ); + jpqlQuery.setParameter( position, values ); + return this; + } + @Override public

    QueryImplementor setParameter(QueryParameter

    parameter, P value, Type type) { - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( parameter ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), value, type ); } @@ -596,7 +638,8 @@ public

    QueryImplementor setParameter(QueryParameter

    parameter, P value @Override public QueryImplementor setParameter(Parameter param, Instant value, TemporalType temporalType){ - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), value, temporalType ); } @@ -608,7 +651,8 @@ public QueryImplementor setParameter(Parameter param, Instant value, @Override public QueryImplementor setParameter(Parameter param, LocalDateTime value, TemporalType temporalType){ - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), value, temporalType ); } @@ -620,7 +664,8 @@ public QueryImplementor setParameter(Parameter param, LocalDat @Override public QueryImplementor setParameter(Parameter param, ZonedDateTime value, TemporalType temporalType){ - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), value, temporalType ); } @@ -632,7 +677,8 @@ public QueryImplementor setParameter(Parameter param, ZonedDat @Override public QueryImplementor setParameter(Parameter param, OffsetDateTime value, TemporalType temporalType){ - final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); + entityManager.checkOpen( false ); + final ExplicitParameterInfo parameterInfo = resolveParameterInfo( param ); if ( parameterInfo.isNamed() ) { jpqlQuery.setParameter( parameterInfo.getName(), value, temporalType ); } @@ -644,32 +690,32 @@ public QueryImplementor setParameter(Parameter param, OffsetD @Override public QueryImplementor setParameter(String name, Instant value, TemporalType temporalType){ - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateCalendarBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, value, temporalType ); return this; } @Override public QueryImplementor setParameter(String name, LocalDateTime value, TemporalType temporalType){ - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateCalendarBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, value, temporalType ); return this; } @Override public QueryImplementor setParameter(String name, ZonedDateTime value, TemporalType temporalType){ - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateCalendarBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, value, temporalType ); return this; } @Override public QueryImplementor setParameter(String name, OffsetDateTime value, TemporalType temporalType){ - ExplicitParameterInfo parameterInfo = locateParameterByName( name ); - parameterInfo.validateCalendarBind(); + entityManager.checkOpen( false ); + locateParameterByName( name ); jpqlQuery.setParameter( name, value, temporalType ); return this; } @@ -714,56 +760,56 @@ public void setOptionalObject(Object optionalObject) { @Override public QueryImplementor setParameter(int position, LocalDateTime value, TemporalType temporalType) { - final ExplicitParameterInfo explicitParameterInfo = locateParameterByPosition( position ); - explicitParameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByPosition( position ); jpqlQuery.setParameter( position, value, temporalType ); return this; } @Override public QueryImplementor setParameter(int position, Instant value, TemporalType temporalType) { - final ExplicitParameterInfo explicitParameterInfo = locateParameterByPosition( position ); - explicitParameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByPosition( position ); jpqlQuery.setParameter( position, value, temporalType ); return this; } @Override public QueryImplementor setParameter(int position, ZonedDateTime value, TemporalType temporalType) { - final ExplicitParameterInfo explicitParameterInfo = locateParameterByPosition( position ); - explicitParameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByPosition( position ); jpqlQuery.setParameter( position, value, temporalType ); return this; } @Override public QueryImplementor setParameter(int position, OffsetDateTime value, TemporalType temporalType) { - final ExplicitParameterInfo explicitParameterInfo = locateParameterByPosition( position ); - explicitParameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByPosition( position ); jpqlQuery.setParameter( position, value, temporalType ); return this; } @Override public QueryImplementor setParameter(int position, Object val, Type type) { - final ExplicitParameterInfo explicitParameterInfo = locateParameterByPosition( position ); - explicitParameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByPosition( position ); jpqlQuery.setParameter( position, val, type ); return this; } @Override public QueryImplementor setEntity(int position, Object val) { - final ExplicitParameterInfo explicitParameterInfo = locateParameterByPosition( position ); - explicitParameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByPosition( position ); jpqlQuery.setParameter( position, val ); return this; } @Override public

    QueryImplementor setParameter(int position, P val, TemporalType temporalType) { - final ExplicitParameterInfo explicitParameterInfo = locateParameterByPosition( position ); - explicitParameterInfo.validateDateBind(); + entityManager.checkOpen( false ); + locateParameterByPosition( position ); jpqlQuery.setParameter( position, val, temporalType ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java index 586c8139d33b..a251f2ad19cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java @@ -8,6 +8,9 @@ import javax.persistence.criteria.ParameterExpression; +import org.hibernate.dialect.Dialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + /** * Used to provide a context and services to the rendering. * @@ -19,7 +22,7 @@ public interface RenderingContext { * * @return The generated correlation name */ - public String generateAlias(); + String generateAlias(); /** * Register parameters explicitly encountered in the criteria query. @@ -28,17 +31,17 @@ public interface RenderingContext { * * @return The JPA-QL parameter name */ - public ExplicitParameterInfo registerExplicitParameter(ParameterExpression criteriaQueryParameter); + ExplicitParameterInfo registerExplicitParameter(ParameterExpression criteriaQueryParameter); /** * Register a parameter that was not part of the criteria query (at least not as a parameter). * * @param literal The literal value - * @param javaType The java type as whcih to handle the literal value. + * @param javaType The java type as which to handle the literal value. * * @return The JPA-QL parameter name */ - public String registerLiteralParameterBinding(Object literal, Class javaType); + String registerLiteralParameterBinding(Object literal, Class javaType); /** * Given a java type, determine the proper cast type name. @@ -47,5 +50,21 @@ public interface RenderingContext { * * @return The cast type name. */ - public String getCastType(Class javaType); + String getCastType(Class javaType); + + /** + * Current Dialect. + * + * @return Dialect + */ + Dialect getDialect(); + + /** + * How literals are going to be handled. + * + * @return literal handling strategy + */ + default LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return LiteralHandlingMode.AUTO; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/BinaryArithmeticOperation.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/BinaryArithmeticOperation.java index 804b270f6113..eda788a95757 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/BinaryArithmeticOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/BinaryArithmeticOperation.java @@ -213,9 +213,4 @@ public String render(RenderingContext renderingContext) { ( (Renderable) getRightHandOperand() ).render( renderingContext ) ); } - - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CoalesceExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CoalesceExpression.java index 5fce76e33532..32a79b076cae 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CoalesceExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CoalesceExpression.java @@ -76,8 +76,4 @@ public String render(RenderingContext renderingContext) { } return buffer.append( ")" ).toString(); } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java index 3771145c2cef..a6a8e469b539 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java @@ -88,8 +88,4 @@ public String render(RenderingContext renderingContext) { } return buff.toString(); } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ConcatExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ConcatExpression.java index 1ec193d6f749..c1293ecb885a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ConcatExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ConcatExpression.java @@ -68,8 +68,4 @@ public String render(RenderingContext renderingContext) { + " || " + ( (Renderable) getString2() ).render( renderingContext ) + ')' ; } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/EntityTypeExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/EntityTypeExpression.java index 8ac5e43d3969..dca77f0bbd8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/EntityTypeExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/EntityTypeExpression.java @@ -30,8 +30,4 @@ public String render(RenderingContext renderingContext) { // todo : is it valid for this to get rendered into the query itself? throw new IllegalArgumentException( "Unexpected call on EntityTypeExpression#render" ); } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ListIndexExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ListIndexExpression.java index 8930869f339b..3bf9706fe104 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ListIndexExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ListIndexExpression.java @@ -46,8 +46,4 @@ public String render(RenderingContext renderingContext) { + origin.getPathIdentifier() + ")"; } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java index 2f9d0160aff9..fa66140b6478 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java @@ -8,6 +8,7 @@ import java.io.Serializable; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.ParameterRegistry; import org.hibernate.query.criteria.internal.ValueHandlerFactory; @@ -46,26 +47,51 @@ public void registerParameters(ParameterRegistry registry) { @SuppressWarnings({ "unchecked" }) public String render(RenderingContext renderingContext) { - if ( ValueHandlerFactory.isNumeric( literal ) ) { - return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal ); + + LiteralHandlingMode literalHandlingMode = renderingContext.getCriteriaLiteralHandlingMode(); + + switch ( literalHandlingMode ) { + case AUTO: + if ( ValueHandlerFactory.isNumeric( literal ) ) { + return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal ); + } + else { + return bindLiteral( renderingContext ); + } + case BIND: + return bindLiteral( renderingContext ); + case INLINE: + Object literalValue = literal; + if ( String.class.equals( literal.getClass() ) ) { + literalValue = renderingContext.getDialect().inlineLiteral( (String) literal ); + } + return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literalValue ); + default: + throw new IllegalArgumentException( "Unexpected LiteralHandlingMode: " + literalHandlingMode ); } + } - // else... + private String bindLiteral(RenderingContext renderingContext) { final String parameterName = renderingContext.registerLiteralParameterBinding( getLiteral(), getJavaType() ); return ':' + parameterName; } @SuppressWarnings({ "unchecked" }) public String renderProjection(RenderingContext renderingContext) { + if ( ValueHandlerFactory.isCharacter( literal ) ) { + // In case literal is a Character, pass literal.toString() as the argument. + return renderingContext.getDialect().inlineLiteral( literal.toString() ); + } + // some drivers/servers do not like parameters in the select clause final ValueHandlerFactory.ValueHandler handler = ValueHandlerFactory.determineAppropriateHandler( literal.getClass() ); - if ( ValueHandlerFactory.isCharacter( literal ) ) { - return '\'' + handler.render( literal ) + '\''; - } - else { - return handler.render( literal ); - } + return handler.render( literal ); + } + + @Override + public String renderGroupBy(RenderingContext renderingContext) { + return renderProjection( renderingContext ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullifExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullifExpression.java index 55ee327eee9e..607643806475 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullifExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullifExpression.java @@ -67,8 +67,4 @@ public String render(RenderingContext renderingContext) { + ( (Renderable) getSecondaryExpression() ).render( renderingContext ) + ")"; } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } 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 71e92e03239f..3bac2a93ccf1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java @@ -52,28 +52,29 @@ public ParameterExpressionImpl( this.position = null; } + @Override public String getName() { return name; } + @Override public Integer getPosition() { return position; } + @Override public Class getParameterType() { return getJavaType(); } + @Override public void registerParameters(ParameterRegistry registry) { registry.registerParameter( this ); } + @Override public String render(RenderingContext renderingContext) { final ExplicitParameterInfo parameterInfo = renderingContext.registerExplicitParameter( this ); return parameterInfo.render(); } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/PathTypeExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/PathTypeExpression.java index 49798e7a4349..6abcefcdc4a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/PathTypeExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/PathTypeExpression.java @@ -33,8 +33,4 @@ public void registerParameters(ParameterRegistry registry) { public String render(RenderingContext renderingContext) { return "type(" + pathImpl.getPathIdentifier() + ")"; } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java index 1a81bcec25b4..6ce205463cd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java @@ -33,7 +33,6 @@ public class SearchedCaseExpression extends ExpressionImpl implements Case, Serializable { - private Class javaType; // overrides the javaType kept on tuple-impl so that we can adjust it private List whenClauses = new ArrayList(); private Expression otherwiseResult; @@ -59,7 +58,6 @@ public SearchedCaseExpression( CriteriaBuilderImpl criteriaBuilder, Class javaType) { super( criteriaBuilder, javaType ); - this.javaType = javaType; } public Case when(Expression condition, R result) { @@ -77,24 +75,17 @@ private LiteralExpression buildLiteral(R result) { public Case when(Expression condition, Expression result) { WhenClause whenClause = new WhenClause( condition, result ); whenClauses.add( whenClause ); - adjustJavaType( result ); + resetJavaType( result.getJavaType() ); return this; } - @SuppressWarnings({"unchecked"}) - private void adjustJavaType(Expression exp) { - if ( javaType == null ) { - javaType = (Class) exp.getJavaType(); - } - } - public Expression otherwise(R result) { return otherwise( buildLiteral( result ) ); } public Expression otherwise(Expression result) { this.otherwiseResult = result; - adjustJavaType( result ); + resetJavaType( result.getJavaType() ); return this; } @@ -128,6 +119,14 @@ public String renderProjection(RenderingContext renderingContext) { ); } + @Override + public String renderGroupBy(RenderingContext renderingContext) { + return render( + renderingContext, + (Renderable expression, RenderingContext context) -> expression.renderGroupBy( context ) + ); + } + private String render( RenderingContext renderingContext, BiFunction formatter) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java index c941bf889b30..8074b5fca271 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java @@ -32,7 +32,6 @@ public class SimpleCaseExpression extends ExpressionImpl implements SimpleCase, Serializable { - private Class javaType; private final Expression expression; private List whenClauses = new ArrayList(); private Expression otherwiseResult; @@ -61,7 +60,6 @@ public SimpleCaseExpression( Class javaType, Expression expression) { super( criteriaBuilder, javaType); - this.javaType = javaType; this.expression = expression; } @@ -88,24 +86,17 @@ public SimpleCase when(C condition, Expression result) { result ); whenClauses.add( whenClause ); - adjustJavaType( result ); + resetJavaType( result.getJavaType() ); return this; } - @SuppressWarnings({ "unchecked" }) - private void adjustJavaType(Expression exp) { - if ( javaType == null ) { - javaType = (Class) exp.getJavaType(); - } - } - public Expression otherwise(R result) { return otherwise( buildLiteral(result) ); } public Expression otherwise(Expression result) { this.otherwiseResult = result; - adjustJavaType( result ); + resetJavaType( result.getJavaType() ); return this; } @@ -125,6 +116,7 @@ public void registerParameters(ParameterRegistry registry) { Helper.possibleParameter( getOtherwiseResult(), registry ); } + @Override public String render(RenderingContext renderingContext) { return render( renderingContext, @@ -132,6 +124,7 @@ public String render(RenderingContext renderingContext) { ); } + @Override public String renderProjection(RenderingContext renderingContext) { return render( renderingContext, @@ -139,6 +132,14 @@ public String renderProjection(RenderingContext renderingContext) { ); } + @Override + public String renderGroupBy(RenderingContext renderingContext) { + return render( + renderingContext, + (Renderable expression, RenderingContext context) -> expression.renderGroupBy( context ) + ); + } + private String render( RenderingContext renderingContext, BiFunction formatter) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfPluralAttributeExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfPluralAttributeExpression.java index 7c03411b5d4e..003b5578fd8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfPluralAttributeExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfPluralAttributeExpression.java @@ -50,8 +50,4 @@ public void registerParameters(ParameterRegistry registry) { public String render(RenderingContext renderingContext) { return "size(" + getPluralAttributePath().render( renderingContext ) + ")"; } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SubqueryComparisonModifierExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SubqueryComparisonModifierExpression.java index 6c05d7965648..e262f08fdbb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SubqueryComparisonModifierExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SubqueryComparisonModifierExpression.java @@ -70,8 +70,4 @@ public void registerParameters(ParameterRegistry registry) { public String render(RenderingContext renderingContext) { return getModifier().rendered() + ( (Renderable) getSubquery() ).render( renderingContext ); } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/UnaryArithmeticOperation.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/UnaryArithmeticOperation.java index fcdc83b34308..6b38fefa1d0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/UnaryArithmeticOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/UnaryArithmeticOperation.java @@ -59,9 +59,4 @@ public String render(RenderingContext renderingContext) { return ( getOperation() == Operation.UNARY_MINUS ? '-' : '+' ) + ( (Renderable) getOperand() ).render( renderingContext ); } - - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/BasicFunctionExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/BasicFunctionExpression.java index 090631de0066..967fdec1ac1c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/BasicFunctionExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/BasicFunctionExpression.java @@ -51,8 +51,4 @@ public void registerParameters(ParameterRegistry registry) { public String render(RenderingContext renderingContext) { return getFunctionName() + "()"; } - - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractFromImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractFromImpl.java index d2f6b54ea7cd..ac6a702558e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractFromImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractFromImpl.java @@ -91,16 +91,11 @@ public void prepareAlias(RenderingContext renderingContext) { } @Override - public String renderProjection(RenderingContext renderingContext) { + public String render(RenderingContext renderingContext) { prepareAlias( renderingContext ); return getAlias(); } - @Override - public String render(RenderingContext renderingContext) { - return renderProjection( renderingContext ); - } - @Override public Attribute getAttribute() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractJoinImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractJoinImpl.java index ba7c33cf8e48..57fe90526b94 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractJoinImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractJoinImpl.java @@ -19,7 +19,7 @@ import org.hibernate.query.criteria.internal.JoinImplementor; import org.hibernate.query.criteria.internal.PathSource; import org.hibernate.query.criteria.internal.compile.RenderingContext; -import org.hibernate.query.criteria.internal.predicate.AbstractPredicateImpl; +import org.hibernate.query.criteria.internal.predicate.PredicateImplementor; /** * Convenience base class for various {@link javax.persistence.criteria.Join} implementations. @@ -83,7 +83,7 @@ public String renderTableExpression(RenderingContext renderingContext) { .append( getAlias() ); if ( suppliedJoinCondition != null ) { tableExpression.append( " with " ) - .append( ( (AbstractPredicateImpl) suppliedJoinCondition ).render( renderingContext ) ); + .append( ( (PredicateImplementor) suppliedJoinCondition ).render( renderingContext ) ); } return tableExpression.toString(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractPathImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractPathImpl.java index 38f1f482e5cf..5c577904ee30 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractPathImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/AbstractPathImpl.java @@ -247,9 +247,4 @@ public String render(RenderingContext renderingContext) { return getAttribute().getName(); } } - - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/RootImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/RootImpl.java index 652c6dee1e66..34514e68e9e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/RootImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/RootImpl.java @@ -90,11 +90,6 @@ public String render(RenderingContext renderingContext) { prepareAlias( renderingContext ); return getAlias(); } - - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } public Set> getTreats() { return treats; diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/AbstractSimplePredicate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/AbstractSimplePredicate.java index 2242a8c1b19b..3d0c8cdb0512 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/AbstractSimplePredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/AbstractSimplePredicate.java @@ -48,9 +48,4 @@ public String render(RenderingContext renderingContext) { return render( isNegated(), renderingContext ); } - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/CompoundPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/CompoundPredicate.java index 054272844b5a..42f72793a34c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/CompoundPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/CompoundPredicate.java @@ -119,11 +119,6 @@ private String operatorTextWithSeparator() { return operatorTextWithSeparator( this.getOperator() ); } - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } - /** * Create negation of compound predicate by using logic rules: * 1. not (x || y) is (not x && not y) diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/InPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/InPredicate.java index 283767be4a99..30e0a3a9cd95 100755 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/InPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/InPredicate.java @@ -25,7 +25,7 @@ import org.hibernate.type.Type; /** - * Models an [NOT] IN restriction + * Models a [NOT] IN restriction * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/MemberOfPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/MemberOfPredicate.java index 9653f1111998..099e9ca82813 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/MemberOfPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/MemberOfPredicate.java @@ -18,7 +18,7 @@ import org.hibernate.query.criteria.internal.path.PluralAttributePath; /** - * Models an [NOT] MEMBER OF restriction + * Models a [NOT] MEMBER OF restriction * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/NegatedPredicateWrapper.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/NegatedPredicateWrapper.java index 314a12098080..99f9668742b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/NegatedPredicateWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/NegatedPredicateWrapper.java @@ -102,9 +102,4 @@ public String render(boolean isNegated, RenderingContext renderingContext) { public String render(RenderingContext renderingContext) { return render( isNegated(), renderingContext ); } - - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index c072d4c61681..81f3f209088a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -25,6 +25,9 @@ import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.persistence.CacheRetrieveMode; @@ -59,7 +62,6 @@ import org.hibernate.internal.EntityManagerMessageLogger; import org.hibernate.internal.HEMLogging; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.internal.util.collections.EmptyIterator; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.TypedParameterValue; import org.hibernate.jpa.graph.internal.EntityGraphImpl; @@ -74,11 +76,14 @@ import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterListBinding; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; +import org.jboss.logging.Logger; + import static org.hibernate.LockOptions.WAIT_FOREVER; import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_SCOPE; import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT; @@ -94,6 +99,7 @@ import static org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE; import static org.hibernate.jpa.QueryHints.HINT_FOLLOW_ON_LOCKING; import static org.hibernate.jpa.QueryHints.HINT_LOADGRAPH; +import static org.hibernate.jpa.QueryHints.HINT_NATIVE_SPACES; import static org.hibernate.jpa.QueryHints.HINT_READONLY; import static org.hibernate.jpa.QueryHints.HINT_TIMEOUT; import static org.hibernate.jpa.QueryHints.SPEC_HINT_TIMEOUT; @@ -102,11 +108,11 @@ * @author Steve Ebersole */ public abstract class AbstractProducedQuery implements QueryImplementor { - private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( AbstractProducedQuery.class ); + private static final EntityManagerMessageLogger MSG_LOGGER = HEMLogging.messageLogger( AbstractProducedQuery.class ); + private static final Logger LOGGER = Logger.getLogger( AbstractProducedQuery.class ); private final SharedSessionContractImplementor producer; private final ParameterMetadata parameterMetadata; - private final QueryParameterBindingsImpl queryParameterBindings; private FlushMode flushMode; private CacheStoreMode cacheStoreMode; @@ -136,7 +142,6 @@ public AbstractProducedQuery( ParameterMetadata parameterMetadata) { this.producer = producer; this.parameterMetadata = parameterMetadata; - this.queryParameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, producer.getFactory() ); } @Override @@ -163,6 +168,7 @@ public QueryImplementor setFlushMode(FlushMode flushMode) { @Override public FlushModeType getFlushMode() { + getProducer().checkOpen(); return ( flushMode == null ? getProducer().getFlushMode() : FlushModeTypeHelper.getFlushModeType( flushMode ) @@ -172,6 +178,7 @@ public FlushModeType getFlushMode() { @Override @SuppressWarnings("unchecked") public QueryImplementor setFlushMode(FlushModeType flushModeType) { + getProducer().checkOpen(); setHibernateFlushMode( FlushModeTypeHelper.getFlushMode( flushModeType ) ); return this; } @@ -277,6 +284,7 @@ public QueryImplementor setLockMode(String alias, LockMode lockMode) { @Override @SuppressWarnings("unchecked") public QueryImplementor setLockMode(LockModeType lockModeType) { + getProducer().checkOpen(); if ( !LockModeType.NONE.equals( lockModeType ) ) { if ( !isSelect() ) { throw new IllegalStateException( "Illegal attempt to set lock mode on a non-SELECT query" ); @@ -383,27 +391,32 @@ public QueryImplementor setParameter(int position, ZonedDateTime value, Tempo @Override public QueryImplementor setParameter(int position, OffsetDateTime value, TemporalType temporalType) { - locateBinding( position ).setBindValue( value, temporalType ); + final QueryParameterBinding binding = getQueryParameterBindings().getBinding( + getParameterMetadata().getQueryParameter( position ) + ); + + binding.setBindValue( value, temporalType ); + return this; } @Override @SuppressWarnings("unchecked") public

    QueryImplementor setParameter(QueryParameter

    parameter, P value) { - locateBinding( parameter ).setBindValue( value ); + getQueryParameterBindings().getBinding( (QueryParameter) parameter ).setBindValue( value ); return this; } @SuppressWarnings("unchecked") private

    QueryParameterBinding

    locateBinding(Parameter

    parameter) { if ( parameter instanceof QueryParameter ) { - return queryParameterBindings.getBinding( (QueryParameter) parameter ); + return getQueryParameterBindings().getBinding( (QueryParameter) parameter ); } else if ( parameter.getName() != null ) { - return queryParameterBindings.getBinding( parameter.getName() ); + return getQueryParameterBindings().getBinding( parameter.getName() ); } else if ( parameter.getPosition() != null ) { - return queryParameterBindings.getBinding( parameter.getPosition() ); + return getQueryParameterBindings().getBinding( parameter.getPosition() ); } throw getExceptionConverter().convert( @@ -412,24 +425,25 @@ else if ( parameter.getPosition() != null ) { } private

    QueryParameterBinding

    locateBinding(QueryParameter

    parameter) { - return queryParameterBindings.getBinding( parameter ); + return getQueryParameterBindings().getBinding( parameter ); } private

    QueryParameterBinding

    locateBinding(String name) { - return queryParameterBindings.getBinding( name ); + return getQueryParameterBindings().getBinding( name ); } private

    QueryParameterBinding

    locateBinding(int position) { - return queryParameterBindings.getBinding( position ); + return getQueryParameterBindings().getBinding( position ); } @Override @SuppressWarnings("unchecked") public

    QueryImplementor setParameter(Parameter

    parameter, P value) { + getProducer().checkOpen(); if ( value instanceof TypedParameterValue ) { setParameter( parameter, ( (TypedParameterValue) value ).getValue(), ( (TypedParameterValue) value ).getType() ); } - else if ( value instanceof Collection ) { + else if ( value instanceof Collection && !isRegisteredAsBasicType( value.getClass() ) ) { locateListBinding( parameter ).setBindValues( (Collection) value ); } else { @@ -447,7 +461,7 @@ private

    void setParameter(Parameter

    parameter, Object value, Type type) { else if ( value == null ) { locateBinding( parameter ).setBindValue( null, type ); } - else if ( value instanceof Collection ) { + else if ( value instanceof Collection && !isRegisteredAsBasicType( value.getClass() ) ) { locateListBinding( parameter ).setBindValues( (Collection) value, type ); } else { @@ -457,29 +471,30 @@ else if ( value instanceof Collection ) { private QueryParameterListBinding locateListBinding(Parameter parameter) { if ( parameter instanceof QueryParameter ) { - return queryParameterBindings.getQueryParameterListBinding( (QueryParameter) parameter ); + return getQueryParameterBindings().getQueryParameterListBinding( (QueryParameter) parameter ); } else { - return queryParameterBindings.getQueryParameterListBinding( parameter.getName() ); + return getQueryParameterBindings().getQueryParameterListBinding( parameter.getName() ); } } private QueryParameterListBinding locateListBinding(String name) { - return queryParameterBindings.getQueryParameterListBinding( name ); + return getQueryParameterBindings().getQueryParameterListBinding( name ); } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Object value) { + getProducer().checkOpen(); if ( value instanceof TypedParameterValue ) { final TypedParameterValue typedValueWrapper = (TypedParameterValue) value; setParameter( name, typedValueWrapper.getValue(), typedValueWrapper.getType() ); } - else if ( value instanceof Collection ) { + else if ( value instanceof Collection && !isRegisteredAsBasicType( value.getClass() ) ) { setParameterList( name, (Collection) value ); } else { - queryParameterBindings.getBinding( name ).setBindValue( value ); + getQueryParameterBindings().getBinding( name ).setBindValue( value ); } return this; @@ -488,15 +503,16 @@ else if ( value instanceof Collection ) { @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Object value) { + getProducer().checkOpen(); if ( value instanceof TypedParameterValue ) { final TypedParameterValue typedParameterValue = (TypedParameterValue) value; setParameter( position, typedParameterValue.getValue(), typedParameterValue.getType() ); } - if ( value instanceof Collection ) { - setParameterList( Integer.toString( position ), (Collection) value ); + else if ( value instanceof Collection && !isRegisteredAsBasicType( value.getClass() ) ) { + setParameterList( getParameterMetadata().getQueryParameter( position ), (Collection) value ); } else { - queryParameterBindings.getBinding( position ).setBindValue( value ); + getQueryParameterBindings().getBinding( position ).setBindValue( value ); } return this; } @@ -504,129 +520,165 @@ public QueryImplementor setParameter(int position, Object value) { @Override @SuppressWarnings("unchecked") public

    QueryImplementor setParameter(QueryParameter

    parameter, P value, Type type) { - queryParameterBindings.getBinding( parameter ).setBindValue( value, type ); + getQueryParameterBindings().getBinding( parameter ).setBindValue( value, type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Object value, Type type) { - queryParameterBindings.getBinding( name ).setBindValue( value, type ); + getQueryParameterBindings().getBinding( name ).setBindValue( value, type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Object value, Type type) { - queryParameterBindings.getBinding( position ).setBindValue( value, type ); + getQueryParameterBindings().getBinding( position ).setBindValue( value, type ); return this; } @Override @SuppressWarnings("unchecked") public

    QueryImplementor setParameter(QueryParameter

    parameter, P value, TemporalType temporalType) { - queryParameterBindings.getBinding( parameter ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( parameter ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Object value, TemporalType temporalType) { - queryParameterBindings.getBinding( name ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( name ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Object value, TemporalType temporalType) { - queryParameterBindings.getBinding( position ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( position ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public

    QueryImplementor setParameterList(QueryParameter

    parameter, Collection

    values) { - queryParameterBindings.getQueryParameterListBinding( parameter ).setBindValues( values ); + getQueryParameterBindings().getQueryParameterListBinding( parameter ).setBindValues( values ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Collection values) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( values ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( values ); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public QueryImplementor setParameterList(int position, Collection values) { + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( values ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Collection values, Type type) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( values, type ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( values, type ); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public QueryImplementor setParameterList(int position, Collection values, Type type) { + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( values, type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Object[] values, Type type) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ), type ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ), type ); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public QueryImplementor setParameterList(int position, Object[] values, Type type) { + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( Arrays.asList( values ), type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Object[] values) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ) ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ) ); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public QueryImplementor setParameterList(int position, Object[] values) { + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( Arrays.asList( values ) ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(Parameter param, Calendar value, TemporalType temporalType) { - queryParameterBindings.getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); + getProducer().checkOpen(); + getQueryParameterBindings().getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(Parameter param, Date value, TemporalType temporalType) { - queryParameterBindings.getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); + getProducer().checkOpen(); + getQueryParameterBindings().getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Calendar value, TemporalType temporalType) { - queryParameterBindings.getBinding( name ).setBindValue( value, temporalType ); + getProducer().checkOpen(); + getQueryParameterBindings().getBinding( name ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Date value, TemporalType temporalType) { - queryParameterBindings.getBinding( name ).setBindValue( value, temporalType ); + getProducer().checkOpen(); + getQueryParameterBindings().getBinding( name ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Calendar value, TemporalType temporalType) { - queryParameterBindings.getBinding( position ).setBindValue( value, temporalType ); + getProducer().checkOpen(); + getQueryParameterBindings().getBinding( position ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Date value, TemporalType temporalType) { - queryParameterBindings.getBinding( position ).setBindValue( value, temporalType ); + getProducer().checkOpen(); + getQueryParameterBindings().getBinding( position ).setBindValue( value, temporalType ); return this; } @Override public Set> getParameters() { + getProducer().checkOpen( false ); return getParameterMetadata().collectAllParametersJpa(); } @Override - public Parameter getParameter(String name) { + public QueryParameter getParameter(String name) { + getProducer().checkOpen( false ); try { return getParameterMetadata().getQueryParameter( name ); } @@ -637,7 +689,8 @@ public Parameter getParameter(String name) { @Override @SuppressWarnings("unchecked") - public Parameter getParameter(String name, Class type) { + public QueryParameter getParameter(String name, Class type) { + getProducer().checkOpen( false ); try { final QueryParameter parameter = getParameterMetadata().getQueryParameter( name ); if ( !parameter.getParameterType().isAssignableFrom( type ) ) { @@ -655,19 +708,8 @@ public Parameter getParameter(String name, Class type) { } @Override - public Parameter getParameter(int position) { - // It is important to understand that there are 2 completely distinct conceptualization of - // "positional parameters" in play here: - // 1) The legacy Hibernate concept is akin to JDBC PreparedStatement parameters. Very limited and - // deprecated since 5.x. These are numbered starting from 0 and kept in the - // ParameterMetadata positional-parameter array keyed by this zero-based position - // 2) JPA's definition is really just a named parameter, but expected to explicitly be - // sequential integers starting from 1 (ONE); they can repeat. - // - // It is considered illegal to mix positional-parameter with named parameters of any kind. So therefore. - // if ParameterMetadata reports that it has any positional-parameters it is talking about the - // legacy Hibernate concept. - // lookup jpa-based positional parameters first by name. + public QueryParameter getParameter(int position) { + getProducer().checkOpen( false ); try { if ( getParameterMetadata().getPositionalParameterCount() == 0 ) { try { @@ -687,7 +729,8 @@ public Parameter getParameter(int position) { @Override @SuppressWarnings("unchecked") - public Parameter getParameter(int position, Class type) { + public QueryParameter getParameter(int position, Class type) { + getProducer().checkOpen( false ); try { final QueryParameter parameter = getParameterMetadata().getQueryParameter( position ); if ( !parameter.getParameterType().isAssignableFrom( type ) ) { @@ -706,23 +749,111 @@ public Parameter getParameter(int position, Class type) { @Override public boolean isBound(Parameter parameter) { - return queryParameterBindings.isBound( (QueryParameter) parameter ); + getProducer().checkOpen(); + return getQueryParameterBindings().isBound( (QueryParameter) parameter ); } @Override - @SuppressWarnings("unchecked") public T getParameterValue(Parameter parameter) { - return (T) queryParameterBindings.getBinding( (QueryParameter) parameter ).getBindValue(); + LOGGER.tracef( "#getParameterValue(%s)", parameter ); + getProducer().checkOpen( false ); + + return (T) getParameterValue( + (QueryParameter) parameter, + (queryParameter) -> new IllegalStateException( "Parameter value not yet bound : " + queryParameter.toString() ), + (queryParameter, e) -> { + final String message = "Parameter reference [" + queryParameter + "] did not come from this query"; + if ( e == null ) { + return new IllegalArgumentException( message ); + } + return new IllegalArgumentException( message, e ); + }, + (queryParameter, isBound) -> LOGGER.debugf( + "Checking whether parameter reference [%s] is bound : %s", + queryParameter, + isBound + ) + ); } @Override public Object getParameterValue(String name) { - return queryParameterBindings.getBinding( name ).getBindValue(); + getProducer().checkOpen( false ); + + final QueryParameter queryParameter = getParameterMetadata().getQueryParameter( name ); + return getParameterValue( + queryParameter, + (parameter) -> new IllegalStateException( "Parameter value not yet bound : " + parameter.getName() ), + (parameter, e) -> { + final String message = "Could not resolve parameter by name - " + parameter.getName(); + if ( e == null ) { + return new IllegalArgumentException( message ); + } + return new IllegalArgumentException( message, e ); + }, + (parameter, isBound) -> LOGGER.debugf( + "Checking whether positional named [%s] is bound : %s", + parameter.getName(), + isBound + ) + ); } @Override public Object getParameterValue(int position) { - return queryParameterBindings.getBinding( position ).getBindValue(); + getProducer().checkOpen( false ); + + final QueryParameter queryParameter = getParameterMetadata().getQueryParameter( position ); + return getParameterValue( + queryParameter, + (parameter) -> new IllegalStateException( "Parameter value not yet bound : " + parameter.getPosition() ), + (parameter, e) -> { + String message = "Could not resolve parameter by position - " + parameter.getPosition(); + if ( e == null ) { + return new IllegalArgumentException( message ); + } + return new IllegalArgumentException( message, e ); + }, + (parameter, isBound) -> LOGGER.debugf( + "Checking whether positional parameter [%s] is bound : %s", + parameter.getPosition(), + isBound + ) + ); + } + + private Object getParameterValue( + QueryParameter queryParameter, + Function notBoundParamenterException, + BiFunction couldNotResolveParameterException, + BiConsumer boundCheckingLogger) { + try { + final QueryParameterBindings parameterBindings = getQueryParameterBindings(); + + if ( queryParameter == null ) { + throw couldNotResolveParameterException.apply( queryParameter, null ); + } + if ( parameterBindings.isMultiValuedBinding( queryParameter ) ) { + final QueryParameterListBinding queryParameterListBinding = parameterBindings + .getQueryParameterListBinding( queryParameter ); + final Collection bindValues = queryParameterListBinding.getBindValues(); + if ( bindValues == null ) { + throw notBoundParamenterException.apply( queryParameter ); + } + return bindValues; + } + + final QueryParameterBinding binding = parameterBindings.getBinding( queryParameter ); + final boolean bound = binding.isBound(); + boundCheckingLogger.accept( queryParameter, bound ); + if ( !bound ) { + throw notBoundParamenterException.apply( queryParameter ); + } + return binding.getBindValue(); + } + catch (QueryParameterException e) { + throw couldNotResolveParameterException.apply( queryParameter, e ); + } } @Override @@ -758,9 +889,9 @@ else if ( retType.isArray() ) { } protected Type determineType(String namedParam, Class retType) { - Type type = queryParameterBindings.getBinding( namedParam ).getBindType(); + Type type = getQueryParameterBindings().getBinding( namedParam ).getBindType(); if ( type == null ) { - type = getParameterMetadata().getQueryParameter( namedParam ).getType(); + type = getParameterMetadata().getQueryParameter( namedParam ).getHibernateType(); } if ( type == null ) { type = getProducer().getFactory().resolveParameterBindType( retType ); @@ -809,6 +940,7 @@ public RowSelection getQueryOptions() { @Override public int getMaxResults() { + getProducer().checkOpen(); // to be JPA compliant this method returns an int - specifically the "magic number" Integer.MAX_VALUE defined by the spec. // For access to the Integer (for checking), use #getQueryOptions#getMaxRows instead return queryOptions.getMaxRows() == null ? Integer.MAX_VALUE : queryOptions.getMaxRows(); @@ -817,9 +949,10 @@ public int getMaxResults() { @Override @SuppressWarnings("unchecked") public QueryImplementor setMaxResults(int maxResult) { + getProducer().checkOpen(); + if ( maxResult < 0 ) { - // treat zero and negatives specially as meaning no limit... - queryOptions.setMaxRows( null ); + throw new IllegalArgumentException( "max-results cannot be negative" ); } else { queryOptions.setMaxRows( maxResult ); @@ -829,6 +962,7 @@ public QueryImplementor setMaxResults(int maxResult) { @Override public int getFirstResult() { + getProducer().checkOpen(); // to be JPA compliant this method returns an int - specifically the "magic number" 0 (ZERO) defined by the spec. // For access to the Integer (for checking), use #getQueryOptions#getFirstRow instead return queryOptions.getFirstRow() == null ? 0 : queryOptions.getFirstRow(); @@ -837,6 +971,10 @@ public int getFirstResult() { @Override @SuppressWarnings("unchecked") public QueryImplementor setFirstResult(int startPosition) { + getProducer().checkOpen(); + if ( startPosition < 0 ) { + throw new IllegalArgumentException( "first-result value cannot be negative : " + startPosition ); + } queryOptions.setFirstRow( startPosition ); return this; } @@ -970,6 +1108,9 @@ else if ( JPA_SHARED_CACHE_STORE_MODE.equals( hintName ) ) { final CacheStoreMode storeMode = value != null ? CacheStoreMode.valueOf( value.toString() ) : null; applied = applyJpaCacheStoreMode( storeMode ); } + else if ( HINT_NATIVE_SPACES.equals( hintName ) ) { + applied = applyQuerySpaces( value ); + } else if ( QueryHints.HINT_NATIVE_LOCKMODE.equals( hintName ) ) { applied = applyNativeQueryLockMode( value ); } @@ -983,7 +1124,7 @@ else if ( hintName.startsWith( ALIAS_SPECIFIC_LOCK_MODE ) ) { applyAliasSpecificLockModeHint( alias, lockMode ); } catch ( Exception e ) { - log.unableToDetermineLockModeValue( hintName, value ); + MSG_LOGGER.unableToDetermineLockModeValue( hintName, value ); applied = false; } } @@ -996,7 +1137,7 @@ else if ( HINT_FETCHGRAPH.equals( hintName ) || HINT_LOADGRAPH.equals( hintName applyEntityGraphQueryHint( new EntityGraphQueryHint( hintName, (EntityGraphImpl) value ) ); } else { - log.warnf( "The %s hint was set, but the value was not an EntityGraph!", hintName ); + MSG_LOGGER.warnf( "The %s hint was set, but the value was not an EntityGraph!", hintName ); } applied = true; } @@ -1007,7 +1148,7 @@ else if ( QueryHints.HINT_PASS_DISTINCT_THROUGH.equals( hintName ) ) { applied = applyPassDistinctThrough( ConfigurationHelper.getBoolean( value ) ); } else { - log.ignoringUnrecognizedQueryHint( hintName ); + MSG_LOGGER.ignoringUnrecognizedQueryHint( hintName ); } } catch ( ClassCastException e ) { @@ -1015,12 +1156,22 @@ else if ( QueryHints.HINT_PASS_DISTINCT_THROUGH.equals( hintName ) ) { } if ( !applied ) { - log.debugf( "Skipping unsupported query hint [%s]", hintName ); + handleUnrecognizedHint( hintName, value ); } return this; } + protected boolean applyQuerySpaces(Object value) { + throw new IllegalStateException( + "Illegal attempt to apply native-query spaces to a non-native query" + ); + } + + protected void handleUnrecognizedHint(String hintName, Object value) { + MSG_LOGGER.debugf( "Skipping unsupported query hint [%s]", hintName ); + } + protected boolean applyJpaCacheRetrieveMode(CacheRetrieveMode mode) { this.cacheRetrieveMode = mode; return true; @@ -1219,6 +1370,10 @@ protected boolean applyPassDistinctThrough(boolean passDistinctThrough) { @Override public LockModeType getLockMode() { + getProducer().checkOpen( false ); + if ( !isSelect() ) { + throw new IllegalStateException( "Illegal attempt to get lock mode on a non-SELECT query" ); + } return LockModeTypeHelper.getLockModeType( lockOptions.getLockMode() ); } @@ -1231,8 +1386,8 @@ public T unwrap(Class cls) { if ( cls.isInstance( getParameterMetadata() ) ) { return (T) getParameterMetadata(); } - if ( cls.isInstance( queryParameterBindings ) ) { - return (T) queryParameterBindings; + if ( cls.isInstance( getQueryParameterBindings() ) ) { + return (T) getQueryParameterBindings(); } if ( cls.isInstance( this ) ) { return (T) this; @@ -1242,30 +1397,24 @@ public T unwrap(Class cls) { // throw new IllegalArgumentException( "Could not unwrap this [" + toString() + "] as requested Java type [" + cls.getName() + "]" ); } - public QueryParameters getQueryParameters() { + protected QueryParameters makeQueryParametersForExecution(String hql) { final HQLQueryPlan entityGraphHintedQueryPlan; if ( entityGraphQueryHint == null) { entityGraphHintedQueryPlan = null; } else { - queryParameterBindings.verifyParametersBound( false ); - - // todo : ideally we'd update the instance state related to queryString but that is final atm - - final String expandedQuery = queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ); + final SharedSessionContractImplementor producer = getProducer(); entityGraphHintedQueryPlan = new HQLQueryPlan( - expandedQuery, + hql, false, - getProducer().getLoadQueryInfluencers().getEnabledFilters(), - getProducer().getFactory(), + producer.getLoadQueryInfluencers().getEnabledFilters(), + producer.getFactory(), entityGraphQueryHint ); } - QueryParameters queryParameters = new QueryParameters( - getPositionalParameterTypes(), - getPositionalParameterValues(), - getNamedParameterMap(), + QueryParameters queryParameters = new QueryParameters( + getQueryParameterBindings(), getLockOptions(), queryOptions, true, @@ -1287,26 +1436,33 @@ public QueryParameters getQueryParameters() { return queryParameters; } + public QueryParameters getQueryParameters() { + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); + return makeQueryParametersForExecution( expandedQuery ); + } + @SuppressWarnings("deprecation") protected Type[] getPositionalParameterTypes() { - return queryParameterBindings.collectPositionalBindTypes(); + return getQueryParameterBindings().collectPositionalBindTypes(); } @SuppressWarnings("deprecation") protected Object[] getPositionalParameterValues() { - return queryParameterBindings.collectPositionalBindValues(); + return getQueryParameterBindings().collectPositionalBindValues(); } @SuppressWarnings("deprecation") protected Map getNamedParameterMap() { - return queryParameterBindings.collectNamedParameterBindings(); + return getQueryParameterBindings().collectNamedParameterBindings(); } private FlushMode sessionFlushMode; private CacheMode sessionCacheMode; protected void beforeQuery() { - queryParameterBindings.verifyParametersBound( isCallable() ); + if ( optionalId == null ) { + getQueryParameterBindings().verifyParametersBound( isCallable() ); + } assert sessionFlushMode == null; assert sessionCacheMode == null; @@ -1347,10 +1503,10 @@ public Iterator iterate() { @SuppressWarnings("unchecked") protected Iterator doIterate() { if (getMaxResults() == 0){ - return EmptyIterator.INSTANCE; + return Collections.emptyIterator(); } return getProducer().iterate( - queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ), + getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ), getQueryParameters() ); } @@ -1375,8 +1531,8 @@ protected ScrollableResultsImplementor doScroll(ScrollMode scrollMode) { if (getMaxResults() == 0){ return EmptyScrollableResults.INSTANCE; } - final String query = queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ); - QueryParameters queryParameters = getQueryParameters(); + final String query = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); + QueryParameters queryParameters = makeQueryParametersForExecution( query ); queryParameters.setScrollMode( scrollMode ); return getProducer().scroll( query, queryParameters ); } @@ -1416,7 +1572,7 @@ public List list() { throw new IllegalArgumentException( e ); } catch (HibernateException he) { - throw getExceptionConverter().convert( he ); + throw getExceptionConverter().convert( he, getLockOptions() ); } finally { afterQuery(); @@ -1438,15 +1594,14 @@ protected List doList() { } } + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return getProducer().list( - queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ), - getQueryParameters() + expandedQuery, + makeQueryParametersForExecution( expandedQuery ) ); } - public QueryParameterBindingsImpl getQueryParameterBindings() { - return queryParameterBindings; - } + protected abstract QueryParameterBindings getQueryParameterBindings(); @Override public R uniqueResult() { @@ -1463,12 +1618,7 @@ public R getSingleResult() { return uniqueElement( list ); } catch ( HibernateException e ) { - if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - throw getExceptionConverter().convert( e ); - } - else { - throw e; - } + throw getExceptionConverter().convert( e, getLockOptions() ); } } @@ -1488,13 +1638,8 @@ public static R uniqueElement(List list) throws NonUniqueResultException @Override public int executeUpdate() throws HibernateException { - if ( ! getProducer().isTransactionInProgress() ) { - throw getProducer().getExceptionConverter().convert( - new TransactionRequiredException( - "Executing an update/delete query" - ) - ); - } + getProducer().checkTransactionNeededForUpdateOperation( "Executing an update/delete query" ); + beforeQuery(); try { return doExecuteUpdate(); @@ -1506,12 +1651,7 @@ public int executeUpdate() throws HibernateException { throw new IllegalArgumentException( e ); } catch ( HibernateException e) { - if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - throw getExceptionConverter().convert( e ); - } - else { - throw e; - } + throw getExceptionConverter().convert( e ); } finally { afterQuery(); @@ -1519,9 +1659,10 @@ public int executeUpdate() throws HibernateException { } protected int doExecuteUpdate() { + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return getProducer().executeUpdate( - queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ), - getQueryParameters() + expandedQuery, + makeQueryParametersForExecution( expandedQuery ) ); } @@ -1573,4 +1714,8 @@ private boolean isSelect() { protected ExceptionConverter getExceptionConverter(){ return producer.getExceptionConverter(); } + + private boolean isRegisteredAsBasicType(Class cl) { + return producer.getFactory().getTypeResolver().basic( cl.getName() ) != null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java index 3bf85341051a..fe41bc9eddfa 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java @@ -11,8 +11,10 @@ import org.hibernate.HibernateException; import org.hibernate.ScrollMode; +import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.Query; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.type.Type; @@ -24,6 +26,7 @@ public class CollectionFilterImpl extends org.hibernate.query.internal.AbstractProducedQuery { private final String queryString; private Object collection; + private final QueryParameterBindingsImpl queryParameterBindings; public CollectionFilterImpl( String queryString, @@ -33,6 +36,16 @@ public CollectionFilterImpl( super( session, parameterMetadata ); this.queryString = queryString; this.collection = collection; + this.queryParameterBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + session.getFactory(), + session.isQueryParametersValidationEnabled() + ); + } + + @Override + protected QueryParameterBindings getQueryParameterBindings() { + return queryParameterBindings; } @Override @@ -48,20 +61,24 @@ public String getQueryString() { @Override public Iterator iterate() throws HibernateException { getQueryParameterBindings().verifyParametersBound( false ); + + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return getProducer().iterateFilter( collection, - getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ), - getQueryParameters() + expandedQuery, + makeQueryParametersForExecution( expandedQuery ) ); } @Override public List list() throws HibernateException { getQueryParameterBindings().verifyParametersBound( false ); + + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return getProducer().listFilter( collection, - getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ), - getQueryParameters() + expandedQuery, + makeQueryParametersForExecution( expandedQuery ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java index 2139429b4709..9a33069425af 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java @@ -7,13 +7,19 @@ package org.hibernate.query.internal; import java.io.Serializable; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.StringTokenizer; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.Parameter; @@ -21,12 +27,15 @@ import org.hibernate.CacheMode; import org.hibernate.FlushMode; +import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.ScrollMode; +import org.hibernate.SynchronizeableQuery; import org.hibernate.engine.ResultSetMappingDefinition; +import org.hibernate.engine.query.spi.EntityGraphQueryHint; import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn; @@ -35,10 +44,12 @@ import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.NativeQuery; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.NativeQueryImplementor; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; @@ -50,6 +61,7 @@ */ public class NativeQueryImpl extends AbstractProducedQuery implements NativeQueryImplementor { private final String sqlString; + private final QueryParameterBindingsImpl queryParameterBindings; private List queryReturns; private List queryReturnBuilders; private boolean autoDiscoverTypes; @@ -75,7 +87,7 @@ public NativeQueryImpl( this.sqlString = queryDef.getQueryString(); this.callable = queryDef.isCallable(); - this.querySpaces = queryDef.getQuerySpaces(); + this.querySpaces = queryDef.getQuerySpaces() == null ? null : new ArrayList<>( queryDef.getQuerySpaces() ); if ( queryDef.getResultSetRef() != null ) { ResultSetMappingDefinition definition = session.getFactory() @@ -95,6 +107,13 @@ else if ( queryDef.getQueryReturns() != null && queryDef.getQueryReturns().lengt else { this.queryReturns = new ArrayList<>(); } + + + this.queryParameterBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + session.getFactory(), + session.isQueryParametersValidationEnabled() + ); } public NativeQueryImpl( @@ -108,6 +127,17 @@ public NativeQueryImpl( this.sqlString = sqlString; this.callable = callable; this.querySpaces = new ArrayList<>(); + + this.queryParameterBindings = QueryParameterBindingsImpl.from( + sqlParameterMetadata, + session.getFactory(), + session.isQueryParametersValidationEnabled() + ); + } + + @Override + protected QueryParameterBindings getQueryParameterBindings() { + return queryParameterBindings; } @Override @@ -121,10 +151,6 @@ public NativeQuery setResultSetMapping(String name) { return this; } - public void setZeroBasedParametersIndex(boolean zeroBasedParametersIndex) { - getParameterMetadata().setOrdinalParametersZeroBased( zeroBasedParametersIndex ); - } - @Override public String getQueryString() { return sqlString; @@ -223,7 +249,7 @@ else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) { super.beforeQuery(); - if ( getSynchronizedQuerySpaces() != null && !getSynchronizedQuerySpaces().isEmpty() ) { + if ( CollectionHelper.isNotEmpty( getSynchronizedQuerySpaces() ) ) { // The application defined query spaces on the Hibernate native SQLQuery which means the query will already // perform a partial flush according to the defined query spaces, no need to do a full flush. return; @@ -239,6 +265,11 @@ else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) { } } + @Override + public Iterator iterate() { + throw new UnsupportedOperationException( "SQL queries do not currently support iteration" ); + } + private boolean shouldFlush() { if ( getProducer().isTransactionInProgress() ) { FlushMode effectiveFlushMode = getHibernateFlushMode(); @@ -410,12 +441,18 @@ public NativeQueryImplementor addSynchronizedQuerySpace(String querySpace) { return this; } + @Override + public SynchronizeableQuery addSynchronizedQuerySpace(String... querySpaces) { + addQuerySpaces( querySpaces ); + return this; + } + protected void addQuerySpaces(String... spaces) { if ( spaces != null ) { if ( querySpaces == null ) { querySpaces = new ArrayList<>(); } - querySpaces.addAll( Arrays.asList( (String[]) spaces ) ); + querySpaces.addAll( Arrays.asList( spaces ) ); } } @@ -440,6 +477,36 @@ public NativeQueryImplementor addSynchronizedEntityClass(Class entityClass) t return this; } + @Override + protected boolean applyQuerySpaces(Object value) { + if ( value == null ) { + return false; + } + + if ( value instanceof String[] ) { + addSynchronizedQuerySpace( (String[]) value ); + return true; + } + + if ( value instanceof Collection ) { + if ( querySpaces == null ) { + querySpaces = new ArrayList<>(); + } + querySpaces.addAll( (Collection) value ); + return true; + } + + if ( value instanceof String ) { + final StringTokenizer spaces = new StringTokenizer( (String) value, "," ); + while ( spaces.hasMoreTokens() ) { + addQuerySpaces( spaces.nextToken() ); + } + return true; + } + + return false; + } + @Override protected boolean isNativeQuery() { return true; @@ -620,6 +687,78 @@ public NativeQueryImplementor setParameter(int position, Object value, Tempor return this; } + @Override + public NativeQueryImplementor setParameter(Parameter param, Instant value, TemporalType temporalType) { + super.setParameter( param, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(Parameter param, LocalDateTime value, TemporalType temporalType) { + super.setParameter( param, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(Parameter param, ZonedDateTime value, TemporalType temporalType) { + super.setParameter( param, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(Parameter param, OffsetDateTime value, TemporalType temporalType) { + super.setParameter( param, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(String name, Instant value, TemporalType temporalType) { + super.setParameter( name, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(String name, LocalDateTime value, TemporalType temporalType) { + super.setParameter( name, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(String name, ZonedDateTime value, TemporalType temporalType) { + super.setParameter( name, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(String name, OffsetDateTime value, TemporalType temporalType) { + super.setParameter( name, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(int position, Instant value, TemporalType temporalType) { + super.setParameter( position, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(int position, LocalDateTime value, TemporalType temporalType) { + super.setParameter( position, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(int position, ZonedDateTime value, TemporalType temporalType) { + super.setParameter( position, value, temporalType ); + return this; + } + + @Override + public NativeQueryImplementor setParameter(int position, OffsetDateTime value, TemporalType temporalType) { + super.setParameter( position, value, temporalType ); + return this; + } + @Override public NativeQueryImplementor setParameterList(QueryParameter parameter, Collection values) { super.setParameterList( parameter, values ); @@ -723,4 +862,11 @@ public NativeQueryImplementor setHint(String hintName, Object value) { super.setHint( hintName, value ); return this; } + + @Override + protected void applyEntityGraphQueryHint(EntityGraphQueryHint hint) { + throw new HibernateException( + "A native SQL query cannot use EntityGraphs" + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java index c913ceceeb05..dd114d949de9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java @@ -6,17 +6,24 @@ */ package org.hibernate.query.internal; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; + import javax.persistence.Parameter; +import org.hibernate.QueryException; import org.hibernate.QueryParameterException; import org.hibernate.engine.query.spi.NamedParameterDescriptor; import org.hibernate.engine.query.spi.OrdinalParameterDescriptor; -import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.compare.ComparableComparator; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.type.Type; @@ -27,73 +34,69 @@ * @author Steve Ebersole */ public class ParameterMetadataImpl implements ParameterMetadata { - private static final OrdinalParameterDescriptor[] EMPTY_ORDINALS = new OrdinalParameterDescriptor[0]; - - private final OrdinalParameterDescriptor[] ordinalDescriptors; + private final Map ordinalDescriptorMap; private final Map namedDescriptorMap; - private boolean isOrdinalParametersZeroBased = true; - - private ParameterMetadataImpl( - OrdinalParameterDescriptor[] ordinalDescriptors, - Map namedDescriptorMap, boolean isOrdinalParametersZeroBased) { - this.ordinalDescriptors = ordinalDescriptors; - this.namedDescriptorMap = namedDescriptorMap; - this.isOrdinalParametersZeroBased = isOrdinalParametersZeroBased; - } - /** - * Instantiates a ParameterMetadata container. - * - * @param ordinalDescriptors Descriptors of the ordinal parameters - * @param namedDescriptorMap Descriptors of the named parameters - */ public ParameterMetadataImpl( - OrdinalParameterDescriptor[] ordinalDescriptors, - Map namedDescriptorMap) { - if ( ordinalDescriptors == null ) { - this.ordinalDescriptors = EMPTY_ORDINALS; - } - else { - final OrdinalParameterDescriptor[] copy = new OrdinalParameterDescriptor[ ordinalDescriptors.length ]; - System.arraycopy( ordinalDescriptors, 0, copy, 0, ordinalDescriptors.length ); - this.ordinalDescriptors = copy; - } + Map ordinalDescriptorMap, + Map namedDescriptorMap) { + this.ordinalDescriptorMap = ordinalDescriptorMap == null + ? Collections.emptyMap() + : Collections.unmodifiableMap( ordinalDescriptorMap ); + this.namedDescriptorMap = namedDescriptorMap == null + ? Collections.emptyMap() + : Collections.unmodifiableMap( namedDescriptorMap ); + + if (ordinalDescriptorMap != null && ! ordinalDescriptorMap.isEmpty() ) { + final List sortedPositions = new ArrayList<>( ordinalDescriptorMap.keySet() ); + sortedPositions.sort( ComparableComparator.INSTANCE ); + + int lastPosition = -1; + for ( Integer sortedPosition : sortedPositions ) { + if ( lastPosition == -1 ) { + lastPosition = sortedPosition; + continue; + } + + if ( sortedPosition != lastPosition + 1 ) { + throw new QueryException( + String.format( + Locale.ROOT, + "Unexpected gap in ordinal parameter labels [%s -> %s] : [%s]", + lastPosition, + sortedPosition, + StringHelper.join( ",", sortedPositions.iterator() ) + ) + ); + } + + lastPosition = sortedPosition; + } - if ( namedDescriptorMap == null ) { - this.namedDescriptorMap = java.util.Collections.emptyMap(); - } - else { - final int size = (int) ( ( namedDescriptorMap.size() / .75 ) + 1 ); - final Map copy = new HashMap<>( size ); - copy.putAll( namedDescriptorMap ); - this.namedDescriptorMap = java.util.Collections.unmodifiableMap( copy ); } } @Override - @SuppressWarnings("unchecked") - public Set> collectAllParameters() { - if ( hasNamedParameters() || hasPositionalParameters() ) { - final HashSet allParameters = new HashSet(); - allParameters.addAll( namedDescriptorMap.values() ); - allParameters.addAll( ArrayHelper.toList( ordinalDescriptors ) ); - return allParameters; - } + public Collection getPositionalParameters() { + return Collections.unmodifiableCollection( ordinalDescriptorMap.values() ); + } - return Collections.emptySet(); + @Override + public Collection getNamedParameters() { + return Collections.unmodifiableCollection( namedDescriptorMap.values() ); } + @Override - @SuppressWarnings("unchecked") - public Set> collectAllParametersJpa() { - if ( hasNamedParameters() || hasPositionalParameters() ) { - final HashSet allParameters = new HashSet(); - allParameters.addAll( namedDescriptorMap.values() ); - allParameters.addAll( ArrayHelper.toList( ordinalDescriptors ) ); - return allParameters; - } + public int getParameterCount() { + return ordinalDescriptorMap.size() + namedDescriptorMap.size(); + } - return Collections.emptySet(); + @Override + @SuppressWarnings("SuspiciousMethodCalls") + public boolean containsReference(QueryParameter parameter) { + return ordinalDescriptorMap.containsValue( parameter ) + || namedDescriptorMap.containsValue( parameter ); } @Override @@ -112,7 +115,16 @@ public int getPositionalParameterCount() { } public int getOrdinalParameterCount() { - return ordinalDescriptors.length; + return ordinalDescriptorMap.size(); + } + + @Override + public Set getNamedParameterNames() { + return namedDescriptorMap.keySet(); + } + + public Set getOrdinalParameterLabels() { + return ordinalDescriptorMap.keySet(); } /** @@ -125,16 +137,18 @@ public int getOrdinalParameterCount() { * @throws QueryParameterException If the position is out of range */ public OrdinalParameterDescriptor getOrdinalParameterDescriptor(int position) { - if ( !isOrdinalParametersZeroBased ) { - position--; - } - if ( position < 0 || position >= ordinalDescriptors.length ) { - throw new QueryParameterException( - "Position beyond number of declared ordinal parameters. " + - "Remember that ordinal parameters are 0-based! Position: " + position + final OrdinalParameterDescriptor descriptor = ordinalDescriptorMap.get( position ); + if ( descriptor == null ) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Could not locate ordinal parameter [%s], expecting one of [%s]", + position, + StringHelper.join( ", ", ordinalDescriptorMap.keySet().iterator()) + ) ); } - return ordinalDescriptors[position]; + return descriptor; } /** @@ -159,25 +173,17 @@ public Type getOrdinalParameterExpectedType(int position) { * * @return The source location * - * @deprecated Use {@link OrdinalParameterDescriptor#getSourceLocation()} from the + * @deprecated Use {@link OrdinalParameterDescriptor#getPosition()} from the * {@link #getOrdinalParameterDescriptor} return instead */ @Deprecated public int getOrdinalParameterSourceLocation(int position) { - return getOrdinalParameterDescriptor( position ).getSourceLocation(); - } - - /** - * Access to the names of all named parameters - * - * @return The named parameter names - */ - public Set getNamedParameterNames() { - return namedDescriptorMap.keySet(); + return getOrdinalParameterDescriptor( position ).getPosition(); } @Override public QueryParameter getQueryParameter(String name) { + //noinspection unchecked return getNamedParameterDescriptor( name ); } @@ -193,14 +199,6 @@ public QueryParameter resolve(Parameter param) { return (QueryParameter) param; } - if ( param.getName() != null ) { - return getQueryParameter( param.getName() ); - } - - if ( param.getPosition() != null ) { - return getQueryParameter( param.getPosition() ); - } - throw new IllegalArgumentException( "Could not resolve javax.persistence.Parameter to org.hibernate.query.QueryParameter" ); } @@ -214,11 +212,32 @@ public QueryParameter resolve(Parameter param) { * @throws QueryParameterException If the name could not be resolved to a named parameter */ public NamedParameterDescriptor getNamedParameterDescriptor(String name) { - final NamedParameterDescriptor meta = namedDescriptorMap.get( name ); - if ( meta == null ) { - throw new QueryParameterException( "could not locate named parameter [" + name + "]" ); + final NamedParameterDescriptor descriptor = namedDescriptorMap.get( name ); + if ( descriptor == null ) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Could not locate named parameter [%s], expecting one of [%s]", + name, + String.join( ", ", namedDescriptorMap.keySet() ) + ) + ); + } + return descriptor; + } + + @Override + public void visitRegistrations(Consumer action) { + if ( hasPositionalParameters() ) { + for ( OrdinalParameterDescriptor descriptor : ordinalDescriptorMap.values() ) { + action.accept( descriptor ); + } + } + else if ( hasNamedParameters() ) { + for ( NamedParameterDescriptor descriptor : namedDescriptorMap.values() ) { + action.accept( descriptor ); + } } - return meta; } /** @@ -243,7 +262,7 @@ public Type getNamedParameterExpectedType(String name) { * * @return The type * - * @deprecated Use {@link NamedParameterDescriptor#getSourceLocations()} from the + * @deprecated Use {@link NamedParameterDescriptor#getPosition()} from the * {@link #getNamedParameterDescriptor} return instead */ @Deprecated @@ -252,20 +271,28 @@ public int[] getNamedParameterSourceLocations(String name) { } @Override - public boolean isOrdinalParametersZeroBased() { - return isOrdinalParametersZeroBased; + @SuppressWarnings("unchecked") + public Set> collectAllParameters() { + if ( hasNamedParameters() || hasPositionalParameters() ) { + final HashSet allParameters = new HashSet(); + allParameters.addAll( namedDescriptorMap.values() ); + allParameters.addAll( ordinalDescriptorMap.values() ); + return allParameters; + } + + return Collections.emptySet(); } @Override - public void setOrdinalParametersZeroBased(boolean isZeroBased) { - this.isOrdinalParametersZeroBased = isZeroBased; - } + @SuppressWarnings("unchecked") + public Set> collectAllParametersJpa() { + if ( hasNamedParameters() || hasPositionalParameters() ) { + final HashSet allParameters = new HashSet(); + allParameters.addAll( namedDescriptorMap.values() ); + allParameters.addAll( ordinalDescriptorMap.values() ); + return allParameters; + } - public ParameterMetadataImpl getOrdinalParametersZeroBasedCopy() { - return new ParameterMetadataImpl( - this.ordinalDescriptors, - this.namedDescriptorMap, - true - ); + return Collections.emptySet(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java index f084b241a54d..cda7388a8cad 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java @@ -9,6 +9,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.Query; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.type.Type; /** @@ -17,12 +18,24 @@ public class QueryImpl extends AbstractProducedQuery implements Query { private final String queryString; + private final QueryParameterBindingsImpl queryParameterBindings; + public QueryImpl( SharedSessionContractImplementor producer, ParameterMetadata parameterMetadata, String queryString) { super( producer, parameterMetadata ); this.queryString = queryString; + this.queryParameterBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + producer.getFactory(), + producer.isQueryParametersValidationEnabled() + ); + } + + @Override + protected QueryParameterBindings getQueryParameterBindings() { + return queryParameterBindings; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java index 42af5a7fdf1e..2e36931b22a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java @@ -10,6 +10,7 @@ import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindingTypeResolver; +import org.hibernate.query.spi.QueryParameterBindingValidator; import org.hibernate.type.Type; /** @@ -17,15 +18,20 @@ */ public class QueryParameterBindingImpl implements QueryParameterBinding { private final QueryParameterBindingTypeResolver typeResolver; + private final boolean isBindingValidationRequired; private boolean isBound; private Type bindType; private T bindValue; - public QueryParameterBindingImpl(Type type, QueryParameterBindingTypeResolver typeResolver) { + public QueryParameterBindingImpl( + Type type, + QueryParameterBindingTypeResolver typeResolver, + boolean isBindingValidationRequired) { this.bindType = type; this.typeResolver = typeResolver; + this.isBindingValidationRequired = isBindingValidationRequired; } @Override @@ -45,25 +51,50 @@ public Type getBindType() { @Override public void setBindValue(T value) { - this.isBound = true; - this.bindValue = value; - - if ( bindType == null ) { - this.bindType = typeResolver.resolveParameterBindType( value ); + if ( isBindingValidationRequired ) { + validate( value ); } + bindValue( value ); } @Override public void setBindValue(T value, Type clarifiedType) { - setBindValue( value ); + if ( isBindingValidationRequired ) { + validate( value, clarifiedType ); + } if ( clarifiedType != null ) { this.bindType = clarifiedType; } + bindValue( value ); } @Override public void setBindValue(T value, TemporalType clarifiedTemporalType) { - setBindValue( value ); + if ( isBindingValidationRequired ) { + validate( value, clarifiedTemporalType ); + } + bindValue( value ); this.bindType = BindingTypeHelper.INSTANCE.determineTypeForTemporalType( clarifiedTemporalType, bindType, value ); } + + private void bindValue(T value) { + this.isBound = true; + this.bindValue = value; + + if ( bindType == null ) { + this.bindType = typeResolver.resolveParameterBindType( value ); + } + } + + private void validate(T value) { + QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value ); + } + + private void validate(T value, Type clarifiedType) { + QueryParameterBindingValidator.INSTANCE.validate( clarifiedType, value ); + } + + private void validate(T value, TemporalType clarifiedTemporalType) { + QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, clarifiedTemporalType ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java index 4420de5a44dd..10132576161d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java @@ -7,13 +7,15 @@ package org.hibernate.query.internal; import java.util.Collection; -import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; +import javax.persistence.Parameter; import org.hibernate.HibernateException; import org.hibernate.Incubating; @@ -22,13 +24,16 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.query.spi.NamedParameterDescriptor; +import org.hibernate.engine.query.spi.OrdinalParameterDescriptor; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.TypedValue; -import org.hibernate.hql.internal.classic.ParserHelper; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.MathHelper; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.QueryParameterBinding; @@ -49,245 +54,165 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { private final SessionFactoryImplementor sessionFactory; private final ParameterMetadata parameterMetadata; + private final boolean queryParametersValidationEnabled; + + private final int ordinalParamValueOffset; + + private final int jdbcStyleOrdinalCountBase; private Map parameterBindingMap; private Map parameterListBindingMap; - private Map positionalParameterBindings; + private Set parametersConvertedToListBindings; - public static QueryParameterBindingsImpl from(ParameterMetadata parameterMetadata, - SessionFactoryImplementor sessionFactory) { + public static QueryParameterBindingsImpl from( + ParameterMetadata parameterMetadata, + SessionFactoryImplementor sessionFactory, + boolean queryParametersValidationEnabled) { if ( parameterMetadata == null ) { - return new QueryParameterBindingsImpl( sessionFactory, parameterMetadata ); - } - else { - return new QueryParameterBindingsImpl( sessionFactory, parameterMetadata.collectAllParameters(), parameterMetadata ); + throw new QueryParameterException( "Query parameter metadata cannot be null" ); } - } - public QueryParameterBindingsImpl(SessionFactoryImplementor sessionFactory, ParameterMetadata parameterMetadata) { - this( sessionFactory, Collections.emptySet(), parameterMetadata ); + return new QueryParameterBindingsImpl( + sessionFactory, + parameterMetadata, + queryParametersValidationEnabled + ); } - public QueryParameterBindingsImpl(SessionFactoryImplementor sessionFactory, - Set> queryParameters, - ParameterMetadata parameterMetadata) { + private QueryParameterBindingsImpl( + SessionFactoryImplementor sessionFactory, + ParameterMetadata parameterMetadata, + boolean queryParametersValidationEnabled) { this.sessionFactory = sessionFactory; this.parameterMetadata = parameterMetadata; - this.positionalParameterBindings = new TreeMap<>( ); + this.queryParametersValidationEnabled = queryParametersValidationEnabled; - if ( queryParameters == null || queryParameters.isEmpty() ) { - parameterBindingMap = Collections.emptyMap(); - } - else { - parameterBindingMap = new HashMap<>(); + this.parameterBindingMap = CollectionHelper.concurrentMap( parameterMetadata.getParameterCount() ); + + this.jdbcStyleOrdinalCountBase = sessionFactory.getSessionFactoryOptions().jdbcStyleParamsZeroBased() ? 0 : 1; - for ( QueryParameter queryParameter : queryParameters ) { + if ( parameterMetadata.hasPositionalParameters() ) { + int smallestOrdinalParamLabel = Integer.MAX_VALUE; + for ( QueryParameter queryParameter : parameterMetadata.getPositionalParameters() ) { if ( queryParameter.getPosition() == null ) { - // only cache the non-positional parameters in this map - // positional parameters will be bound dynamically with getBinding(int) - parameterBindingMap.put( queryParameter, makeBinding( queryParameter ) ); + throw new HibernateException( "Non-ordinal parameter ended up in ordinal param list" ); } - } - } - - parameterListBindingMap = new HashMap<>(); - } - - protected QueryParameterBinding makeBinding(QueryParameter queryParameter) { - return makeBinding( queryParameter.getType() ); - } - - protected QueryParameterBinding makeBinding(Type bindType) { - return new QueryParameterBindingImpl( bindType, sessionFactory ); - } - public boolean isBound(QueryParameter parameter) { - final QueryParameterBinding binding = locateBinding( parameter ); - if ( binding != null ) { - return binding.getBindValue() != null; + if ( queryParameter.getPosition() < smallestOrdinalParamLabel ) { + smallestOrdinalParamLabel = queryParameter.getPosition(); + } + } + ordinalParamValueOffset = smallestOrdinalParamLabel; } - - final QueryParameterListBinding listBinding = locateQueryParameterListBinding( parameter ); - if ( listBinding != null ) { - return listBinding.getBindValues() != null; + else { + ordinalParamValueOffset = 0; } - - return false; } - @SuppressWarnings("unchecked") - public QueryParameterBinding getBinding(QueryParameter parameter) { - final QueryParameterBinding binding = locateBinding( parameter ); + @SuppressWarnings("WeakerAccess") + protected QueryParameterBinding makeBinding(QueryParameter queryParameter) { + assert ! parameterBindingMap.containsKey( queryParameter ); - if ( binding == null ) { + if ( ! parameterMetadata.containsReference( queryParameter ) ) { throw new IllegalArgumentException( - "Could not resolve QueryParameter reference [" + parameter + "] to QueryParameterBinding" + "Cannot create binding for parameter reference [" + queryParameter + "] - reference is not a parameter of this query" ); } + final QueryParameterBinding binding = makeBinding( queryParameter.getHibernateType() ); + parameterBindingMap.put( queryParameter, binding ); + return binding; } - @SuppressWarnings("unchecked") - public QueryParameterBinding locateBinding(QueryParameter parameter) { - // see if this exact instance is known as a key - if ( parameterBindingMap.containsKey( parameter ) ) { - return parameterBindingMap.get( parameter ); - } - - // if the incoming parameter has a name, try to find it by name - if ( StringHelper.isNotEmpty( parameter.getName() ) ) { - final QueryParameterBinding binding = locateBinding( parameter.getName() ); - if ( binding != null ) { - return binding; - } - } - - // if the incoming parameter has a position, try to find it by position - if ( parameter.getPosition() != null ) { - final QueryParameterBinding binding = locateBinding( parameter.getPosition() ); - if ( binding != null ) { - return binding; - } - } - - return null; + @SuppressWarnings("WeakerAccess") + protected QueryParameterBinding makeBinding(Type bindType) { + return new QueryParameterBindingImpl( bindType, sessionFactory, shouldValidateBindingValue() ); } - protected QueryParameterBinding locateBinding(String name) { - for ( Map.Entry entry : parameterBindingMap.entrySet() ) { - if ( name.equals( entry.getKey().getName() ) ) { - return entry.getValue(); - } + @SuppressWarnings({"unchecked", "WeakerAccess"}) + protected QueryParameterListBinding makeListBinding(QueryParameter param) { + if ( parametersConvertedToListBindings == null ) { + parametersConvertedToListBindings = new HashSet<>(); } - return null; - } + parametersConvertedToListBindings.add( param ); - protected QueryParameterBinding locateAndRemoveBinding(String name) { - final Iterator> entryIterator = parameterBindingMap.entrySet().iterator(); - while ( entryIterator.hasNext() ) { - final Map.Entry entry = entryIterator.next(); - if ( name.equals( entry.getKey().getName() ) ) { - entryIterator.remove(); - return entry.getValue(); - } + if ( parameterListBindingMap == null ) { + parameterListBindingMap = new HashMap<>(); } - return null; + return parameterListBindingMap.computeIfAbsent( + param, + p -> new QueryParameterListBindingImpl( + param.getHibernateType(), + shouldValidateBindingValue() + ) + ); } - protected QueryParameterBinding locateBinding(int position) { - if ( position < positionalParameterBindings.size() ) { - return positionalParameterBindings.get( position ); - } + @Override + @SuppressWarnings( "unchecked" ) + public boolean isBound(QueryParameter parameter) { + final QueryParameterBinding binding = getBinding( parameter ); - return null; + return binding.isBound(); } - public QueryParameterBinding getBinding(String name) { - final QueryParameterBinding binding = locateBinding( name ); + @SuppressWarnings("unchecked") + public QueryParameterBinding getBinding(QueryParameter parameter) { + QueryParameterBinding binding = parameterBindingMap.get( parameter ); + if ( binding == null ) { - throw new IllegalArgumentException( "Unknown parameter name : " + name ); + if ( ! parameterMetadata.containsReference( parameter ) ) { + throw new IllegalArgumentException( + "Could not resolve QueryParameter reference [" + parameter + "] to QueryParameterBinding" + ); + } + + binding = makeBinding( parameter ); } return binding; } + @Override + @SuppressWarnings("unchecked") public QueryParameterBinding getBinding(int position) { - int positionAdjustment = 0; - if ( !parameterMetadata.isOrdinalParametersZeroBased() ) { - positionAdjustment = -1; - } - QueryParameterBinding binding = null; - if ( parameterMetadata != null ) { - if ( !parameterMetadata.hasPositionalParameters() ) { - // no positional parameters, assume jpa named. - binding = locateBinding( Integer.toString( position ) ); - } - else { - try { - binding = positionalParameterBindings.get( position + positionAdjustment ); - if ( binding == null ) { - binding = makeBinding( parameterMetadata.getQueryParameter( position ) ); - positionalParameterBindings.put( position + positionAdjustment, binding ); - } - } - catch (QueryParameterException e) { - // treat this as null binding - } - } - } - - if ( binding == null ) { - throw new IllegalArgumentException( "Unknown parameter position: " + position ); - } + return getBinding( parameterMetadata.getQueryParameter( position ) ); + } - return binding; + @Override + @SuppressWarnings("unchecked") + public QueryParameterBinding getBinding(String name) { + return getBinding( parameterMetadata.getQueryParameter( name ) ); } public void verifyParametersBound(boolean reserveFirstParameter) { - // verify named parameters bound - for ( Map.Entry bindEntry : parameterBindingMap.entrySet() ) { - if ( !bindEntry.getValue().isBound() ) { - if ( bindEntry.getKey().getName() != null ) { - throw new QueryException( "Named parameter [" + bindEntry.getKey().getName() + "] not set" ); - } - else { - throw new QueryException( "Parameter memento [" + bindEntry.getKey() + "] not set" ); - } - } - } - // verify position parameters bound - int startIndex = 0; - if ( !parameterMetadata.isOrdinalParametersZeroBased() ) { - startIndex = 1; - } - for ( int i = startIndex; i < positionalParameterBindings.size(); i++ ) { - QueryParameterBinding binding = null; - if ( parameterMetadata.isOrdinalParametersZeroBased() ) { - binding = positionalParameterBindings.get( i ); - } - else { - binding = positionalParameterBindings.get( i - 1 ); - } - if ( binding == null || !binding.isBound() ) { - throw new QueryException( "Positional parameter [" + i + "] not set" ); + for ( QueryParameter parameter : parameterMetadata.collectAllParameters() ) { + // check the "normal" bindings + if ( parameterBindingMap.containsKey( parameter ) ) { + continue; } - } - // verify position parameter count is correct - final int positionalValueSpan = calculatePositionalValueSpan( reserveFirstParameter ); - final int positionCounts = parameterMetadata.getPositionalParameterCount(); - if ( positionCounts != positionalValueSpan ) { - if ( reserveFirstParameter && positionCounts - 1 != positionalValueSpan ) { - throw new QueryException( - "Expected positional parameter count: " + - ( positionCounts - 1 ) + - ", actually detected " + positionalValueSpan - ); + + // next check the "list" bindings + if ( parameterListBindingMap != null + && parameterListBindingMap.containsKey( parameter ) ) { + continue; } - else if ( !reserveFirstParameter ) { - throw new QueryException( - "Expected positional parameter count: " + - ( positionCounts ) + - ", actually detected " + positionalValueSpan - ); + + if ( parametersConvertedToListBindings != null + && parametersConvertedToListBindings.contains( parameter ) ) { + continue; } - } - } - private int calculatePositionalValueSpan(boolean reserveFirstParameter) { - int positionalValueSpan = 0; - for ( QueryParameterBinding binding : positionalParameterBindings.values() ) { - if ( binding.isBound() ) { - Type bindType = binding.getBindType(); - if ( bindType == null ) { - bindType = SerializableType.INSTANCE; - } - positionalValueSpan += bindType.getColumnSpan( sessionFactory ); + if ( parameter.getName() != null ) { + throw new QueryException( "Named parameter not bound : " + parameter.getName() ); + } + else { + throw new QueryException( "Ordinal parameter not bound : " + parameter.getPosition() ); } } - return positionalValueSpan; } /** @@ -317,42 +242,87 @@ public Collection collectBindValues() { */ @Deprecated public Type[] collectPositionalBindTypes() { - Type[] types = new Type[ positionalParameterBindings.size() ]; - - // NOTE : bindings should be ordered by position by nature of a TreeMap... - // NOTE : we also assume the contiguity of the positions + return ArrayHelper.EMPTY_TYPE_ARRAY; +// if ( ! parameterMetadata.hasPositionalParameters() ) { +// return ArrayHelper.EMPTY_TYPE_ARRAY; +// } +// +// // callers expect these in ordinal order. In a way that is natural, but at the same +// // time long term a way to find types/values by name/position would be better +// +// final TreeMap sortedPositionalParamBindings = getSortedPositionalParamBindingMap(); +// final List types = CollectionHelper.arrayList( sortedPositionalParamBindings.size() ); +// +// for ( Map.Entry entry : sortedPositionalParamBindings.entrySet() ) { +// if ( entry.getKey().getPosition() == null ) { +// continue; +// } +// +// Type type = entry.getValue().getBindType(); +// if ( type == null ) { +// type = entry.getKey().getType(); +// } +// +// if ( type == null ) { +// log.debugf( +// "Binding for positional-parameter [%s] did not define type, using SerializableType", +// entry.getKey().getPosition() +// ); +// type = SerializableType.INSTANCE; +// } +// +// types.add( type ); +// } +// +// return types.toArray( new Type[ types.size() ] ); + } - for ( Map.Entry entry : positionalParameterBindings.entrySet() ) { - final int position = entry.getKey(); + private TreeMap getSortedPositionalParamBindingMap() { + final TreeMap map = new TreeMap<>( Comparator.comparing( Parameter::getPosition ) ); - Type type = entry.getValue().getBindType(); - if ( type == null ) { - log.debugf( "Binding for positional-parameter [%s] did not define type, using SerializableType", position ); - type = SerializableType.INSTANCE; + for ( Map.Entry entry : parameterBindingMap.entrySet() ) { + if ( entry.getKey().getPosition() == null ) { + continue; } - types[ position ] = type; + map.put( entry.getKey(), entry.getValue() ); } - return types; + return map; } + private static final Object[] EMPTY_VALUES = new Object[0]; + /** * @deprecated (since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 */ @Deprecated + @SuppressWarnings("unchecked") public Object[] collectPositionalBindValues() { - Object[] values = new Object[ positionalParameterBindings.size() ]; - - // NOTE : bindings should be ordered by position by nature of a TreeMap... - // NOTE : we also assume the contiguity of the positions + return EMPTY_VALUES; +// if ( ! parameterMetadata.hasPositionalParameters() ) { +// return EMPTY_VALUES; +// } +// +// final TreeMap sortedPositionalParamBindings = getSortedPositionalParamBindingMap(); +// final List values = CollectionHelper.arrayList( sortedPositionalParamBindings.size() ); +// +// for ( Map.Entry entry : sortedPositionalParamBindings.entrySet() ) { +// if ( entry.getKey().getPosition() == null ) { +// continue; +// } +// values.add( entry.getValue().getBindValue() ); +// } +// +// return values.toArray( new Object[values.size()] ); + } - for ( Map.Entry entry : positionalParameterBindings.entrySet() ) { - final int position = entry.getKey(); - values[ position ] = entry.getValue().getBindValue(); + @Override + public boolean isMultiValuedBinding(QueryParameter parameter) { + if ( parameterListBindingMap == null ) { + return false; } - - return values; + return parameterListBindingMap.containsKey( parameter ); } /** @@ -360,20 +330,25 @@ public Object[] collectPositionalBindValues() { */ @Deprecated public Map collectNamedParameterBindings() { - Map collectedBindings = new HashMap<>(); + final Map collectedBindings = new HashMap<>(); + for ( Map.Entry entry : parameterBindingMap.entrySet() ) { - if ( entry.getKey().getName() == null ) { - continue; + final String key; + if ( entry.getKey().getPosition() != null ) { + key = Integer.toString( entry.getKey().getPosition() ); + } + else { + key = entry.getKey().getName(); } Type bindType = entry.getValue().getBindType(); if ( bindType == null ) { - log.debugf( "Binding for named-parameter [%s] did not define type", entry.getKey().getName() ); + log.debugf( "Binding for parameter [%s] did not define type", key ); bindType = SerializableType.INSTANCE; } collectedBindings.put( - entry.getKey().getName(), + key, new TypedValue( bindType, entry.getValue().getBindValue() ) ); } @@ -391,11 +366,11 @@ public Map collectNamedParameterBindings() { @Deprecated @SuppressWarnings("unchecked") public QueryParameterListBinding getQueryParameterListBinding(QueryParameter queryParameter) { - QueryParameterListBinding result = parameterListBindingMap.get( queryParameter ); - if ( result == null ) { - result = transformQueryParameterBindingToQueryParameterListBinding( queryParameter ); + if ( parameterListBindingMap == null ) { + parameterListBindingMap = new HashMap<>(); } - return result; + + return transformQueryParameterBindingToQueryParameterListBinding( queryParameter ); } /** @@ -403,38 +378,49 @@ public QueryParameterListBinding getQueryParameterListBinding(QueryParame */ @Deprecated private QueryParameterListBinding locateQueryParameterListBinding(QueryParameter queryParameter) { - QueryParameterListBinding result = parameterListBindingMap.get( queryParameter ); + if ( parameterListBindingMap == null ) { + parameterListBindingMap = new HashMap<>(); + } - if ( result == null && queryParameter.getName() != null ) { - for ( Map.Entry entry : parameterListBindingMap.entrySet() ) { - if ( queryParameter.getName().equals( entry.getKey().getName() ) ) { - result = entry.getValue(); - break; - } + QueryParameterListBinding binding = parameterListBindingMap.get( queryParameter ); + if ( binding == null ) { + QueryParameter resolved = resolveParameter( queryParameter ); + if ( resolved != queryParameter ) { + binding = parameterListBindingMap.get( resolved ); } } - return result; + if ( binding == null ) { + throw new IllegalArgumentException( "Could not locate parameter list binding" ); + } + + return binding; + } + + private QueryParameter resolveParameter(QueryParameter queryParameter) { + if ( queryParameter.getName() != null ) { + return parameterMetadata.getQueryParameter( queryParameter.getName() ); + } + else { + return parameterMetadata.getQueryParameter( queryParameter.getPosition() ); + } } /** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */ @Deprecated + @SuppressWarnings("unchecked") private QueryParameterListBinding transformQueryParameterBindingToQueryParameterListBinding(QueryParameter queryParameter) { log.debugf( "Converting QueryParameterBinding to QueryParameterListBinding for given QueryParameter : %s", queryParameter ); - final QueryParameterBinding binding = getAndRemoveBinding( queryParameter ); - if ( binding == null ) { - throw new IllegalArgumentException( - "Could not locate QueryParameterBinding for given QueryParameter : " + queryParameter + - "; parameter list must be defined using named parameter" - ); - } - final QueryParameterListBinding convertedBinding = new QueryParameterListBindingImpl<>( binding.getBindType() ); - parameterListBindingMap.put( queryParameter, convertedBinding ); + getAndRemoveBinding( queryParameter ); - return convertedBinding; + return makeListBinding( queryParameter ); + } + + private boolean shouldValidateBindingValue() { + return sessionFactory.getSessionFactoryOptions().isJpaBootstrap() && queryParametersValidationEnabled; } /** @@ -443,25 +429,23 @@ private QueryParameterListBinding transformQueryParameterBindingToQueryPa @Deprecated @SuppressWarnings("unchecked") private QueryParameterBinding getAndRemoveBinding(QueryParameter parameter) { - // see if this exact instance is known as a key - if ( parameterBindingMap.containsKey( parameter ) ) { - return parameterBindingMap.remove( parameter ); - } + QueryParameterBinding binding = parameterBindingMap.remove( parameter ); - // if the incoming parameter has a name, try to find it by name - if ( StringHelper.isNotEmpty( parameter.getName() ) ) { - final QueryParameterBinding binding = locateAndRemoveBinding( parameter.getName() ); - if ( binding != null ) { - return binding; + if ( binding == null ) { + if ( parameter.getName() != null ) { + parameter = parameterMetadata.getQueryParameter( parameter.getName() ); + } + else { + parameter = parameterMetadata.getQueryParameter( parameter.getPosition() ); } - } - // NOTE : getAndRemoveBinding is only intended for usage from #transformQueryParameterBindingToQueryParameterListBinding - // which only supports named parameters, so there is no need to look into legacy positional parameters + if ( parameter == null ) { + throw new HibernateException( "Unable to resolve QueryParameter" ); + } + } + binding = parameterBindingMap.remove( parameter ); - throw new IllegalArgumentException( - "Could not resolve QueryParameter reference [" + parameter + "] to QueryParameterBinding" - ); + return binding; } /** @@ -481,21 +465,43 @@ public QueryParameterListBinding getQueryParameterListBinding(String name @Deprecated @SuppressWarnings("unchecked") private QueryParameter resolveQueryParameter(String name) { - for ( QueryParameter queryParameter : parameterListBindingMap.keySet() ) { - if ( name.equals( queryParameter.getName() ) ) { - return queryParameter; - } + final QueryParameter param = parameterMetadata.getQueryParameter( name ); + + if ( param == null ) { + throw new IllegalArgumentException( + "Unable to resolve given parameter name [" + name + "] to QueryParameter reference" + ); } - for ( QueryParameter queryParameter : parameterBindingMap.keySet() ) { - if ( name.equals( queryParameter.getName() ) ) { - return queryParameter; - } + return (QueryParameter) param; + } + + /** + * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 + */ + @Deprecated + @SuppressWarnings("unchecked") + public QueryParameterListBinding getQueryParameterListBinding(int name) { + // find the QueryParameter instance for the given name + final QueryParameter queryParameter = resolveQueryParameter( name ); + return getQueryParameterListBinding( queryParameter ); + } + + /** + * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 + */ + @Deprecated + @SuppressWarnings("unchecked") + private QueryParameter resolveQueryParameter(int name) { + final QueryParameter param = parameterMetadata.getQueryParameter( name ); + + if ( param == null ) { + throw new IllegalArgumentException( + "Unable to resolve given parameter name [" + name + "] to QueryParameter reference" + ); } - throw new IllegalArgumentException( - "Unable to resolve given parameter name [" + name + "] to QueryParameter reference" - ); + return (QueryParameter) param; } /** @@ -508,6 +514,10 @@ public String expandListValuedParameters(String queryString, SharedSessionContra return null; } + if ( parameterListBindingMap == null || parameterListBindingMap.isEmpty() ) { + return queryString; + } + // more-or-less... for each entry in parameterListBindingMap we will create an // entry in parameterBindingMap for each of the values in the bound value list. afterwards // we will clear the parameterListBindingMap. @@ -520,25 +530,47 @@ public String expandListValuedParameters(String queryString, SharedSessionContra final Dialect dialect = session.getFactory().getServiceRegistry().getService( JdbcServices.class ).getJdbcEnvironment().getDialect(); final int inExprLimit = dialect.getInExpressionCountLimit(); + int maxOrdinalPosition = getMaxOrdinalPosition(); + for ( Map.Entry entry : parameterListBindingMap.entrySet() ) { - final NamedParameterDescriptor sourceParam = (NamedParameterDescriptor) entry.getKey(); + final QueryParameter sourceParam = entry.getKey(); final Collection bindValues = entry.getValue().getBindValues(); - if ( inExprLimit > 0 && bindValues.size() > inExprLimit ) { - log.tooManyInExpressions( dialect.getClass().getName(), inExprLimit, sourceParam.getName(), bindValues.size() ); + int bindValueCount = bindValues.size(); + int bindValueMaxCount = bindValueCount; + + boolean inClauseParameterPaddingEnabled = + session.getFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() && + bindValueCount > 2; + + if ( inClauseParameterPaddingEnabled ) { + int bindValuePaddingCount = MathHelper.ceilingPowerOfTwo( bindValueCount ); + + if ( bindValueCount < bindValuePaddingCount && (inExprLimit == 0 || bindValuePaddingCount < inExprLimit) ) { + bindValueMaxCount = bindValuePaddingCount; + } + } + + if ( inExprLimit > 0 && bindValueCount > inExprLimit ) { + log.tooManyInExpressions( dialect.getClass().getName(), inExprLimit, sourceParam.getName(), bindValueCount ); + } + + final String sourceToken; + if ( sourceParam instanceof NamedParameterDescriptor ) { + sourceToken = ":" + NamedParameterDescriptor.class.cast( sourceParam ).getName(); + } + else { + sourceToken = "?" + OrdinalParameterDescriptor.class.cast( sourceParam ).getPosition(); } - final boolean isJpaPositionalParam = sourceParam.isJpaPositionalParameter(); - final String paramPrefix = isJpaPositionalParam ? "?" : ParserHelper.HQL_VARIABLE_PREFIX; - final String placeholder = paramPrefix + sourceParam.getName(); - final int loc = queryString.indexOf( placeholder ); + final int loc = StringHelper.indexOfIdentifierWord( queryString, sourceToken ); if ( loc < 0 ) { continue; } final String beforePlaceholder = queryString.substring( 0, loc ); - final String afterPlaceholder = queryString.substring( loc + placeholder.length() ); + final String afterPlaceholder = queryString.substring( loc + sourceToken.length() ); // check if placeholder is already immediately enclosed in parentheses // (ignoring whitespace) @@ -557,33 +589,66 @@ public String expandListValuedParameters(String queryString, SharedSessionContra StringBuilder expansionList = new StringBuilder(); - int i = 0; - for ( Object bindValue : entry.getValue().getBindValues() ) { - // for each value in the bound list-of-values we: - // 1) create a synthetic named parameter - // 2) expand the queryString to include each synthetic named param in place of the original - // 3) create a new synthetic binding for just that single value under the synthetic name - final String syntheticName = ( isJpaPositionalParam ? 'x' : "" ) + sourceParam.getName() + '_' + i; + Iterator bindValueIterator = entry.getValue().getBindValues().iterator(); + Object bindValue = null; + + for ( int i = 0; i < bindValueMaxCount; i++ ) { + + if ( i < bindValueCount ) { + bindValue = bindValueIterator.next(); + } + if ( i > 0 ) { expansionList.append( ", " ); } - expansionList.append( ParserHelper.HQL_VARIABLE_PREFIX ).append( syntheticName ); - final QueryParameter syntheticParam = new QueryParameterNamedImpl<>( - syntheticName, - sourceParam.getSourceLocations(), - sourceParam.isJpaPositionalParameter(), - sourceParam.getType() - ); + + final QueryParameter syntheticParam; + if ( sourceParam instanceof NamedParameterDescriptor ) { + // in the case of a named parameter, for each value in the bound list-of-values we: + // 1) create a synthetic named parameter + // 2) expand the queryString to include each synthetic named param in place of the original + // 3) create a new synthetic binding for just that single value under the synthetic name + + final String syntheticName = NamedParameterDescriptor.class.cast( sourceParam ).getName() + '_' + i; + expansionList.append( ":" ).append( syntheticName ); + + syntheticParam = new NamedParameterDescriptor( + syntheticName, + sourceParam.getHibernateType(), + sourceParam.getSourceLocations() + ); + } + else { + // in the case of an ordinal parameter, for each value in the bound list-of-values we: + // 1) create a new ordinal parameter at a synthetic position of maxOrdinalPosition + 1 + // 2) expand the queryString to include each new ordinal param in place of the original + // 3) create a new ordinal binding for just that single value under the synthetic position + // for the first item, we reuse the original parameter to avoid gaps in the positions + if ( i == 0 ) { + syntheticParam = sourceParam; + } + else { + int syntheticPosition = ++maxOrdinalPosition; + syntheticParam = new OrdinalParameterDescriptor( + syntheticPosition, + syntheticPosition - jdbcStyleOrdinalCountBase, + sourceParam.getHibernateType(), + sourceParam.getSourceLocations() + ); + } + + expansionList.append( "?" ).append( syntheticParam.getPosition() ); + } + final QueryParameterBinding syntheticBinding = makeBinding( entry.getValue().getBindType() ); syntheticBinding.setBindValue( bindValue ); parameterBindingMap.put( syntheticParam, syntheticBinding ); - i++; } queryString = StringHelper.replace( beforePlaceholder, afterPlaceholder, - placeholder, + sourceToken, expansionList.toString(), true, true @@ -592,4 +657,19 @@ public String expandListValuedParameters(String queryString, SharedSessionContra return queryString; } + + private int getMaxOrdinalPosition() { + int maxOrdinalPosition = 0; + for ( QueryParameter queryParameter : parameterBindingMap.keySet() ) { + if ( queryParameter instanceof OrdinalParameterDescriptor ) { + maxOrdinalPosition = Math.max( maxOrdinalPosition, queryParameter.getPosition() ); + } + } + for ( QueryParameter queryParameter : parameterListBindingMap.keySet() ) { + if ( queryParameter instanceof OrdinalParameterDescriptor ) { + maxOrdinalPosition = Math.max( maxOrdinalPosition, queryParameter.getPosition() ); + } + } + return maxOrdinalPosition; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java index f4e47e3a824d..8ffa92f60145 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java @@ -18,17 +18,21 @@ * @author Steve Ebersole */ public abstract class QueryParameterImpl implements QueryParameter { - private final Type expectedType; + private Type expectedType; public QueryParameterImpl(Type expectedType) { this.expectedType = expectedType; } @Override - public Type getType() { + public Type getHibernateType() { return expectedType; } + public void setHibernateType(Type expectedType) { + this.expectedType = expectedType; + } + @Override public Class getParameterType() { return expectedType == null ? null : expectedType.getReturnedClass(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterListBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterListBindingImpl.java index 47b6f8b2fea6..e74c81d8bb10 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterListBindingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterListBindingImpl.java @@ -9,6 +9,7 @@ import java.util.Collection; import javax.persistence.TemporalType; +import org.hibernate.query.spi.QueryParameterBindingValidator; import org.hibernate.query.spi.QueryParameterListBinding; import org.hibernate.type.Type; @@ -16,30 +17,39 @@ * @author Steve Ebersole */ public class QueryParameterListBindingImpl implements QueryParameterListBinding { + private final boolean isBindingValidationRequired; + private Collection bindValues; private Type bindType; - public QueryParameterListBindingImpl(Type type) { + public QueryParameterListBindingImpl(Type type, boolean isBindingValidationRequired) { this.bindType = type; + this.isBindingValidationRequired = isBindingValidationRequired; } @Override public void setBindValues(Collection bindValues) { - if ( bindValues == null ) { - throw new IllegalArgumentException( "Collection must be not null!" ); + if ( isBindingValidationRequired ) { + validate( bindValues ); } - this.bindValues = bindValues; + bindValue( bindValues ); } @Override public void setBindValues(Collection values, Type clarifiedType) { - setBindValues( values ); + if ( isBindingValidationRequired ) { + validate( bindValues, clarifiedType ); + } + bindValue( values ); this.bindType = clarifiedType; } @Override public void setBindValues(Collection values, TemporalType clarifiedTemporalType) { - setBindValues( values ); + if ( isBindingValidationRequired ) { + validate( values, clarifiedTemporalType ); + } + bindValue( values ); final Object anElement = values.isEmpty() ? null : values.iterator().next(); this.bindType = BindingTypeHelper.INSTANCE.determineTypeForTemporalType( clarifiedTemporalType, bindType, anElement ); } @@ -53,4 +63,23 @@ public Collection getBindValues() { public Type getBindType() { return bindType; } + + private void bindValue(Collection bindValues) { + if ( bindValues == null ) { + throw new IllegalArgumentException( "Collection must be not null!" ); + } + this.bindValues = bindValues; + } + + private void validate(Collection value) { + QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value ); + } + + private void validate(Collection value, Type clarifiedType) { + QueryParameterBindingValidator.INSTANCE.validate( clarifiedType, value ); + } + + private void validate(Collection value, TemporalType clarifiedTemporalType) { + QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, clarifiedTemporalType ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java index f31b6e51aed0..55731593e973 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java @@ -22,13 +22,11 @@ public class QueryParameterNamedImpl extends QueryParameterImpl implements QueryParameter { private final String name; private final int[] sourceLocations; - private final boolean jpaStyle; - public QueryParameterNamedImpl(String name, int[] sourceLocations, boolean jpaStyle, Type expectedType) { + public QueryParameterNamedImpl(String name, int[] sourceLocations, Type expectedType) { super( expectedType ); this.name = name; this.sourceLocations = sourceLocations; - this.jpaStyle = jpaStyle; } @Override @@ -41,15 +39,11 @@ public Integer getPosition() { return null; } + @Override public int[] getSourceLocations() { return sourceLocations; } - @Override - public boolean isJpaPositionalParameter() { - return jpaStyle; - } - @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java index e97f90efed7a..de10e2c7c3da 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java @@ -36,11 +36,12 @@ public boolean hasNext() { @Override @SuppressWarnings("unchecked") public T next() { - if ( scrollableResults.getNumberOfTypes() == 1 ) { - return (T) scrollableResults.get()[0]; + Object[] next = scrollableResults.get(); + if ( next.length == 1 ) { + return (T) next[0]; } else { - return (T) scrollableResults.get(); + return (T) next; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java index 050608d0ab38..0bd9c4a2fde3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java @@ -8,12 +8,16 @@ import javax.persistence.ParameterMode; +import org.hibernate.Incubating; import org.hibernate.procedure.spi.ParameterRegistrationImplementor; import org.hibernate.query.QueryParameter; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ +@Incubating public interface ProcedureParameter extends QueryParameter { /** * Retrieves the parameter "mode". Only really pertinent in regards to procedure/function calls. diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java new file mode 100644 index 000000000000..ee317ecb8220 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java @@ -0,0 +1,135 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.procedure.internal; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.ParameterMode; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.procedure.ParameterBind; +import org.hibernate.procedure.internal.ParameterBindImpl; +import org.hibernate.procedure.internal.ProcedureCallImpl; +import org.hibernate.query.QueryParameter; +import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.spi.QueryParameterListBinding; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class ProcedureParamBindings implements QueryParameterBindings { + private final ProcedureParameterMetadata parameterMetadata; + private final ProcedureCallImpl procedureCall; + + private final Map bindingMap = new HashMap<>(); + + public ProcedureParamBindings( + ProcedureParameterMetadata parameterMetadata, + ProcedureCallImpl procedureCall) { + this.parameterMetadata = parameterMetadata; + this.procedureCall = procedureCall; + } + + public ProcedureParameterMetadata getParameterMetadata() { + return parameterMetadata; + } + + public ProcedureCallImpl getProcedureCall() { + return procedureCall; + } + + @Override + public boolean isBound(QueryParameter parameter) { + return getBinding( parameter ).isBound(); + } + + @Override + public QueryParameterBinding getBinding(QueryParameter parameter) { + final ProcedureParameterImplementor procParam = parameterMetadata.resolve( parameter ); + ParameterBind binding = bindingMap.get( procParam ); + + if ( binding == null ) { + if ( ! parameterMetadata.containsReference( parameter ) ) { + throw new IllegalArgumentException( "Passed parameter is not registered with this query" ); + } + + binding = new ParameterBindImpl( procParam, this ); + bindingMap.put( procParam, binding ); + } + + return binding; + } + + @Override + public QueryParameterBinding getBinding(String name) { + return getBinding( parameterMetadata.getQueryParameter( name ) ); + } + + @Override + public QueryParameterBinding getBinding(int position) { + return getBinding( parameterMetadata.getQueryParameter( position ) ); + } + + @Override + public void verifyParametersBound(boolean callable) { + parameterMetadata.visitRegistrations( + queryParameter -> { + final ProcedureParameterImplementor procParam = (ProcedureParameterImplementor) queryParameter; + if ( procParam.getMode() == ParameterMode.IN + || procParam.getMode() == ParameterMode.INOUT ) { + if ( !getBinding( procParam ).isBound() ) { + // depending on "pass nulls" this might be ok... + // for now, just log a warning + } + } + } + ); + } + + @Override + public String expandListValuedParameters(String queryString, SharedSessionContractImplementor producer) { + return queryString; + } + + @Override + public QueryParameterListBinding getQueryParameterListBinding(QueryParameter parameter) { + return null; + } + + @Override + public QueryParameterListBinding getQueryParameterListBinding(String name) { + return null; + } + + @Override + public QueryParameterListBinding getQueryParameterListBinding(int position) { + return null; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // I think these are not needed for proc call execution + + @Override + public Type[] collectPositionalBindTypes() { + return new Type[0]; + } + + @Override + public Object[] collectPositionalBindValues() { + return new Object[0]; + } + + @Override + public Map collectNamedParameterBindings() { + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterBindingImpl.java deleted file mode 100644 index 46ff69ba918a..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterBindingImpl.java +++ /dev/null @@ -1,55 +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.query.procedure.internal; - -import javax.persistence.TemporalType; - -import org.hibernate.query.procedure.spi.ProcedureParameterBindingImplementor; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; -import org.hibernate.type.Type; - -/** - * @author Steve Ebersole - */ -public class ProcedureParameterBindingImpl implements ProcedureParameterBindingImplementor { - private final ProcedureParameterImplementor parameter; - - public ProcedureParameterBindingImpl(ProcedureParameterImplementor parameter) { - this.parameter = parameter; - } - - @Override - public boolean isBound() { - return parameter.getNativeParameterRegistration().getBind() != null; - } - - @Override - public void setBindValue(T value) { - parameter.getNativeParameterRegistration().bindValue( value ); - } - - @Override - public void setBindValue(T value, Type clarifiedType) { - parameter.getNativeParameterRegistration().setHibernateType( clarifiedType ); - parameter.getNativeParameterRegistration().bindValue( value ); - } - - @Override - public void setBindValue(T value, TemporalType clarifiedTemporalType) { - parameter.getNativeParameterRegistration().bindValue( value, clarifiedTemporalType ); - } - - @Override - public T getBindValue() { - return parameter.getNativeParameterRegistration().getBind().getValue(); - } - - @Override - public Type getBindType() { - return parameter.getNativeParameterRegistration().getHibernateType(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterBindings.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterBindings.java deleted file mode 100644 index d90362ad40cd..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterBindings.java +++ /dev/null @@ -1,121 +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.query.procedure.internal; - -import java.util.HashMap; -import java.util.Map; - -import org.hibernate.internal.util.StringHelper; -import org.hibernate.query.QueryParameter; -import org.hibernate.query.procedure.spi.ProcedureParameterBindingImplementor; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; -import org.hibernate.query.spi.QueryParameterBinding; -import org.hibernate.query.spi.QueryParameterBindings; - -/** - * @author Steve Ebersole - */ -public class ProcedureParameterBindings implements QueryParameterBindings { - private Map parameterBindingMap; - - public void registerParameter(ProcedureParameterImplementor parameter) { - if ( parameterBindingMap == null ) { - parameterBindingMap = new HashMap<>(); - } - - parameterBindingMap.put( - parameter, - new ProcedureParameterBindingImpl<>( parameter ) - ); - } - - @Override - public boolean isBound(QueryParameter parameter) { - return false; - } - - @Override - public QueryParameterBinding getBinding(QueryParameter parameter) { - final QueryParameterBinding binding = locateBinding( parameter ); - - if ( binding == null ) { - throw new IllegalArgumentException( - "Could not resolve QueryParameter reference [" + parameter + "] to QueryParameterBinding" - ); - } - - return binding; - } - - @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) - private QueryParameterBinding locateBinding(QueryParameter parameter) { - // see if this exact instance is known as a key - if ( parameterBindingMap.containsKey( parameter ) ) { - return parameterBindingMap.get( parameter ); - } - - // if the incoming parameter has a name, try to find it by name - if ( StringHelper.isNotEmpty( parameter.getName() ) ) { - final QueryParameterBinding binding = locateBinding( parameter.getName() ); - if ( binding != null ) { - return binding; - } - } - - // if the incoming parameter has a position, try to find it by position - if ( parameter.getPosition() != null ) { - final QueryParameterBinding binding = locateBinding( parameter.getPosition() ); - if ( binding != null ) { - return binding; - } - } - - return null; - } - - private QueryParameterBinding locateBinding(String name) { - for ( Map.Entry entry : parameterBindingMap.entrySet() ) { - if ( name.equals( entry.getKey().getName() ) ) { - return entry.getValue(); - } - } - - return null; - } - - private QueryParameterBinding locateBinding(int position) { - for ( Map.Entry entry : parameterBindingMap.entrySet() ) { - if ( entry.getKey().getPosition() != null && position == entry.getKey().getPosition() ) { - return entry.getValue(); - } - } - - return null; - } - - @Override - @SuppressWarnings("unchecked") - public QueryParameterBinding getBinding(String name) { - final QueryParameterBinding binding = locateBinding( name ); - if ( binding == null ) { - throw new IllegalArgumentException( "Unknown parameter name : " + name ); - } - - return binding; - } - - @Override - @SuppressWarnings("unchecked") - public QueryParameterBinding getBinding(int position) { - final QueryParameterBinding binding = locateBinding( position ); - if ( binding == null ) { - throw new IllegalArgumentException( "Unknown parameter position : " + position ); - } - - return binding; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java index d5b378abce7e..63a4c0528d5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java @@ -6,55 +6,397 @@ */ package org.hibernate.query.procedure.internal; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Calendar; import javax.persistence.ParameterMode; +import javax.persistence.TemporalType; -import org.hibernate.procedure.spi.ParameterRegistrationImplementor; +import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; +import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.procedure.ParameterBind; +import org.hibernate.procedure.ParameterMisuseException; +import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.procedure.internal.ProcedureCallImpl; +import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.query.internal.QueryParameterImpl; import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.type.CalendarDateType; +import org.hibernate.type.CalendarTimeType; +import org.hibernate.type.CalendarType; +import org.hibernate.type.ProcedureParameterExtractionAware; +import org.hibernate.type.ProcedureParameterNamedBinder; +import org.hibernate.type.Type; + +import org.jboss.logging.Logger; /** * @author Steve Ebersole */ -public class ProcedureParameterImpl extends QueryParameterImpl implements ProcedureParameterImplementor { - private ParameterRegistrationImplementor nativeParamRegistration; +public class ProcedureParameterImpl + extends QueryParameterImpl + implements ProcedureParameterImplementor, ParameterRegistration { + private static final Logger log = Logger.getLogger( ProcedureParameterImpl.class ); + + private final ProcedureCallImpl procedureCall; + private final String name; + private final Integer position; + private final ParameterMode mode; + private final Class javaType; + + private int[] sqlTypes; + private boolean passNullsEnabled; + + // in-flight state needed between prepare and extract + private int startIndex; + + public ProcedureParameterImpl( + ProcedureCallImpl procedureCall, + String name, + ParameterMode mode, + Class javaType, + Type hibernateType, + boolean initialPassNullsSetting) { + super( hibernateType ); + this.procedureCall = procedureCall; + this.name = name; + this.position = null; + this.mode = mode; + this.javaType = javaType; + this.passNullsEnabled = initialPassNullsSetting; + + setHibernateType( hibernateType ); + } + + public ProcedureParameterImpl( + ProcedureCallImpl procedureCall, + Integer position, + ParameterMode mode, + Class javaType, + Type hibernateType, + boolean initialPassNullsSetting) { + super( hibernateType ); + this.procedureCall = procedureCall; + this.name = null; + this.position = position; + this.mode = mode; + this.javaType = javaType; + this.passNullsEnabled = initialPassNullsSetting; - public ProcedureParameterImpl(ParameterRegistrationImplementor nativeParamRegistration) { - super( nativeParamRegistration.getHibernateType() ); - this.nativeParamRegistration = nativeParamRegistration; + setHibernateType( hibernateType ); } @Override public ParameterMode getMode() { - return nativeParamRegistration.getMode(); + return mode; } @Override public boolean isPassNullsEnabled() { - return nativeParamRegistration.isPassNullsEnabled(); + return passNullsEnabled; } @Override public void enablePassingNulls(boolean enabled) { - nativeParamRegistration.enablePassingNulls( enabled ); + this.passNullsEnabled = enabled; } @Override - public boolean isJpaPositionalParameter() { - return false; + public int[] getSourceLocations() { + return new int[0]; } @Override public String getName() { - return nativeParamRegistration.getName(); + return name; } @Override public Integer getPosition() { - return nativeParamRegistration.getPosition(); + return position; + } + + @Override + public void setHibernateType(Type expectedType) { + super.setHibernateType( expectedType ); + + if ( mode == ParameterMode.REF_CURSOR ) { + sqlTypes = new int[] { Types.REF_CURSOR }; + } + else { + if ( expectedType == null ) { + throw new IllegalArgumentException( "Type cannot be null" ); + } + else { + sqlTypes = expectedType.sqlTypes( procedureCall.getSession().getFactory() ); + } + } + + } + + @Override + public Class getParameterType() { + return javaType; + } + + @Override + public ParameterBind getBind() { + return (ParameterBind) procedureCall.getQueryParameterBindings().getBinding( this ); + } + + @Override + @SuppressWarnings("unchecked") + public void bindValue(Object value) { + getBind().setBindValue( (T) value ); } @Override - public ParameterRegistrationImplementor getNativeParameterRegistration() { - return nativeParamRegistration; + @SuppressWarnings("unchecked") + public void bindValue(Object value, TemporalType explicitTemporalType) { + getBind().setBindValue( (T) value, explicitTemporalType ); + } + + @Override + public void prepare(CallableStatement statement, int startIndex) throws SQLException { + // initially set up the Type we will use for binding as the explicit type. + Type typeToUse = getHibernateType(); + int[] sqlTypesToUse = sqlTypes; + + final ParameterBind bind = getBind(); + + // however, for Calendar binding with an explicit TemporalType we may need to adjust this... + if ( bind != null && bind.getExplicitTemporalType() != null ) { + if ( Calendar.class.isInstance( bind.getValue() ) ) { + switch ( bind.getExplicitTemporalType() ) { + case TIMESTAMP: { + typeToUse = CalendarType.INSTANCE; + sqlTypesToUse = typeToUse.sqlTypes( procedureCall.getSession().getFactory() ); + break; + } + case DATE: { + typeToUse = CalendarDateType.INSTANCE; + sqlTypesToUse = typeToUse.sqlTypes( procedureCall.getSession().getFactory() ); + break; + } + case TIME: { + typeToUse = CalendarTimeType.INSTANCE; + sqlTypesToUse = typeToUse.sqlTypes( procedureCall.getSession().getFactory() ); + break; + } + } + } + } + + this.startIndex = startIndex; + if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( sqlTypesToUse.length > 1 ) { + // there is more than one column involved; see if the Hibernate Type can handle + // multi-param extraction... + final boolean canHandleMultiParamExtraction = + ProcedureParameterExtractionAware.class.isInstance( typeToUse ) + && ( (ProcedureParameterExtractionAware) typeToUse ).canDoExtraction(); + if ( ! canHandleMultiParamExtraction ) { + // it cannot... + throw new UnsupportedOperationException( + "Type [" + typeToUse + "] does support multi-parameter value extraction" + ); + } + } + // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). + // The idea is that an embeddable/custom type can have more than one column values + // that correspond with embeddable/custom attribute value. This does not seem to + // be working yet. For now, if sqlTypesToUse.length > 1, then register + // the out parameters by position (since we only have one name). + // This will cause a failure if there are other parameters bound by + // name and the dialect does not support "mixed" named/positional parameters; + // e.g., Oracle. + if ( sqlTypesToUse.length == 1 && + procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && + canDoNameParameterBinding( typeToUse ) ) { + statement.registerOutParameter( getName(), sqlTypesToUse[0] ); + } + else { + for ( int i = 0; i < sqlTypesToUse.length; i++ ) { + statement.registerOutParameter( startIndex + i, sqlTypesToUse[i] ); + } + } + } + + if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { + if ( bind == null || bind.getValue() == null ) { + // the user did not bind a value to the parameter being processed. This is the condition + // defined by `passNulls` and that value controls what happens here. If `passNulls` is + // {@code true} we will bind the NULL value into the statement; if `passNulls` is + // {@code false} we will not. + // + // Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure + // parameter defines a default value. Deferring to that information would be the best option + if ( isPassNullsEnabled() ) { + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", + procedureCall.getProcedureName(), + this + ); + if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { + ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( + statement, + null, + this.getName(), + procedureCall.getSession() + ); + } + else { + typeToUse.nullSafeSet( statement, null, startIndex, procedureCall.getSession() ); + } + } + else { + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value", + procedureCall.getProcedureName(), + this + ); + } + } + else { + if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { + ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( + statement, + bind.getValue(), + this.getName(), + procedureCall.getSession() + ); + } + else { + typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, procedureCall.getSession() ); + } + } + } + } + else { + // we have a REF_CURSOR type param + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { + procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, getName() ); + } + else { + procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, startIndex ); + } + } + } + + private boolean canDoNameParameterBinding(Type hibernateType) { + final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession() + .getJdbcCoordinator() + .getJdbcSessionOwner() + .getJdbcSessionContext() + .getServiceRegistry().getService( JdbcEnvironment.class ) + .getExtractedDatabaseMetaData(); + return + databaseMetaData.supportsNamedParameters() + && ProcedureParameterNamedBinder.class.isInstance( hibernateType ) + && ((ProcedureParameterNamedBinder) hibernateType).canDoSetting(); + } + + @Override + public int[] getSqlTypes() { + if ( mode == ParameterMode.REF_CURSOR ) { + // we could use the Types#REF_CURSOR added in Java 8, but that would require requiring Java 8... + throw new IllegalStateException( "REF_CURSOR parameters do not have a SQL/JDBC type" ); + } + + return determineHibernateType().sqlTypes( procedureCall.getSession().getFactory() ); + } + + private Type determineHibernateType() { + final ParameterBind bind = getBind(); + + // if the bind defines a type, that should be the most specific... + final Type bindType = bind.getBindType(); + if ( bindType != null ) { + return bindType; + } + + // Next, see if the parameter itself has an expected type, and if so use that... + final Type paramType = getHibernateType(); + if ( paramType != null ) { + return paramType; + } + + // here we just have guessing games + if ( bind.getValue() != null ) { + return procedureCall.getSession() + .getFactory() + .getTypeResolver() + .heuristicType( bind.getValue().getClass().getName() ); + } + + throw new IllegalStateException( "Unable to determine SQL type(s) - Hibernate Type not known" ); + } + + @Override + @SuppressWarnings("unchecked") + public T extract(CallableStatement statement) { + if ( mode == ParameterMode.IN ) { + throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); + } + try { + if ( mode == ParameterMode.REF_CURSOR ) { + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { + return (T) statement.getObject( name ); + } + else { + return (T) statement.getObject( startIndex ); + } + } + else { + final Type hibernateType = determineHibernateType(); + final int[] sqlTypes = hibernateType.sqlTypes( procedureCall.getSession().getFactory() ); + + // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). + // For now, if sqlTypes.length > 1 with a named parameter, then extract + // parameter values by position (since we only have one name). + final boolean useNamed = sqlTypes.length == 1 && + procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && + canDoNameParameterBinding( hibernateType ); + + + if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { + if ( useNamed ) { + return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( + statement, + new String[] { getName() }, + procedureCall.getSession() + ); + } + else { + return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( + statement, + startIndex, + procedureCall.getSession() + ); + } + } + else { + if ( useNamed ) { + return (T) statement.getObject( name ); + } + else { + return (T) statement.getObject( startIndex ); + } + } + } + } + catch (SQLException e) { + throw procedureCall.getSession().getFactory().getSQLExceptionHelper().convert( + e, + "Unable to extract OUT/INOUT parameter value" + ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java index eaf775a6226c..db949837d927 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java @@ -7,13 +7,19 @@ package org.hibernate.query.procedure.internal; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.persistence.Parameter; -import org.hibernate.QueryParameterException; +import org.hibernate.procedure.internal.ProcedureCallImpl; +import org.hibernate.procedure.spi.ParameterRegistrationImplementor; +import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.query.procedure.ProcedureParameter; @@ -23,40 +29,51 @@ * @author Steve Ebersole */ public class ProcedureParameterMetadata implements ParameterMetadata { - private List parameters; + private final ProcedureCallImpl procedureCall; + private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN; + private List parameters = new ArrayList<>(); - private boolean hasNamed; - private int ordinalParamCount; - - public ProcedureParameterMetadata() { - parameters = new ArrayList<>( ); + public ProcedureParameterMetadata(ProcedureCallImpl procedureCall) { + this.procedureCall = procedureCall; } public void registerParameter(ProcedureParameterImplementor parameter) { + if ( parameter.getName() != null ) { + if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { + throw new IllegalArgumentException( "Cannot mix named parameter with positional parameter registrations" ); + } + parameterStrategy = ParameterStrategy.NAMED; + } + else if ( parameter.getPosition() != null ) { + if ( parameterStrategy == ParameterStrategy.NAMED ) { + throw new IllegalArgumentException( "Cannot mix positional parameter with named parameter registrations" ); + } + this.parameterStrategy = ParameterStrategy.POSITIONAL; + } + else { + throw new IllegalArgumentException( "Unrecognized parameter type : " + parameter ); + } + + if ( parameters == null ) { parameters = new ArrayList<>(); } parameters.add( parameter ); - - this.hasNamed = hasNamed || parameter.getName() != null; - if ( parameter.getPosition() != null ) { - ordinalParamCount++; - } } @Override public boolean hasNamedParameters() { - return hasNamed; + return parameterStrategy == ParameterStrategy.NAMED; } @Override public boolean hasPositionalParameters() { - return ordinalParamCount > 0; + return parameterStrategy == ParameterStrategy.POSITIONAL; } @Override public Set> collectAllParameters() { - final Set> rtn = new HashSet<>(); + final Set> rtn = new LinkedHashSet<>(); for ( ProcedureParameter parameter : parameters ) { rtn.add( parameter ); } @@ -65,7 +82,7 @@ public Set> collectAllParameters() { @Override public Set> collectAllParametersJpa() { - final Set> rtn = new HashSet<>(); + final Set> rtn = new LinkedHashSet<>(); for ( ProcedureParameter parameter : parameters ) { rtn.add( parameter ); } @@ -74,7 +91,7 @@ public Set> collectAllParametersJpa() { @Override public Set getNamedParameterNames() { - if ( !hasNamed ) { + if ( !hasNamedParameters() ) { return Collections.emptySet(); } @@ -89,68 +106,85 @@ public Set getNamedParameterNames() { @Override public int getPositionalParameterCount() { - return ordinalParamCount; + return hasPositionalParameters() ? parameters.size() : 0; } @Override @SuppressWarnings("unchecked") - public QueryParameter getQueryParameter(String name) { + public ParameterRegistrationImplementor getQueryParameter(String name) { assert name != null; - QueryParameter result = null; - if ( hasNamed ) { - for ( ProcedureParameter parameter : parameters ) { + + if ( hasNamedParameters() ) { + for ( ParameterRegistrationImplementor parameter : parameters ) { if ( name.equals( parameter.getName() ) ) { - result = parameter; - break; + return parameter; } } } - if ( result != null ) { - return result; - } - throw new QueryParameterException( "could not locate named parameter [" + name + "]" ); + + throw new IllegalArgumentException( "Named parameter [" + name + "] is not registered with this procedure call" ); } @Override @SuppressWarnings("unchecked") - public QueryParameter getQueryParameter(Integer position) { + public ParameterRegistrationImplementor getQueryParameter(Integer position) { assert position != null; - if ( ordinalParamCount > 0 ) { - for ( ProcedureParameter parameter : parameters ) { + if ( hasPositionalParameters() ) { + for ( ParameterRegistrationImplementor parameter : parameters ) { if ( parameter.getPosition() != null && position.intValue() == parameter.getPosition() ) { return parameter; } } } - throw new QueryParameterException( "could not locate parameter at position [" + position + "]" ); + + throw new IllegalArgumentException( "Positional parameter [" + position + "] is not registered with this procedure call" ); } @Override @SuppressWarnings("unchecked") - public QueryParameter resolve(Parameter param) { - // first see if that instance exists here... - for ( ProcedureParameter parameter : parameters ) { - if ( parameter == param ) { - return parameter; + public ProcedureParameterImplementor resolve(Parameter param) { + if ( ProcedureParameterImplementor.class.isInstance( param ) ) { + for ( ProcedureParameterImplementor parameter : parameters ) { + if ( parameter == param ) { + return parameter; + } } } - // otherwise, try name/position from the incoming param - if ( param.getPosition() != null || param.getName() != null ) { - for ( ProcedureParameter parameter : parameters ) { - // name - if ( param.getName() != null && param.getName().equals( parameter.getName() ) ) { - return parameter; - } + throw new IllegalArgumentException( "Could not resolve javax.persistence.Parameter to org.hibernate.query.QueryParameter" ); + } - // position - if ( param.getPosition() != null && param.getPosition().equals( parameter.getPosition() ) ) { - return parameter; - } - } + @Override + public Collection getPositionalParameters() { + return parameters.stream().filter( p -> p.getPosition() != null ).collect( Collectors.toList() ); + } + + @Override + public Collection getNamedParameters() { + return parameters.stream().filter( p -> p.getPosition() == null ).collect( Collectors.toList() ); + } + + @Override + public int getParameterCount() { + return parameters.size(); + } + + @Override + @SuppressWarnings("SuspiciousMethodCalls") + public boolean containsReference(QueryParameter parameter) { + return parameters.contains( parameter ); + } + + public ParameterStrategy getParameterStrategy() { + return parameterStrategy; + } + + @Override + public void visitRegistrations(Consumer action) { + for ( ProcedureParameterImplementor parameter : parameters ) { + action.accept( parameter ); } - return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java index 3f11d5edf55e..a5558058ff8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java @@ -6,12 +6,15 @@ */ package org.hibernate.query.procedure.spi; +import org.hibernate.Incubating; import org.hibernate.procedure.spi.ParameterRegistrationImplementor; import org.hibernate.query.procedure.ProcedureParameter; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ -public interface ProcedureParameterImplementor extends ProcedureParameter { - ParameterRegistrationImplementor getNativeParameterRegistration(); +@Incubating +public interface ProcedureParameterImplementor extends ProcedureParameter, ParameterRegistrationImplementor { } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java new file mode 100644 index 000000000000..914c7e0d3310 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java @@ -0,0 +1,182 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.spi; + +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import javax.persistence.TemporalType; + +import org.hibernate.type.Type; + +/** + * @author Andrea Boriero + */ +public class QueryParameterBindingValidator { + + public static final QueryParameterBindingValidator INSTANCE = new QueryParameterBindingValidator(); + + private QueryParameterBindingValidator() { + } + + public

    void validate(Type paramType, Object bind) { + validate( paramType, bind, null ); + } + + public

    void validate(Type paramType, Object bind, TemporalType temporalType) { + if ( bind == null || paramType == null ) { + // nothing we can check + return; + } + final Class parameterType = paramType.getReturnedClass(); + if ( parameterType == null ) { + // nothing we can check + return; + } + + if ( Collection.class.isInstance( bind ) && !Collection.class.isAssignableFrom( parameterType ) ) { + // we have a collection passed in where we are expecting a non-collection. + // NOTE : this can happen in Hibernate's notion of "parameter list" binding + // NOTE2 : the case of a collection value and an expected collection (if that can even happen) + // will fall through to the main check. + validateCollectionValuedParameterBinding( parameterType, (Collection) bind, temporalType ); + } + else if ( bind.getClass().isArray() ) { + validateArrayValuedParameterBinding( parameterType, bind, temporalType ); + } + else { + if ( !isValidBindValue( parameterType, bind, temporalType ) ) { + throw new IllegalArgumentException( + String.format( + "Parameter value [%s] did not match expected type [%s (%s)]", + bind, + parameterType.getName(), + extractName( temporalType ) + ) + ); + } + } + } + + private String extractName(TemporalType temporalType) { + return temporalType == null ? "n/a" : temporalType.name(); + } + + private void validateCollectionValuedParameterBinding( + Class parameterType, + Collection value, + TemporalType temporalType) { + // validate the elements... + for ( Object element : value ) { + if ( !isValidBindValue( parameterType, element, temporalType ) ) { + throw new IllegalArgumentException( + String.format( + "Parameter value element [%s] did not match expected type [%s (%s)]", + element, + parameterType.getName(), + extractName( temporalType ) + ) + ); + } + } + } + + private static boolean isValidBindValue(Class expectedType, Object value, TemporalType temporalType) { + if ( expectedType.isPrimitive() ) { + if ( expectedType == boolean.class ) { + return Boolean.class.isInstance( value ); + } + else if ( expectedType == char.class ) { + return Character.class.isInstance( value ); + } + else if ( expectedType == byte.class ) { + return Byte.class.isInstance( value ); + } + else if ( expectedType == short.class ) { + return Short.class.isInstance( value ); + } + else if ( expectedType == int.class ) { + return Integer.class.isInstance( value ); + } + else if ( expectedType == long.class ) { + return Long.class.isInstance( value ); + } + else if ( expectedType == float.class ) { + return Float.class.isInstance( value ); + } + else if ( expectedType == double.class ) { + return Double.class.isInstance( value ); + } + return false; + } + else if ( value == null) { + return true; + } + else if ( expectedType.isInstance( value ) ) { + return true; + } + else if ( temporalType != null ) { + final boolean parameterDeclarationIsTemporal = Date.class.isAssignableFrom( expectedType ) + || Calendar.class.isAssignableFrom( expectedType ); + final boolean bindIsTemporal = Date.class.isInstance( value ) + || Calendar.class.isInstance( value ); + + if ( parameterDeclarationIsTemporal && bindIsTemporal ) { + return true; + } + } + + return false; + } + + private void validateArrayValuedParameterBinding( + Class parameterType, + Object value, + TemporalType temporalType) { + if ( !parameterType.isArray() ) { + throw new IllegalArgumentException( + String.format( + "Encountered array-valued parameter binding, but was expecting [%s (%s)]", + parameterType.getName(), + extractName( temporalType ) + ) + ); + } + + if ( value.getClass().getComponentType().isPrimitive() ) { + // we have a primitive array. we validate that the actual array has the component type (type of elements) + // we expect based on the component type of the parameter specification + if ( !parameterType.getComponentType().isAssignableFrom( value.getClass().getComponentType() ) ) { + throw new IllegalArgumentException( + String.format( + "Primitive array-valued parameter bind value type [%s] did not match expected type [%s (%s)]", + value.getClass().getComponentType().getName(), + parameterType.getName(), + extractName( temporalType ) + ) + ); + } + } + else { + // we have an object array. Here we loop over the array and physically check each element against + // the type we expect based on the component type of the parameter specification + final Object[] array = (Object[]) value; + for ( Object element : array ) { + if ( !isValidBindValue( parameterType.getComponentType(), element, temporalType ) ) { + throw new IllegalArgumentException( + String.format( + "Array-valued parameter value element [%s] did not match expected type [%s (%s)]", + element, + parameterType.getName(), + extractName( temporalType ) + ) + ); + } + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java index 78d89ef5d6cb..24667470fa1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java @@ -6,8 +6,13 @@ */ package org.hibernate.query.spi; +import java.util.Map; + import org.hibernate.Incubating; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; import org.hibernate.query.QueryParameter; +import org.hibernate.type.Type; /** * @author Steve Ebersole @@ -19,4 +24,23 @@ public interface QueryParameterBindings { QueryParameterBinding getBinding(QueryParameter parameter); QueryParameterBinding getBinding(String name); QueryParameterBinding getBinding(int position); + + void verifyParametersBound(boolean callable); + String expandListValuedParameters(String queryString, SharedSessionContractImplementor producer); + + QueryParameterListBinding getQueryParameterListBinding(QueryParameter parameter); + QueryParameterListBinding getQueryParameterListBinding(String name); + QueryParameterListBinding getQueryParameterListBinding(int position); + + Type[] collectPositionalBindTypes(); + Object[] collectPositionalBindValues(); + Map collectNamedParameterBindings(); + + /** + * @deprecated expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 + */ + @Deprecated + default boolean isMultiValuedBinding(QueryParameter parameter) { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBasedBeanContainer.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBasedBeanContainer.java new file mode 100644 index 000000000000..dae03cdd134b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBasedBeanContainer.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.resource.beans.container.internal; + +import javax.enterprise.inject.spi.BeanManager; + +import org.hibernate.resource.beans.container.spi.BeanContainer; + +/** + * @author Steve Ebersole + */ +public interface CdiBasedBeanContainer extends BeanContainer { + BeanManager getUsableBeanManager(); +} 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 new file mode 100644 index 000000000000..86f1decfdd6b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java @@ -0,0 +1,107 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.internal; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Locale; + +import org.hibernate.HibernateException; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.config.spi.StandardConverters; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.spi.ManagedBeanRegistryInitiator; +import org.hibernate.service.ServiceRegistry; + +/** + * Helper class for helping deal with the reflection calls relating to CDI + * in terms of building CDI-based {@link org.hibernate.resource.beans.container.spi.BeanContainer} + * instance + * + * We need to to avoid statically linking CDI classed into the ClassLoader which + * would lead to errors if CDI is not available on the classpath. + * + * @author Steve Ebersole + */ +public class CdiBeanContainerBuilder { + private static final String CONTAINER_FQN_IMMEDIATE = "org.hibernate.resource.beans.container.internal.CdiBeanContainerImmediateAccessImpl"; + private static final String CONTAINER_FQN_DELAYED = "org.hibernate.resource.beans.container.internal.CdiBeanContainerDelayedAccessImpl"; + private static final String CONTAINER_FQN_EXTENDED = "org.hibernate.resource.beans.container.internal.CdiBeanContainerExtendedAccessImpl"; + + private static final String BEAN_MANAGER_EXTENSION_FQN = "org.hibernate.jpa.event.spi.jpa.ExtendedBeanManager"; + + @SuppressWarnings("unchecked") + public static BeanContainer fromBeanManagerReference( + Object beanManagerRef, + ServiceRegistry serviceRegistry) { + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + final Class beanManagerClass = ManagedBeanRegistryInitiator.cdiBeanManagerClass( classLoaderService ); + final Class extendedBeanManagerClass = getHibernateClass( BEAN_MANAGER_EXTENSION_FQN ); + + final Class containerClass; + final Class ctorArgType; + + if ( extendedBeanManagerClass.isInstance( beanManagerRef ) ) { + containerClass = getHibernateClass( CONTAINER_FQN_EXTENDED ); + ctorArgType = extendedBeanManagerClass; + } + else { + ctorArgType = beanManagerClass; + + final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class ); + final boolean delayAccessToCdi = cfgService.getSetting( AvailableSettings.DELAY_CDI_ACCESS, StandardConverters.BOOLEAN, false ); + if ( delayAccessToCdi ) { + containerClass = getHibernateClass( CONTAINER_FQN_DELAYED ); + } + else { + containerClass = getHibernateClass( CONTAINER_FQN_IMMEDIATE ); + } + } + + try { + final Constructor ctor = containerClass.getDeclaredConstructor( ctorArgType ); + try { + ReflectHelper.ensureAccessibility( ctor ); + return ctor.newInstance( ctorArgType.cast( beanManagerRef ) ); + } + catch (InvocationTargetException e) { + throw new HibernateException( "Problem building " + containerClass.getName(), e.getCause() ); + } + catch (Exception e) { + throw new HibernateException( "Problem building " + containerClass.getName(), e ); + } + } + catch (NoSuchMethodException e) { + throw new HibernateException( + String.format( + Locale.ENGLISH, + "Could not locate proper %s constructor", + containerClass.getName() + ), + e + ); + } + } + + @SuppressWarnings("unchecked") + private static Class getHibernateClass(String fqn) { + // we use this Class's ClassLoader... + try { + return (Class) Class.forName( + fqn, + true, + CdiBeanContainerBuilder.class.getClassLoader() + ); + } + catch (ClassNotFoundException e) { + throw new HibernateException( "Unable to locate Hibernate class by name via reflection : " + fqn, e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerDelayedAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerDelayedAccessImpl.java new file mode 100644 index 000000000000..1b165abd738e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerDelayedAccessImpl.java @@ -0,0 +1,125 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.internal; + +import javax.enterprise.inject.spi.BeanManager; + +import org.hibernate.resource.beans.container.spi.AbstractCdiBeanContainer; +import org.hibernate.resource.beans.container.spi.BeanLifecycleStrategy; +import org.hibernate.resource.beans.container.spi.ContainedBeanImplementor; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class CdiBeanContainerDelayedAccessImpl extends AbstractCdiBeanContainer { + private final BeanManager beanManager; + + private CdiBeanContainerDelayedAccessImpl(BeanManager beanManager) { + this.beanManager = beanManager; + } + + @Override + public BeanManager getUsableBeanManager() { + return beanManager; + } + + @Override + protected ContainedBeanImplementor createBean( + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + return new BeanImpl<>( beanType, lifecycleStrategy, fallbackProducer ); + } + + @Override + protected ContainedBeanImplementor createBean( + String name, + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + return new NamedBeanImpl<>( name, beanType, lifecycleStrategy, fallbackProducer ); + } + + private class BeanImpl implements ContainedBeanImplementor { + private final Class beanType; + private final BeanLifecycleStrategy lifecycleStrategy; + private final BeanInstanceProducer fallbackProducer; + + private ContainedBeanImplementor delegateBean; + + private BeanImpl( + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + this.beanType = beanType; + this.lifecycleStrategy = lifecycleStrategy; + this.fallbackProducer = fallbackProducer; + } + + @Override + public void initialize() { + if ( delegateBean == null ) { + delegateBean = lifecycleStrategy.createBean( beanType, fallbackProducer, CdiBeanContainerDelayedAccessImpl.this ); + } + } + + @Override + public B getBeanInstance() { + if ( delegateBean == null ) { + initialize(); + } + return delegateBean.getBeanInstance(); + } + + @Override + public void release() { + delegateBean.release(); + } + } + + private class NamedBeanImpl implements ContainedBeanImplementor { + private final String name; + private final Class beanType; + private final BeanLifecycleStrategy lifecycleStrategy; + private final BeanInstanceProducer fallbackProducer; + + private ContainedBeanImplementor delegateBean; + + private NamedBeanImpl( + String name, + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + this.name = name; + this.beanType = beanType; + this.lifecycleStrategy = lifecycleStrategy; + this.fallbackProducer = fallbackProducer; + } + + @Override + public void initialize() { + if ( delegateBean == null ) { + delegateBean = lifecycleStrategy.createBean( name, beanType, fallbackProducer, CdiBeanContainerDelayedAccessImpl.this ); + } + } + + @Override + public B getBeanInstance() { + if ( delegateBean == null ) { + initialize(); + } + return delegateBean.getBeanInstance(); + } + + @Override + public void release() { + delegateBean.release(); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerExtendedAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerExtendedAccessImpl.java new file mode 100644 index 000000000000..c05f44d14dbf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerExtendedAccessImpl.java @@ -0,0 +1,207 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.internal; + +import javax.enterprise.inject.spi.BeanManager; + +import org.hibernate.jpa.event.spi.jpa.ExtendedBeanManager; +import org.hibernate.resource.beans.container.spi.AbstractCdiBeanContainer; +import org.hibernate.resource.beans.container.spi.BeanLifecycleStrategy; +import org.hibernate.resource.beans.container.spi.ContainedBean; +import org.hibernate.resource.beans.container.spi.ContainedBeanImplementor; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class CdiBeanContainerExtendedAccessImpl + extends AbstractCdiBeanContainer + implements ExtendedBeanManager.LifecycleListener { + + // NOTE : we continue to use the deprecated form for now since that is what WildFly needs for the time being still + + private static final Logger log = Logger.getLogger( CdiBeanContainerExtendedAccessImpl.class ); + + private BeanManager usableBeanManager; + + private CdiBeanContainerExtendedAccessImpl(ExtendedBeanManager beanManager) { + beanManager.registerLifecycleListener( this ); + log.debugf( "Extended access requested to CDI BeanManager : " + beanManager ); + } + + @Override + protected ContainedBeanImplementor createBean( + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + if ( usableBeanManager == null ) { + return new BeanImpl<>( beanType, lifecycleStrategy, fallbackProducer ); + } + else { + return lifecycleStrategy.createBean( beanType, fallbackProducer, this ); + } + } + + @Override + protected ContainedBeanImplementor createBean( + String name, + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + if ( usableBeanManager == null ) { + return new NamedBeanImpl<>( + name, + beanType, + lifecycleStrategy, + fallbackProducer + ); + } + else { + return lifecycleStrategy.createBean( name, beanType, fallbackProducer, this ); + } + } + + @Override + public void beanManagerInitialized(BeanManager beanManager) { + this.usableBeanManager = beanManager; + forEachBean( ContainedBeanImplementor::initialize ); + } + + @Override + public void beforeBeanManagerDestroyed(BeanManager beanManager) { + stop(); + this.usableBeanManager = null; + } + + @Override + public BeanManager getUsableBeanManager() { + if ( usableBeanManager == null ) { + throw new IllegalStateException( "ExtendedBeanManager.LifecycleListener callback not yet called: CDI not (yet) usable" ); + } + return usableBeanManager; + } + + private class BeanImpl implements ContainedBeanImplementor { + private final Class beanType; + private final BeanLifecycleStrategy lifecycleStrategy; + private final BeanInstanceProducer fallbackProducer; + + private ContainedBeanImplementor delegateContainedBean; + + private BeanImpl( + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + this.beanType = beanType; + this.lifecycleStrategy = lifecycleStrategy; + this.fallbackProducer = fallbackProducer; + } + + + @Override + public void initialize() { + if ( delegateContainedBean == null ) { + delegateContainedBean = lifecycleStrategy.createBean( beanType, fallbackProducer, DUMMY_BEAN_CONTAINER ); + } + delegateContainedBean.initialize(); + } + + @Override + public B getBeanInstance() { + if ( delegateContainedBean == null ) { + initialize(); + } + return delegateContainedBean.getBeanInstance(); + } + + @Override + public void release() { + delegateContainedBean.release(); + delegateContainedBean = null; + } + } + + private class NamedBeanImpl implements ContainedBeanImplementor { + private final String name; + private final Class beanType; + private final BeanLifecycleStrategy lifecycleStrategy; + private final BeanInstanceProducer fallbackProducer; + + private ContainedBeanImplementor delegateContainedBean; + + private NamedBeanImpl( + String name, + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + this.name = name; + this.beanType = beanType; + this.lifecycleStrategy = lifecycleStrategy; + this.fallbackProducer = fallbackProducer; + } + + @Override + public void initialize() { + if ( delegateContainedBean == null ) { + delegateContainedBean = lifecycleStrategy.createBean( + name, + beanType, + fallbackProducer, + DUMMY_BEAN_CONTAINER + ); + delegateContainedBean.initialize(); + } + } + + @Override + public B getBeanInstance() { + if ( delegateContainedBean == null ) { + initialize(); + } + return delegateContainedBean.getBeanInstance(); + } + + @Override + public void release() { + delegateContainedBean.release(); + delegateContainedBean = null; + } + } + + private final CdiBasedBeanContainer DUMMY_BEAN_CONTAINER = new CdiBasedBeanContainer() { + @Override + public BeanManager getUsableBeanManager() { + return usableBeanManager; + } + + @Override + public ContainedBean getBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + // todo (5.3) : should this throw an exception instead? + return CdiBeanContainerExtendedAccessImpl.this.getBean( beanType, lifecycleOptions, fallbackProducer ); + } + + @Override + public ContainedBean getBean( + String beanName, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + // todo (5.3) : should this throw an exception instead? + return CdiBeanContainerExtendedAccessImpl.this.getBean( beanName, beanType, lifecycleOptions, fallbackProducer ); + } + + @Override + public void stop() { + } + }; +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerImmediateAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerImmediateAccessImpl.java new file mode 100644 index 000000000000..6285784a7b19 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerImmediateAccessImpl.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.resource.beans.container.internal; + +import javax.enterprise.inject.spi.BeanManager; + +import org.hibernate.resource.beans.container.spi.AbstractCdiBeanContainer; +import org.hibernate.resource.beans.container.spi.BeanLifecycleStrategy; +import org.hibernate.resource.beans.container.spi.ContainedBeanImplementor; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class CdiBeanContainerImmediateAccessImpl extends AbstractCdiBeanContainer { + private static final Logger log = Logger.getLogger( CdiBeanContainerImmediateAccessImpl.class ); + + private final BeanManager beanManager; + + private CdiBeanContainerImmediateAccessImpl(BeanManager beanManager) { + log.debugf( "Standard access requested to CDI BeanManager : " + beanManager ); + this.beanManager = beanManager; + } + + @Override + public BeanManager getUsableBeanManager() { + return beanManager; + } + + @Override + protected ContainedBeanImplementor createBean( + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + final ContainedBeanImplementor bean = lifecycleStrategy.createBean( beanType, fallbackProducer, this ); + bean.initialize(); + return bean; + } + + @Override + protected ContainedBeanImplementor createBean( + String name, + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer) { + final ContainedBeanImplementor bean = lifecycleStrategy.createBean( + name, + beanType, + fallbackProducer, + this + ); + bean.initialize(); + return bean; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/ContainerManagedLifecycleStrategy.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/ContainerManagedLifecycleStrategy.java new file mode 100644 index 000000000000..38f4a3699583 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/ContainerManagedLifecycleStrategy.java @@ -0,0 +1,215 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.internal; + +import javax.enterprise.context.ContextNotActiveException; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.BeanManager; + +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.container.spi.BeanLifecycleStrategy; +import org.hibernate.resource.beans.container.spi.ContainedBeanImplementor; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +import org.jboss.logging.Logger; + +/** + * A {@link BeanLifecycleStrategy} to use when CDI compliance is required + * (i.e. when the bean lifecycle is to be managed by the CDI runtime, not the JPA runtime). + * + * The main characteristic of this strategy is that every create/destroy operation is delegated + * to the CDI runtime. + * + * In particular, @Singleton-scoped or @ApplicationScoped beans are retrieved from the CDI context, + * and are not duplicated, in contrast to {@link JpaCompliantLifecycleStrategy}. + */ +public class ContainerManagedLifecycleStrategy implements BeanLifecycleStrategy { + private static final Logger log = Logger.getLogger( ContainerManagedLifecycleStrategy.class ); + + public static final ContainerManagedLifecycleStrategy INSTANCE = new ContainerManagedLifecycleStrategy(); + + private ContainerManagedLifecycleStrategy() { + // private constructor, do not use + } + + + @Override + public ContainedBeanImplementor createBean( + Class beanClass, + BeanInstanceProducer fallbackProducer, + BeanContainer beanContainer) { + return new BeanImpl<>( beanClass, fallbackProducer, ( (CdiBasedBeanContainer) beanContainer ).getUsableBeanManager() ); + } + + @Override + public ContainedBeanImplementor createBean( + String beanName, + Class beanClass, + BeanInstanceProducer fallbackProducer, + BeanContainer beanContainer) { + return new NamedBeanImpl<>( beanName, beanClass, fallbackProducer, ( (CdiBasedBeanContainer) beanContainer ).getUsableBeanManager() ); + } + + + + private static abstract class AbstractBeanImpl implements ContainedBeanImplementor { + final Class beanType; + + BeanInstanceProducer fallbackProducer; + BeanManager beanManager; + + Instance instance; + B beanInstance; + + private AbstractBeanImpl( + Class beanType, + BeanInstanceProducer fallbackProducer, + BeanManager beanManager) { + this.beanType = beanType; + this.fallbackProducer = fallbackProducer; + this.beanManager = beanManager; + } + + @Override + public B getBeanInstance() { + if ( beanInstance == null ) { + initialize(); + } + return beanInstance; + } + + @Override + public void initialize() { + if ( beanInstance != null ) { + return; + } + + try { + this.instance = resolveContainerInstance(); + this.beanInstance = this.instance.get(); + } + catch (NotYetReadyException e) { + throw e; + } + catch (Exception e) { + log.debugf( "Error resolving CDI bean [%s] - using fallback" ); + this.beanInstance = produceFallbackInstance(); + this.instance = null; + } + + this.beanManager = null; + } + + protected abstract Instance resolveContainerInstance(); + + @Override + public void release() { + if ( beanInstance == null ) { + return; + } + + try { + if ( instance == null ) { + // todo : BeanInstanceProducer#release? + return; + } + + instance.destroy( beanInstance ); + } + catch (ContextNotActiveException e) { + log.debugf( + "Error destroying managed bean instance [%s] - the context is not active anymore." + + " The instance must have been destroyed already - ignoring.", + instance, + e + ); + } + finally { + beanInstance = null; + instance = null; + beanManager = null; + fallbackProducer = null; + } + } + + protected abstract B produceFallbackInstance(); + } + + private static class BeanImpl extends AbstractBeanImpl { + private BeanImpl( + Class beanType, + BeanInstanceProducer fallbackProducer, + BeanManager beanManager) { + super( beanType, fallbackProducer, beanManager ); + } + + @Override + @SuppressWarnings("unchecked") + protected Instance resolveContainerInstance() { + final Instance root; + try { + root = beanManager.createInstance(); + } + catch (Exception e) { + // this indicates that the BeanManager is not yet ready to use, which + // should be consider an error + throw new NotYetReadyException( e ); + } + + try { + return root.select( beanType ); + } + catch (Exception e) { + throw new NoSuchBeanException( "Bean class not known to CDI : " + beanType.getName(), e ); + } + } + + @Override + protected B produceFallbackInstance() { + return fallbackProducer.produceBeanInstance( beanType ); + } + } + + private static class NamedBeanImpl extends AbstractBeanImpl { + private final String beanName; + + private NamedBeanImpl( + String beanName, + Class beanType, + BeanInstanceProducer fallbackProducer, + BeanManager beanManager) { + super( beanType, fallbackProducer, beanManager ); + this.beanName = beanName; + } + + @Override + @SuppressWarnings("unchecked") + protected Instance resolveContainerInstance() { + final Instance root; + try { + root = beanManager.createInstance(); + } + catch (Exception e) { + // this indicates that the BeanManager is not yet ready to use, which + // should be consider an error + throw new NotYetReadyException( e ); + } + + try { + return root.select( beanType, new NamedBeanQualifier( beanName ) ); + } + catch (Exception e) { + throw new NoSuchBeanException( "Bean class not known to CDI : " + beanType.getName(), e ); + } + } + + @Override + protected B produceFallbackInstance() { + return fallbackProducer.produceBeanInstance( beanName, beanType ); + } + } +} 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 new file mode 100644 index 000000000000..b848a5b9cc20 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.internal; + +import java.util.Set; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InjectionTarget; + +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.container.spi.BeanLifecycleStrategy; +import org.hibernate.resource.beans.container.spi.ContainedBeanImplementor; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +import org.jboss.logging.Logger; + +/** + * A {@link BeanLifecycleStrategy} to use when JPA compliance is required + * (i.e. when the bean lifecycle is to be managed by the JPA runtime, not the CDI runtime). + * + * The main characteristic of this strategy is that each requested bean is instantiated directly + * and guaranteed to not be shared in the CDI context. + * + * In particular, @Singleton-scoped or @ApplicationScoped beans are instantiated directly by this strategy, + * even if there is already an instance in the CDI context. + * This means singletons are not really singletons, but this seems to be the behavior required by + * the JPA 2.2 spec. + */ +public class JpaCompliantLifecycleStrategy implements BeanLifecycleStrategy { + private static final Logger log = Logger.getLogger( JpaCompliantLifecycleStrategy.class ); + + public static final JpaCompliantLifecycleStrategy INSTANCE = new JpaCompliantLifecycleStrategy(); + + private JpaCompliantLifecycleStrategy() { + // private constructor, do not use + } + + @Override + public ContainedBeanImplementor createBean( + Class beanClass, + BeanInstanceProducer fallbackProducer, + BeanContainer beanContainer) { + return new BeanImpl<>( + beanClass, + fallbackProducer, + ( (CdiBasedBeanContainer) beanContainer ).getUsableBeanManager() + ); + } + + @Override + public ContainedBeanImplementor createBean( + String beanName, + Class beanClass, + BeanInstanceProducer fallbackProducer, + BeanContainer beanContainer) { + return new NamedBeanImpl<>( + beanName, + beanClass, + fallbackProducer, + ( (CdiBasedBeanContainer) beanContainer ).getUsableBeanManager() + ); + } + + + + private static class BeanImpl implements ContainedBeanImplementor { + private final Class beanType; + + private BeanInstanceProducer fallbackProducer; + + private BeanManager beanManager; + private InjectionTarget injectionTarget; + private CreationalContext creationalContext; + + private B beanInstance; + + public BeanImpl( + Class beanType, + BeanInstanceProducer fallbackProducer, + BeanManager beanManager) { + this.beanType = beanType; + this.fallbackProducer = fallbackProducer; + this.beanManager = beanManager; + } + + @Override + public B getBeanInstance() { + if ( beanInstance == null ) { + initialize(); + } + + return beanInstance; + } + + @Override + public void initialize() { + if ( beanInstance != null ) { + return; + } + + final AnnotatedType annotatedType; + try { + annotatedType = beanManager.createAnnotatedType( beanType ); + } + catch (Exception e) { + throw new IllegalStateException( new NotYetReadyException( e ) ); + } + + try { + this.injectionTarget = beanManager.createInjectionTarget( annotatedType ); + this.creationalContext = beanManager.createCreationalContext( null ); + + this.beanInstance = this.injectionTarget.produce( creationalContext ); + injectionTarget.inject( beanInstance, creationalContext ); + + injectionTarget.postConstruct( beanInstance ); + } + catch (NotYetReadyException e) { + throw e; + } + catch (Exception e) { + log.debugf( "Error resolving CDI bean [%s] - using fallback" ); + this.beanInstance = fallbackProducer.produceBeanInstance( beanType ); + + try { + if ( this.creationalContext != null ) { + this.creationalContext.release(); + } + } + catch (Exception ignore) { + } + + this.creationalContext = null; + this.injectionTarget = null; + } + + this.beanManager = null; + } + + @Override + public void release() { + if ( beanInstance == null ) { + return; + } + + try { + if ( injectionTarget == null ) { + // todo : BeanInstanceProducer#release? + return; + } + injectionTarget.preDestroy( beanInstance ); + injectionTarget.dispose( beanInstance ); + this.creationalContext.release(); + } + catch (Exception ignore) { + + } + finally { + this.beanInstance = null; + this.creationalContext = null; + this.injectionTarget = null; + } + } + } + + + private class NamedBeanImpl implements ContainedBeanImplementor { + private final Class beanType; + private final String beanName; + + private BeanInstanceProducer fallbackProducer; + + private BeanManager beanManager; + private Bean bean; + private CreationalContext creationalContext; + + private B beanInstance; + + private NamedBeanImpl( + String beanName, + Class beanType, + BeanInstanceProducer fallbackProducer, + BeanManager beanManager) { + this.beanName = beanName; + this.beanType = beanType; + this.fallbackProducer = fallbackProducer; + this.beanManager = beanManager; + } + + @Override + public B getBeanInstance() { + if ( beanInstance == null ) { + initialize(); + } + return beanInstance; + } + + @Override + @SuppressWarnings("unchecked") + public void initialize() { + if ( beanInstance != null ) { + return; + } + + + try { + this.creationalContext = beanManager.createCreationalContext( null ); + } + catch (Exception e) { + throw new NotYetReadyException( e ); + } + + try { + Set> beans = beanManager.getBeans( beanType, new NamedBeanQualifier( beanName ) ); + this.bean = (Bean) beanManager.resolve( beans ); + this.beanInstance = bean.create( creationalContext ); + } + catch (Exception e) { + log.debugf( "Error resolving CDI bean [%s] - using fallback" ); + this.beanInstance = fallbackProducer.produceBeanInstance( beanName, beanType ); + + try { + if ( this.creationalContext != null ) { + this.creationalContext.release(); + } + } + catch (Exception ignore) { + } + + this.creationalContext = null; + this.bean = null; + } + } + + @Override + public void release() { + if ( beanInstance == null ) { + return; + } + + try { + if ( bean == null ) { + // todo : BeanInstanceProducer#release? + return; + } + bean.destroy( beanInstance, creationalContext ); + } + catch (Exception ignore) { + } + finally { + if ( creationalContext != null ) { + try { + creationalContext.release(); + } + catch (Exception ignore) { + } + } + + this.beanInstance = null; + this.creationalContext = null; + this.bean = null; + this.beanManager = null; + } + } + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NamedBeanQualifier.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NamedBeanQualifier.java new file mode 100644 index 000000000000..84cacc192e92 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NamedBeanQualifier.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.resource.beans.container.internal; + +import javax.inject.Named; + +/** + * Used to locate named CDI beans. + * + * @author Yoann Rodière + * @author Steve Ebersole + */ +public class NamedBeanQualifier extends javax.enterprise.util.AnnotationLiteral implements Named { + private final String name; + + NamedBeanQualifier(String name) { + this.name = name; + } + + @Override + public String value() { + return name; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NoSuchBeanException.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NoSuchBeanException.java new file mode 100644 index 000000000000..f7fab74213ab --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NoSuchBeanException.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.resource.beans.container.internal; + +import org.hibernate.HibernateException; + +/** + * Exception indicating that the given class is not known as a CDI bean - triggers + * fallback handling + * + * @author Steve Ebersole + */ +public class NoSuchBeanException extends HibernateException { + public NoSuchBeanException(Throwable cause) { + super( cause ); + } + + public NoSuchBeanException(String message, Throwable cause) { + super( message, cause ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NotYetReadyException.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NotYetReadyException.java new file mode 100644 index 000000000000..7867e957ff93 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/NotYetReadyException.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.resource.beans.container.internal; + +import org.hibernate.HibernateException; + +/** + * Exception indicating an attempt to access the CDI BeanManager before it is ready for use + * + * @author Steve Ebersole + */ +public class NotYetReadyException extends HibernateException { + public NotYetReadyException(Throwable cause) { + super( "CDI BeanManager not (yet) ready to use", cause ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/package-info.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/package-info.java new file mode 100644 index 000000000000..9319719cafa3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/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 + */ + +/** + * Package for integrating dependency-injection bean containers + */ +package org.hibernate.resource.beans.container; diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/AbstractCdiBeanContainer.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/AbstractCdiBeanContainer.java new file mode 100644 index 000000000000..8fc8b4f044d9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/AbstractCdiBeanContainer.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.spi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.hibernate.resource.beans.container.internal.CdiBasedBeanContainer; +import org.hibernate.resource.beans.container.internal.ContainerManagedLifecycleStrategy; +import org.hibernate.resource.beans.container.internal.JpaCompliantLifecycleStrategy; +import org.hibernate.resource.beans.internal.BeansMessageLogger; +import org.hibernate.resource.beans.internal.Helper; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractCdiBeanContainer implements CdiBasedBeanContainer { + private Map> beanCache = new HashMap<>(); + private List> registeredBeans = new ArrayList<>(); + + @Override + public ContainedBean getBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + if ( lifecycleOptions.canUseCachedReferences() ) { + return getCacheableBean( beanType, lifecycleOptions, fallbackProducer ); + } + else { + return createBean( beanType, lifecycleOptions, fallbackProducer ); + } + } + + @SuppressWarnings("unchecked") + private ContainedBean getCacheableBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + final String beanCacheKey = Helper.INSTANCE.determineBeanCacheKey( beanType ); + + final ContainedBeanImplementor existing = beanCache.get( beanCacheKey ); + if ( existing != null ) { + return existing; + } + + final ContainedBeanImplementor bean = createBean( beanType, lifecycleOptions, fallbackProducer ); + beanCache.put( beanCacheKey, bean ); + return bean; + } + + @SuppressWarnings("unchecked") + private ContainedBeanImplementor createBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + final ContainedBeanImplementor bean = createBean( + beanType, + lifecycleOptions.useJpaCompliantCreation() + ? JpaCompliantLifecycleStrategy.INSTANCE + : ContainerManagedLifecycleStrategy.INSTANCE, + fallbackProducer + ); + registeredBeans.add( bean ); + return bean; + } + + protected abstract ContainedBeanImplementor createBean( + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer); + + @Override + public ContainedBean getBean( + String beanName, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + if ( lifecycleOptions.canUseCachedReferences() ) { + return getCacheableBean( beanName, beanType, lifecycleOptions, fallbackProducer ); + } + else { + return createBean( beanName, beanType, lifecycleOptions, fallbackProducer ); + } + } + + @SuppressWarnings("unchecked") + private ContainedBeanImplementor getCacheableBean( + String beanName, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + final String beanCacheKey = Helper.INSTANCE.determineBeanCacheKey( beanName, beanType ); + + final ContainedBeanImplementor existing = beanCache.get( beanCacheKey ); + if ( existing != null ) { + return existing; + } + + final ContainedBeanImplementor bean = createBean( beanName, beanType, lifecycleOptions, fallbackProducer ); + beanCache.put( beanCacheKey, bean ); + return bean; + } + + @SuppressWarnings("unchecked") + private ContainedBeanImplementor createBean( + String beanName, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + final ContainedBeanImplementor bean = createBean( + beanName, + beanType, + lifecycleOptions.useJpaCompliantCreation() + ? JpaCompliantLifecycleStrategy.INSTANCE + : ContainerManagedLifecycleStrategy.INSTANCE, + fallbackProducer + ); + registeredBeans.add( bean ); + return bean; + } + + protected abstract ContainedBeanImplementor createBean( + String name, + Class beanType, + BeanLifecycleStrategy lifecycleStrategy, + BeanInstanceProducer fallbackProducer); + + + @SuppressWarnings("WeakerAccess") + protected final void forEachBean(Consumer> consumer) { + registeredBeans.forEach( consumer ); + } + + @Override + public final void stop() { + BeansMessageLogger.BEANS_LOGGER.stoppingBeanContainer( this ); + forEachBean( ContainedBeanImplementor::release ); + registeredBeans.clear(); + beanCache.clear(); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/BeanContainer.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/BeanContainer.java new file mode 100644 index 000000000000..0119033c9a92 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/BeanContainer.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.resource.beans.container.spi; + +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.service.spi.Stoppable; + +/** + * Represents a backend "bean container" - CDI, Spring, etc + * + * @author Steve Ebersole + */ +public interface BeanContainer extends Stoppable { + interface LifecycleOptions { + boolean canUseCachedReferences(); + boolean useJpaCompliantCreation(); + } + + ContainedBean getBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer); + + ContainedBean getBean( + String name, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/BeanLifecycleStrategy.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/BeanLifecycleStrategy.java new file mode 100644 index 000000000000..117e0b034600 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/BeanLifecycleStrategy.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.resource.beans.container.spi; + +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +/** + * Models how the lifecycle for a bean should be managed. + */ +public interface BeanLifecycleStrategy { + ContainedBeanImplementor createBean( + Class beanClass, + BeanInstanceProducer fallbackProducer, + BeanContainer beanContainer); + + ContainedBeanImplementor createBean( + String beanName, + Class beanClass, + BeanInstanceProducer fallbackProducer, + BeanContainer beanContainer); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ContainedBean.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ContainedBean.java new file mode 100644 index 000000000000..f4eb5cf1ee4a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ContainedBean.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.resource.beans.container.spi; + +/** + * @author Steve Ebersole + */ +public interface ContainedBean { + // todo (5.3) : can we combine ContainedBean and org.hibernate.resource.beans.spi.ManagedBean into the same thing? + /** + * Get the bean instance producer associated with this container-backed bean + */ + B getBeanInstance(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ContainedBeanImplementor.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ContainedBeanImplementor.java new file mode 100644 index 000000000000..d71944f2613f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ContainedBeanImplementor.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.spi; + +/** + * Release-able extension to ContainedBean. We make the split to make it + * clear that generally speaking the callers to BeanContainer should not perform + * the release + * + * @author Steve Ebersole + */ +public interface ContainedBeanImplementor extends ContainedBean { + /** + * Allow the container to force initialize the lifecycle-generated bean + */ + void initialize(); + + /** + * Release any resources + */ + void release(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ExtendedBeanManager.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ExtendedBeanManager.java new file mode 100644 index 000000000000..4c7b6d71cd91 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/ExtendedBeanManager.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.resource.beans.container.spi; + +import javax.enterprise.inject.spi.BeanManager; + +/** + * This contract and the nested LifecycleListener contract represent the changes + * we'd like to propose to the CDI spec. The idea being simply to allow contextual + * registration of BeanManager lifecycle callbacks + * + * @author Steve Ebersole + */ +public interface ExtendedBeanManager { + /** + * Register a BeanManager LifecycleListener + * + * @param lifecycleListener The listener to register + */ + void registerLifecycleListener(LifecycleListener lifecycleListener); + + /** + * Contract for things interested in receiving notifications of + * BeanManager lifecycle events. + *

    + * A "beanManagerDestroyed" notifications would probably also be generally + * useful, although we do not need it here and not sure WildFly can really + * tell us that reliably. + */ + interface LifecycleListener { + void beanManagerInitialized(BeanManager beanManager); + void beforeBeanManagerDestroyed(BeanManager beanManager); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/FallbackContainedBean.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/FallbackContainedBean.java new file mode 100644 index 000000000000..294a8ce84c78 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/spi/FallbackContainedBean.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.container.spi; + +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.resource.beans.spi.ManagedBean; + +/** + * @author Steve Ebersole + */ +public class FallbackContainedBean implements ContainedBean, ManagedBean { + private final Class beanType; + + private final B beanInstance; + + + public FallbackContainedBean(Class beanType, BeanInstanceProducer producer) { + this.beanType = beanType; + this.beanInstance = producer.produceBeanInstance( beanType ); + } + + public FallbackContainedBean(String beanName, Class beanType, BeanInstanceProducer producer) { + this.beanType = beanType; + this.beanInstance = producer.produceBeanInstance( beanName, beanType ); + } + + @Override + public Class getBeanClass() { + return beanType; + } + + @Override + public B getBeanInstance() { + return beanInstance; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/BeansMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/BeansMessageLogger.java new file mode 100644 index 000000000000..3647c462c23d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/BeansMessageLogger.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.resource.beans.internal; + +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; + +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.LogMessage; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; +import org.jboss.logging.annotations.ValidIdRange; + +import static org.jboss.logging.Logger.Level.INFO; +import static org.jboss.logging.Logger.Level.WARN; + +/** + * @author Steve Ebersole + */ +@MessageLogger( projectCode = "HHH" ) +@ValidIdRange( min = 10005001, max = 10010000 ) +public interface BeansMessageLogger { + /** + * *The* BeansMessageLogger instance + */ + BeansMessageLogger BEANS_LOGGER = Logger.getMessageLogger( + BeansMessageLogger.class, + "org.hibernate.orm.beans" + ); + + @LogMessage( level = WARN ) + @Message( + id = 10005001, + value = "An explicit CDI BeanManager reference [%s] was passed to Hibernate, " + + "but CDI is not available on the Hibernate ClassLoader. This is likely " + + "going to lead to exceptions later on in bootstrap" + ) + void beanManagerButCdiNotAvailable(Object cdiBeanManagerReference); + + @LogMessage( level = INFO ) + @Message( + id = 10005002, + value = "No explicit CDI BeanManager reference was passed to Hibernate, " + + "but CDI is available on the Hibernate ClassLoader." + ) + void noBeanManagerButCdiAvailable(); + + @LogMessage( level = INFO ) + @Message( + id = 10005003, + value = "Stopping ManagedBeanRegistry : %s" + ) + void stoppingManagedBeanRegistry(ManagedBeanRegistry registry); + + @LogMessage( level = INFO ) + @Message( + id = 10005004, + value = "Stopping BeanContainer : %s" + ) + void stoppingBeanContainer(BeanContainer beanContainer); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/FallbackBeanInstanceProducer.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/FallbackBeanInstanceProducer.java new file mode 100644 index 000000000000..076bad4c27c7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/FallbackBeanInstanceProducer.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.resource.beans.internal; + +import org.hibernate.InstantiationException; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +import org.jboss.logging.Logger; + +/** + * BeanInstanceProducer implementation based on direct instantiation + * + * In normal Hibernate use this is used when either: + * * there is no configured back-end container + * * the back-end container did not define a bean for this class + * + * @author Steve Ebersole + */ +public class FallbackBeanInstanceProducer implements BeanInstanceProducer { + private static final Logger log = Logger.getLogger( FallbackBeanInstanceProducer.class ); + + /** + * Singleton access + */ + public static final FallbackBeanInstanceProducer INSTANCE = new FallbackBeanInstanceProducer(); + + private FallbackBeanInstanceProducer() { + } + + @Override + public B produceBeanInstance(Class beanType) { + log.tracef( "Creating ManagedBean(%s) using direct instantiation", beanType.getName() ); + try { + return beanType.newInstance(); + } + catch (Exception e) { + throw new InstantiationException( "Could not instantiate managed bean directly", beanType, e ); + } + } + + @Override + public B produceBeanInstance(String name, Class beanType) { + return produceBeanInstance( beanType ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/Helper.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/Helper.java new file mode 100644 index 000000000000..2c5c19b79ddc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/Helper.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.resource.beans.internal; + +import org.hibernate.resource.beans.container.spi.BeanLifecycleStrategy; +import org.hibernate.resource.beans.container.internal.ContainerManagedLifecycleStrategy; +import org.hibernate.resource.beans.container.internal.JpaCompliantLifecycleStrategy; + +/** + * @author Steve Ebersole + */ +public class Helper { + /** + * Singleton access + */ + public static final Helper INSTANCE = new Helper(); + + private Helper() { + } + + public String determineBeanCacheKey(Class beanType) { + return beanType.getName(); + } + + public String determineBeanCacheKey(String name, Class beanType) { + return beanType.getName() + ':' + name; + } + + @SuppressWarnings("unused") + public BeanLifecycleStrategy getLifecycleStrategy(boolean shouldRegistryManageLifecycle) { + if ( shouldRegistryManageLifecycle ) { + return JpaCompliantLifecycleStrategy.INSTANCE; + } + else { + return ContainerManagedLifecycleStrategy.INSTANCE; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/ManagedBeanRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/ManagedBeanRegistryImpl.java new file mode 100644 index 000000000000..c90730721e10 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/internal/ManagedBeanRegistryImpl.java @@ -0,0 +1,142 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.internal; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.container.spi.ContainedBean; +import org.hibernate.resource.beans.container.spi.FallbackContainedBean; +import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.service.spi.Stoppable; + +/** + * Abstract support (template pattern) for ManagedBeanRegistry implementations + * + * @author Steve Ebersole + */ +public class ManagedBeanRegistryImpl implements ManagedBeanRegistry, BeanContainer.LifecycleOptions, Stoppable { + private Map> registrations = new HashMap<>(); + + private final BeanContainer beanContainer; + + public ManagedBeanRegistryImpl(BeanContainer beanContainer) { + this.beanContainer = beanContainer; + } + + @Override + public BeanContainer getBeanContainer() { + return beanContainer; + } + + @Override + public boolean canUseCachedReferences() { + return true; + } + + @Override + public boolean useJpaCompliantCreation() { + return true; + } + + @Override + @SuppressWarnings("unchecked") + public ManagedBean getBean(Class beanClass) { + final ManagedBean existing = registrations.get( beanClass.getName() ); + if ( existing != null ) { + return existing; + } + + final ManagedBean bean; + if ( beanContainer == null ) { + bean = new FallbackContainedBean( beanClass, FallbackBeanInstanceProducer.INSTANCE ); + } + else { + final ContainedBean containedBean = beanContainer.getBean( + beanClass, + this, + FallbackBeanInstanceProducer.INSTANCE + ); + + if ( containedBean instanceof ManagedBean ) { + bean = (ManagedBean) containedBean; + } + else { + bean = new ContainedBeanManagedBeanAdapter( beanClass, containedBean ); + } + } + + registrations.put( beanClass.getName(), bean ); + + return bean; + } + + @Override + @SuppressWarnings("unchecked") + public ManagedBean getBean(String beanName, Class beanContract) { + final String key = beanContract.getName() + ':' + beanName; + + final ManagedBean existing = registrations.get( key ); + if ( existing != null ) { + return existing; + } + + final ManagedBean bean; + if ( beanContainer == null ) { + bean = new FallbackContainedBean( beanName, beanContract, FallbackBeanInstanceProducer.INSTANCE ); + } + else { + final ContainedBean containedBean = beanContainer.getBean( + beanName, + beanContract, + this, + FallbackBeanInstanceProducer.INSTANCE + ); + + if ( containedBean instanceof ManagedBean ) { + bean = (ManagedBean) containedBean; + } + else { + bean = new ContainedBeanManagedBeanAdapter( beanContract, containedBean ); + } + } + + registrations.put( key, bean ); + + return bean; + } + + @Override + public void stop() { + if ( beanContainer != null ) { + beanContainer.stop(); + } + registrations.clear(); + } + + private class ContainedBeanManagedBeanAdapter implements ManagedBean { + private final Class beanClass; + private final ContainedBean containedBean; + + private ContainedBeanManagedBeanAdapter(Class beanClass, ContainedBean containedBean) { + this.beanClass = beanClass; + this.containedBean = containedBean; + } + + @Override + public Class getBeanClass() { + return beanClass; + } + + @Override + public B getBeanInstance() { + return containedBean.getBeanInstance(); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/package-info.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/package-info.java new file mode 100644 index 000000000000..a9f9f438f571 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/package-info.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 . + */ + +/** + * Defines Hibernate's integration with CDI. Because CDI may or may not be available + * a lot of this support is directed toward abstracting/encapsulating CDI. The + * central contracts here from a consumption point-of-view are + * {@link org.hibernate.resource.beans.spi.ManagedBean} and + * {@link org.hibernate.resource.beans.spi.ManagedBeanRegistry} which may or may not + * really be backed by CDI. + */ +package org.hibernate.resource.beans; diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/BeanInstanceProducer.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/BeanInstanceProducer.java new file mode 100644 index 000000000000..d10a93a1e5ad --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/BeanInstanceProducer.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.resource.beans.spi; + +/** + * Contract for producing a bean instance + * + * @author Steve Ebersole + */ +public interface BeanInstanceProducer { + /** + * Produce a bean instance + * + * @param beanType The Java type of bean to produce + */ + B produceBeanInstance(Class beanType); + + /** + * Produce a named bean instance + * + * @param name The bean name + * @param beanType The Java type that the produced bean should be typed as + */ + B produceBeanInstance(String name, Class beanType); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBean.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBean.java new file mode 100644 index 000000000000..da264a4e69ac --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBean.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 . + */ +package org.hibernate.resource.beans.spi; + +/** + * Generalized contract for a "ManagedBean" as seen by Hibernate + * + * @author Steve Ebersole + */ +public interface ManagedBean { + /** + * The bean Java type + */ + Class getBeanClass(); + + /** + * The bean reference + */ + T getBeanInstance(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBeanRegistry.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBeanRegistry.java new file mode 100644 index 000000000000..c8f2d8187432 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBeanRegistry.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.resource.beans.spi; + +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.service.Service; + +/** + * A registry for ManagedBean instances. Responsible for managing the lifecycle. + *

    + * Access to the beans and usage of them are only valid between the time + * the registry is initialized and released (however those events are recognized). + * + * @author Steve Ebersole + */ +public interface ManagedBeanRegistry extends Service { + /** + * Get a bean reference by class. + */ + ManagedBean getBean(Class beanClass); + + /** + * Get a bean reference by name and contract. + */ + ManagedBean getBean(String beanName, Class beanContract); + + /** + * Get a reference to the underlying BeanContainer. May return {@code null} + * indicating that no back-end container has been configured + */ + BeanContainer getBeanContainer(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBeanRegistryInitiator.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBeanRegistryInitiator.java new file mode 100644 index 000000000000..0a7166947db2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ManagedBeanRegistryInitiator.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.resource.beans.spi; + +import java.util.Map; + +import org.hibernate.InstantiationException; +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.resource.beans.container.internal.CdiBeanContainerBuilder; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.internal.BeansMessageLogger; +import org.hibernate.resource.beans.internal.ManagedBeanRegistryImpl; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * Hibernate's standard initializer for the ManagedBeanRegistry service. + * + * Produces a {@link org.hibernate.resource.beans.internal.ManagedBeanRegistryImpl} + * + * @author Steve Ebersole + */ +public class ManagedBeanRegistryInitiator implements StandardServiceInitiator { + /** + * Singleton access + */ + public static final ManagedBeanRegistryInitiator INSTANCE = new ManagedBeanRegistryInitiator(); + + @Override + public Class getServiceInitiated() { + return ManagedBeanRegistry.class; + } + + @Override + public ManagedBeanRegistry initiateService( + Map configurationValues, + ServiceRegistryImplementor serviceRegistry) { + return new ManagedBeanRegistryImpl( resoveBeanContainer( configurationValues, serviceRegistry ) ); + } + + private BeanContainer resoveBeanContainer(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + final ConfigurationService cfgSvc = serviceRegistry.getService( ConfigurationService.class ); + + // was a specific container explicitly specified? + final Object explicitBeanContainer = configurationValues.get( AvailableSettings.BEAN_CONTAINER ); + if ( explicitBeanContainer != null ) { + return interpretExplicitBeanContainer( explicitBeanContainer, classLoaderService, serviceRegistry ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // simplified CDI support + + final boolean isCdiAvailable = isCdiAvailable( classLoaderService ); + final Object beanManagerRef = cfgSvc.getSettings().get( AvailableSettings.CDI_BEAN_MANAGER ); + if ( beanManagerRef != null ) { + if ( !isCdiAvailable ) { + BeansMessageLogger.BEANS_LOGGER.beanManagerButCdiNotAvailable( beanManagerRef ); + } + + return CdiBeanContainerBuilder.fromBeanManagerReference( beanManagerRef, serviceRegistry ); + } + else { + if ( isCdiAvailable ) { + BeansMessageLogger.BEANS_LOGGER.noBeanManagerButCdiAvailable(); + } + } + + return null; + } + + private BeanContainer interpretExplicitBeanContainer( + Object explicitSetting, + ClassLoaderService classLoaderService, ServiceRegistryImplementor serviceRegistry) { + if ( explicitSetting == null ) { + return null; + } + + if ( explicitSetting instanceof BeanContainer ) { + return (BeanContainer) explicitSetting; + } + + // otherwise we ultimately need to resolve this to a class + final Class containerClass; + if ( explicitSetting instanceof Class ) { + containerClass = (Class) explicitSetting; + } + else { + final String name = explicitSetting.toString(); + // try the StrategySelector service + final Class selected = serviceRegistry.getService( StrategySelector.class ) + .selectStrategyImplementor( BeanContainer.class, name ); + if ( selected != null ) { + containerClass = selected; + } + else { + containerClass = classLoaderService.classForName( name ); + } + } + + try { + return (BeanContainer) containerClass.newInstance(); + } + catch (Exception e) { + throw new InstantiationException( "Unable to instantiate specified BeanContainer : " + containerClass.getName(), containerClass, e ); + } + } + + private static boolean isCdiAvailable(ClassLoaderService classLoaderService) { + // is CDI available on our ClassLoader? + try { + cdiBeanManagerClass( classLoaderService ); + return true; + } + catch (ClassLoadingException e) { + return false; + } + } + + public static Class cdiBeanManagerClass(ClassLoaderService classLoaderService) throws ClassLoadingException { + return classLoaderService.classForName( "javax.enterprise.inject.spi.BeanManager" ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ProvidedInstanceManagedBeanImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ProvidedInstanceManagedBeanImpl.java new file mode 100644 index 000000000000..e7f68ce0aadd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/spi/ProvidedInstanceManagedBeanImpl.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.resource.beans.spi; + +/** + * ManagedBean implementation for cases where we have been handed an actual + * instance to use. + * + * @author Steve Ebersole + */ +public class ProvidedInstanceManagedBeanImpl implements ManagedBean { + private final T instance; + + public ProvidedInstanceManagedBeanImpl(T instance) { + if ( instance == null ) { + throw new IllegalArgumentException( "Bean instance cannot be null" ); + } + + this.instance = instance; + } + + @Override + @SuppressWarnings("unchecked") + public Class getBeanClass() { + return (Class) instance.getClass(); + } + + @Override + public T getBeanInstance() { + return instance; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java index 6f97cbc4380b..0202fa637d30 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java @@ -62,10 +62,12 @@ public void afterTransaction() { @Override public void begin() { try { - log.trace( "Preparing to begin transaction via JDBC Connection.setAutoCommit(false)" ); - getConnectionForTransactionManagement().setAutoCommit( false ); + if ( !doConnectionsFromProviderHaveAutoCommitDisabled() ) { + log.trace( "Preparing to begin transaction via JDBC Connection.setAutoCommit(false)" ); + getConnectionForTransactionManagement().setAutoCommit( false ); + log.trace( "Transaction begun via JDBC Connection.setAutoCommit(false)" ); + } status = TransactionStatus.ACTIVE; - log.trace( "Transaction begun via JDBC Connection.setAutoCommit(false)" ); } catch( SQLException e ) { throw new TransactionException( "JDBC begin transaction failed: ", e ); @@ -95,14 +97,14 @@ protected void afterCompletion() { protected void resetConnection(boolean initiallyAutoCommit) { try { if ( initiallyAutoCommit ) { - log.trace( "re-enabling auto-commit on JDBC Connection afterQuery completion of JDBC-based transaction" ); + log.trace( "re-enabling auto-commit on JDBC Connection after completion of JDBC-based transaction" ); getConnectionForTransactionManagement().setAutoCommit( true ); status = TransactionStatus.NOT_ACTIVE; } } catch ( Exception e ) { log.debug( - "Could not re-enable auto-commit on JDBC Connection afterQuery completion of JDBC-based transaction : " + e + "Could not re-enable auto-commit on JDBC Connection after completion of JDBC-based transaction : " + e ); } } @@ -116,6 +118,7 @@ public void rollback() { log.trace( "Transaction rolled-back via JDBC Connection.rollback()" ); } catch( SQLException e ) { + status = TransactionStatus.FAILED_ROLLBACK; throw new TransactionException( "Unable to rollback against JDBC Connection", e ); } @@ -136,4 +139,8 @@ protected static boolean determineInitialAutoCommitMode(Connection providedConne public TransactionStatus getStatus(){ return status; } + + protected boolean doConnectionsFromProviderHaveAutoCommitDisabled() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java index fb14ffd06d18..18b5774ece51 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java @@ -43,11 +43,7 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple private transient Connection physicalConnection; private boolean closed; - public LogicalConnectionManagedImpl( - JdbcConnectionAccess jdbcConnectionAccess, - JdbcSessionContext jdbcSessionContext) { - this( jdbcConnectionAccess, jdbcSessionContext, new ResourceRegistryStandardImpl() ); - } + private boolean providerDisablesAutoCommit; public LogicalConnectionManagedImpl( JdbcConnectionAccess jdbcConnectionAccess, @@ -70,6 +66,17 @@ public LogicalConnectionManagedImpl( if ( connectionHandlingMode.getAcquisitionMode() == ConnectionAcquisitionMode.IMMEDIATELY ) { acquireConnectionIfNeeded(); } + + this.providerDisablesAutoCommit = jdbcSessionContext.doesConnectionProviderDisableAutoCommit(); + if ( providerDisablesAutoCommit ) { + log.debug( + "`hibernate.connection.provider_disables_autocommit` was enabled. This setting should only be " + + "enabled when you are certain that the Connections given to Hibernate by the " + + "ConnectionProvider have auto-commit disabled. Enabling this setting when the " + + "Connections do not have auto-commit disabled will lead to Hibernate executing " + + "SQL operations outside of any JDBC/SQL transaction." + ); + } } private PhysicalConnectionHandlingMode determineConnectionHandlingMode( @@ -135,7 +142,7 @@ public void afterStatement() { if ( connectionHandlingMode.getReleaseMode() == ConnectionReleaseMode.AFTER_STATEMENT ) { if ( getResourceRegistry().hasRegisteredResources() ) { - log.debug( "Skipping aggressive release of JDBC Connection afterQuery-statement due to held resources" ); + log.debug( "Skipping aggressive release of JDBC Connection after-statement due to held resources" ); } else { log.debug( "Initiating JDBC connection release from afterStatement" ); @@ -251,15 +258,21 @@ protected Connection getConnectionForTransactionManagement() { @Override public void begin() { - initiallyAutoCommit = determineInitialAutoCommitMode( getConnectionForTransactionManagement() ); + initiallyAutoCommit = !doConnectionsFromProviderHaveAutoCommitDisabled() && determineInitialAutoCommitMode( + getConnectionForTransactionManagement() ); super.begin(); } @Override protected void afterCompletion() { - afterTransaction(); - resetConnection( initiallyAutoCommit ); initiallyAutoCommit = false; + + afterTransaction(); + } + + @Override + protected boolean doConnectionsFromProviderHaveAutoCommitDisabled() { + return providerDisablesAutoCommit; } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java index fa6f1c5611ab..9e6ba5d32970 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java @@ -28,10 +28,6 @@ public class LogicalConnectionProvidedImpl extends AbstractLogicalConnectionImpl private final boolean initiallyAutoCommit; private boolean closed; - public LogicalConnectionProvidedImpl(Connection providedConnection) { - this( providedConnection, new ResourceRegistryStandardImpl() ); - } - public LogicalConnectionProvidedImpl(Connection providedConnection, ResourceRegistry resourceRegistry) { this.resourceRegistry = resourceRegistry; if ( providedConnection == null ) { @@ -89,7 +85,7 @@ public Connection getPhysicalConnection() { public LogicalConnectionImplementor makeShareableCopy() { errorIfClosed(); - return new LogicalConnectionProvidedImpl( providedConnection ); + return new LogicalConnectionProvidedImpl( providedConnection, new ResourceRegistryStandardImpl() ); } @Override @@ -130,7 +126,7 @@ else if ( connection == providedConnection ) { } else if ( providedConnection != null ) { throw new IllegalArgumentException( - "cannot reconnect to a new user-supplied connection because currently connected; must disconnect beforeQuery reconnecting." + "cannot reconnect to a new user-supplied connection because currently connected; must disconnect before reconnecting." ); } providedConnection = connection; diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java index 9084cc882503..0f34c14d9378 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java @@ -14,6 +14,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -25,6 +26,7 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.resource.jdbc.ResourceRegistry; +import org.hibernate.resource.jdbc.spi.JdbcObserver; /** * @author Steve Ebersole @@ -32,6 +34,8 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { private static final CoreMessageLogger log = CoreLogging.messageLogger( ResourceRegistryStandardImpl.class ); + private final JdbcObserver jdbcObserver; + private final Map> xref = new HashMap>(); private final Set unassociatedResultSets = new HashSet(); @@ -41,6 +45,14 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { private Statement lastQuery; + public ResourceRegistryStandardImpl() { + this( null ); + } + + public ResourceRegistryStandardImpl(JdbcObserver jdbcObserver) { + this.jdbcObserver = jdbcObserver; + } + @Override public boolean hasRegisteredResources() { return hasRegistered( xref ) @@ -51,12 +63,14 @@ public boolean hasRegisteredResources() { } @Override + @SuppressWarnings("unchecked") public void register(Statement statement, boolean cancelable) { log.tracef( "Registering statement [%s]", statement ); - if ( xref.containsKey( statement ) ) { + + Set previousValue = xref.putIfAbsent( statement, Collections.EMPTY_SET ); + if ( previousValue != null ) { throw new HibernateException( "JDBC Statement already registered" ); } - xref.put( statement, null ); if ( cancelable ) { lastQuery = statement; @@ -67,18 +81,16 @@ public void register(Statement statement, boolean cancelable) { public void release(Statement statement) { log.tracev( "Releasing statement [{0}]", statement ); - // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a - // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. - if ( log.isDebugEnabled() && !xref.containsKey( statement ) ) { - log.unregisteredStatement(); + final Set resultSets = xref.remove( statement ); + if ( resultSets != null ) { + closeAll( resultSets ); } else { - final Set resultSets = xref.get( statement ); - if ( resultSets != null ) { - closeAll( resultSets ); - } - xref.remove( statement ); + // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a + // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. + log.unregisteredStatement(); } + close( statement ); if ( lastQuery == statement ) { @@ -190,13 +202,15 @@ public void register(ResultSet resultSet, Statement statement) { } } if ( statement != null ) { + Set resultSets = xref.get( statement ); + // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. - if ( log.isDebugEnabled() && !xref.containsKey( statement ) ) { + if ( log.isDebugEnabled() && resultSets == null ) { log.debug( "ResultSet statement was not registered (on register)" ); } - Set resultSets = xref.get( statement ); - if ( resultSets == null ) { + + if ( resultSets == null || resultSets == Collections.EMPTY_SET ) { resultSets = new HashSet(); xref.put( statement, resultSets ); } @@ -285,6 +299,10 @@ public void cancelLastQuery() { public void releaseResources() { log.trace( "Releasing JDBC resources" ); + if ( jdbcObserver != null ) { + jdbcObserver.jdbcReleaseRegistryResourcesStart(); + } + for ( Map.Entry> entry : xref.entrySet() ) { if ( entry.getValue() != null ) { closeAll( entry.getValue() ); @@ -331,6 +349,9 @@ public void releaseResources() { nclobs.clear(); } + if ( jdbcObserver != null ) { + jdbcObserver.jdbcReleaseRegistryResourcesEnd(); + } } private boolean hasRegistered(Map resource) { diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java index 27ace23d21e8..49aa95db793d 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java @@ -27,4 +27,8 @@ public interface JdbcObserver { public void jdbcExecuteBatchStart(); public void jdbcExecuteBatchEnd(); + default public void jdbcReleaseRegistryResourcesStart() {} + default public void jdbcReleaseRegistryResourcesEnd() {} + + } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java index 4abb6726a9e0..8116dd04ecab 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java @@ -23,6 +23,8 @@ public interface JdbcSessionContext { PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode(); + boolean doesConnectionProviderDisableAutoCommit(); + /** * @deprecated Use {@link #getPhysicalConnectionHandlingMode} instead */ diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java index 15d1144f3214..abdf0302475c 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java @@ -33,20 +33,28 @@ public interface JdbcSessionOwner { TransactionCoordinator getTransactionCoordinator(); /** - * A afterQuery-begin callback from the coordinator to its owner. + * Callback indicating recognition of entering into a transactional + * context whether that is explicitly via the Hibernate + * {@link org.hibernate.Transaction} API or via registration + * of Hibernate's JTA Synchronization impl with a JTA Transaction + */ + void startTransactionBoundary(); + + /** + * A after-begin callback from the coordinator to its owner. */ void afterTransactionBegin(); /** - * A beforeQuery-completion callback to the owner. + * A before-completion callback to the owner. */ void beforeTransactionCompletion(); /** - * An afterQuery-completion callback to the owner. + * An after-completion callback to the owner. * * @param successful Was the transaction successful? - * @param delayed Is this a delayed afterQuery transaction completion call (aka afterQuery a timeout)? + * @param delayed Is this a delayed after transaction completion call (aka after a timeout)? */ void afterTransactionCompletion(boolean successful, boolean delayed); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/LogicalConnectionImplementor.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/LogicalConnectionImplementor.java index 4fdc8a0b0a8a..04b56175042f 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/LogicalConnectionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/LogicalConnectionImplementor.java @@ -51,7 +51,7 @@ public interface LogicalConnectionImplementor extends LogicalConnection { Connection manualDisconnect(); /** - * Manually reconnect the underlying JDBC Connection. Should be called at some point afterQuery manualDisconnect(). + * Manually reconnect the underlying JDBC Connection. Should be called at some point after manualDisconnect(). * * @param suppliedConnection For user supplied connection strategy the user needs to hand us the connection * with which to reconnect. It is an error to pass a connection in the other strategies. @@ -62,7 +62,10 @@ public interface LogicalConnectionImplementor extends LogicalConnection { * Creates a shareable copy of itself for use in "shared sessions" * * @return The shareable copy. + * + * @deprecated This method is not used by Hibernate. */ + @Deprecated LogicalConnectionImplementor makeShareableCopy(); PhysicalJdbcTransaction getPhysicalJdbcTransaction(); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/PhysicalConnectionHandlingMode.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/PhysicalConnectionHandlingMode.java index fcaad9724046..ca9cd77f1996 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/PhysicalConnectionHandlingMode.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/PhysicalConnectionHandlingMode.java @@ -37,12 +37,12 @@ public enum PhysicalConnectionHandlingMode { DELAYED_ACQUISITION_AND_HOLD( AS_NEEDED, ON_CLOSE ), /** * The Connection will be acquired as soon as it is needed; it will be released - * afterQuery each statement is executed. + * after each statement is executed. */ DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT( AS_NEEDED, AFTER_STATEMENT ), /** * The Connection will be acquired as soon as it is needed; it will be released - * afterQuery each transaction is completed. + * after each transaction is completed. */ DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION( AS_NEEDED, AFTER_TRANSACTION ) ; diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorBuilderImpl.java index 8048ae3acb48..98e5696d49cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorBuilderImpl.java @@ -31,7 +31,11 @@ public class JdbcResourceLocalTransactionCoordinatorBuilderImpl implements Trans @Override public TransactionCoordinator buildTransactionCoordinator(TransactionCoordinatorOwner owner, Options options) { if ( owner instanceof JdbcResourceTransactionAccess ) { - return new JdbcResourceLocalTransactionCoordinatorImpl( this, owner, (JdbcResourceTransactionAccess) owner ); + return new JdbcResourceLocalTransactionCoordinatorImpl( + this, + owner, + (JdbcResourceTransactionAccess) owner + ); } throw new HibernateException( diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java index fa011d3e81d2..210f8a9228fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java @@ -8,13 +8,14 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.RollbackException; import javax.transaction.Status; -import org.hibernate.TransactionException; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.engine.transaction.spi.TransactionObserver; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.resource.jdbc.spi.JdbcSessionOwner; import org.hibernate.resource.transaction.backend.jdbc.spi.JdbcResourceTransaction; import org.hibernate.resource.transaction.backend.jdbc.spi.JdbcResourceTransactionAccess; @@ -43,6 +44,8 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC private final TransactionCoordinatorOwner transactionCoordinatorOwner; private final SynchronizationRegistryStandardImpl synchronizationRegistry = new SynchronizationRegistryStandardImpl(); + private final JpaCompliance jpaCompliance; + private TransactionDriverControlImpl physicalTransactionDelegate; private int timeOut = -1; @@ -59,16 +62,32 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC TransactionCoordinatorBuilder transactionCoordinatorBuilder, TransactionCoordinatorOwner owner, JdbcResourceTransactionAccess jdbcResourceTransactionAccess) { - this.observers = new ArrayList(); + this.observers = new ArrayList<>(); this.transactionCoordinatorBuilder = transactionCoordinatorBuilder; this.jdbcResourceTransactionAccess = jdbcResourceTransactionAccess; this.transactionCoordinatorOwner = owner; + + this.jpaCompliance = owner.getJdbcSessionOwner() + .getJdbcSessionContext() + .getSessionFactory() + .getSessionFactoryOptions() + .getJpaCompliance(); + } + + /** + * Needed because while iterating the observers list and executing the before/update callbacks, + * some observers might get removed from the list. + * + * @return TransactionObserver + */ + private Iterable observers() { + return new ArrayList<>( observers ); } @Override public TransactionDriver getTransactionDriverControl() { // Again, this PhysicalTransactionDelegate will act as the bridge from the local transaction back into the - // coordinator. We lazily build it as we invalidate each delegate afterQuery each transaction (a delegate is + // coordinator. We lazily build it as we invalidate each delegate after each transaction (a delegate is // valid for just one transaction) if ( physicalTransactionDelegate == null ) { physicalTransactionDelegate = new TransactionDriverControlImpl( jdbcResourceTransactionAccess.getResourceLocalTransaction() ); @@ -97,6 +116,11 @@ public SynchronizationRegistry getLocalSynchronizations() { return synchronizationRegistry; } + @Override + public JpaCompliance getJpaCompliance() { + return jpaCompliance; + } + @Override public boolean isActive() { return transactionCoordinatorOwner.isActive(); @@ -133,8 +157,16 @@ private void afterBeginCallback() { if(this.timeOut > 0) { transactionCoordinatorOwner.setTransactionTimeOut( this.timeOut ); } + + + // report entering into a "transactional context" + transactionCoordinatorOwner.startTransactionBoundary(); + + // trigger the Transaction-API-only after-begin callback transactionCoordinatorOwner.afterTransactionBegin(); - for ( TransactionObserver observer : observers ) { + + // notify all registered observers + for ( TransactionObserver observer : observers() ) { observer.afterBegin(); } log.trace( "ResourceLocalTransactionCoordinatorImpl#afterBeginCallback" ); @@ -145,7 +177,7 @@ private void beforeCompletionCallback() { try { transactionCoordinatorOwner.beforeTransactionCompletion(); synchronizationRegistry.notifySynchronizationsBeforeTransactionCompletion(); - for ( TransactionObserver observer : observers ) { + for ( TransactionObserver observer : observers() ) { observer.beforeCompletion(); } } @@ -164,11 +196,12 @@ private void afterCompletionCallback(boolean successful) { synchronizationRegistry.notifySynchronizationsAfterTransactionCompletion( statusToSend ); transactionCoordinatorOwner.afterTransactionCompletion( successful, false ); - for ( TransactionObserver observer : observers ) { + for ( TransactionObserver observer : observers() ) { observer.afterCompletion( successful, false ); } } + @Override public void addObserver(TransactionObserver observer) { observers.add( observer ); } @@ -214,13 +247,34 @@ protected void errorIfInvalid() { public void commit() { try { if ( rollbackOnly ) { - throw new TransactionException( "Transaction was marked for rollback only; cannot commit" ); + log.debugf( "On commit, transaction was marked for roll-back only, rolling back" ); + + try { + rollback(); + + if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) { + log.debugf( "Throwing RollbackException on roll-back of transaction marked rollback-only on commit" ); + throw new RollbackException( "Transaction was marked for rollback-only" ); + } + + return; + } + catch (RollbackException e) { + throw e; + } + catch (RuntimeException e) { + log.debug( "Encountered failure rolling back failed commit", e ); + throw e; + } } JdbcResourceLocalTransactionCoordinatorImpl.this.beforeCompletionCallback(); jdbcResourceTransaction.commit(); JdbcResourceLocalTransactionCoordinatorImpl.this.afterCompletionCallback( true ); } + catch (RollbackException e) { + throw e; + } catch (RuntimeException e) { try { rollback(); @@ -234,10 +288,15 @@ public void commit() { @Override public void rollback() { - if ( rollbackOnly || getStatus() == TransactionStatus.ACTIVE ) { + try { + TransactionStatus status = jdbcResourceTransaction.getStatus(); + if ( ( rollbackOnly && status != TransactionStatus.NOT_ACTIVE ) || status == TransactionStatus.ACTIVE ) { + jdbcResourceTransaction.rollback(); + JdbcResourceLocalTransactionCoordinatorImpl.this.afterCompletionCallback( false ); + } + } + finally { rollbackOnly = false; - jdbcResourceTransaction.rollback(); - JdbcResourceLocalTransactionCoordinatorImpl.this.afterCompletionCallback( false ); } // no-op otherwise. @@ -250,7 +309,7 @@ public TransactionStatus getStatus() { @Override public void markRollbackOnly() { - if ( getStatus() != TransactionStatus.ROLLED_BACK && getStatus() != TransactionStatus.NOT_ACTIVE ) { + if ( getStatus() != TransactionStatus.ROLLED_BACK ) { if ( log.isDebugEnabled() ) { log.debug( "JDBC transaction marked for rollback-only (exception provided for stack trace)", diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransaction.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransaction.java index e247c38ae3bf..819a4a2bd563 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransaction.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransaction.java @@ -17,17 +17,17 @@ public interface JdbcResourceTransaction { /** * Begin the resource transaction */ - public void begin(); + void begin(); /** * Commit the resource transaction */ - public void commit(); + void commit(); /** * Rollback the resource transaction */ - public void rollback(); + void rollback(); - public TransactionStatus getStatus(); + TransactionStatus getStatus(); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransactionAccess.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransactionAccess.java index fda8438a5d39..e54166ceb0b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransactionAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/spi/JdbcResourceTransactionAccess.java @@ -19,5 +19,5 @@ public interface JdbcResourceTransactionAccess { * * @return The resource-local transaction */ - public JdbcResourceTransaction getResourceLocalTransaction(); + JdbcResourceTransaction getResourceLocalTransaction(); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionAdapter.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionAdapter.java index c361e0d8cf9c..1913c7771e07 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionAdapter.java @@ -21,21 +21,21 @@ public interface JtaTransactionAdapter { /** * Call begin on the underlying transaction object */ - public void begin(); + void begin(); /** * Call commit on the underlying transaction object */ - public void commit(); + void commit(); /** * Call rollback on the underlying transaction object */ - public void rollback(); + void rollback(); - public TransactionStatus getStatus(); + TransactionStatus getStatus(); - public void markRollbackOnly(); + void markRollbackOnly(); - public void setTimeOut(int seconds); + void setTimeOut(int seconds); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java index 9b64406b7e65..93837dce6509 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java @@ -19,6 +19,7 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.engine.transaction.spi.TransactionObserver; +import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.resource.jdbc.spi.JdbcSessionContext; import org.hibernate.resource.jdbc.spi.JdbcSessionOwner; import org.hibernate.resource.transaction.TransactionRequiredForJoinException; @@ -74,11 +75,12 @@ public class JtaTransactionCoordinatorImpl implements TransactionCoordinator, Sy TransactionCoordinatorBuilder transactionCoordinatorBuilder, TransactionCoordinatorOwner owner, boolean autoJoinTransactions) { - this.observers = new ArrayList(); this.transactionCoordinatorBuilder = transactionCoordinatorBuilder; this.transactionCoordinatorOwner = owner; this.autoJoinTransactions = autoJoinTransactions; + this.observers = new ArrayList<>(); + final JdbcSessionContext jdbcSessionContext = owner.getJdbcSessionOwner().getJdbcSessionContext(); this.jtaPlatform = jdbcSessionContext.getServiceRegistry().getService( JtaPlatform.class ); @@ -100,7 +102,6 @@ public JtaTransactionCoordinatorImpl( boolean preferUserTransactions, boolean performJtaThreadTracking, TransactionObserver... observers) { - this.observers = new ArrayList(); this.transactionCoordinatorBuilder = transactionCoordinatorBuilder; this.transactionCoordinatorOwner = owner; this.autoJoinTransactions = autoJoinTransactions; @@ -108,6 +109,8 @@ public JtaTransactionCoordinatorImpl( this.preferUserTransactions = preferUserTransactions; this.performJtaThreadTracking = performJtaThreadTracking; + this.observers = new ArrayList<>(); + if ( observers != null ) { Collections.addAll( this.observers, observers ); } @@ -115,7 +118,16 @@ public JtaTransactionCoordinatorImpl( synchronizationRegistered = false; pulse(); + } + /** + * Needed because while iterating the observers list and executing the before/update callbacks, + * some observers might get removed from the list. + * + * @return TransactionObserver + */ + private Iterable observers() { + return new ArrayList<>( observers ); } public SynchronizationCallbackCoordinator getSynchronizationCallbackCoordinator() { @@ -159,6 +171,9 @@ private void joinJtaTransaction() { getSynchronizationCallbackCoordinator().synchronizationRegistered(); synchronizationRegistered = true; log.debug( "Hibernate RegisteredSynchronization successfully registered with JTA platform" ); + + // report entering into a "transactional context" + getTransactionCoordinatorOwner().startTransactionBoundary(); } @Override @@ -197,6 +212,15 @@ public TransactionCoordinatorOwner getTransactionCoordinatorOwner(){ return this.transactionCoordinatorOwner; } + @Override + public JpaCompliance getJpaCompliance() { + return transactionCoordinatorOwner.getJdbcSessionOwner() + .getJdbcSessionContext() + .getSessionFactory() + .getSessionFactoryOptions() + .getJpaCompliance(); + } + @Override public TransactionDriver getTransactionDriverControl() { if ( physicalTransactionDelegate == null ) { @@ -312,6 +336,14 @@ public int getTimeOut() { return this.timeOut; } + @Override + public void invalidate() { + if ( physicalTransactionDelegate != null ) { + physicalTransactionDelegate.invalidate(); + } + physicalTransactionDelegate = null; + } + // SynchronizationCallbackTarget ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override @@ -329,7 +361,7 @@ public void beforeCompletion() { } finally { synchronizationRegistry.notifySynchronizationsBeforeTransactionCompletion(); - for ( TransactionObserver observer : observers ) { + for ( TransactionObserver observer : observers() ) { observer.beforeCompletion(); } } @@ -348,15 +380,10 @@ public void afterCompletion(boolean successful, boolean delayed) { transactionCoordinatorOwner.afterTransactionCompletion( successful, delayed ); - for ( TransactionObserver observer : observers ) { + for ( TransactionObserver observer : observers() ) { observer.afterCompletion( successful, delayed ); } - if ( physicalTransactionDelegate != null ) { - physicalTransactionDelegate.invalidate(); - } - - physicalTransactionDelegate = null; synchronizationRegistered = false; } @@ -369,7 +396,6 @@ public void removeObserver(TransactionObserver observer) { observers.remove( observer ); } - /** * Implementation of the LocalInflow for this TransactionCoordinator. Allows the * local transaction ({@link org.hibernate.Transaction} to callback into this @@ -406,7 +432,7 @@ public void commit() { errorIfInvalid(); getTransactionCoordinatorOwner().flushBeforeTransactionCompletion(); - // we don't have to perform any beforeQuery/afterQuery completion processing here. We leave that for + // we don't have to perform any before/after completion processing here. We leave that for // the Synchronization callbacks jtaTransactionAdapter.commit(); } @@ -415,7 +441,7 @@ public void commit() { public void rollback() { errorIfInvalid(); - // we don't have to perform any afterQuery completion processing here. We leave that for + // we don't have to perform any after completion processing here. We leave that for // the Synchronization callbacks jtaTransactionAdapter.rollback(); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinator.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinator.java index f9d809a33893..1139a85b7f32 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinator.java @@ -21,7 +21,7 @@ public interface SynchronizationCallbackCoordinator extends Synchronization { /** * Called by the TransactionCoordinator to allow the SynchronizationCallbackCoordinator to process any - * afterQuery-completion handling that it may have delayed due to thread affinity + * after-completion handling that it may have delayed due to thread affinity */ public void processAnyDelayedAfterCompletion(); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinatorTrackingImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinatorTrackingImpl.java index 8ed868e65252..3db6199892c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinatorTrackingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinatorTrackingImpl.java @@ -35,9 +35,9 @@ public void reset() { super.reset(); // NOTE : reset is typically called: // 1) on initialization, and - // 2) afterQuery "afterQuery completion" handling is finished. + // 2) after "after completion" handling is finished. // - // Here we use that to "clear out" all 'delayed afterQuery-completion" state. The registrationThreadId will + // Here we use that to "clear out" all 'delayed after-completion" state. The registrationThreadId will // "lazily" be re-populated on the next synchronizationRegistered call to allow for the potential of the // next Session transaction occurring on a different thread (though that transaction would need to completely // operate on that thread). diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackTarget.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackTarget.java index 3fdc70fa54ac..7813655df5de 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackTarget.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackTarget.java @@ -42,14 +42,14 @@ public interface SynchronizationCallbackTarget { boolean isActive(); /** - * Callback of beforeQuery-completion. + * Callback of before-completion. * * @see javax.transaction.Synchronization#beforeCompletion */ void beforeCompletion(); /** - * Callback of afterQuery-completion. + * Callback of after-completion. * * @param successful Was the transaction successful? * diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/SynchronizationRegistryImplementor.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/SynchronizationRegistryImplementor.java index d378e9b33e04..2c02d4c20a9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/SynchronizationRegistryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/SynchronizationRegistryImplementor.java @@ -19,7 +19,7 @@ public interface SynchronizationRegistryImplementor extends SynchronizationRegis /** * Delegates the {@link javax.transaction.Synchronization#afterCompletion} call to each registered Synchronization. Will also - * clear the registered Synchronizations afterQuery all have been notified. + * clear the registered Synchronizations after all have been notified. * * @param status The transaction status, per {@link javax.transaction.Status} constants */ @@ -27,7 +27,7 @@ public interface SynchronizationRegistryImplementor extends SynchronizationRegis /** * Clears all synchronizations from this registry. Note that synchronizations are automatically cleared during - * afterQuery-completion handling; see {@link #notifySynchronizationsAfterTransactionCompletion} + * after-completion handling; see {@link #notifySynchronizationsAfterTransactionCompletion} */ void clearSynchronizations(); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinator.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinator.java index da4ee56c8bb4..eae2b7a1c868 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinator.java @@ -8,6 +8,7 @@ import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.engine.transaction.spi.TransactionObserver; +import org.hibernate.jpa.spi.JpaCompliance; /** * Models the coordination of all transaction related flows. @@ -15,6 +16,28 @@ * @author Steve Ebersole */ public interface TransactionCoordinator { + /** + * Access to the builder that generated this coordinator + */ + TransactionCoordinatorBuilder getTransactionCoordinatorBuilder(); + + /** + * Get the delegate used by the local transaction driver to control the underlying transaction + * + * @return The control delegate. + */ + TransactionDriver getTransactionDriverControl(); + + /** + * Get access to the local registry of Synchronization instances + * + * @return The local Synchronization registry + */ + SynchronizationRegistry getLocalSynchronizations(); + + + JpaCompliance getJpaCompliance(); + /** * Indicates an explicit request to join a transaction. This is mainly intended to handle the JPA requirement * around {@link javax.persistence.EntityManager#joinTransaction()}, and generally speaking only has an impact in @@ -35,20 +58,6 @@ public interface TransactionCoordinator { */ void pulse(); - /** - * Get the delegate used by the local transaction driver to control the underlying transaction - * - * @return The control delegate. - */ - TransactionDriver getTransactionDriverControl(); - - /** - * Get access to the local registry of Synchronization instances - * - * @return The local Synchronization registry - */ - SynchronizationRegistry getLocalSynchronizations(); - /** * Is this transaction still active? *

    @@ -85,12 +94,6 @@ public interface TransactionCoordinator { */ void removeObserver(TransactionObserver observer); - /** - * - * @return - */ - TransactionCoordinatorBuilder getTransactionCoordinatorBuilder(); - void setTimeOut(int seconds); int getTimeOut(); @@ -103,6 +106,8 @@ default boolean isTransactionActive(boolean isMarkedRollbackConsideredActive) { return isJoined() && getTransactionDriverControl().isActive( isMarkedRollbackConsideredActive ); } + default void invalidate(){} + /** * Provides the means for "local transactions" (as transaction drivers) to control the * underlying "physical transaction" currently associated with the TransactionCoordinator. diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java index ab638eafbecf..e27af47551e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java @@ -23,7 +23,7 @@ public interface TransactionCoordinatorBuilder extends Service { /** * Access to options to are specific to each TransactionCoordinator instance */ - static interface Options { + interface Options { /** * Indicates whether an active transaction should be automatically joined. Only relevant * for JTA-based TransactionCoordinator instances. diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorOwner.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorOwner.java index d2840e3718eb..fbde477e7496 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorOwner.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorOwner.java @@ -29,20 +29,30 @@ public interface TransactionCoordinatorOwner { boolean isActive(); /** - * A afterQuery-begin callback from the coordinator to its owner. + * Callback indicating recognition of entering into a transactional + * context whether that is explicitly via the Hibernate + * {@link org.hibernate.Transaction} API or via registration + * of Hibernate's JTA Synchronization impl with a JTA Transaction + */ + default void startTransactionBoundary() { + getJdbcSessionOwner().startTransactionBoundary(); + } + + /** + * A after-begin callback from the coordinator to its owner. */ void afterTransactionBegin(); /** - * A beforeQuery-completion callback from the coordinator to its owner. + * A before-completion callback from the coordinator to its owner. */ void beforeTransactionCompletion(); /** - * An afterQuery-completion callback from the coordinator to its owner. + * An after-completion callback from the coordinator to its owner. * * @param successful Was the transaction successful? - * @param delayed Is this a delayed afterQuery transaction completion call (aka afterQuery a timeout)? + * @param delayed Is this a delayed after transaction completion call (aka after a timeout)? */ void afterTransactionCompletion(boolean successful, boolean delayed); @@ -51,7 +61,7 @@ public interface TransactionCoordinatorOwner { /** * Set the effective transaction timeout period for the current transaction, in seconds. * - * @param seconds The number of seconds beforeQuery a time out should occur. + * @param seconds The number of seconds before a time out should occur. */ void setTransactionTimeOut(int seconds); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionStatus.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionStatus.java index 16113f758be7..6853878bd1a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionStatus.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionStatus.java @@ -13,15 +13,15 @@ */ public enum TransactionStatus { /** - * The transaction has not yet been begun + * The transaction has not yet been started. */ NOT_ACTIVE, /** - * The transaction has been begun, but not yet completed. + * The transaction has been started, but not yet completed. */ ACTIVE, /** - * The transaction has been competed successfully. + * The transaction has been completed successfully. */ COMMITTED, /** @@ -29,17 +29,21 @@ public enum TransactionStatus { */ ROLLED_BACK, /** - * The transaction has been marked for rollback only. + * The transaction has been marked for rollback only. */ MARKED_ROLLBACK, /** * The transaction attempted to commit, but failed. */ FAILED_COMMIT, + /** + * The transaction attempted to rollback, but failed. + */ + FAILED_ROLLBACK, /** * Status code indicating a transaction that has begun the second * phase of the two-phase commit protocol, but not yet completed - * this phase + * this phase. */ COMMITTING, /** diff --git a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java index 1ff43316a48a..f611b264b6e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java @@ -9,18 +9,23 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import org.hibernate.JDBCException; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.loader.EntityAliases; import org.hibernate.loader.custom.CustomLoader; import org.hibernate.loader.custom.CustomQuery; +import org.hibernate.loader.custom.Return; +import org.hibernate.loader.custom.RootReturn; import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor; +import org.hibernate.param.ParameterBinder; import org.hibernate.result.NoMoreReturnsException; import org.hibernate.result.Output; import org.hibernate.result.Outputs; @@ -201,6 +206,10 @@ protected Output buildResultSetOutput(List list) { return new ResultSetOutputImpl( list ); } + protected Output buildResultSetOutput(Supplier listSupplier) { + return new ResultSetOutputImpl( listSupplier ); + } + protected Output buildUpdateCountOutput(int updateCount) { return new UpdateCountOutputImpl( updateCount ); } @@ -226,7 +235,7 @@ private static CustomLoaderExtension buildSpecializedCustomLoader(final ResultCo context.getSession().getFactory() ); processor.process(); - final List customReturns = processor.generateCustomReturns( false ); + final List customReturns = processor.generateCallableReturns(); CustomQuery customQuery = new CustomQuery() { @Override @@ -240,9 +249,9 @@ public Set getQuerySpaces() { } @Override - public Map getNamedParameterBindPoints() { - // no named parameters in terms of embedded in the SQL string - return null; + public List getParameterValueBinders() { + // no parameters in terms of embedded in the SQL string + return Collections.emptyList(); } @Override @@ -259,8 +268,11 @@ public List getCustomQueryReturns() { } private static class CustomLoaderExtension extends CustomLoader { - private QueryParameters queryParameters; - private SharedSessionContractImplementor session; + private static final EntityAliases[] NO_ALIASES = new EntityAliases[0]; + + private final QueryParameters queryParameters; + private final SharedSessionContractImplementor session; + private final EntityAliases[] entityAliases; private boolean needsDiscovery = true; @@ -271,8 +283,33 @@ public CustomLoaderExtension( super( customQuery, session.getFactory() ); this.queryParameters = queryParameters; this.session = session; + + entityAliases = interpretEntityAliases( customQuery.getCustomQueryReturns() ); + } + + private EntityAliases[] interpretEntityAliases(List customQueryReturns) { + final List entityAliases = new ArrayList<>(); + for ( Return queryReturn : customQueryReturns ) { + if ( !RootReturn.class.isInstance( queryReturn ) ) { + continue; + } + + entityAliases.add( ( (RootReturn) queryReturn ).getEntityAliases() ); + } + + if ( entityAliases.isEmpty() ) { + return NO_ALIASES; + } + + return entityAliases.toArray( new EntityAliases[ entityAliases.size() ] ); } + @Override + protected EntityAliases[] getEntityAliases() { + return entityAliases; + } + + // todo : this would be a great way to add locking to stored procedure support (at least where returning entities). public List processResultSet(ResultSet resultSet) throws SQLException { diff --git a/hibernate-core/src/main/java/org/hibernate/result/internal/ResultSetOutputImpl.java b/hibernate-core/src/main/java/org/hibernate/result/internal/ResultSetOutputImpl.java index fa85ace48017..0917833ef623 100644 --- a/hibernate-core/src/main/java/org/hibernate/result/internal/ResultSetOutputImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/result/internal/ResultSetOutputImpl.java @@ -6,7 +6,11 @@ */ package org.hibernate.result.internal; +import java.sql.ResultSet; import java.util.List; +import java.util.function.Supplier; + +import javax.enterprise.inject.spi.Producer; import org.hibernate.result.ResultSetOutput; @@ -16,10 +20,14 @@ * @author Steve Ebersole */ class ResultSetOutputImpl implements ResultSetOutput { - private final List results; + private final Supplier resultSetSupplier; public ResultSetOutputImpl(List results) { - this.results = results; + this.resultSetSupplier = () -> results; + } + + public ResultSetOutputImpl(Supplier resultSetSupplier) { + this.resultSetSupplier = resultSetSupplier; } @Override @@ -30,7 +38,7 @@ public boolean isResultSet() { @Override @SuppressWarnings("unchecked") public List getResultList() { - return results; + return resultSetSupplier.get(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java old mode 100755 new mode 100644 index 4a309ea4c278..41fea761320f --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java @@ -11,7 +11,7 @@ import org.hibernate.secure.spi.PermissibleAction; /** - * Check security beforeQuery any deletion + * Check security before any deletion * * @author Kabir Khan * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java old mode 100755 new mode 100644 index 7a9c46c3d497..c9ff548d5af8 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java @@ -11,7 +11,7 @@ import org.hibernate.secure.spi.PermissibleAction; /** - * Check security beforeQuery an insertion + * Check security before an insertion * * @author Kabir Khan * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java old mode 100755 new mode 100644 index 64d9fb3901ab..7f3dc7b4ba5f --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java @@ -11,7 +11,7 @@ import org.hibernate.secure.spi.PermissibleAction; /** - * Check security beforeQuery any load + * Check security before any load * * @author Kabir Khan * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java old mode 100755 new mode 100644 index b9e82fdceb59..fa5a0abfbb75 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java @@ -11,7 +11,7 @@ import org.hibernate.secure.spi.PermissibleAction; /** - * Check security beforeQuery any update + * Check security before any update * * @author Kabir Khan * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/service/ServiceRegistry.java b/hibernate-core/src/main/java/org/hibernate/service/ServiceRegistry.java index fb77f5281079..da5e34d78228 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/ServiceRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/service/ServiceRegistry.java @@ -11,7 +11,7 @@ * * @author Steve Ebersole */ -public interface ServiceRegistry { +public interface ServiceRegistry extends AutoCloseable { /** * Retrieve this registry's parent registry. * @@ -56,4 +56,6 @@ default R requireService(Class serviceRole) { return service; } + @Override + void close(); } diff --git a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java index b837cd3c27ad..a96d76b303c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java +++ b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java @@ -31,6 +31,7 @@ import org.hibernate.persister.internal.PersisterClassResolverInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; +import org.hibernate.resource.beans.spi.ManagedBeanRegistryInitiator; import org.hibernate.resource.transaction.internal.TransactionCoordinatorBuilderInitiator; import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator; import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractorInitiator; @@ -84,6 +85,8 @@ private static List buildStandardServiceInitiatorList( serviceInitiators.add( TransactionCoordinatorBuilderInitiator.INSTANCE ); + serviceInitiators.add( ManagedBeanRegistryInitiator.INSTANCE ); + return Collections.unmodifiableList( serviceInitiators ); } } 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 24e125ed8bad..422717dc8a29 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 @@ -14,7 +14,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Function; +import java.util.function.Consumer; import org.hibernate.boot.registry.BootstrapServiceRegistry; import org.hibernate.cfg.Environment; @@ -127,6 +127,10 @@ protected void createServiceBinding(ProvidedService provi registerService( binding, providedService.getService() ); } + protected void visitServiceBindings(Consumer action) { + serviceBindingList.forEach( action ); + } + @Override @SuppressWarnings( {"unchecked"}) public ServiceRegistry getParentServiceRegistry() { diff --git a/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryBuilderImpl.java index 574c5f19dce6..a16a8dec1772 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryBuilderImpl.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.Service; @@ -72,12 +73,14 @@ public SessionFactoryServiceRegistryBuilder addService(final Class serviceRole, public SessionFactoryServiceRegistry buildSessionFactoryServiceRegistry( SessionFactoryImplementor sessionFactory, + BootstrapContext bootstrapContext, SessionFactoryOptions options) { return new SessionFactoryServiceRegistryImpl( parent, initiators, providedServices, sessionFactory, + bootstrapContext, options ); } diff --git a/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryFactoryImpl.java index a9cc421b6e2b..700f84486a31 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryFactoryImpl.java @@ -7,6 +7,7 @@ package org.hibernate.service.internal; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -30,6 +31,7 @@ public SessionFactoryServiceRegistryFactoryImpl(ServiceRegistryImplementor theBa @Override public SessionFactoryServiceRegistry buildServiceRegistry( SessionFactoryImplementor sessionFactory, + BootstrapContext bootstrapContext, SessionFactoryOptions options) { final ClassLoaderService cls = options.getServiceRegistry().getService( ClassLoaderService.class ); final SessionFactoryServiceRegistryBuilderImpl builder = new SessionFactoryServiceRegistryBuilderImpl( theBasicServiceRegistry ); @@ -38,6 +40,6 @@ public SessionFactoryServiceRegistry buildServiceRegistry( contributor.contribute( builder ); } - return builder.buildSessionFactoryServiceRegistry( sessionFactory, options ); + return builder.buildSessionFactoryServiceRegistry( sessionFactory, bootstrapContext, options ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryImpl.java index d136a993fe79..93679192b842 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/service/internal/SessionFactoryServiceRegistryImpl.java @@ -8,37 +8,46 @@ import java.util.List; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.service.Service; -import org.hibernate.service.UnknownServiceException; +import org.hibernate.service.spi.Configurable; import org.hibernate.service.spi.ServiceBinding; import org.hibernate.service.spi.ServiceInitiator; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.SessionFactoryServiceInitiator; +import org.hibernate.service.spi.SessionFactoryServiceInitiatorContext; import org.hibernate.service.spi.SessionFactoryServiceRegistry; /** * @author Steve Ebersole */ -public class SessionFactoryServiceRegistryImpl extends AbstractServiceRegistryImpl implements SessionFactoryServiceRegistry { +public class SessionFactoryServiceRegistryImpl + extends AbstractServiceRegistryImpl + implements SessionFactoryServiceRegistry, SessionFactoryServiceInitiatorContext { private final SessionFactoryOptions sessionFactoryOptions; private final SessionFactoryImplementor sessionFactory; private EventListenerRegistry cachedEventListenerRegistry; + private final BootstrapContext bootstrapContext; + @SuppressWarnings( {"unchecked"}) public SessionFactoryServiceRegistryImpl( ServiceRegistryImplementor parent, List initiators, List providedServices, SessionFactoryImplementor sessionFactory, + BootstrapContext bootstrapContext, SessionFactoryOptions sessionFactoryOptions) { super( parent ); this.sessionFactory = sessionFactory; this.sessionFactoryOptions = sessionFactoryOptions; + this.bootstrapContext = bootstrapContext; // for now, just use the standard initiator list for ( SessionFactoryServiceInitiator initiator : initiators ) { @@ -50,17 +59,40 @@ public SessionFactoryServiceRegistryImpl( createServiceBinding( providedService ); } + bootstrapContext = null; } @Override public R initiateService(ServiceInitiator serviceInitiator) { SessionFactoryServiceInitiator sessionFactoryServiceInitiator = (SessionFactoryServiceInitiator) serviceInitiator; - return sessionFactoryServiceInitiator.initiateService( sessionFactory, sessionFactoryOptions, this ); + return sessionFactoryServiceInitiator.initiateService( this ); } @Override public void configureService(ServiceBinding serviceBinding) { - //TODO nothing to do here or should we inject SessionFactory properties? + if ( Configurable.class.isInstance( serviceBinding.getService() ) ) { + ( (Configurable) serviceBinding.getService() ).configure( getService( ConfigurationService.class ).getSettings() ); + } + } + + @Override + public BootstrapContext getBootstrapContext() { + return bootstrapContext; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public SessionFactoryOptions getSessionFactoryOptions() { + return sessionFactoryOptions; + } + + @Override + public ServiceRegistryImplementor getServiceRegistry() { + return this; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/OptionallyManageable.java b/hibernate-core/src/main/java/org/hibernate/service/spi/OptionallyManageable.java new file mode 100644 index 000000000000..9b46dc2c54d9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/OptionallyManageable.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.service.spi; + +import java.util.List; + +/** + * Extension to Manageable for things that are optionally Manageable depending + * on some internal state. E.g. services that wrap other services wanting to + * delegate manageablity if the wrapped service is Manageable. + * + * @author Steve Ebersole + */ +public interface OptionallyManageable extends Manageable { + /** + * Any wrapped services that are Manageable. Never return `null`; an empty + * List should be returned instead. + */ + List getRealManageables(); + + @Override + default String getManagementDomain() { + // Generally the wrapper is not Manageable itself + return null; + } + + @Override + default String getManagementServiceType() { + // Generally the wrapper is not Manageable itself + return null; + } + + @Override + default Object getManagementBean() { + // Generally the wrapper is not Manageable itself + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceContributor.java b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceContributor.java index c86ef70a8e05..fd0fe4aa79e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceContributor.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceContributor.java @@ -11,6 +11,9 @@ /** * Contract for contributing services. * + * @implSpec Implementations can be auto-discovered via Java's {@link java.util.ServiceLoader} + * mechanism. + * * @author Steve Ebersole */ public interface ServiceContributor { diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceInitiator.java b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceInitiator.java index fd0678c32fd1..8f9bc4916acd 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceInitiator.java @@ -19,5 +19,5 @@ public interface ServiceInitiator { * * @return The service role. */ - public Class getServiceInitiated(); + Class getServiceInitiated(); } diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryImplementor.java b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryImplementor.java index f1712694d97f..51227274985c 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryImplementor.java @@ -23,22 +23,27 @@ public interface ServiceRegistryImplementor extends ServiceRegistry { * * @return The located binding; may be {@code null} */ - public ServiceBinding locateServiceBinding(Class serviceRole); + ServiceBinding locateServiceBinding(Class serviceRole); + + @Override + default void close() { + destroy(); + } /** * Release resources */ - public void destroy(); + void destroy(); /** * When a registry is created with a parent, the parent is notified of the child * via this callback. */ - public void registerChild(ServiceRegistryImplementor child); + void registerChild(ServiceRegistryImplementor child); /** * When a registry is created with a parent, the parent is notified of the child * via this callback. */ - public void deRegisterChild(ServiceRegistryImplementor child); + void deRegisterChild(ServiceRegistryImplementor child); } diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceInitiator.java b/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceInitiator.java index dca125395d8f..21a8f20a52fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceInitiator.java @@ -17,6 +17,23 @@ * @author Steve Ebersole */ public interface SessionFactoryServiceInitiator extends ServiceInitiator{ + /** + * Initiates the managed service. + *

    + * Note for implementors: signature is guaranteed to change once redesign of SessionFactory building is complete + * + * @param context Access to initialization contextual info + * + * @return The initiated service. + */ + default R initiateService(SessionFactoryServiceInitiatorContext context) { + return initiateService( + context.getSessionFactory(), + context.getSessionFactoryOptions(), + context.getServiceRegistry() + ); + } + /** * Initiates the managed service. *

    @@ -28,10 +45,12 @@ public interface SessionFactoryServiceInitiator extends Servi * @param registry The service registry. Can be used to locate services needed to fulfill initiation. * * @return The initiated service. + * + * @deprecated Use {@link SessionFactoryServiceInitiator#initiateService(SessionFactoryServiceInitiatorContext)} instead. */ - public R initiateService( + @Deprecated + R initiateService( SessionFactoryImplementor sessionFactory, SessionFactoryOptions sessionFactoryOptions, ServiceRegistryImplementor registry); - } diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceInitiatorContext.java b/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceInitiatorContext.java new file mode 100644 index 000000000000..66fb30a0f244 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceInitiatorContext.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.service.spi; + +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * @author Steve Ebersole + */ +public interface SessionFactoryServiceInitiatorContext { + BootstrapContext getBootstrapContext(); + SessionFactoryImplementor getSessionFactory(); + SessionFactoryOptions getSessionFactoryOptions(); + ServiceRegistryImplementor getServiceRegistry(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceRegistryFactory.java b/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceRegistryFactory.java index 143d90372eb1..272cb17ab7c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceRegistryFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/SessionFactoryServiceRegistryFactory.java @@ -6,6 +6,7 @@ */ package org.hibernate.service.spi; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.Service; @@ -23,13 +24,16 @@ public interface SessionFactoryServiceRegistryFactory extends Service { * * @param sessionFactory The (still being built) session factory. Generally this is useful * for grabbing a reference for later use. However, care should be taken when invoking on - * the session factory until afterQuery it has been fully initialized. + * the session factory until after it has been fully initialized. + * @param sessionFactoryOptions The build options. + * @param bootstrapContext The (still active) BootstrapContext. * @param sessionFactoryOptions The build options. * * @return The registry */ SessionFactoryServiceRegistry buildServiceRegistry( SessionFactoryImplementor sessionFactory, + BootstrapContext bootstrapContext, SessionFactoryOptions sessionFactoryOptions); } diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/Stoppable.java b/hibernate-core/src/main/java/org/hibernate/service/spi/Stoppable.java index 769f2a720754..6c815399a51f 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/Stoppable.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/Stoppable.java @@ -15,5 +15,5 @@ public interface Stoppable { /** * Stop phase notification */ - public void stop(); + void stop(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java index f32f945c0f5d..2aa6f77235f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java @@ -89,6 +89,63 @@ public void addJoin( } + public void addJoin( + String rhsTableName, + String rhsAlias, + String[][] lhsColumns, + String[] rhsColumns, + JoinType joinType, + String on) { + final String joinString; + switch (joinType) { + case INNER_JOIN: + joinString = " inner join "; + break; + case LEFT_OUTER_JOIN: + joinString = " left outer join "; + break; + case RIGHT_OUTER_JOIN: + joinString = " right outer join "; + break; + case FULL_JOIN: + joinString = " full outer join "; + break; + default: + throw new AssertionFailure("undefined join type"); + } + + this.buffer.append(joinString) + .append(rhsTableName) + .append(' ') + .append(rhsAlias) + .append(" on "); + + + if ( lhsColumns.length > 1 ) { + this.buffer.append( "(" ); + } + for ( int i = 0; i < lhsColumns.length; i++ ) { + for ( int j=0; j 1 ) { + this.buffer.append( ")" ); + } + + addCondition( buffer, on ); + } + @Override public String toFromFragmentString() { return this.buffer.toString(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Delete.java b/hibernate-core/src/main/java/org/hibernate/sql/Delete.java index a6badc03051b..876dd5f76a5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Delete.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Delete.java @@ -9,6 +9,8 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.hibernate.dialect.Dialect; + /** * An SQL DELETE statement * @@ -36,7 +38,7 @@ public Delete setTableName(String tableName) { public String toStatementString() { StringBuilder buf = new StringBuilder( tableName.length() + 10 ); if ( comment!=null ) { - buf.append( "/* " ).append(comment).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "delete from " ).append(tableName); if ( where != null || !primaryKeyColumns.isEmpty() || versionColumnName != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java index 6d7030cbe6e5..df3f4554aade 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java @@ -69,11 +69,11 @@ public ForUpdateFragment(Dialect dialect, LockOptions lockOptions, Map 1 ) { + throw new UnsupportedOperationException( "The join fragment does not support multiple foreign key columns: " + getClass() ); + } + addJoin( tableName, alias, fkColumns[0], pkColumns, joinType, on ); + } + /** * Adds a cross join to the specified table. * diff --git a/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java index c2b1ca2d8d61..ee21fb223119 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java @@ -38,6 +38,40 @@ public void addJoin(String tableName, String alias, String[] fkColumns, String[] } } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType) { + addCrossJoin( tableName, alias ); + + if ( fkColumns.length > 1 ) { + afterWhere.append( "(" ); + } + for ( int i = 0; i < fkColumns.length; i++ ) { + afterWhere.append( " and " ); + for ( int j = 0; j < fkColumns[i].length; j++ ) { + setHasThetaJoins( true ); + afterWhere.append( fkColumns[i][j] ); + if ( joinType == JoinType.RIGHT_OUTER_JOIN || joinType == JoinType.FULL_JOIN ) { + afterWhere.append( "(+)" ); + } + afterWhere.append( '=' ) + .append( alias ) + .append( '.' ) + .append( pkColumns[j] ); + if ( joinType == JoinType.LEFT_OUTER_JOIN || joinType == JoinType.FULL_JOIN ) { + afterWhere.append( "(+)" ); + } + if ( j < fkColumns[i].length - 1 ) { + afterWhere.append( " and " ); + } + } + if ( i < fkColumns.length - 1 ) { + afterWhere.append( " or " ); + } + } + if ( fkColumns.length > 1 ) { + afterWhere.append( ")" ); + } + } + public String toFromFragmentString() { return afterFrom.toString(); } @@ -101,6 +135,20 @@ else if ( joinType == JoinType.LEFT_OUTER_JOIN ) { } } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType, String on) { + //arbitrary on clause ignored!! + addJoin( tableName, alias, fkColumns, pkColumns, joinType ); + if ( joinType == JoinType.INNER_JOIN ) { + addCondition( on ); + } + else if ( joinType == JoinType.LEFT_OUTER_JOIN ) { + addLeftOuterJoinCondition( on ); + } + else { + throw new UnsupportedOperationException( "join type not supported by OracleJoinFragment (use Oracle9iDialect/Oracle10gDialect)" ); + } + } + /** * This method is a bit of a hack, and assumes * that the column on the "right" side of the diff --git a/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java index ecb76ac54e1c..1fe427c931ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql; import org.hibernate.dialect.Dialect; +import org.hibernate.internal.util.StringHelper; /** * A join that appears in a translated HQL query @@ -32,6 +33,14 @@ public void addJoin(String tableName, String alias, String[] fkColumns, String[] addJoin( tableName, alias, alias, fkColumns, pkColumns, joinType, on ); } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType) { + addJoin( tableName, alias, alias, fkColumns, pkColumns, joinType, null ); + } + + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType, String on) { + addJoin( tableName, alias, alias, fkColumns, pkColumns, joinType, on ); + } + private void addJoin(String tableName, String alias, String concreteAlias, String[] fkColumns, String[] pkColumns, JoinType joinType, String on) { if ( !useThetaStyleInnerJoins || joinType != JoinType.INNER_JOIN ) { JoinFragment jf = dialect.createOuterJoinFragment(); @@ -45,6 +54,19 @@ private void addJoin(String tableName, String alias, String concreteAlias, Strin } } + private void addJoin(String tableName, String alias, String concreteAlias, String[][] fkColumns, String[] pkColumns, JoinType joinType, String on) { + if ( !useThetaStyleInnerJoins || joinType != JoinType.INNER_JOIN ) { + JoinFragment jf = dialect.createOuterJoinFragment(); + jf.addJoin( tableName, alias, fkColumns, pkColumns, joinType, on ); + addFragment( jf ); + } + else { + addCrossJoin( tableName, alias ); + addCondition( concreteAlias, fkColumns, pkColumns ); + addCondition( on ); + } + } + public String toFromFragmentString() { return afterFrom.toString(); } @@ -94,6 +116,31 @@ public void addCondition(String alias, String[] fkColumns, String[] pkColumns) { } } + public void addCondition(String alias, String[][] fkColumns, String[] pkColumns) { + afterWhere.append( " and " ); + if ( fkColumns.length > 1 ) { + afterWhere.append( "(" ); + } + for ( int i = 0; i < fkColumns.length; i++ ) { + for ( int j = 0; j < fkColumns[i].length; j++ ) { + afterWhere.append( fkColumns[i][j] ) + .append( '=' ) + .append( alias ) + .append( '.' ) + .append( pkColumns[j] ); + if ( j < fkColumns[i].length - 1 ) { + afterWhere.append( " and " ); + } + } + if ( i < fkColumns.length - 1 ) { + afterWhere.append( " or " ); + } + } + if ( fkColumns.length > 1 ) { + afterWhere.append( ")" ); + } + } + /** * Add the condition string to the join fragment. * @@ -103,6 +150,7 @@ public void addCondition(String alias, String[] fkColumns, String[] pkColumns) { public boolean addCondition(String condition) { // if the condition is not already there... if ( + !StringHelper.isEmpty( condition ) && afterFrom.toString().indexOf( condition.trim() ) < 0 && afterWhere.toString().indexOf( condition.trim() ) < 0 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java b/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java index 649d97359407..fc685182d8a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java @@ -126,7 +126,7 @@ public void addOrderBy(String orderByString) { public String toQueryString() { StringBuilder buf = new StringBuilder( 50 ); if ( comment != null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "select " ); if ( distinct ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Select.java b/hibernate-core/src/main/java/org/hibernate/sql/Select.java index 3cfb475c45e7..e497d7839339 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Select.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Select.java @@ -40,7 +40,7 @@ public Select(Dialect dialect) { public String toStatementString() { StringBuilder buf = new StringBuilder(guesstimatedBufferSize); if ( StringHelper.isNotEmpty(comment) ) { - buf.append("/* ").append(comment).append(" */ "); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append("select ").append(selectClause) @@ -52,7 +52,7 @@ public String toStatementString() { if ( StringHelper.isNotEmpty(whereClause) || StringHelper.isNotEmpty(outerJoinsAfterWhere) ) { buf.append(" where " ); - // the outerJoinsAfterWhere needs to come beforeQuery where clause to properly + // the outerJoinsAfterWhere needs to come before where clause to properly // handle dynamic filters if ( StringHelper.isNotEmpty(outerJoinsAfterWhere) ) { buf.append(outerJoinsAfterWhere); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java b/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java index f27f407b2ceb..4c574cf5a6f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java @@ -148,7 +148,7 @@ public String toStatementString() { ); if ( comment != null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "select " ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java index 534c297b32b3..8b9623afdec0 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java @@ -8,7 +8,7 @@ /** - * An old Sybase-style join (beforeQuery Sybase supported the ANSI style "inner join" etc syntax) + * An old Sybase-style join (before Sybase supported the ANSI style "inner join" etc syntax) * This is needed for Sybase 11.9.2 and earlier, using the HQL 2.* syntax with Collections. * * @author Colm O' Flaherty @@ -47,6 +47,49 @@ public void addJoin(String tableName, String alias, String[] fkColumns, String[] } } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType) { + + addCrossJoin( tableName, alias ); + + if ( fkColumns.length > 1 ) { + afterWhere.append( "(" ); + } + for ( int i = 0; i < fkColumns.length; i++ ) { + afterWhere.append( " and " ); + for ( int j = 0; j < fkColumns[i].length; j++ ) { + //full joins are not supported.. yet! + if ( joinType == JoinType.FULL_JOIN ) { + throw new UnsupportedOperationException(); + } + + afterWhere.append( fkColumns[i][j] ) + .append( " " ); + + if ( joinType == JoinType.LEFT_OUTER_JOIN ) { + afterWhere.append( '*' ); + } + afterWhere.append( '=' ); + if ( joinType == JoinType.RIGHT_OUTER_JOIN ) { + afterWhere.append( "*" ); + } + + afterWhere.append( " " ) + .append( alias ) + .append( '.' ) + .append( pkColumns[j] ); + if ( j < fkColumns[i].length - 1 ) { + afterWhere.append( " and " ); + } + } + if ( i < fkColumns.length - 1 ) { + afterWhere.append( " or " ); + } + } + if ( fkColumns.length > 1 ) { + afterWhere.append( ")" ); + } + } + public String toFromFragmentString() { return afterFrom.toString(); } @@ -109,4 +152,15 @@ public void addJoin( addJoin( tableName, alias, fkColumns, pkColumns, joinType ); addCondition( on ); } + + public void addJoin( + String tableName, + String alias, + String[][] fkColumns, + String[] pkColumns, + JoinType joinType, + String on) { + addJoin( tableName, alias, fkColumns, pkColumns, joinType ); + addCondition( on ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Template.java b/hibernate-core/src/main/java/org/hibernate/sql/Template.java index 419563474834..a22867658fb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Template.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Template.java @@ -88,6 +88,16 @@ public final class Template { private Template() {} + public static String renderTransformerReadFragment( + String fragment, + String... columnNames) { + // NOTE : would need access to SessionFactoryImplementor to make this configurable + for ( String columnName : columnNames ) { + fragment = fragment.replace( columnName, TEMPLATE + '.' + columnName ); + } + return fragment; + } + public static String renderWhereStringTemplate(String sqlWhereString, Dialect dialect, SQLFunctionRegistry functionRegistry) { return renderWhereStringTemplate(sqlWhereString, TEMPLATE, dialect, functionRegistry); } @@ -736,7 +746,7 @@ private static boolean isType(String lcToken, Dialect dialect) { } private static boolean isFunction(String lcToken, String nextToken, SQLFunctionRegistry functionRegistry) { - // checking for "(" is currently redundant because it is checked beforeQuery getting here; + // checking for "(" is currently redundant because it is checked before getting here; // doing the check anyhow, in case that earlier check goes away; if ( "(".equals( nextToken ) ) { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Update.java b/hibernate-core/src/main/java/org/hibernate/sql/Update.java index 93611d73a4fe..67ac8d8faef5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Update.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Update.java @@ -166,7 +166,7 @@ public Update setWhere(String where) { public String toStatementString() { StringBuilder buf = new StringBuilder( (columns.size() * 15) + tableName.length() + 10 ); if ( comment!=null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "update " ).append( tableName ).append( " set " ); boolean assignmentsAppended = false; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java index 8b888d8b8265..ea0eb25d45bf 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java @@ -9,6 +9,7 @@ import org.hibernate.NullPrecedence; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.ast.util.ASTPrinter; +import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.hibernate.internal.util.StringHelper; import org.jboss.logging.Logger; @@ -25,7 +26,6 @@ public class OrderByFragmentRenderer extends GeneratedOrderByFragmentRenderer { private static final Logger LOG = Logger.getLogger( OrderByFragmentRenderer.class.getName() ); - private static final ASTPrinter printer = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class ); private final SessionFactoryImplementor sessionFactory; @@ -56,7 +56,7 @@ public void traceIn(String ruleName, AST tree) { private String buildTraceNodeName(AST tree) { return tree == null ? "???" - : tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; + : tree.getText() + " [" + TokenPrinters.ORDERBY_FRAGMENT_PRINTER.getTokenTypeName( tree.getType() ) + "]"; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java index 21c3305e77f9..69ae897682e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java @@ -11,6 +11,7 @@ import org.hibernate.HibernateException; import org.hibernate.hql.internal.ast.util.ASTPrinter; +import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.jboss.logging.Logger; @@ -36,6 +37,8 @@ public class OrderByFragmentTranslator { * @return The translation. */ public static OrderByTranslation translate(TranslationContext context, String fragment) { + LOG.tracef( "Beginning parsing of order-by fragment : " + fragment ); + GeneratedOrderByLexer lexer = new GeneratedOrderByLexer( new StringReader( fragment ) ); // Perform the parsing (and some analysis/resolution). Another important aspect is the collection @@ -53,8 +56,7 @@ public static OrderByTranslation translate(TranslationContext context, String fr } if ( LOG.isTraceEnabled() ) { - ASTPrinter printer = new ASTPrinter( OrderByTemplateTokenTypes.class ); - LOG.trace( printer.showAsString( parser.getAST(), "--- {order-by fragment} ---" ) ); + LOG.trace( TokenPrinters.ORDERBY_FRAGMENT_PRINTER.showAsString( parser.getAST(), "--- {order-by fragment} ---" ) ); } // Render the parsed tree to text. diff --git a/hibernate-core/src/main/java/org/hibernate/stat/CacheRegionStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/CacheRegionStatistics.java new file mode 100755 index 000000000000..eebb576532df --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/CacheRegionStatistics.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat; + +import java.io.Serializable; + +/** + * Second level cache statistics of a specific region + * + * @author Gavin King + */ +public interface CacheRegionStatistics extends Serializable { + /** + * The value returned from {@link #getElementCountInMemory}, + * {@link #getElementCountOnDisk()} and {@link #getSizeInMemory()} + * for cache providers that do not support such "extended" statistics. + */ + long NO_EXTENDED_STAT_SUPPORT_RETURN = Long.MIN_VALUE; + + String getRegionName(); + + /** + * The number of cache puts into the region since the last Statistics + * clearing + */ + long getPutCount(); + + /** + * The number of successful cache look-ups against the region since the + * last Statistics clearing + */ + long getHitCount(); + + /** + * The number of unsuccessful cache look-ups against the region since the + * last Statistics clearing + */ + long getMissCount(); + + /** + * The number of elements currently in memory within the cache provider. + * + * This is an optional value contingent upon the underlying cache provider + * providing extended stats support via + * {@link org.hibernate.cache.spi.ExtendedStatisticsSupport}. If the provider + * does not support extended stats, {@link #NO_EXTENDED_STAT_SUPPORT_RETURN} + * is returned instead. + */ + long getElementCountInMemory(); + + /** + * The number of elements currently stored to disk within the cache provider. + * + * This is an optional value contingent upon the underlying cache provider + * providing extended stats support via + * {@link org.hibernate.cache.spi.ExtendedStatisticsSupport}. If the provider + * does not support extended stats, {@link #NO_EXTENDED_STAT_SUPPORT_RETURN} + * is returned instead. + */ + long getElementCountOnDisk(); + + /** + * The size that the in-memory elements take up within the cache provider. + * + * This is an optional value contingent upon the underlying cache provider + * providing extended stats support via + * {@link org.hibernate.cache.spi.ExtendedStatisticsSupport}. If the provider + * does not support extended stats, {@link #NO_EXTENDED_STAT_SUPPORT_RETURN} + * is returned instead. + */ + long getSizeInMemory(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/CacheableDataStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/CacheableDataStatistics.java new file mode 100644 index 000000000000..a9a9bfe8664d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/CacheableDataStatistics.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.stat; + +/** + * @author Steve Ebersole + */ +public interface CacheableDataStatistics { + long NOT_CACHED_COUNT = Long.MIN_VALUE; + + /** + * The name of the region where this data is cached. + */ + String getCacheRegionName(); + + /** + * The number of times this data has been into its configured cache region + * since the last Statistics clearing + */ + long getCachePutCount(); + + /** + * The number of successful cache look-ups for this data from its + * configured cache region since the last Statistics clearing + */ + long getCacheHitCount(); + + /** + * The number of unsuccessful cache look-ups for this data from its + * configured cache region since the last Statistics clearing + */ + long getCacheMissCount(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/CollectionStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/CollectionStatistics.java index 35395d33a2de..d2a7b85fe73e 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/CollectionStatistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/CollectionStatistics.java @@ -12,16 +12,36 @@ * Collection related statistics * * @author Gavin King + * @author Steve Ebersole */ -public interface CollectionStatistics extends Serializable { - +public interface CollectionStatistics extends CacheableDataStatistics, Serializable { + /** + * Number of times (since last Statistics clearing) this collection + * has been loaded + */ long getLoadCount(); + /** + * Number of times (since last Statistics clearing) this collection + * has been fetched + */ long getFetchCount(); + /** + * Number of times (since last Statistics clearing) this collection + * has been recreated (rows potentially deleted and then rows (re-)inserted) + */ long getRecreateCount(); + /** + * Number of times (since last Statistics clearing) this collection + * has been removed + */ long getRemoveCount(); + /** + * Number of times (since last Statistics clearing) this collection + * has been updated + */ long getUpdateCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/EntityStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/EntityStatistics.java index b881972446f0..ae8456ea238c 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/EntityStatistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/EntityStatistics.java @@ -12,18 +12,42 @@ * Entity related statistics * * @author Gavin King + * @author Steve Ebersole */ -public interface EntityStatistics extends Serializable { +public interface EntityStatistics extends CacheableDataStatistics, Serializable { + /** + * Number of times (since last Statistics clearing) this entity + * has been deleted + */ long getDeleteCount(); + /** + * Number of times (since last Statistics clearing) this entity + * has been inserted + */ long getInsertCount(); - long getLoadCount(); - + /** + * Number of times (since last Statistics clearing) this entity + * has been updated + */ long getUpdateCount(); + /** + * Number of times (since last Statistics clearing) this entity + * has been loaded + */ + long getLoadCount(); + + /** + * Number of times (since last Statistics clearing) this entity + * has been fetched + */ long getFetchCount(); + /** + * Number of times (since last Statistics clearing) this entity + * has experienced an optimistic lock failure. + */ long getOptimisticFailureCount(); - } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdCacheStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdCacheStatistics.java index 30509f88cd5d..c806c2b6493f 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdCacheStatistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdCacheStatistics.java @@ -1,35 +1,53 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.stat; import java.io.Serializable; +import java.util.Collections; import java.util.Map; /** - * NaturalId query statistics - *

    - * Note that for a cached natural id, the cache miss is equals to the db count + * @deprecated (since 5.3) Use {@link NaturalIdStatistics} - unfortunately the + * old statistics contracts exposed these by region name, rather than the name of + * the entity defining the natural-id * - * @author Eric Dalquist + * @author Steve Ebersole */ +@Deprecated public interface NaturalIdCacheStatistics extends Serializable { + /** + * Number of times (since last Statistics clearing) the "natural id + * resolution" query has been executed + */ + long getExecutionCount(); + + /** + * The average amount of time it takes (since last Statistics clearing) for + * the execution of this "natural id resolution" query + */ + long getExecutionAvgTime(); + + /** + * The maximum amount of time it takes (since last Statistics clearing) for + * the execution of this "natural id resolution" query + */ + long getExecutionMaxTime(); + + /** + * The minimum amount of time it takes (since last Statistics clearing) for + * the execution of this "natural id resolution" query + */ + long getExecutionMinTime(); + long getHitCount(); long getMissCount(); long getPutCount(); - - long getExecutionCount(); - - long getExecutionAvgTime(); - - long getExecutionMaxTime(); - - long getExecutionMinTime(); long getElementCountInMemory(); @@ -37,5 +55,7 @@ public interface NaturalIdCacheStatistics extends Serializable { long getSizeInMemory(); - Map getEntries(); + default Map getEntries() { + return Collections.emptyMap(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdStatistics.java new file mode 100644 index 000000000000..7e086b49f9fb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdStatistics.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 . + */ +package org.hibernate.stat; + +import java.io.Serializable; + +/** + * Statistics pertaining to the execution of the "natural id resolution" query. + * + * @apiNote The natural-id resolution data is allowed to be stored in the + * second level cache, and if so stored will have available caching stats as + * well available via {@link Statistics#getDomainDataRegionStatistics} using the + * configured region name + * + * todo (6.0) : consider a means to get the cache Region statistics for: + * 1) an entity by name + * 2) a collection by role + * 3) a natural-id by entity name + * + * @author Eric Dalquist + * @author Steve Ebersole + */ +public interface NaturalIdStatistics extends CacheableDataStatistics, Serializable { + /** + * Number of times (since last Statistics clearing) the "natural id + * resolution" query has been executed + */ + long getExecutionCount(); + + /** + * The average amount of time it takes (since last Statistics clearing) for + * the execution of this "natural id resolution" query + */ + long getExecutionAvgTime(); + + /** + * The maximum amount of time it takes (since last Statistics clearing) for + * the execution of this "natural id resolution" query + */ + long getExecutionMaxTime(); + + /** + * The minimum amount of time it takes (since last Statistics clearing) for + * the execution of this "natural id resolution" query + */ + long getExecutionMinTime(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/QueryStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/QueryStatistics.java index 2380ac790050..2e892d44c1e3 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/QueryStatistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/QueryStatistics.java @@ -14,25 +14,65 @@ * Note that for a cached query, the cache miss is equals to the db count * * @author Gavin King + * @author Steve Ebersole */ public interface QueryStatistics extends Serializable { + /** + * How many times has this query been executed? + */ long getExecutionCount(); - long getCacheHitCount(); - - long getCachePutCount(); - - long getCacheMissCount(); - + /** + * How many ResultSet rows have been processed for this query ? + */ long getExecutionRowCount(); + /** + * What is the average amount time taken to execute this query? + */ long getExecutionAvgTime(); + /** + * What is the max amount time taken to execute this query? + */ long getExecutionMaxTime(); + /** + * What is the min amount time taken to execute this query? + */ long getExecutionMinTime(); + /** + * How long, cumulatively, have all executions of this query taken? + */ long getExecutionTotalTime(); double getExecutionAvgTimeAsDouble(); + + /** + * The number of cache hits for this query. + * + * @apiNote Note that a query can be saved into different + * regions at different times. This value represents the + * sum total across all of those regions + */ + long getCacheHitCount(); + + /** + * The number of cache misses for this query + * + * @apiNote Note that a query can be saved into different + * regions at different times. This value represents the + * sum total across all of those regions + */ + long getCacheMissCount(); + + /** + * The number of cache puts for this query + * + * @apiNote Note that a query can be saved into different + * regions at different times. This value represents the + * sum total across all of those regions + */ + long getCachePutCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/SecondLevelCacheStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/SecondLevelCacheStatistics.java index 1854c0c09277..eee4546edc2c 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/SecondLevelCacheStatistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/SecondLevelCacheStatistics.java @@ -1,32 +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 . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.stat; -import java.io.Serializable; +import java.util.Collections; import java.util.Map; /** - * Second level cache statistics of a specific region + * Cache statistics pertaining to a specific data region * * @author Gavin King + * @author Steve Ebersole + * + * @deprecated Use {@link CacheRegionStatistics} instead */ -public interface SecondLevelCacheStatistics extends Serializable { - - long getHitCount(); - - long getMissCount(); - - long getPutCount(); - - long getElementCountInMemory(); - - long getElementCountOnDisk(); - - long getSizeInMemory(); - - Map getEntries(); +@Deprecated +public interface SecondLevelCacheStatistics extends CacheRegionStatistics { + default Map getEntries() { + return Collections.emptyMap(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/SessionStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/SessionStatistics.java index 749758a53d5c..328c7467a4d8 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/SessionStatistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/SessionStatistics.java @@ -17,21 +17,21 @@ public interface SessionStatistics { /** * Get the number of entity instances associated with the session */ - public int getEntityCount(); + int getEntityCount(); /** * Get the number of collection instances associated with the session */ - public int getCollectionCount(); + int getCollectionCount(); /** * Get the set of all EntityKeys * @see org.hibernate.engine.spi.EntityKey */ - public Set getEntityKeys(); + Set getEntityKeys(); /** * Get the set of all CollectionKeys * @see org.hibernate.engine.spi.CollectionKey */ - public Set getCollectionKeys(); + Set getCollectionKeys(); } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java b/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java index bba558232abd..226dd590e8af 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java @@ -14,10 +14,26 @@ * @author Emmanuel Bernard */ public interface Statistics { + + /** + * Are statistics enabled + */ + boolean isStatisticsEnabled(); + + /** + * Enable statistics logs (this is a dynamic parameter) + */ + void setStatisticsEnabled(boolean b); /** * reset all statistics */ - public void clear(); + void clear(); + + /** + * log in info level the main statistics + */ + void logSummary(); + /** * find entity statistics per name @@ -25,234 +41,323 @@ public interface Statistics { * @param entityName entity name * @return EntityStatistics object */ - public EntityStatistics getEntityStatistics(String entityName); + EntityStatistics getEntityStatistics(String entityName); + /** * Get collection statistics per role * * @param role collection role * @return CollectionStatistics */ - public CollectionStatistics getCollectionStatistics(String role); + CollectionStatistics getCollectionStatistics(String role); - /** - * Second level cache statistics per region - * - * @param regionName region name - * @return SecondLevelCacheStatistics - */ - public SecondLevelCacheStatistics getSecondLevelCacheStatistics(String regionName); - - /** - * Natural id cache statistics per region - * - * @param regionName region name + /** + * Natural id resolution query statistics for an entity type + * + * @param entityName The entity name that is the root of the hierarchy containing the + * natural id * @return NaturalIdCacheStatistics */ - public NaturalIdCacheStatistics getNaturalIdCacheStatistics(String regionName); + NaturalIdStatistics getNaturalIdStatistics(String entityName); - /** + /** * Query statistics from query string (HQL or SQL) - * + * * @param queryString query string * @return QueryStatistics */ - public QueryStatistics getQueryStatistics(String queryString); + QueryStatistics getQueryStatistics(String queryString); + + /** + * Second level cache statistics per domain data (entity, collection, natural-id) region + * + * @param regionName The unqualified region name + * + * @return The stats for the named region, or {@code null} if the second level cache is + * not enabled + * + * @throws IllegalArgumentException if the region name could not be resolved + */ + CacheRegionStatistics getDomainDataRegionStatistics(String regionName); + + /** + * Second level cache statistics per query region + * + * @param regionName The unqualified region name + * + * @return Stats for the named region, or {@code null} if (1) query result caching is + * not enabled or (2) no query region exists with that name + */ + CacheRegionStatistics getQueryRegionStatistics(String regionName); + + /** + * Get statistics for either a domain-data or query-result region - this + * method checks both, preferring domain data region if one. Think of it + * as a cascading check to:

      + *
    1. {@link #getDomainDataRegionStatistics}
    2. + *
    3. {@link #getQueryRegionStatistics}
    4. + *
    + * Note that returning null is preferred here over throwing an exception when + * no region exists with that name. + * + * @param regionName The unqualified region name + * + * @return Stats for the named region, or {@code null} if no such region exists + */ + CacheRegionStatistics getCacheRegionStatistics(String regionName); /** * Get global number of entity deletes * @return entity deletion count */ - public long getEntityDeleteCount(); + long getEntityDeleteCount(); /** * Get global number of entity inserts * @return entity insertion count */ - public long getEntityInsertCount(); + long getEntityInsertCount(); /** * Get global number of entity loads * @return entity load (from DB) */ - public long getEntityLoadCount(); + long getEntityLoadCount(); + /** * Get global number of entity fetchs * @return entity fetch (from DB) */ - public long getEntityFetchCount(); + long getEntityFetchCount(); /** * Get global number of entity updates * @return entity update */ - public long getEntityUpdateCount(); + long getEntityUpdateCount(); /** * Get global number of executed queries * @return query execution count */ - public long getQueryExecutionCount(); + long getQueryExecutionCount(); /** * Get the time in milliseconds of the slowest query. */ - public long getQueryExecutionMaxTime(); + long getQueryExecutionMaxTime(); + /** * Get the query string for the slowest query. */ - public String getQueryExecutionMaxTimeQueryString(); + String getQueryExecutionMaxTimeQueryString(); /** * Get the global number of cached queries successfully retrieved from cache */ - public long getQueryCacheHitCount(); + long getQueryCacheHitCount(); + /** * Get the global number of cached queries *not* found in cache */ - public long getQueryCacheMissCount(); + long getQueryCacheMissCount(); + /** * Get the global number of cacheable queries put in cache */ - public long getQueryCachePutCount(); + long getQueryCachePutCount(); + /** * Get the global number of naturalId queries executed against the database */ - public long getNaturalIdQueryExecutionCount(); + long getNaturalIdQueryExecutionCount(); + /** * Get the global maximum query time for naturalId queries executed against the database */ - public long getNaturalIdQueryExecutionMaxTime(); + long getNaturalIdQueryExecutionMaxTime(); + /** * Get the region for the maximum naturalId query time */ - public String getNaturalIdQueryExecutionMaxTimeRegion(); + String getNaturalIdQueryExecutionMaxTimeRegion(); + + String getNaturalIdQueryExecutionMaxTimeEntity(); + /** * Get the global number of cached naturalId lookups successfully retrieved from cache */ - public long getNaturalIdCacheHitCount(); + long getNaturalIdCacheHitCount(); + /** * Get the global number of cached naturalId lookups *not* found in cache */ - public long getNaturalIdCacheMissCount(); + long getNaturalIdCacheMissCount(); + /** * Get the global number of cacheable naturalId lookups put in cache */ - public long getNaturalIdCachePutCount(); + long getNaturalIdCachePutCount(); + /** * Get the global number of timestamps successfully retrieved from cache */ - public long getUpdateTimestampsCacheHitCount(); + long getUpdateTimestampsCacheHitCount(); + /** * Get the global number of tables for which no update timestamps was *not* found in cache */ - public long getUpdateTimestampsCacheMissCount(); + long getUpdateTimestampsCacheMissCount(); + /** * Get the global number of timestamps put in cache */ - public long getUpdateTimestampsCachePutCount(); + long getUpdateTimestampsCachePutCount(); + /** * Get the global number of flush executed by sessions (either implicit or explicit) */ - public long getFlushCount(); + long getFlushCount(); + /** * Get the global number of connections asked by the sessions * (the actual number of connections used may be much smaller depending * whether you use a connection pool or not) */ - public long getConnectCount(); + long getConnectCount(); + /** * Global number of cacheable entities/collections successfully retrieved from the cache */ - public long getSecondLevelCacheHitCount(); + long getSecondLevelCacheHitCount(); + /** * Global number of cacheable entities/collections not found in the cache and loaded from the database. */ - public long getSecondLevelCacheMissCount(); + long getSecondLevelCacheMissCount(); + /** * Global number of cacheable entities/collections put in the cache */ - public long getSecondLevelCachePutCount(); + long getSecondLevelCachePutCount(); + /** * Global number of sessions closed */ - public long getSessionCloseCount(); + long getSessionCloseCount(); + /** * Global number of sessions opened */ - public long getSessionOpenCount(); + long getSessionOpenCount(); + /** * Global number of collections loaded */ - public long getCollectionLoadCount(); + long getCollectionLoadCount(); + /** * Global number of collections fetched */ - public long getCollectionFetchCount(); + long getCollectionFetchCount(); + /** * Global number of collections updated */ - public long getCollectionUpdateCount(); + long getCollectionUpdateCount(); + /** * Global number of collections removed */ //even on inverse="true" - public long getCollectionRemoveCount(); + long getCollectionRemoveCount(); + /** * Global number of collections recreated */ - public long getCollectionRecreateCount(); - /** - * @return start time in ms (JVM standards {@link System#currentTimeMillis()}) - */ - public long getStartTime(); - /** - * log in info level the main statistics - */ - public void logSummary(); - /** - * Are statistics logged - */ - public boolean isStatisticsEnabled(); + long getCollectionRecreateCount(); + /** - * Enable statistics logs (this is a dynamic parameter) + * The milliseconds (JVM standard {@link System#currentTimeMillis()}) from + * which all statistics accessed since the initial creation of this Statistics + * instance or the last {@link #clear()} + * + * @apiNote This time(stamp) is */ - public void setStatisticsEnabled(boolean b); + long getStartTime(); /** * Get all executed query strings */ - public String[] getQueries(); + String[] getQueries(); + /** * Get the names of all entities */ - public String[] getEntityNames(); + String[] getEntityNames(); + /** * Get the names of all collection roles */ - public String[] getCollectionRoleNames(); + String[] getCollectionRoleNames(); + /** - * Get all second-level cache region names + * Get all second-level cache region names. Note: for backwards + * compatibility this method returns just the names of regions + * storing domain data, not query result regions */ - public String[] getSecondLevelCacheRegionNames(); + String[] getSecondLevelCacheRegionNames(); + /** * The number of transactions we know to have been successful */ - public long getSuccessfulTransactionCount(); + long getSuccessfulTransactionCount(); + /** * The number of transactions we know to have completed */ - public long getTransactionCount(); + long getTransactionCount(); + /** * The number of prepared statements that were acquired */ - public long getPrepareStatementCount(); + long getPrepareStatementCount(); + /** * The number of prepared statements that were released */ - public long getCloseStatementCount(); + long getCloseStatementCount(); + /** * The number of StaleObjectStateExceptions * that occurred */ - public long getOptimisticFailureCount(); + long getOptimisticFailureCount(); + + + /** + * Second level cache statistics per region + * + * @param regionName qualified region name + * + * @return SecondLevelCacheStatistics or {@code null} if the second level cache is not enabled + * + * @throws IllegalArgumentException if the region name could not be resolved + * + * @deprecated (since 5.3) Use {@link #getDomainDataRegionStatistics} instead + */ + @Deprecated + SecondLevelCacheStatistics getSecondLevelCacheStatistics(String regionName); + + /** + * Natural id cache statistics per region + * + * @param regionName region name + * @return NaturalIdCacheStatistics + * + * @deprecated (since 5.3) Use {@link #getNaturalIdStatistics} or + * {@link @getDomainDataRegionStatistics} instead depending on need + */ + @Deprecated + NaturalIdCacheStatistics getNaturalIdCacheStatistics(String regionName); } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/AbstractCacheableDataStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/AbstractCacheableDataStatistics.java new file mode 100644 index 000000000000..91956fab64d1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/AbstractCacheableDataStatistics.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.stat.internal; + +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Supplier; + +import org.hibernate.cache.spi.Region; +import org.hibernate.stat.CacheableDataStatistics; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractCacheableDataStatistics implements CacheableDataStatistics { + private static final Logger log = Logger.getLogger( AbstractCacheableDataStatistics.class ); + + private final String cacheRegionName; + private final LongAdder cacheHitCount; + private final LongAdder cacheMissCount; + private final LongAdder cachePutCount; + + public AbstractCacheableDataStatistics(Supplier regionSupplier) { + final Region region = regionSupplier.get(); + if ( region == null ) { + this.cacheRegionName = null; + this.cacheHitCount = null; + this.cacheMissCount = null; + this.cachePutCount = null; + } + else { + this.cacheRegionName = region.getName(); + this.cacheHitCount = new LongAdder(); + this.cacheMissCount = new LongAdder(); + this.cachePutCount = new LongAdder(); + } + } + + @Override + public String getCacheRegionName() { + return cacheRegionName; + } + + public long getCacheHitCount() { + if ( cacheRegionName == null ) { + return NOT_CACHED_COUNT; + } + + return cacheHitCount.sum(); + } + + public long getCachePutCount() { + if ( cacheRegionName == null ) { + return NOT_CACHED_COUNT; + } + + return cachePutCount.sum(); + } + + public long getCacheMissCount() { + if ( cacheRegionName == null ) { + return NOT_CACHED_COUNT; + } + + return cacheMissCount.sum(); + } + + public void incrementCacheHitCount() { + if ( cacheRegionName == null ) { + throw new IllegalStateException( "Illegal attempt to increment cache hit count for non-cached data" ); + } + + cacheHitCount.increment(); + } + + public void incrementCacheMissCount() { + if ( cacheRegionName == null ) { + throw new IllegalStateException( "Illegal attempt to increment cache miss count for non-cached data" ); + } + + cacheMissCount.increment(); + } + + public void incrementCachePutCount() { + if ( cacheRegionName == null ) { + throw new IllegalStateException( "Illegal attempt to increment cache put count for non-cached data" ); + } + + cachePutCount.increment(); + } + + protected void appendCacheStats(StringBuilder buf) { + buf.append( ",cacheRegion=" ).append( cacheRegionName ); + + if ( cacheRegionName == null ) { + return; + } + + buf.append( ",cacheHitCount=" ).append( getCacheHitCount() ) + .append( ",cacheMissCount=" ).append( getCacheMissCount() ) + .append( ",cachePutCount=" ).append( getCachePutCount() ); + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/CacheRegionStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/CacheRegionStatisticsImpl.java new file mode 100644 index 000000000000..d393f2a36ef2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/CacheRegionStatisticsImpl.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import java.io.Serializable; +import java.util.concurrent.atomic.LongAdder; + +import org.hibernate.cache.spi.ExtendedStatisticsSupport; +import org.hibernate.cache.spi.Region; +import org.hibernate.stat.CacheRegionStatistics; +import org.hibernate.stat.SecondLevelCacheStatistics; + +/** + * Second level cache statistics of a specific region + * + * @author Alex Snaps + */ +public class CacheRegionStatisticsImpl implements CacheRegionStatistics, SecondLevelCacheStatistics, Serializable { + private final transient Region region; + + private final LongAdder hitCount = new LongAdder(); + private final LongAdder missCount = new LongAdder(); + private final LongAdder putCount = new LongAdder(); + + CacheRegionStatisticsImpl(Region region) { + this.region = region; + } + + @Override + public String getRegionName() { + return region.getName(); + } + + @Override + public long getHitCount() { + return hitCount.sum(); + } + + @Override + public long getMissCount() { + return missCount.sum(); + } + + @Override + public long getPutCount() { + return putCount.sum(); + } + + @Override + public long getElementCountInMemory() { + if ( region instanceof ExtendedStatisticsSupport ) { + return ( (ExtendedStatisticsSupport) region ).getElementCountInMemory(); + } + return NO_EXTENDED_STAT_SUPPORT_RETURN; + } + + @Override + public long getElementCountOnDisk() { + if ( region instanceof ExtendedStatisticsSupport ) { + return ( (ExtendedStatisticsSupport) region ).getElementCountOnDisk(); + } + return NO_EXTENDED_STAT_SUPPORT_RETURN; + } + + @Override + public long getSizeInMemory() { + if ( region instanceof ExtendedStatisticsSupport ) { + return ( (ExtendedStatisticsSupport) region ).getSizeInMemory(); + } + return NO_EXTENDED_STAT_SUPPORT_RETURN; + } + + void incrementHitCount() { + hitCount.increment(); + } + + void incrementMissCount() { + missCount.increment(); + } + + void incrementPutCount() { + putCount.increment(); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder().append( "CacheRegionStatistics" ) + .append( "[region=").append( region.getName() ) + .append( ",hitCount=").append( this.hitCount ) + .append( ",missCount=").append( this.missCount ) + .append( ",putCount=").append( this.putCount ) + .append( ",elementCountInMemory=" ).append( this.getElementCountInMemory() ) + .append( ",elementCountOnDisk=" ).append( this.getElementCountOnDisk() ) + .append( ",sizeInMemory=" ).append( this.getSizeInMemory() ) + .append( ']' ); + return buf.toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/CollectionStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/CollectionStatisticsImpl.java new file mode 100644 index 000000000000..49f8e7822855 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/CollectionStatisticsImpl.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import java.io.Serializable; +import java.util.concurrent.atomic.LongAdder; + +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.stat.CollectionStatistics; + +/** + * Collection related statistics + * + * @author Alex Snaps + */ +public class CollectionStatisticsImpl extends AbstractCacheableDataStatistics implements CollectionStatistics, Serializable { + + private final String collectionRole; + private final LongAdder loadCount = new LongAdder(); + private final LongAdder fetchCount = new LongAdder(); + private final LongAdder updateCount = new LongAdder(); + private final LongAdder removeCount = new LongAdder(); + private final LongAdder recreateCount = new LongAdder(); + + CollectionStatisticsImpl(CollectionPersister persister) { + super( + () -> persister.getCacheAccessStrategy() != null + ? persister.getCacheAccessStrategy().getRegion() + : null + ); + + this.collectionRole = persister.getRole(); + } + + public long getLoadCount() { + return loadCount.sum(); + } + + public long getFetchCount() { + return fetchCount.sum(); + } + + public long getRecreateCount() { + return recreateCount.sum(); + } + + public long getRemoveCount() { + return removeCount.sum(); + } + + public long getUpdateCount() { + return updateCount.sum(); + } + + void incrementLoadCount() { + loadCount.increment(); + } + + void incrementFetchCount() { + fetchCount.increment(); + } + + void incrementUpdateCount() { + updateCount.increment(); + } + + void incrementRecreateCount() { + recreateCount.increment(); + } + + void incrementRemoveCount() { + removeCount.increment(); + } + + public String toString() { + final StringBuilder buffer = new StringBuilder() + .append( "CollectionStatistics" ) + .append( "[collectionRole=" ).append( collectionRole ) + .append( ",loadCount=" ).append( this.loadCount ) + .append( ",fetchCount=" ).append( this.fetchCount ) + .append( ",recreateCount=" ).append( this.recreateCount ) + .append( ",removeCount=" ).append( this.removeCount ) + .append( ",updateCount=" ).append( this.updateCount ); + appendCacheStats( buffer ); + return buffer.append(']').toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentCollectionStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentCollectionStatisticsImpl.java deleted file mode 100644 index 59d099e1d493..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentCollectionStatisticsImpl.java +++ /dev/null @@ -1,80 +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.stat.internal; - -import java.util.concurrent.atomic.AtomicLong; - -import org.hibernate.stat.CollectionStatistics; - -/** - * Collection related statistics - * - * @author Alex Snaps - */ -public class ConcurrentCollectionStatisticsImpl extends CategorizedStatistics implements CollectionStatistics { - ConcurrentCollectionStatisticsImpl(String role) { - super(role); - } - - private AtomicLong loadCount = new AtomicLong(); - private AtomicLong fetchCount = new AtomicLong(); - private AtomicLong updateCount = new AtomicLong(); - private AtomicLong removeCount = new AtomicLong(); - private AtomicLong recreateCount = new AtomicLong(); - - public long getLoadCount() { - return loadCount.get(); - } - - public long getFetchCount() { - return fetchCount.get(); - } - - public long getRecreateCount() { - return recreateCount.get(); - } - - public long getRemoveCount() { - return removeCount.get(); - } - - public long getUpdateCount() { - return updateCount.get(); - } - - public String toString() { - return new StringBuilder() - .append("CollectionStatistics") - .append("[loadCount=").append(this.loadCount) - .append(",fetchCount=").append(this.fetchCount) - .append(",recreateCount=").append(this.recreateCount) - .append(",removeCount=").append(this.removeCount) - .append(",updateCount=").append(this.updateCount) - .append(']') - .toString(); - } - - void incrementLoadCount() { - loadCount.getAndIncrement(); - } - - void incrementFetchCount() { - fetchCount.getAndIncrement(); - } - - void incrementUpdateCount() { - updateCount.getAndIncrement(); - } - - void incrementRecreateCount() { - recreateCount.getAndIncrement(); - } - - void incrementRemoveCount() { - removeCount.getAndIncrement(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentEntityStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentEntityStatisticsImpl.java deleted file mode 100644 index 00a289c835b5..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentEntityStatisticsImpl.java +++ /dev/null @@ -1,91 +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.stat.internal; - -import java.util.concurrent.atomic.AtomicLong; - -import org.hibernate.stat.EntityStatistics; - -/** - * Entity related statistics - * - * @author Alex Snaps - */ -public class ConcurrentEntityStatisticsImpl extends CategorizedStatistics implements EntityStatistics { - - ConcurrentEntityStatisticsImpl(String name) { - super(name); - } - - private AtomicLong loadCount = new AtomicLong(); - private AtomicLong updateCount = new AtomicLong(); - private AtomicLong insertCount = new AtomicLong(); - private AtomicLong deleteCount = new AtomicLong(); - private AtomicLong fetchCount = new AtomicLong(); - private AtomicLong optimisticFailureCount = new AtomicLong(); - - public long getDeleteCount() { - return deleteCount.get(); - } - - public long getInsertCount() { - return insertCount.get(); - } - - public long getLoadCount() { - return loadCount.get(); - } - - public long getUpdateCount() { - return updateCount.get(); - } - - public long getFetchCount() { - return fetchCount.get(); - } - - public long getOptimisticFailureCount() { - return optimisticFailureCount.get(); - } - - public String toString() { - return new StringBuilder() - .append("EntityStatistics") - .append("[loadCount=").append(this.loadCount) - .append(",updateCount=").append(this.updateCount) - .append(",insertCount=").append(this.insertCount) - .append(",deleteCount=").append(this.deleteCount) - .append(",fetchCount=").append(this.fetchCount) - .append(",optimisticLockFailureCount=").append(this.optimisticFailureCount) - .append(']') - .toString(); - } - - void incrementLoadCount() { - loadCount.getAndIncrement(); - } - - void incrementFetchCount() { - fetchCount.getAndIncrement(); - } - - void incrementUpdateCount() { - updateCount.getAndIncrement(); - } - - void incrementInsertCount() { - insertCount.getAndIncrement(); - } - - void incrementDeleteCount() { - deleteCount.getAndIncrement(); - } - - void incrementOptimisticFailureCount() { - optimisticFailureCount.getAndIncrement(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentNaturalIdCacheStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentNaturalIdCacheStatisticsImpl.java deleted file mode 100644 index 7eedbe4676f5..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentNaturalIdCacheStatisticsImpl.java +++ /dev/null @@ -1,190 +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.stat.internal; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.hibernate.cache.spi.Region; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; -import org.hibernate.stat.NaturalIdCacheStatistics; - -/** - * NaturalId cache statistics of a specific region - * - * @author Eric Dalquist - */ -public class ConcurrentNaturalIdCacheStatisticsImpl extends CategorizedStatistics implements NaturalIdCacheStatistics { - private static final long serialVersionUID = 1L; - private final transient Region region; - private final transient NaturalIdRegionAccessStrategy accessStrategy; - private final AtomicLong hitCount = new AtomicLong(); - private final AtomicLong missCount = new AtomicLong(); - private final AtomicLong putCount = new AtomicLong(); - private final AtomicLong executionCount = new AtomicLong(); - private final AtomicLong executionMaxTime = new AtomicLong(); - private final AtomicLong executionMinTime = new AtomicLong( Long.MAX_VALUE ); - private final AtomicLong totalExecutionTime = new AtomicLong(); - - private final Lock readLock; - private final Lock writeLock; - - { - final ReadWriteLock lock = new ReentrantReadWriteLock(); - this.readLock = lock.readLock(); - this.writeLock = lock.writeLock(); - } - - ConcurrentNaturalIdCacheStatisticsImpl(Region region, NaturalIdRegionAccessStrategy accessStrategy) { - super( region.getName() ); - this.region = region; - this.accessStrategy = accessStrategy; - } - - @Override - public long getHitCount() { - return this.hitCount.get(); - } - - @Override - public long getMissCount() { - return this.missCount.get(); - } - - @Override - public long getPutCount() { - return this.putCount.get(); - } - - /** - * queries executed to the DB - */ - @Override - public long getExecutionCount() { - return this.executionCount.get(); - } - - /** - * average time in ms taken by the excution of this query onto the DB - */ - @Override - public long getExecutionAvgTime() { - // We write lock here to be sure that we always calculate the average time - // with all updates from the executed applied: executionCount and totalExecutionTime - // both used in the calculation - this.writeLock.lock(); - try { - long avgExecutionTime = 0; - if ( this.executionCount.get() > 0 ) { - avgExecutionTime = this.totalExecutionTime.get() / this.executionCount.get(); - } - return avgExecutionTime; - } - finally { - this.writeLock.unlock(); - } - } - - /** - * max time in ms taken by the excution of this query onto the DB - */ - @Override - public long getExecutionMaxTime() { - return this.executionMaxTime.get(); - } - - /** - * min time in ms taken by the excution of this query onto the DB - */ - @Override - public long getExecutionMinTime() { - return this.executionMinTime.get(); - } - - @Override - public long getElementCountInMemory() { - return this.region.getElementCountInMemory(); - } - - @Override - public long getElementCountOnDisk() { - return this.region.getElementCountOnDisk(); - } - - @Override - public long getSizeInMemory() { - return this.region.getSizeInMemory(); - } - - @Override - @SuppressWarnings("unchecked") - public Map getEntries() { - final Map map = new HashMap(); - for ( Object o : this.region.toMap().entrySet() ) { - Map.Entry me = (Map.Entry) o; - map.put( accessStrategy.getNaturalIdValues(me.getKey()), me.getValue() ); - } - return map; - } - - @Override - public String toString() { - final StringBuilder buf = new StringBuilder() - .append( "NaturalIdCacheStatistics" ) - .append( "[hitCount=" ).append( this.hitCount ) - .append( ",missCount=" ).append( this.missCount ) - .append( ",putCount=" ).append( this.putCount ) - .append( ",executionCount=" ).append( this.executionCount ) - .append( ",executionAvgTime=" ).append( this.getExecutionAvgTime() ) - .append( ",executionMinTime=" ).append( this.executionMinTime ) - .append( ",executionMaxTime=" ).append( this.executionMaxTime ); - // not sure if this would ever be null but wanted to be careful - if ( this.region != null ) { - buf.append( ",elementCountInMemory=" ).append( this.getElementCountInMemory() ) - .append( ",elementCountOnDisk=" ).append( this.getElementCountOnDisk() ) - .append( ",sizeInMemory=" ).append( this.getSizeInMemory() ); - } - buf.append( ']' ); - return buf.toString(); - } - - void incrementHitCount() { - this.hitCount.getAndIncrement(); - } - - void incrementMissCount() { - this.missCount.getAndIncrement(); - } - - void incrementPutCount() { - this.putCount.getAndIncrement(); - } - - void queryExecuted(long time) { - // read lock is enough, concurrent updates are supported by the underlying type AtomicLong - // this only guards executed(long, long) to be called, when another thread is executing getExecutionAvgTime() - this.readLock.lock(); - try { - // Less chances for a context switch - //noinspection StatementWithEmptyBody - for ( long old = this.executionMinTime.get(); time < old && !this.executionMinTime.compareAndSet( old, time ); old = this.executionMinTime.get() ) { - } - //noinspection StatementWithEmptyBody - for ( long old = this.executionMaxTime.get(); time > old && !this.executionMaxTime.compareAndSet( old, time ); old = this.executionMaxTime.get() ) { - } - this.executionCount.getAndIncrement(); - this.totalExecutionTime.addAndGet( time ); - } - finally { - this.readLock.unlock(); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentQueryStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentQueryStatisticsImpl.java deleted file mode 100644 index 4216a4be8ae3..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentQueryStatisticsImpl.java +++ /dev/null @@ -1,177 +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.stat.internal; - -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.hibernate.stat.QueryStatistics; - -/** - * Query statistics (HQL and SQL) - *

    - * Note that for a cached query, the cache miss is equals to the db count - * - * @author Alex Snaps - */ -public class ConcurrentQueryStatisticsImpl extends CategorizedStatistics implements QueryStatistics { - private final AtomicLong cacheHitCount = new AtomicLong(); - private final AtomicLong cacheMissCount = new AtomicLong(); - private final AtomicLong cachePutCount = new AtomicLong(); - private final AtomicLong executionCount = new AtomicLong(); - private final AtomicLong executionRowCount = new AtomicLong(); - private final AtomicLong executionMaxTime = new AtomicLong(); - private final AtomicLong executionMinTime = new AtomicLong(Long.MAX_VALUE); - private final AtomicLong totalExecutionTime = new AtomicLong(); - - private final Lock readLock; - private final Lock writeLock; - - { - ReadWriteLock lock = new ReentrantReadWriteLock(); - readLock = lock.readLock(); - writeLock = lock.writeLock(); - } - - ConcurrentQueryStatisticsImpl(String query) { - super(query); - } - - /** - * queries executed to the DB - */ - public long getExecutionCount() { - return executionCount.get(); - } - - /** - * Queries retrieved successfully from the cache - */ - public long getCacheHitCount() { - return cacheHitCount.get(); - } - - public long getCachePutCount() { - return cachePutCount.get(); - } - - public long getCacheMissCount() { - return cacheMissCount.get(); - } - - /** - * Number of lines returned by all the executions of this query (from DB) - * For now, {@link org.hibernate.Query#iterate()} - * and {@link org.hibernate.Query#scroll()()} do not fill this statistic - * - * @return The number of rows cumulatively returned by the given query; iterate - * and scroll queries do not effect this total as their number of returned rows - * is not known at execution time. - */ - public long getExecutionRowCount() { - return executionRowCount.get(); - } - - /** - * average time in ms taken by the execution of this query onto the DB - */ - public long getExecutionAvgTime() { - return (long) getExecutionAvgTimeAsDouble(); - } - - /** - * average time in ms as double taken by the execution of this query onto the DB - */ - public double getExecutionAvgTimeAsDouble() { - // We write lock here to be sure that we always calculate the average time - // with all updates from the executed applied: executionCount and totalExecutionTime - // both used in the calculation - writeLock.lock(); - try { - double avgExecutionTime = 0; - if ( executionCount.get() > 0 ) { - avgExecutionTime = totalExecutionTime.get() / (double) executionCount - .get(); - } - return avgExecutionTime; - } - finally { - writeLock.unlock(); - } - } - - /** - * max time in ms taken by the execution of this query onto the DB - */ - public long getExecutionMaxTime() { - return executionMaxTime.get(); - } - - /** - * min time in ms taken by the execution of this query onto the DB - */ - public long getExecutionMinTime() { - return executionMinTime.get(); - } - - /** - * total time in ms taken by the execution of this query onto the DB - */ - public long getExecutionTotalTime() { - return totalExecutionTime.get(); - } - - /** - * add statistics report of a DB query - * - * @param rows rows count returned - * @param time time taken - */ - void executed(long rows, long time) { - // read lock is enough, concurrent updates are supported by the underlying type AtomicLong - // this only guards executed(long, long) to be called, when another thread is executing getExecutionAvgTime() - readLock.lock(); - try { - // Less chances for a context switch - for (long old = executionMinTime.get(); (time < old) && !executionMinTime.compareAndSet(old, time); old = executionMinTime.get()) {} - for (long old = executionMaxTime.get(); (time > old) && !executionMaxTime.compareAndSet(old, time); old = executionMaxTime.get()) {} - executionCount.getAndIncrement(); - executionRowCount.addAndGet(rows); - totalExecutionTime.addAndGet(time); - } - finally { - readLock.unlock(); - } - } - - public String toString() { - return "QueryStatistics" - + "[cacheHitCount=" + this.cacheHitCount - + ",cacheMissCount=" + this.cacheMissCount - + ",cachePutCount=" + this.cachePutCount - + ",executionCount=" + this.executionCount - + ",executionRowCount=" + this.executionRowCount - + ",executionAvgTime=" + this.getExecutionAvgTime() - + ",executionMaxTime=" + this.executionMaxTime - + ",executionMinTime=" + this.executionMinTime - + ']'; - } - - void incrementCacheHitCount() { - cacheHitCount.getAndIncrement(); - } - - void incrementCacheMissCount() { - cacheMissCount.getAndIncrement(); - } - - void incrementCachePutCount() { - cachePutCount.getAndIncrement(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentSecondLevelCacheStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentSecondLevelCacheStatisticsImpl.java deleted file mode 100644 index 3866fafb3bc6..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentSecondLevelCacheStatisticsImpl.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.stat.internal; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; - -import org.hibernate.cache.spi.Region; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.stat.SecondLevelCacheStatistics; - -/** - * Second level cache statistics of a specific region - * - * @author Alex Snaps - */ -public class ConcurrentSecondLevelCacheStatisticsImpl extends CategorizedStatistics implements SecondLevelCacheStatistics { - private final transient Region region; - private final transient EntityRegionAccessStrategy entityRegionAccessStrategy; - private final transient CollectionRegionAccessStrategy collectionRegionAccessStrategy; - private AtomicLong hitCount = new AtomicLong(); - private AtomicLong missCount = new AtomicLong(); - private AtomicLong putCount = new AtomicLong(); - - ConcurrentSecondLevelCacheStatisticsImpl( - Region region, - EntityRegionAccessStrategy entityRegionAccessStrategy, - CollectionRegionAccessStrategy collectionRegionAccessStrategy) { - super( region.getName() ); - this.region = region; - this.entityRegionAccessStrategy = entityRegionAccessStrategy; - this.collectionRegionAccessStrategy = collectionRegionAccessStrategy; - } - - public long getHitCount() { - return hitCount.get(); - } - - public long getMissCount() { - return missCount.get(); - } - - public long getPutCount() { - return putCount.get(); - } - - public long getElementCountInMemory() { - return region.getElementCountInMemory(); - } - - public long getElementCountOnDisk() { - return region.getElementCountOnDisk(); - } - - public long getSizeInMemory() { - return region.getSizeInMemory(); - } - - public Map getEntries() { - Map map = new HashMap(); - for ( Object o : region.toMap().entrySet() ) { - Map.Entry me = (Map.Entry) o; - Object id; - if ( entityRegionAccessStrategy != null ) { - id = entityRegionAccessStrategy.getCacheKeyId( me.getKey() ); - } - else if ( collectionRegionAccessStrategy != null ) { - id = collectionRegionAccessStrategy.getCacheKeyId( me.getKey() ); - } - else { - id = me.getKey(); - } - map.put( id, me.getValue() ); - } - return map; - } - - public String toString() { - StringBuilder buf = new StringBuilder() - .append( "SecondLevelCacheStatistics" ) - .append( "[hitCount=").append( this.hitCount ) - .append( ",missCount=").append( this.missCount ) - .append( ",putCount=").append( this.putCount ); - //not sure if this would ever be null but wanted to be careful - if ( region != null ) { - buf.append( ",elementCountInMemory=" ).append( this.getElementCountInMemory() ) - .append( ",elementCountOnDisk=" ).append( this.getElementCountOnDisk() ) - .append( ",sizeInMemory=" ).append( this.getSizeInMemory() ); - } - buf.append( ']' ); - return buf.toString(); - } - - void incrementHitCount() { - hitCount.getAndIncrement(); - } - - void incrementMissCount() { - missCount.getAndIncrement(); - } - - void incrementPutCount() { - putCount.getAndIncrement(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentStatisticsImpl.java deleted file mode 100644 index d220c360588c..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/ConcurrentStatisticsImpl.java +++ /dev/null @@ -1,880 +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.stat.internal; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicLong; - -import org.hibernate.cache.spi.Region; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.service.Service; -import org.hibernate.stat.spi.StatisticsImplementor; - -import static org.hibernate.internal.CoreLogging.messageLogger; - -/** - * Implementation of {@link org.hibernate.stat.Statistics} based on the {@link java.util.concurrent} package. - * - * @author Alex Snaps - */ -@SuppressWarnings({ "unchecked" }) -public class ConcurrentStatisticsImpl implements StatisticsImplementor, Service { - private static final CoreMessageLogger LOG = messageLogger( ConcurrentStatisticsImpl.class ); - - private SessionFactoryImplementor sessionFactory; - - private volatile boolean isStatisticsEnabled; - private volatile long startTime; - private AtomicLong sessionOpenCount = new AtomicLong(); - private AtomicLong sessionCloseCount = new AtomicLong(); - private AtomicLong flushCount = new AtomicLong(); - private AtomicLong connectCount = new AtomicLong(); - - private AtomicLong prepareStatementCount = new AtomicLong(); - private AtomicLong closeStatementCount = new AtomicLong(); - - private AtomicLong entityLoadCount = new AtomicLong(); - private AtomicLong entityUpdateCount = new AtomicLong(); - private AtomicLong entityInsertCount = new AtomicLong(); - private AtomicLong entityDeleteCount = new AtomicLong(); - private AtomicLong entityFetchCount = new AtomicLong(); - private AtomicLong collectionLoadCount = new AtomicLong(); - private AtomicLong collectionUpdateCount = new AtomicLong(); - private AtomicLong collectionRemoveCount = new AtomicLong(); - private AtomicLong collectionRecreateCount = new AtomicLong(); - private AtomicLong collectionFetchCount = new AtomicLong(); - - private AtomicLong secondLevelCacheHitCount = new AtomicLong(); - private AtomicLong secondLevelCacheMissCount = new AtomicLong(); - private AtomicLong secondLevelCachePutCount = new AtomicLong(); - - private AtomicLong naturalIdCacheHitCount = new AtomicLong(); - private AtomicLong naturalIdCacheMissCount = new AtomicLong(); - private AtomicLong naturalIdCachePutCount = new AtomicLong(); - private AtomicLong naturalIdQueryExecutionCount = new AtomicLong(); - private AtomicLong naturalIdQueryExecutionMaxTime = new AtomicLong(); - private volatile String naturalIdQueryExecutionMaxTimeRegion; - - private AtomicLong queryExecutionCount = new AtomicLong(); - private AtomicLong queryExecutionMaxTime = new AtomicLong(); - private volatile String queryExecutionMaxTimeQueryString; - private AtomicLong queryCacheHitCount = new AtomicLong(); - private AtomicLong queryCacheMissCount = new AtomicLong(); - private AtomicLong queryCachePutCount = new AtomicLong(); - - private AtomicLong updateTimestampsCacheHitCount = new AtomicLong(); - private AtomicLong updateTimestampsCacheMissCount = new AtomicLong(); - private AtomicLong updateTimestampsCachePutCount = new AtomicLong(); - - private AtomicLong committedTransactionCount = new AtomicLong(); - private AtomicLong transactionCount = new AtomicLong(); - - private AtomicLong optimisticFailureCount = new AtomicLong(); - - private final ConcurrentMap entityStatistics = new ConcurrentHashMap(); - private final ConcurrentMap naturalIdCacheStatistics = new ConcurrentHashMap(); - private final ConcurrentMap collectionStatistics = new ConcurrentHashMap(); - private final ConcurrentMap secondLevelCacheStatistics = new ConcurrentHashMap<>(); - private final ConcurrentMap queryStatistics = new ConcurrentHashMap(); - - @SuppressWarnings({ "UnusedDeclaration" }) - public ConcurrentStatisticsImpl() { - clear(); - } - - public ConcurrentStatisticsImpl(SessionFactoryImplementor sessionFactory) { - clear(); - this.sessionFactory = sessionFactory; - } - - /** - * reset all statistics - */ - public void clear() { - secondLevelCacheHitCount.set( 0 ); - secondLevelCacheMissCount.set( 0 ); - secondLevelCachePutCount.set( 0 ); - - naturalIdCacheHitCount.set( 0 ); - naturalIdCacheMissCount.set( 0 ); - naturalIdCachePutCount.set( 0 ); - naturalIdQueryExecutionCount.set( 0 ); - naturalIdQueryExecutionMaxTime.set( 0 ); - naturalIdQueryExecutionMaxTimeRegion = null; - - sessionCloseCount.set( 0 ); - sessionOpenCount.set( 0 ); - flushCount.set( 0 ); - connectCount.set( 0 ); - - prepareStatementCount.set( 0 ); - closeStatementCount.set( 0 ); - - entityDeleteCount.set( 0 ); - entityInsertCount.set( 0 ); - entityUpdateCount.set( 0 ); - entityLoadCount.set( 0 ); - entityFetchCount.set( 0 ); - - collectionRemoveCount.set( 0 ); - collectionUpdateCount.set( 0 ); - collectionRecreateCount.set( 0 ); - collectionLoadCount.set( 0 ); - collectionFetchCount.set( 0 ); - - queryExecutionCount.set( 0 ); - queryCacheHitCount.set( 0 ); - queryExecutionMaxTime.set( 0 ); - queryExecutionMaxTimeQueryString = null; - queryCacheMissCount.set( 0 ); - queryCachePutCount.set( 0 ); - - updateTimestampsCacheMissCount.set( 0 ); - updateTimestampsCacheHitCount.set( 0 ); - updateTimestampsCachePutCount.set( 0 ); - - transactionCount.set( 0 ); - committedTransactionCount.set( 0 ); - - optimisticFailureCount.set( 0 ); - - secondLevelCacheStatistics.clear(); - entityStatistics.clear(); - collectionStatistics.clear(); - queryStatistics.clear(); - naturalIdCacheStatistics.clear(); - - startTime = System.currentTimeMillis(); - } - - public void openSession() { - sessionOpenCount.getAndIncrement(); - } - - public void closeSession() { - sessionCloseCount.getAndIncrement(); - } - - public void flush() { - flushCount.getAndIncrement(); - } - - public void connect() { - connectCount.getAndIncrement(); - } - - public void loadEntity(String entityName) { - entityLoadCount.getAndIncrement(); - getEntityStatistics( entityName ).incrementLoadCount(); - } - - public void fetchEntity(String entityName) { - entityFetchCount.getAndIncrement(); - getEntityStatistics( entityName ).incrementFetchCount(); - } - - /** - * find entity statistics per name - * - * @param entityName entity name - * - * @return EntityStatistics object - */ - public ConcurrentEntityStatisticsImpl getEntityStatistics(String entityName) { - ConcurrentEntityStatisticsImpl es = entityStatistics.get( entityName ); - if ( es == null ) { - es = new ConcurrentEntityStatisticsImpl( entityName ); - ConcurrentEntityStatisticsImpl previous; - if ( ( previous = entityStatistics.putIfAbsent( entityName, es ) ) != null ) { - es = previous; - } - } - return es; - } - - public void updateEntity(String entityName) { - entityUpdateCount.getAndIncrement(); - ConcurrentEntityStatisticsImpl es = getEntityStatistics( entityName ); - es.incrementUpdateCount(); - } - - public void insertEntity(String entityName) { - entityInsertCount.getAndIncrement(); - ConcurrentEntityStatisticsImpl es = getEntityStatistics( entityName ); - es.incrementInsertCount(); - } - - public void deleteEntity(String entityName) { - entityDeleteCount.getAndIncrement(); - ConcurrentEntityStatisticsImpl es = getEntityStatistics( entityName ); - es.incrementDeleteCount(); - } - - /** - * Get collection statistics per role - * - * @param role collection role - * - * @return CollectionStatistics - */ - public ConcurrentCollectionStatisticsImpl getCollectionStatistics(String role) { - ConcurrentCollectionStatisticsImpl cs = collectionStatistics.get( role ); - if ( cs == null ) { - cs = new ConcurrentCollectionStatisticsImpl( role ); - ConcurrentCollectionStatisticsImpl previous; - if ( ( previous = collectionStatistics.putIfAbsent( role, cs ) ) != null ) { - cs = previous; - } - } - return cs; - } - - public void loadCollection(String role) { - collectionLoadCount.getAndIncrement(); - getCollectionStatistics( role ).incrementLoadCount(); - } - - public void fetchCollection(String role) { - collectionFetchCount.getAndIncrement(); - getCollectionStatistics( role ).incrementFetchCount(); - } - - public void updateCollection(String role) { - collectionUpdateCount.getAndIncrement(); - getCollectionStatistics( role ).incrementUpdateCount(); - } - - public void recreateCollection(String role) { - collectionRecreateCount.getAndIncrement(); - getCollectionStatistics( role ).incrementRecreateCount(); - } - - public void removeCollection(String role) { - collectionRemoveCount.getAndIncrement(); - getCollectionStatistics( role ).incrementRemoveCount(); - } - - - @Override - public ConcurrentNaturalIdCacheStatisticsImpl getNaturalIdCacheStatistics(String regionName) { - ConcurrentNaturalIdCacheStatisticsImpl stat = naturalIdCacheStatistics.get( regionName ); - - if ( stat == null ) { - if ( sessionFactory == null ) { - return null; - } - - final NaturalIdRegionAccessStrategy accessStrategy = sessionFactory.getCache().getNaturalIdCacheRegionAccessStrategy( regionName ); - stat = new ConcurrentNaturalIdCacheStatisticsImpl( accessStrategy.getRegion(), accessStrategy ); - ConcurrentNaturalIdCacheStatisticsImpl previous; - if ( ( previous = naturalIdCacheStatistics.putIfAbsent( regionName, stat ) ) != null ) { - stat = previous; - } - } - - return stat; - } - - /** - * Second level cache statistics per region - * - * @param regionName region name - * - * @return SecondLevelCacheStatistics - */ - public ConcurrentSecondLevelCacheStatisticsImpl getSecondLevelCacheStatistics(String regionName) { - ConcurrentSecondLevelCacheStatisticsImpl stat = secondLevelCacheStatistics.get( regionName ); - if ( stat == null ) { - if ( sessionFactory == null ) { - return null; - } - - final EntityRegionAccessStrategy entityRegionAccess = sessionFactory.getCache().getEntityRegionAccess( regionName ); - final CollectionRegionAccessStrategy collectionRegionAccess = sessionFactory.getCache().getCollectionRegionAccess( regionName ); - - if ( entityRegionAccess == null && collectionRegionAccess == null ) { - final Region region = sessionFactory.getCache().getQueryCache( regionName ).getRegion(); - if ( region == null ) { - throw new IllegalArgumentException( "Could not resolve region name [" + regionName + "]" ); - } - stat = new ConcurrentSecondLevelCacheStatisticsImpl( region, null, null ); - } - else { - - final Region region = entityRegionAccess != null - ? entityRegionAccess.getRegion() - : collectionRegionAccess.getRegion(); - - stat = new ConcurrentSecondLevelCacheStatisticsImpl( - region, - entityRegionAccess, - collectionRegionAccess - ); - } - - ConcurrentSecondLevelCacheStatisticsImpl previous; - if ( ( previous = secondLevelCacheStatistics.putIfAbsent( regionName, stat ) ) != null ) { - stat = previous; - } - } - - return stat; - } - - public void secondLevelCachePut(String regionName) { - secondLevelCachePutCount.getAndIncrement(); - getSecondLevelCacheStatistics( regionName ).incrementPutCount(); - } - - public void secondLevelCacheHit(String regionName) { - secondLevelCacheHitCount.getAndIncrement(); - getSecondLevelCacheStatistics( regionName ).incrementHitCount(); - } - - public void secondLevelCacheMiss(String regionName) { - secondLevelCacheMissCount.getAndIncrement(); - getSecondLevelCacheStatistics( regionName ).incrementMissCount(); - } - - @Override - public void naturalIdCachePut(String regionName) { - naturalIdCachePutCount.getAndIncrement(); - getNaturalIdCacheStatistics( regionName ).incrementPutCount(); - } - - @Override - public void naturalIdCacheHit(String regionName) { - naturalIdCacheHitCount.getAndIncrement(); - getNaturalIdCacheStatistics( regionName ).incrementHitCount(); - } - - @Override - public void naturalIdCacheMiss(String regionName) { - naturalIdCacheMissCount.getAndIncrement(); - getNaturalIdCacheStatistics( regionName ).incrementMissCount(); - } - - @Override - public void naturalIdQueryExecuted(String regionName, long time) { - naturalIdQueryExecutionCount.getAndIncrement(); - boolean isLongestQuery; - //noinspection StatementWithEmptyBody - for ( long old = naturalIdQueryExecutionMaxTime.get(); - ( isLongestQuery = time > old ) && ( !naturalIdQueryExecutionMaxTime.compareAndSet( old, time ) ); - old = naturalIdQueryExecutionMaxTime.get() ) { - // nothing to do here given the odd loop structure... - } - if ( isLongestQuery && regionName != null ) { - naturalIdQueryExecutionMaxTimeRegion = regionName; - } - if ( regionName != null ) { - getNaturalIdCacheStatistics( regionName ).queryExecuted( time ); - } - } - - @Override - public void queryExecuted(String hql, int rows, long time) { - LOG.hql(hql, time, (long) rows ); - queryExecutionCount.getAndIncrement(); - boolean isLongestQuery; - //noinspection StatementWithEmptyBody - for ( long old = queryExecutionMaxTime.get(); - ( isLongestQuery = time > old ) && ( !queryExecutionMaxTime.compareAndSet( old, time ) ); - old = queryExecutionMaxTime.get() ) { - // nothing to do here given the odd loop structure... - } - if ( isLongestQuery ) { - queryExecutionMaxTimeQueryString = hql; - } - if ( hql != null ) { - ConcurrentQueryStatisticsImpl qs = getQueryStatistics( hql ); - qs.executed( rows, time ); - } - } - @Override - public void queryCacheHit(String hql, String regionName) { - queryCacheHitCount.getAndIncrement(); - if ( hql != null ) { - ConcurrentQueryStatisticsImpl qs = getQueryStatistics( hql ); - qs.incrementCacheHitCount(); - } - ConcurrentSecondLevelCacheStatisticsImpl slcs = getSecondLevelCacheStatistics( - regionName - ); - slcs.incrementHitCount(); - } - @Override - public void queryCacheMiss(String hql, String regionName) { - queryCacheMissCount.getAndIncrement(); - if ( hql != null ) { - ConcurrentQueryStatisticsImpl qs = getQueryStatistics( hql ); - qs.incrementCacheMissCount(); - } - ConcurrentSecondLevelCacheStatisticsImpl slcs = getSecondLevelCacheStatistics( - regionName - ); - slcs.incrementMissCount(); - } - @Override - public void queryCachePut(String hql, String regionName) { - queryCachePutCount.getAndIncrement(); - if ( hql != null ) { - ConcurrentQueryStatisticsImpl qs = getQueryStatistics( hql ); - qs.incrementCachePutCount(); - } - ConcurrentSecondLevelCacheStatisticsImpl slcs = getSecondLevelCacheStatistics( regionName ); - slcs.incrementPutCount(); - } - - @Override - public void updateTimestampsCacheHit() { - updateTimestampsCacheHitCount.getAndIncrement(); - } - - @Override - public void updateTimestampsCacheMiss() { - updateTimestampsCacheMissCount.getAndIncrement(); - } - - @Override - public void updateTimestampsCachePut() { - updateTimestampsCachePutCount.getAndIncrement(); - } - - /** - * Query statistics from query string (HQL or SQL) - * - * @param queryString query string - * - * @return QueryStatistics - */ - @Override - public ConcurrentQueryStatisticsImpl getQueryStatistics(String queryString) { - ConcurrentQueryStatisticsImpl qs = queryStatistics.get( queryString ); - if ( qs == null ) { - qs = new ConcurrentQueryStatisticsImpl( queryString ); - ConcurrentQueryStatisticsImpl previous; - if ( ( previous = queryStatistics.putIfAbsent( queryString, qs ) ) != null ) { - qs = previous; - } - } - return qs; - } - - /** - * @return entity deletion count - */ - @Override - public long getEntityDeleteCount() { - return entityDeleteCount.get(); - } - - /** - * @return entity insertion count - */ - @Override - public long getEntityInsertCount() { - return entityInsertCount.get(); - } - - /** - * @return entity load (from DB) - */ - @Override - public long getEntityLoadCount() { - return entityLoadCount.get(); - } - - /** - * @return entity fetch (from DB) - */ - @Override - public long getEntityFetchCount() { - return entityFetchCount.get(); - } - - /** - * @return entity update - */ - @Override - public long getEntityUpdateCount() { - return entityUpdateCount.get(); - } - @Override - public long getQueryExecutionCount() { - return queryExecutionCount.get(); - } - @Override - public long getQueryCacheHitCount() { - return queryCacheHitCount.get(); - } - @Override - public long getQueryCacheMissCount() { - return queryCacheMissCount.get(); - } - @Override - public long getQueryCachePutCount() { - return queryCachePutCount.get(); - } - @Override - public long getUpdateTimestampsCacheHitCount() { - return updateTimestampsCacheHitCount.get(); - } - @Override - public long getUpdateTimestampsCacheMissCount() { - return updateTimestampsCacheMissCount.get(); - } - @Override - public long getUpdateTimestampsCachePutCount() { - return updateTimestampsCachePutCount.get(); - } - - /** - * @return flush - */ - @Override - public long getFlushCount() { - return flushCount.get(); - } - - /** - * @return session connect - */ - @Override - public long getConnectCount() { - return connectCount.get(); - } - - /** - * @return second level cache hit - */ - @Override - public long getSecondLevelCacheHitCount() { - return secondLevelCacheHitCount.get(); - } - - /** - * @return second level cache miss - */ - @Override - public long getSecondLevelCacheMissCount() { - return secondLevelCacheMissCount.get(); - } - - /** - * @return second level cache put - */ - @Override - public long getSecondLevelCachePutCount() { - return secondLevelCachePutCount.get(); - } - - @Override - public long getNaturalIdQueryExecutionCount() { - return naturalIdQueryExecutionCount.get(); - } - - @Override - public long getNaturalIdQueryExecutionMaxTime() { - return naturalIdQueryExecutionMaxTime.get(); - } - - @Override - public String getNaturalIdQueryExecutionMaxTimeRegion() { - return naturalIdQueryExecutionMaxTimeRegion; - } - - @Override - public long getNaturalIdCacheHitCount() { - return naturalIdCacheHitCount.get(); - } - - @Override - public long getNaturalIdCacheMissCount() { - return naturalIdCacheMissCount.get(); - } - - @Override - public long getNaturalIdCachePutCount() { - return naturalIdCachePutCount.get(); - } - - /** - * @return session closing - */ - @Override - public long getSessionCloseCount() { - return sessionCloseCount.get(); - } - - /** - * @return session opening - */ - @Override - public long getSessionOpenCount() { - return sessionOpenCount.get(); - } - - /** - * @return collection loading (from DB) - */ - @Override - public long getCollectionLoadCount() { - return collectionLoadCount.get(); - } - - /** - * @return collection fetching (from DB) - */ - @Override - public long getCollectionFetchCount() { - return collectionFetchCount.get(); - } - - /** - * @return collection update - */ - @Override - public long getCollectionUpdateCount() { - return collectionUpdateCount.get(); - } - - /** - * @return collection removal - * FIXME: even if isInverse="true"? - */ - @Override - public long getCollectionRemoveCount() { - return collectionRemoveCount.get(); - } - - /** - * @return collection recreation - */ - @Override - public long getCollectionRecreateCount() { - return collectionRecreateCount.get(); - } - - /** - * @return start time in ms (JVM standards {@link System#currentTimeMillis()}) - */ - @Override - public long getStartTime() { - return startTime; - } - - /** - * log in info level the main statistics - */ - @Override - public void logSummary() { - LOG.loggingStatistics(); - LOG.startTime( startTime ); - LOG.sessionsOpened( sessionOpenCount.get() ); - LOG.sessionsClosed( sessionCloseCount.get() ); - LOG.transactions( transactionCount.get() ); - LOG.successfulTransactions( committedTransactionCount.get() ); - LOG.optimisticLockFailures( optimisticFailureCount.get() ); - LOG.flushes( flushCount.get() ); - LOG.connectionsObtained( connectCount.get() ); - LOG.statementsPrepared( prepareStatementCount.get() ); - LOG.statementsClosed( closeStatementCount.get() ); - LOG.secondLevelCachePuts( secondLevelCachePutCount.get() ); - LOG.secondLevelCacheHits( secondLevelCacheHitCount.get() ); - LOG.secondLevelCacheMisses( secondLevelCacheMissCount.get() ); - LOG.entitiesLoaded( entityLoadCount.get() ); - LOG.entitiesUpdated( entityUpdateCount.get() ); - LOG.entitiesInserted( entityInsertCount.get() ); - LOG.entitiesDeleted( entityDeleteCount.get() ); - LOG.entitiesFetched( entityFetchCount.get() ); - LOG.collectionsLoaded( collectionLoadCount.get() ); - LOG.collectionsUpdated( collectionUpdateCount.get() ); - LOG.collectionsRemoved( collectionRemoveCount.get() ); - LOG.collectionsRecreated( collectionRecreateCount.get() ); - LOG.collectionsFetched( collectionFetchCount.get() ); - LOG.naturalIdCachePuts( naturalIdCachePutCount.get() ); - LOG.naturalIdCacheHits( naturalIdCacheHitCount.get() ); - LOG.naturalIdCacheMisses( naturalIdCacheMissCount.get() ); - LOG.naturalIdMaxQueryTime( naturalIdQueryExecutionMaxTime.get() ); - LOG.naturalIdQueriesExecuted( naturalIdQueryExecutionCount.get() ); - LOG.queriesExecuted( queryExecutionCount.get() ); - LOG.queryCachePuts( queryCachePutCount.get() ); - LOG.timestampCachePuts( updateTimestampsCachePutCount.get() ); - LOG.timestampCacheHits( updateTimestampsCacheHitCount.get() ); - LOG.timestampCacheMisses( updateTimestampsCacheMissCount.get() ); - LOG.queryCacheHits( queryCacheHitCount.get() ); - LOG.queryCacheMisses( queryCacheMissCount.get() ); - LOG.maxQueryTime( queryExecutionMaxTime.get() ); - } - - /** - * Are statistics logged - */ - @Override - public boolean isStatisticsEnabled() { - return isStatisticsEnabled; - } - - /** - * Enable statistics logs (this is a dynamic parameter) - */ - @Override - public void setStatisticsEnabled(boolean b) { - isStatisticsEnabled = b; - } - - /** - * @return Returns the max query execution time, - * for all queries - */ - @Override - public long getQueryExecutionMaxTime() { - return queryExecutionMaxTime.get(); - } - - /** - * Get all executed query strings - */ - @Override - public String[] getQueries() { - return ArrayHelper.toStringArray( queryStatistics.keySet() ); - } - - /** - * Get the names of all entities - */ - @Override - public String[] getEntityNames() { - if ( sessionFactory == null ) { - return ArrayHelper.toStringArray( entityStatistics.keySet() ); - } - else { - return sessionFactory.getMetamodel().getAllEntityNames(); - } - } - - /** - * Get the names of all collection roles - */ - @Override - public String[] getCollectionRoleNames() { - if ( sessionFactory == null ) { - return ArrayHelper.toStringArray( collectionStatistics.keySet() ); - } - else { - return sessionFactory.getMetamodel().getAllCollectionRoles(); - } - } - - /** - * Get all second-level cache region names - */ - @Override - public String[] getSecondLevelCacheRegionNames() { - if ( sessionFactory == null ) { - return ArrayHelper.toStringArray( secondLevelCacheStatistics.keySet() ); - } - else { - return sessionFactory.getCache().getSecondLevelCacheRegionNames(); - } - } - @Override - public void endTransaction(boolean success) { - transactionCount.getAndIncrement(); - if ( success ) { - committedTransactionCount.getAndIncrement(); - } - } - @Override - public long getSuccessfulTransactionCount() { - return committedTransactionCount.get(); - } - @Override - public long getTransactionCount() { - return transactionCount.get(); - } - @Override - public void closeStatement() { - closeStatementCount.getAndIncrement(); - } - @Override - public void prepareStatement() { - prepareStatementCount.getAndIncrement(); - } - @Override - public long getCloseStatementCount() { - return closeStatementCount.get(); - } - @Override - public long getPrepareStatementCount() { - return prepareStatementCount.get(); - } - @Override - public void optimisticFailure(String entityName) { - optimisticFailureCount.getAndIncrement(); - ( (ConcurrentEntityStatisticsImpl) getEntityStatistics( entityName ) ).incrementOptimisticFailureCount(); - } - @Override - public long getOptimisticFailureCount() { - return optimisticFailureCount.get(); - } - - @Override - public String toString() { - return new StringBuilder() - .append( "Statistics[" ) - .append( "start time=" ).append( startTime ) - .append( ",sessions opened=" ).append( sessionOpenCount ) - .append( ",sessions closed=" ).append( sessionCloseCount ) - .append( ",transactions=" ).append( transactionCount ) - .append( ",successful transactions=" ).append( committedTransactionCount ) - .append( ",optimistic lock failures=" ).append( optimisticFailureCount ) - .append( ",flushes=" ).append( flushCount ) - .append( ",connections obtained=" ).append( connectCount ) - .append( ",statements prepared=" ).append( prepareStatementCount ) - .append( ",statements closed=" ).append( closeStatementCount ) - .append( ",second level cache puts=" ).append( secondLevelCachePutCount ) - .append( ",second level cache hits=" ).append( secondLevelCacheHitCount ) - .append( ",second level cache misses=" ).append( secondLevelCacheMissCount ) - .append( ",entities loaded=" ).append( entityLoadCount ) - .append( ",entities updated=" ).append( entityUpdateCount ) - .append( ",entities inserted=" ).append( entityInsertCount ) - .append( ",entities deleted=" ).append( entityDeleteCount ) - .append( ",entities fetched=" ).append( entityFetchCount ) - .append( ",collections loaded=" ).append( collectionLoadCount ) - .append( ",collections updated=" ).append( collectionUpdateCount ) - .append( ",collections removed=" ).append( collectionRemoveCount ) - .append( ",collections recreated=" ).append( collectionRecreateCount ) - .append( ",collections fetched=" ).append( collectionFetchCount ) - .append( ",naturalId queries executed to database=" ).append( naturalIdQueryExecutionCount ) - .append( ",naturalId cache puts=" ).append( naturalIdCachePutCount ) - .append( ",naturalId cache hits=" ).append( naturalIdCacheHitCount ) - .append( ",naturalId cache misses=" ).append( naturalIdCacheMissCount ) - .append( ",naturalId max query time=" ).append( naturalIdQueryExecutionMaxTime ) - .append( ",queries executed to database=" ).append( queryExecutionCount ) - .append( ",query cache puts=" ).append( queryCachePutCount ) - .append( ",query cache hits=" ).append( queryCacheHitCount ) - .append( ",query cache misses=" ).append( queryCacheMissCount ) - .append(",update timestamps cache puts=").append(updateTimestampsCachePutCount) - .append(",update timestamps cache hits=").append(updateTimestampsCacheHitCount) - .append(",update timestamps cache misses=").append(updateTimestampsCacheMissCount) - .append( ",max query time=" ).append( queryExecutionMaxTime ) - .append( ']' ) - .toString(); - } - @Override - public String getQueryExecutionMaxTimeQueryString() { - return queryExecutionMaxTimeQueryString; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/DeprecatedNaturalIdCacheStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/DeprecatedNaturalIdCacheStatisticsImpl.java new file mode 100644 index 000000000000..74eb0bb43ffc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/DeprecatedNaturalIdCacheStatisticsImpl.java @@ -0,0 +1,255 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.stat.internal; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.ExtendedStatisticsSupport; +import org.hibernate.cache.spi.Region; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.stat.NaturalIdCacheStatistics; +import org.hibernate.stat.NaturalIdStatistics; + +import static org.hibernate.stat.CacheRegionStatistics.NO_EXTENDED_STAT_SUPPORT_RETURN; + +/** + * @deprecated (since 5.3) {@link NaturalIdStatistics} is preferred over + * {@link NaturalIdCacheStatistics} + * + * @author Eric Dalquist + */ +@Deprecated +public class DeprecatedNaturalIdCacheStatisticsImpl implements NaturalIdCacheStatistics, Serializable { + private final String regionName; + private final transient Set accessStrategies; + + private final AtomicLong executionCount = new AtomicLong(); + private final AtomicLong executionMaxTime = new AtomicLong(); + private final AtomicLong executionMinTime = new AtomicLong( Long.MAX_VALUE ); + private final AtomicLong totalExecutionTime = new AtomicLong(); + + private final AtomicLong cacheHitCount = new AtomicLong(); + private final AtomicLong cacheMissCount = new AtomicLong(); + private final AtomicLong cachePutCount = new AtomicLong(); + + private final Lock readLock; + private final Lock writeLock; + + DeprecatedNaturalIdCacheStatisticsImpl(String regionName, Set accessStrategies) { + this.regionName = regionName; + this.accessStrategies = accessStrategies; + final ReadWriteLock lock = new ReentrantReadWriteLock(); + this.readLock = lock.readLock(); + this.writeLock = lock.writeLock(); + } + + /** + * queries executed to the DB + */ + @Override + public long getExecutionCount() { + return this.executionCount.get(); + } + + /** + * average time in ms taken by the excution of this query onto the DB + */ + @Override + public long getExecutionAvgTime() { + // We write lock here to be sure that we always calculate the average time + // with all updates from the executed applied: executionCount and totalExecutionTime + // both used in the calculation + this.writeLock.lock(); + try { + long avgExecutionTime = 0; + if ( this.executionCount.get() > 0 ) { + avgExecutionTime = this.totalExecutionTime.get() / this.executionCount.get(); + } + return avgExecutionTime; + } + finally { + this.writeLock.unlock(); + } + } + + /** + * max time in ms taken by the excution of this query onto the DB + */ + @Override + public long getExecutionMaxTime() { + return this.executionMaxTime.get(); + } + + /** + * min time in ms taken by the excution of this query onto the DB + */ + @Override + public long getExecutionMinTime() { + return this.executionMinTime.get(); + } + + @Override + public long getHitCount() { + return this.cacheHitCount.get(); + } + + @Override + public long getMissCount() { + return this.cacheMissCount.get(); + } + + @Override + public long getPutCount() { + return this.cachePutCount.get(); + } + + @Override + public long getElementCountInMemory() { + long count = 0; + HashSet processedRegions = null; + + for ( NaturalIdDataAccess accessStrategy : accessStrategies ) { + final DomainDataRegion region = accessStrategy.getRegion(); + if ( ExtendedStatisticsSupport.class.isInstance( region ) ) { + + } + + if ( region instanceof ExtendedStatisticsSupport ) { + if ( processedRegions == null ) { + processedRegions = new HashSet<>(); + } + if ( processedRegions.add( region ) ) { + count += ( (ExtendedStatisticsSupport) region ).getElementCountInMemory(); + } + } + + } + + if ( count == 0 ) { + return NO_EXTENDED_STAT_SUPPORT_RETURN; + } + + return count; + } + + @Override + public long getElementCountOnDisk() { + long count = 0; + HashSet processedRegions = null; + + for ( NaturalIdDataAccess accessStrategy : accessStrategies ) { + final DomainDataRegion region = accessStrategy.getRegion(); + if ( ExtendedStatisticsSupport.class.isInstance( region ) ) { + + } + + if ( region instanceof ExtendedStatisticsSupport ) { + if ( processedRegions == null ) { + processedRegions = new HashSet<>(); + } + if ( processedRegions.add( region ) ) { + count += ( (ExtendedStatisticsSupport) region ).getElementCountOnDisk(); + } + } + + } + + if ( count == 0 ) { + return NO_EXTENDED_STAT_SUPPORT_RETURN; + } + + return count; + } + + @Override + public long getSizeInMemory() { + long count = 0; + HashSet processedRegions = null; + + for ( NaturalIdDataAccess accessStrategy : accessStrategies ) { + final DomainDataRegion region = accessStrategy.getRegion(); + if ( ExtendedStatisticsSupport.class.isInstance( region ) ) { + + } + + if ( region instanceof ExtendedStatisticsSupport ) { + if ( processedRegions == null ) { + processedRegions = new HashSet<>(); + } + if ( processedRegions.add( region ) ) { + count += ( (ExtendedStatisticsSupport) region ).getElementCountOnDisk(); + } + } + + } + + if ( count == 0 ) { + return NO_EXTENDED_STAT_SUPPORT_RETURN; + } + + return count; + } + + void incrementHitCount() { + cacheHitCount.getAndIncrement(); + } + + void incrementMissCount() { + cacheMissCount.getAndIncrement(); + } + + void incrementPutCount() { + cachePutCount.getAndIncrement(); + } + + void queryExecuted(long time) { + // read lock is enough, concurrent updates are supported by the underlying type AtomicLong + // this only guards executed(long, long) to be called, when another thread is executing getExecutionAvgTime() + this.readLock.lock(); + try { + // Less chances for a context switch + //noinspection StatementWithEmptyBody + for ( long old = this.executionMinTime.get(); time < old && !this.executionMinTime.compareAndSet( old, time ); old = this.executionMinTime.get() ) { + } + //noinspection StatementWithEmptyBody + for ( long old = this.executionMaxTime.get(); time > old && !this.executionMaxTime.compareAndSet( old, time ); old = this.executionMaxTime.get() ) { + } + this.executionCount.getAndIncrement(); + this.totalExecutionTime.addAndGet( time ); + } + finally { + this.readLock.unlock(); + } + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder() + .append( "NaturalIdCacheStatistics(deprecated)" ) + .append( "[regionName=" ).append( regionName ) + .append( ",executionCount=" ).append( getExecutionCount() ) + .append( ",executionAvgTime=" ).append( getExecutionAvgTime() ) + .append( ",executionMinTime=" ).append( getExecutionMinTime() ) + .append( ",executionMaxTime=" ).append( getExecutionMaxTime() ); + + buf.append( ",hitCount=" ).append( getHitCount() ) + .append( ",missCount=" ).append( getMissCount() ) + .append( ",putCount=" ).append( getPutCount() ) + .append( ",elementCountInMemory=" ).append( this.getElementCountInMemory() ) + .append( ",elementCountOnDisk=" ).append( this.getElementCountOnDisk() ) + .append( ",sizeInMemory=" ).append( this.getSizeInMemory() ); + + return buf.append( ']' ).toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/EntityStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/EntityStatisticsImpl.java new file mode 100644 index 000000000000..4d8247db080d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/EntityStatisticsImpl.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import java.io.Serializable; +import java.util.concurrent.atomic.LongAdder; + +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.stat.EntityStatistics; + +/** + * Entity related statistics + * + * @author Alex Snaps + */ +public class EntityStatisticsImpl extends AbstractCacheableDataStatistics implements EntityStatistics, Serializable { + + private final String rootEntityName; + private final LongAdder loadCount = new LongAdder(); + private final LongAdder updateCount = new LongAdder(); + private final LongAdder insertCount = new LongAdder(); + private final LongAdder deleteCount = new LongAdder(); + private final LongAdder fetchCount = new LongAdder(); + private final LongAdder optimisticFailureCount = new LongAdder(); + + EntityStatisticsImpl(EntityPersister rootEntityDescriptor) { + super( + () -> rootEntityDescriptor.getCacheAccessStrategy() != null + ? rootEntityDescriptor.getCacheAccessStrategy().getRegion() + : null + ); + this.rootEntityName = rootEntityDescriptor.getRootEntityName(); + } + + public long getDeleteCount() { + return deleteCount.sum(); + } + + public long getInsertCount() { + return insertCount.sum(); + } + + public long getLoadCount() { + return loadCount.sum(); + } + + public long getUpdateCount() { + return updateCount.sum(); + } + + public long getFetchCount() { + return fetchCount.sum(); + } + + public long getOptimisticFailureCount() { + return optimisticFailureCount.sum(); + } + + void incrementLoadCount() { + loadCount.increment(); + } + + void incrementFetchCount() { + fetchCount.increment(); + } + + void incrementUpdateCount() { + updateCount.increment(); + } + + void incrementInsertCount() { + insertCount.increment(); + } + + void incrementDeleteCount() { + deleteCount.increment(); + } + + void incrementOptimisticFailureCount() { + optimisticFailureCount.increment(); + } + + public String toString() { + final StringBuilder buffer = new StringBuilder() + .append( "EntityStatistics" ) + .append( "[rootEntityName=" ).append( rootEntityName ) + .append( ",loadCount=" ).append( this.loadCount ) + .append( ",updateCount=" ).append( this.updateCount ) + .append( ",insertCount=" ).append( this.insertCount ) + .append( ",deleteCount=" ).append( this.deleteCount ) + .append( ",fetchCount=" ).append( this.fetchCount ) + .append( ",optimisticLockFailureCount=" ).append( this.optimisticFailureCount ); + appendCacheStats( buffer ); + return buffer.append( ']' ).toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/NaturalIdStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/NaturalIdStatisticsImpl.java new file mode 100644 index 000000000000..17428ffc8413 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/NaturalIdStatisticsImpl.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.stat.internal; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.stat.NaturalIdStatistics; + +/** + * NaturalId cache statistics of a specific entity + * + * @author Eric Dalquist + */ +public class NaturalIdStatisticsImpl extends AbstractCacheableDataStatistics implements NaturalIdStatistics, Serializable { + + private final String rootEntityName; + private final AtomicLong executionCount = new AtomicLong(); + private final AtomicLong executionMaxTime = new AtomicLong(); + private final AtomicLong executionMinTime = new AtomicLong( Long.MAX_VALUE ); + private final AtomicLong totalExecutionTime = new AtomicLong(); + + private final Lock readLock; + private final Lock writeLock; + + NaturalIdStatisticsImpl(EntityPersister rootEntityDescriptor) { + super( + () -> rootEntityDescriptor.getNaturalIdCacheAccessStrategy() != null + ? rootEntityDescriptor.getNaturalIdCacheAccessStrategy().getRegion() + : null + ); + this.rootEntityName = rootEntityDescriptor.getRootEntityName(); + final ReadWriteLock lock = new ReentrantReadWriteLock(); + this.readLock = lock.readLock(); + this.writeLock = lock.writeLock(); + } + + /** + * queries executed to the DB + */ + @Override + public long getExecutionCount() { + return this.executionCount.get(); + } + + /** + * average time in ms taken by the excution of this query onto the DB + */ + @Override + public long getExecutionAvgTime() { + // We write lock here to be sure that we always calculate the average time + // with all updates from the executed applied: executionCount and totalExecutionTime + // both used in the calculation + this.writeLock.lock(); + try { + long avgExecutionTime = 0; + if ( this.executionCount.get() > 0 ) { + avgExecutionTime = this.totalExecutionTime.get() / this.executionCount.get(); + } + return avgExecutionTime; + } + finally { + this.writeLock.unlock(); + } + } + + /** + * max time in ms taken by the excution of this query onto the DB + */ + @Override + public long getExecutionMaxTime() { + return this.executionMaxTime.get(); + } + + /** + * min time in ms taken by the execution of this query onto the DB + */ + @Override + public long getExecutionMinTime() { + return this.executionMinTime.get(); + } + + void queryExecuted(long time) { + // read lock is enough, concurrent updates are supported by the underlying type AtomicLong + // this only guards executed(long, long) to be called, when another thread is executing getExecutionAvgTime() + this.readLock.lock(); + try { + // Less chances for a context switch + //noinspection StatementWithEmptyBody + for ( long old = this.executionMinTime.get(); time < old && !this.executionMinTime.compareAndSet( old, time ); old = this.executionMinTime.get() ) { + } + //noinspection StatementWithEmptyBody + for ( long old = this.executionMaxTime.get(); time > old && !this.executionMaxTime.compareAndSet( old, time ); old = this.executionMaxTime.get() ) { + } + this.executionCount.getAndIncrement(); + this.totalExecutionTime.addAndGet( time ); + } + finally { + this.readLock.unlock(); + } + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder() + .append( "NaturalIdCacheStatistics" ) + .append( "[rootEntityName=" ).append( rootEntityName ) + .append( ",executionCount=" ).append( this.executionCount ) + .append( ",executionAvgTime=" ).append( this.getExecutionAvgTime() ) + .append( ",executionMinTime=" ).append( this.executionMinTime ) + .append( ",executionMaxTime=" ).append( this.executionMaxTime ); + appendCacheStats( buf ); + return buf.append( ']' ).toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java new file mode 100644 index 000000000000..00ff28ba3aee --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java @@ -0,0 +1,190 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.hibernate.stat.QueryStatistics; + +import org.jboss.logging.Logger; + +/** + * Query statistics (HQL and SQL) + *

    + * Note that for a cached query, the cache miss is equals to the db count + * + * @author Alex Snaps + */ +public class QueryStatisticsImpl implements QueryStatistics { + private static final Logger log = Logger.getLogger( QueryStatisticsImpl.class ); + + private final String query; + + private final LongAdder cacheHitCount = new LongAdder(); + private final LongAdder cacheMissCount = new LongAdder(); + private final LongAdder cachePutCount = new LongAdder(); + private final LongAdder executionCount = new LongAdder(); + private final LongAdder executionRowCount = new LongAdder(); + private final AtomicLong executionMaxTime = new AtomicLong(); + private final AtomicLong executionMinTime = new AtomicLong(Long.MAX_VALUE); + private final AtomicLong totalExecutionTime = new AtomicLong(); + + private final Lock readLock; + private final Lock writeLock; + + QueryStatisticsImpl(String query) { + this.query = query; + ReadWriteLock lock = new ReentrantReadWriteLock(); + this.readLock = lock.readLock(); + this.writeLock = lock.writeLock(); + } + + /** + * queries executed to the DB + */ + public long getExecutionCount() { + return executionCount.sum(); + } + + /** + * Queries retrieved successfully from the cache + */ + public long getCacheHitCount() { + return cacheHitCount.sum(); + } + + public long getCachePutCount() { + return cachePutCount.sum(); + } + + public long getCacheMissCount() { + return cacheMissCount.sum(); + } + + /** + * Number of lines returned by all the executions of this query (from DB) + * For now, {@link org.hibernate.Query#iterate()} + * and {@link org.hibernate.Query#scroll()()} do not fill this statistic + * + * @return The number of rows cumulatively returned by the given query; iterate + * and scroll queries do not effect this total as their number of returned rows + * is not known at execution time. + */ + public long getExecutionRowCount() { + return executionRowCount.sum(); + } + + /** + * average time in ms taken by the execution of this query onto the DB + */ + public long getExecutionAvgTime() { + return (long) getExecutionAvgTimeAsDouble(); + } + + /** + * average time in ms as double taken by the execution of this query onto the DB + */ + public double getExecutionAvgTimeAsDouble() { + // We write lock here to be sure that we always calculate the average time + // with all updates from the executed applied: executionCount and totalExecutionTime + // both used in the calculation + writeLock.lock(); + try { + double avgExecutionTime = 0; + final long ec = executionCount.sum(); + if ( ec > 0 ) { + avgExecutionTime = totalExecutionTime.get() / (double) ec; + } + return avgExecutionTime; + } + finally { + writeLock.unlock(); + } + } + + /** + * max time in ms taken by the execution of this query onto the DB + */ + public long getExecutionMaxTime() { + return executionMaxTime.get(); + } + + /** + * min time in ms taken by the execution of this query onto the DB + */ + public long getExecutionMinTime() { + return executionMinTime.get(); + } + + /** + * total time in ms taken by the execution of this query onto the DB + */ + public long getExecutionTotalTime() { + return totalExecutionTime.get(); + } + + /** + * add statistics report of a DB query + * + * @param rows rows count returned + * @param time time taken + */ + void executed(long rows, long time) { + log.tracef( "QueryStatistics - query executed : %s", query ); + + // read lock is enough, concurrent updates are supported by the underlying type AtomicLong + // this only guards executed(long, long) to be called, when another thread is executing getExecutionAvgTime() + readLock.lock(); + try { + // Less chances for a context switch + for ( long old = executionMinTime.get(); (time < old) && !executionMinTime.compareAndSet(old, time); old = executionMinTime.get() ) {} + for ( long old = executionMaxTime.get(); (time > old) && !executionMaxTime.compareAndSet(old, time); old = executionMaxTime.get() ) {} + executionCount.increment(); + executionRowCount.add( rows ); + totalExecutionTime.addAndGet( time ); + } + finally { + readLock.unlock(); + } + } + + void incrementCacheHitCount() { + log.tracef( "QueryStatistics - cache hit : %s", query ); + + cacheHitCount.increment(); + } + + void incrementCacheMissCount() { + log.tracef( "QueryStatistics - cache miss : %s", query ); + + cacheMissCount.increment(); + } + + void incrementCachePutCount() { + log.tracef( "QueryStatistics - cache put : %s", query ); + + cachePutCount.increment(); + } + + public String toString() { + return "QueryStatistics" + + "[query=" + query + + ",cacheHitCount=" + this.cacheHitCount + + ",cacheMissCount=" + this.cacheMissCount + + ",cachePutCount=" + this.cachePutCount + + ",executionCount=" + this.executionCount + + ",executionRowCount=" + this.executionRowCount + + ",executionAvgTime=" + this.getExecutionAvgTime() + + ",executionMaxTime=" + this.executionMaxTime + + ",executionMinTime=" + this.executionMinTime + + ']'; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java new file mode 100644 index 000000000000..01ff11cc8fd2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java @@ -0,0 +1,983 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; + +import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.cache.spi.QueryResultsRegion; +import org.hibernate.cache.spi.Region; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.service.Service; +import org.hibernate.stat.spi.StatisticsImplementor; + +import static org.hibernate.internal.CoreLogging.messageLogger; + +/** + * Implementation of {@link org.hibernate.stat.Statistics} based on the {@link java.util.concurrent} package. + * + * @author Alex Snaps + * @author Sanne Grinovero + */ +@SuppressWarnings({ "unchecked" }) +public class StatisticsImpl implements StatisticsImplementor, Service { + private static final CoreMessageLogger LOG = messageLogger( StatisticsImpl.class ); + + private final SessionFactoryImplementor sessionFactory; + + private volatile boolean isStatisticsEnabled; + private volatile long startTime; + + private final LongAdder sessionOpenCount = new LongAdder(); + private final LongAdder sessionCloseCount = new LongAdder(); + private final LongAdder flushCount = new LongAdder(); + private final LongAdder connectCount = new LongAdder(); + + private final LongAdder prepareStatementCount = new LongAdder(); + private final LongAdder closeStatementCount = new LongAdder(); + + private final LongAdder entityLoadCount = new LongAdder(); + private final LongAdder entityUpdateCount = new LongAdder(); + private final LongAdder entityInsertCount = new LongAdder(); + private final LongAdder entityDeleteCount = new LongAdder(); + private final LongAdder entityFetchCount = new LongAdder(); + private final LongAdder collectionLoadCount = new LongAdder(); + private final LongAdder collectionUpdateCount = new LongAdder(); + private final LongAdder collectionRemoveCount = new LongAdder(); + private final LongAdder collectionRecreateCount = new LongAdder(); + private final LongAdder collectionFetchCount = new LongAdder(); + + private final LongAdder secondLevelCacheHitCount = new LongAdder(); + private final LongAdder secondLevelCacheMissCount = new LongAdder(); + private final LongAdder secondLevelCachePutCount = new LongAdder(); + + private final LongAdder naturalIdCacheHitCount = new LongAdder(); + private final LongAdder naturalIdCacheMissCount = new LongAdder(); + private final LongAdder naturalIdCachePutCount = new LongAdder(); + private final LongAdder naturalIdQueryExecutionCount = new LongAdder(); + private final AtomicLong naturalIdQueryExecutionMaxTime = new AtomicLong(); + private volatile String naturalIdQueryExecutionMaxTimeRegion; + private volatile String naturalIdQueryExecutionMaxTimeEntity; + + private final LongAdder queryExecutionCount = new LongAdder(); + private final AtomicLong queryExecutionMaxTime = new AtomicLong(); + private volatile String queryExecutionMaxTimeQueryString; + private final LongAdder queryCacheHitCount = new LongAdder(); + private final LongAdder queryCacheMissCount = new LongAdder(); + private final LongAdder queryCachePutCount = new LongAdder(); + + private final LongAdder updateTimestampsCacheHitCount = new LongAdder(); + private final LongAdder updateTimestampsCacheMissCount = new LongAdder(); + private final LongAdder updateTimestampsCachePutCount = new LongAdder(); + + private final LongAdder committedTransactionCount = new LongAdder(); + private final LongAdder transactionCount = new LongAdder(); + + private final LongAdder optimisticFailureCount = new LongAdder(); + + private final StatsNamedContainer entityStatsMap = new StatsNamedContainer(); + private final StatsNamedContainer naturalIdQueryStatsMap = new StatsNamedContainer(); + private final StatsNamedContainer collectionStatsMap = new StatsNamedContainer(); + + /** + * Keyed by query string + */ + private final StatsNamedContainer queryStatsMap = new StatsNamedContainer(); + + /** + * Keyed by region name + */ + private final StatsNamedContainer l2CacheStatsMap = new StatsNamedContainer<>(); + + private final StatsNamedContainer deprecatedNaturalIdStatsMap = new StatsNamedContainer(); + + + @SuppressWarnings({ "UnusedDeclaration" }) + public StatisticsImpl() { + this( null ); + } + + public StatisticsImpl(SessionFactoryImplementor sessionFactory) { + clear(); + this.sessionFactory = sessionFactory; + } + + /** + * reset all statistics + */ + public void clear() { + secondLevelCacheHitCount.reset(); + secondLevelCacheMissCount.reset(); + secondLevelCachePutCount.reset(); + + naturalIdCacheHitCount.reset(); + naturalIdCacheMissCount.reset(); + naturalIdCachePutCount.reset(); + naturalIdQueryExecutionCount.reset(); + naturalIdQueryExecutionMaxTime.set( 0L ); + naturalIdQueryExecutionMaxTimeRegion = null; + naturalIdQueryExecutionMaxTimeEntity = null; + + sessionCloseCount.reset(); + sessionOpenCount.reset(); + flushCount.reset(); + connectCount.reset(); + + prepareStatementCount.reset(); + closeStatementCount.reset(); + + entityDeleteCount.reset(); + entityInsertCount.reset(); + entityUpdateCount.reset(); + entityLoadCount.reset(); + entityFetchCount.reset(); + + collectionRemoveCount.reset(); + collectionUpdateCount.reset(); + collectionRecreateCount.reset(); + collectionLoadCount.reset(); + collectionFetchCount.reset(); + + queryExecutionCount.reset(); + queryCacheHitCount.reset(); + queryExecutionMaxTime.set( 0L ); + queryExecutionMaxTimeQueryString = null; + queryCacheMissCount.reset(); + queryCachePutCount.reset(); + + updateTimestampsCacheMissCount.reset(); + updateTimestampsCacheHitCount.reset(); + updateTimestampsCachePutCount.reset(); + + transactionCount.reset(); + committedTransactionCount.reset(); + + optimisticFailureCount.reset(); + + entityStatsMap.clear(); + collectionStatsMap.clear(); + naturalIdQueryStatsMap.clear(); + l2CacheStatsMap.clear(); + queryStatsMap.clear(); + deprecatedNaturalIdStatsMap.clear(); + + startTime = System.currentTimeMillis(); + } + + @Override + public long getStartTime() { + return startTime; + } + + @Override + public boolean isStatisticsEnabled() { + return isStatisticsEnabled; + } + + @Override + public void setStatisticsEnabled(boolean b) { + isStatisticsEnabled = b; + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity stats + + @Override + public String[] getEntityNames() { + if ( sessionFactory == null ) { + return entityStatsMap.keysAsArray(); + } + else { + return sessionFactory.getMetamodel().getAllEntityNames(); + } + } + + @Override + public EntityStatisticsImpl getEntityStatistics(String entityName) { + if ( sessionFactory == null ) { + return null; + } + + return entityStatsMap.getOrCompute( + entityName, + s -> new EntityStatisticsImpl( sessionFactory.getMetamodel().entityPersister( s ) ) + ); + } + + @Override + public long getEntityLoadCount() { + return entityLoadCount.sum(); + } + + @Override + public long getEntityFetchCount() { + return entityFetchCount.sum(); + } + + @Override + public long getEntityDeleteCount() { + return entityDeleteCount.sum(); + } + + @Override + public long getEntityInsertCount() { + return entityInsertCount.sum(); + } + + @Override + public long getEntityUpdateCount() { + return entityUpdateCount.sum(); + } + + @Override + public long getOptimisticFailureCount() { + return optimisticFailureCount.sum(); + } + + @Override + public void loadEntity(String entityName) { + entityLoadCount.increment(); + getEntityStatistics( entityName ).incrementLoadCount(); + } + + @Override + public void fetchEntity(String entityName) { + entityFetchCount.increment(); + getEntityStatistics( entityName ).incrementFetchCount(); + } + + @Override + public void updateEntity(String entityName) { + entityUpdateCount.increment(); + getEntityStatistics( entityName ).incrementUpdateCount(); + } + + @Override + public void insertEntity(String entityName) { + entityInsertCount.increment(); + getEntityStatistics( entityName ).incrementInsertCount(); + } + + @Override + public void deleteEntity(String entityName) { + entityDeleteCount.increment(); + getEntityStatistics( entityName ).incrementDeleteCount(); + } + + @Override + public void optimisticFailure(String entityName) { + optimisticFailureCount.increment(); + getEntityStatistics( entityName ).incrementOptimisticFailureCount(); + } + + @Override + public void entityCachePut(NavigableRole entityName, String regionName) { + secondLevelCachePutCount.increment(); + getDomainDataRegionStatistics( regionName ).incrementPutCount(); + getEntityStatistics( entityName.getFullPath() ).incrementCachePutCount(); + } + + @Override + public void entityCacheHit(NavigableRole entityName, String regionName) { + secondLevelCacheHitCount.increment(); + getDomainDataRegionStatistics( regionName ).incrementHitCount(); + getEntityStatistics( entityName.getFullPath() ).incrementCacheHitCount(); + } + + @Override + public void entityCacheMiss(NavigableRole entityName, String regionName) { + secondLevelCacheMissCount.increment(); + getDomainDataRegionStatistics( regionName ).incrementMissCount(); + getEntityStatistics( entityName.getFullPath() ).incrementCacheMissCount(); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection stats + + @Override + public String[] getCollectionRoleNames() { + if ( sessionFactory == null ) { + return collectionStatsMap.keysAsArray(); + } + else { + return sessionFactory.getMetamodel().getAllCollectionRoles(); + } + } + + @Override + public CollectionStatisticsImpl getCollectionStatistics(String role) { + if ( sessionFactory == null ) { + return null; + } + + return collectionStatsMap.getOrCompute( + role, + s -> new CollectionStatisticsImpl( sessionFactory.getMetamodel().collectionPersister( s ) ) + ); + } + + @Override + public long getCollectionLoadCount() { + return collectionLoadCount.sum(); + } + + @Override + public long getCollectionFetchCount() { + return collectionFetchCount.sum(); + } + + @Override + public long getCollectionUpdateCount() { + return collectionUpdateCount.sum(); + } + + @Override + public long getCollectionRemoveCount() { + return collectionRemoveCount.sum(); + } + + @Override + public long getCollectionRecreateCount() { + return collectionRecreateCount.sum(); + } + + @Override + public void loadCollection(String role) { + collectionLoadCount.increment(); + getCollectionStatistics( role ).incrementLoadCount(); + } + + @Override + public void fetchCollection(String role) { + collectionFetchCount.increment(); + getCollectionStatistics( role ).incrementFetchCount(); + } + + @Override + public void updateCollection(String role) { + collectionUpdateCount.increment(); + getCollectionStatistics( role ).incrementUpdateCount(); + } + + @Override + public void recreateCollection(String role) { + collectionRecreateCount.increment(); + getCollectionStatistics( role ).incrementRecreateCount(); + } + + @Override + public void removeCollection(String role) { + collectionRemoveCount.increment(); + getCollectionStatistics( role ).incrementRemoveCount(); + } + + @Override + public void collectionCachePut(NavigableRole collectionRole, String regionName) { + secondLevelCachePutCount.increment(); + getDomainDataRegionStatistics( regionName ).incrementPutCount(); + getCollectionStatistics( collectionRole.getFullPath() ).incrementCachePutCount(); + } + + @Override + public void collectionCacheHit(NavigableRole collectionRole, String regionName) { + secondLevelCacheHitCount.increment(); + getDomainDataRegionStatistics( regionName ).incrementHitCount(); + getCollectionStatistics( collectionRole.getFullPath() ).incrementCacheHitCount(); + } + + @Override + public void collectionCacheMiss(NavigableRole collectionRole, String regionName) { + secondLevelCacheMissCount.increment(); + getDomainDataRegionStatistics( regionName ).incrementMissCount(); + getCollectionStatistics( collectionRole.getFullPath() ).incrementCacheMissCount(); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Natural-id stats + + @Override + public NaturalIdStatisticsImpl getNaturalIdStatistics(String rootEntityName) { + if ( sessionFactory == null ) { + return null; + } + + return naturalIdQueryStatsMap.getOrCompute( + rootEntityName, + s -> { + final EntityPersister entityDescriptor = sessionFactory.getMetamodel().entityPersister( s ); + if ( !entityDescriptor.hasNaturalIdentifier() ) { + throw new IllegalArgumentException( "Given entity [" + s + "] does not define natural-id" ); + } + return new NaturalIdStatisticsImpl( entityDescriptor ); + } + ); + } + + @Override + public DeprecatedNaturalIdCacheStatisticsImpl getNaturalIdCacheStatistics(String regionName) { + final String key = sessionFactory.getCache().unqualifyRegionName( regionName ); + return deprecatedNaturalIdStatsMap.getOrCompute( + key, + unqualifiedRegionName -> new DeprecatedNaturalIdCacheStatisticsImpl( + unqualifiedRegionName, + sessionFactory.getCache().getNaturalIdAccessesInRegion( unqualifiedRegionName ) + ) + ); + } + + @Override + public long getNaturalIdQueryExecutionCount() { + return naturalIdQueryExecutionCount.sum(); + } + + @Override + public long getNaturalIdQueryExecutionMaxTime() { + return naturalIdQueryExecutionMaxTime.get(); + } + + @Override + public String getNaturalIdQueryExecutionMaxTimeRegion() { + return naturalIdQueryExecutionMaxTimeRegion; + } + + @Override + public String getNaturalIdQueryExecutionMaxTimeEntity() { + return naturalIdQueryExecutionMaxTimeEntity; + } + + @Override + public long getNaturalIdCacheHitCount() { + return naturalIdCacheHitCount.sum(); + } + + @Override + public long getNaturalIdCacheMissCount() { + return naturalIdCacheMissCount.sum(); + } + + @Override + public long getNaturalIdCachePutCount() { + return naturalIdCachePutCount.sum(); + } + + @Override + public void naturalIdCachePut( + NavigableRole rootEntityName, + String regionName) { + naturalIdCachePutCount.increment(); + + getDomainDataRegionStatistics( regionName ).incrementPutCount(); + + getNaturalIdStatistics( rootEntityName.getFullPath() ).incrementCachePutCount(); + + getNaturalIdCacheStatistics( qualify( regionName ) ).incrementPutCount(); + } + + @Override + public void naturalIdCacheHit( + NavigableRole rootEntityName, + String regionName) { + naturalIdCacheHitCount.increment(); + + getDomainDataRegionStatistics( regionName ).incrementHitCount(); + + getNaturalIdStatistics( rootEntityName.getFullPath() ).incrementCacheHitCount(); + + getNaturalIdCacheStatistics( qualify( regionName ) ).incrementHitCount(); + } + + @Override + public void naturalIdCacheMiss( + NavigableRole rootEntityName, + String regionName) { + naturalIdCacheMissCount.increment(); + + getDomainDataRegionStatistics( regionName ).incrementMissCount(); + + getNaturalIdStatistics( rootEntityName.getFullPath() ).incrementCacheMissCount(); + + getNaturalIdCacheStatistics( qualify( regionName ) ).incrementMissCount(); + } + + protected String qualify(String regionName) { + return sessionFactory.getSessionFactoryOptions().getCacheRegionPrefix() == null + ? regionName + : sessionFactory.getSessionFactoryOptions().getCacheRegionPrefix() + '.' + regionName; + } + + @Override + public void naturalIdQueryExecuted(String rootEntityName, long time) { + naturalIdQueryExecutionCount.increment(); + + boolean isLongestQuery; + //noinspection StatementWithEmptyBody + for ( long old = naturalIdQueryExecutionMaxTime.get(); + ( isLongestQuery = time > old ) && ( !naturalIdQueryExecutionMaxTime.compareAndSet( old, time ) ); + old = naturalIdQueryExecutionMaxTime.get() ) { + // nothing to do here given the odd loop structure... + } + + if ( isLongestQuery ) { + naturalIdQueryExecutionMaxTimeEntity = rootEntityName; + } + + final EntityPersister rootEntityPersister = sessionFactory.getMetamodel().entityPersister( rootEntityName ); + + getNaturalIdStatistics( rootEntityName ).queryExecuted( time ); + + if ( rootEntityPersister.hasNaturalIdCache() ) { + final String naturalIdRegionName = rootEntityPersister.getNaturalIdCacheAccessStrategy() + .getRegion() + .getName(); + getNaturalIdCacheStatistics( qualify( naturalIdRegionName ) ).queryExecuted( time ); + + if ( isLongestQuery ) { + naturalIdQueryExecutionMaxTimeRegion = naturalIdRegionName; + } + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Second-level cache region stats + + @Override + public String[] getSecondLevelCacheRegionNames() { + if ( sessionFactory == null ) { + throw new IllegalStateException( "Statistics no longer associated with SessionFactory - cannot get (legacy) region names" ); + } + + return sessionFactory.getCache().getSecondLevelCacheRegionNames(); + } + + @Override + public CacheRegionStatisticsImpl getDomainDataRegionStatistics(String regionName) { + if ( sessionFactory == null ) { + return null; + } + + return l2CacheStatsMap.getOrCompute( + regionName, + s -> { + final Region region = sessionFactory.getCache().getRegion( s ); + + if ( region == null ) { + throw new IllegalArgumentException( "Unknown cache region : " + s ); + } + + if ( region instanceof QueryResultsRegion ) { + throw new IllegalArgumentException( + "Region name [" + s + "] referred to a query result region, not a domain data region" + ); + } + + return new CacheRegionStatisticsImpl( region ); + } + ); + } + + @Override + public CacheRegionStatisticsImpl getQueryRegionStatistics(String regionName) { + final CacheRegionStatisticsImpl existing = l2CacheStatsMap.get( regionName ); + if ( existing != null ) { + return existing; + } + + if ( sessionFactory == null ) { + return null; + } + + final QueryResultsCache regionAccess = sessionFactory.getCache() + .getQueryResultsCacheStrictly( regionName ); + if ( regionAccess == null ) { + return null; + } + + return l2CacheStatsMap.getOrCompute( + regionName, + s -> new CacheRegionStatisticsImpl( regionAccess.getRegion() ) + ); + } + + @Override + public CacheRegionStatisticsImpl getCacheRegionStatistics(String regionName) { + if ( sessionFactory == null ) { + return null; + } + + if ( ! sessionFactory.getSessionFactoryOptions().isSecondLevelCacheEnabled() ) { + return null; + } + + return l2CacheStatsMap.getOrCompute( + regionName, + s -> { + Region region = sessionFactory.getCache().getRegion( s ); + + if ( region == null ) { + + if ( ! sessionFactory.getSessionFactoryOptions().isQueryCacheEnabled() ) { + return null; + } + + // this is the pre-5.3 behavior. and since this is a pre-5.3 method it should behave consistently + // NOTE that this method is deprecated + region = sessionFactory.getCache().getQueryResultsCache( s ).getRegion(); + } + + return new CacheRegionStatisticsImpl( region ); + } + ); + } + + @Override + public CacheRegionStatisticsImpl getSecondLevelCacheStatistics(String regionName) { + if ( sessionFactory == null ) { + return null; + } + return getCacheRegionStatistics( sessionFactory.getCache().unqualifyRegionName( regionName ) ); + } + + @Override + public long getSecondLevelCacheHitCount() { + return secondLevelCacheHitCount.sum(); + } + + @Override + public long getSecondLevelCacheMissCount() { + return secondLevelCacheMissCount.sum(); + } + + @Override + public long getSecondLevelCachePutCount() { + return secondLevelCachePutCount.sum(); + } + + @Override + public long getUpdateTimestampsCacheHitCount() { + return updateTimestampsCacheHitCount.sum(); + } + + @Override + public long getUpdateTimestampsCacheMissCount() { + return updateTimestampsCacheMissCount.sum(); + } + + @Override + public long getUpdateTimestampsCachePutCount() { + return updateTimestampsCachePutCount.sum(); + } + + @Override + public void updateTimestampsCacheHit() { + updateTimestampsCacheHitCount.increment(); + } + + @Override + public void updateTimestampsCacheMiss() { + updateTimestampsCacheMissCount.increment(); + } + + @Override + public void updateTimestampsCachePut() { + updateTimestampsCachePutCount.increment(); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Query statistics + + @Override + public String[] getQueries() { + return queryStatsMap.keysAsArray(); + } + + @Override + public QueryStatisticsImpl getQueryStatistics(String queryString) { + return queryStatsMap.getOrCompute( + queryString, + s -> new QueryStatisticsImpl( s ) + ); + } + + @Override + public long getQueryExecutionCount() { + return queryExecutionCount.sum(); + } + + @Override + public long getQueryCacheHitCount() { + return queryCacheHitCount.sum(); + } + + @Override + public long getQueryCacheMissCount() { + return queryCacheMissCount.sum(); + } + + @Override + public long getQueryCachePutCount() { + return queryCachePutCount.sum(); + } + + @Override + public String getQueryExecutionMaxTimeQueryString() { + return queryExecutionMaxTimeQueryString; + } + + @Override + public long getQueryExecutionMaxTime() { + return queryExecutionMaxTime.get(); + } + + @Override + public void queryExecuted(String hql, int rows, long time) { + LOG.hql(hql, time, (long) rows ); + queryExecutionCount.increment(); + + boolean isLongestQuery; + //noinspection StatementWithEmptyBody + for ( long old = queryExecutionMaxTime.get(); + ( isLongestQuery = time > old ) && ( !queryExecutionMaxTime.compareAndSet( old, time ) ); + old = queryExecutionMaxTime.get() ) { + // nothing to do here given the odd loop structure... + } + + if ( isLongestQuery ) { + queryExecutionMaxTimeQueryString = hql; + } + + if ( hql != null ) { + getQueryStatistics( hql ).executed( rows, time ); + } + } + + @Override + public void queryCacheHit(String hql, String regionName) { + LOG.tracef( "Statistics#queryCacheHit( `%s`, `%s` )", hql, regionName ); + + queryCacheHitCount.increment(); + + getQueryRegionStats( regionName ).incrementHitCount(); + + if ( hql != null ) { + getQueryStatistics( hql ).incrementCacheHitCount(); + } + } + + private CacheRegionStatisticsImpl getQueryRegionStats(String regionName) { + return l2CacheStatsMap.getOrCompute( + regionName, + s -> new CacheRegionStatisticsImpl( sessionFactory.getCache().getQueryResultsCache( s ).getRegion() ) + ); + } + + + @Override + public void queryCacheMiss(String hql, String regionName) { + LOG.tracef( "Statistics#queryCacheMiss( `%s`, `%s` )", hql, regionName ); + + queryCacheMissCount.increment(); + + getQueryRegionStats( regionName ).incrementMissCount(); + + if ( hql != null ) { + getQueryStatistics( hql ).incrementCacheMissCount(); + } + } + + @Override + public void queryCachePut(String hql, String regionName) { + LOG.tracef( "Statistics#queryCachePut( `%s`, `%s` )", hql, regionName ); + + queryCachePutCount.increment(); + + getQueryRegionStats( regionName ).incrementPutCount(); + + if ( hql != null ) { + getQueryStatistics( hql ).incrementCachePutCount(); + } + } + + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Session/misc stats + + @Override + public long getSessionOpenCount() { + return sessionOpenCount.sum(); + } + + @Override + public long getSessionCloseCount() { + return sessionCloseCount.sum(); + } + + @Override + public long getFlushCount() { + return flushCount.sum(); + } + + @Override + public long getConnectCount() { + return connectCount.sum(); + } + + @Override + public long getSuccessfulTransactionCount() { + return committedTransactionCount.sum(); + } + + @Override + public long getTransactionCount() { + return transactionCount.sum(); + } + + @Override + public long getCloseStatementCount() { + return closeStatementCount.sum(); + } + + @Override + public long getPrepareStatementCount() { + return prepareStatementCount.sum(); + } + + @Override + public void openSession() { + sessionOpenCount.increment(); + } + + @Override + public void closeSession() { + sessionCloseCount.increment(); + } + + @Override + public void flush() { + flushCount.increment(); + } + + @Override + public void connect() { + connectCount.increment(); + } + + @Override + public void prepareStatement() { + prepareStatementCount.increment(); + } + + @Override + public void closeStatement() { + closeStatementCount.increment(); + } + + @Override + public void endTransaction(boolean success) { + transactionCount.increment(); + if ( success ) { + committedTransactionCount.increment(); + } + } + + + + @Override + public void logSummary() { + LOG.loggingStatistics(); + LOG.startTime( startTime ); + LOG.sessionsOpened( sessionOpenCount.sum() ); + LOG.sessionsClosed( sessionCloseCount.sum() ); + LOG.transactions( transactionCount.sum() ); + LOG.successfulTransactions( committedTransactionCount.sum() ); + LOG.optimisticLockFailures( optimisticFailureCount.sum() ); + LOG.flushes( flushCount.sum() ); + LOG.connectionsObtained( connectCount.sum() ); + LOG.statementsPrepared( prepareStatementCount.sum() ); + LOG.statementsClosed( closeStatementCount.sum() ); + LOG.secondLevelCachePuts( secondLevelCachePutCount.sum() ); + LOG.secondLevelCacheHits( secondLevelCacheHitCount.sum() ); + LOG.secondLevelCacheMisses( secondLevelCacheMissCount.sum() ); + LOG.entitiesLoaded( entityLoadCount.sum() ); + LOG.entitiesUpdated( entityUpdateCount.sum() ); + LOG.entitiesInserted( entityInsertCount.sum() ); + LOG.entitiesDeleted( entityDeleteCount.sum() ); + LOG.entitiesFetched( entityFetchCount.sum() ); + LOG.collectionsLoaded( collectionLoadCount.sum() ); + LOG.collectionsUpdated( collectionUpdateCount.sum() ); + LOG.collectionsRemoved( collectionRemoveCount.sum() ); + LOG.collectionsRecreated( collectionRecreateCount.sum() ); + LOG.collectionsFetched( collectionFetchCount.sum() ); + LOG.naturalIdCachePuts( naturalIdCachePutCount.sum() ); + LOG.naturalIdCacheHits( naturalIdCacheHitCount.sum() ); + LOG.naturalIdCacheMisses( naturalIdCacheMissCount.sum() ); + LOG.naturalIdMaxQueryTime( naturalIdQueryExecutionMaxTime.get() ); + LOG.naturalIdQueriesExecuted( naturalIdQueryExecutionCount.sum() ); + LOG.queriesExecuted( queryExecutionCount.sum() ); + LOG.queryCachePuts( queryCachePutCount.sum() ); + LOG.timestampCachePuts( updateTimestampsCachePutCount.sum() ); + LOG.timestampCacheHits( updateTimestampsCacheHitCount.sum() ); + LOG.timestampCacheMisses( updateTimestampsCacheMissCount.sum() ); + LOG.queryCacheHits( queryCacheHitCount.sum() ); + LOG.queryCacheMisses( queryCacheMissCount.sum() ); + LOG.maxQueryTime( queryExecutionMaxTime.get() ); + } + + @Override + public String toString() { + return new StringBuilder() + .append( "Statistics[" ) + .append( "start time=" ).append( startTime ) + .append( ",sessions opened=" ).append( sessionOpenCount ) + .append( ",sessions closed=" ).append( sessionCloseCount ) + .append( ",transactions=" ).append( transactionCount ) + .append( ",successful transactions=" ).append( committedTransactionCount ) + .append( ",optimistic lock failures=" ).append( optimisticFailureCount ) + .append( ",flushes=" ).append( flushCount ) + .append( ",connections obtained=" ).append( connectCount ) + .append( ",statements prepared=" ).append( prepareStatementCount ) + .append( ",statements closed=" ).append( closeStatementCount ) + .append( ",second level cache puts=" ).append( secondLevelCachePutCount ) + .append( ",second level cache hits=" ).append( secondLevelCacheHitCount ) + .append( ",second level cache misses=" ).append( secondLevelCacheMissCount ) + .append( ",entities loaded=" ).append( entityLoadCount ) + .append( ",entities updated=" ).append( entityUpdateCount ) + .append( ",entities inserted=" ).append( entityInsertCount ) + .append( ",entities deleted=" ).append( entityDeleteCount ) + .append( ",entities fetched=" ).append( entityFetchCount ) + .append( ",collections loaded=" ).append( collectionLoadCount ) + .append( ",collections updated=" ).append( collectionUpdateCount ) + .append( ",collections removed=" ).append( collectionRemoveCount ) + .append( ",collections recreated=" ).append( collectionRecreateCount ) + .append( ",collections fetched=" ).append( collectionFetchCount ) + .append( ",naturalId queries executed to database=" ).append( naturalIdQueryExecutionCount ) + .append( ",naturalId cache puts=" ).append( naturalIdCachePutCount ) + .append( ",naturalId cache hits=" ).append( naturalIdCacheHitCount ) + .append( ",naturalId cache misses=" ).append( naturalIdCacheMissCount ) + .append( ",naturalId max query time=" ).append( naturalIdQueryExecutionMaxTime ) + .append( ",queries executed to database=" ).append( queryExecutionCount ) + .append( ",query cache puts=" ).append( queryCachePutCount ) + .append( ",query cache hits=" ).append( queryCacheHitCount ) + .append( ",query cache misses=" ).append( queryCacheMissCount ) + .append(",update timestamps cache puts=").append(updateTimestampsCachePutCount) + .append(",update timestamps cache hits=").append(updateTimestampsCacheHitCount) + .append(",update timestamps cache misses=").append(updateTimestampsCacheMissCount) + .append( ",max query time=" ).append( queryExecutionMaxTime ) + .append( ']' ) + .toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsInitiator.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsInitiator.java index ba96e129fea2..856eb2b9d01a 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsInitiator.java @@ -14,6 +14,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.SessionFactoryServiceInitiator; +import org.hibernate.service.spi.SessionFactoryServiceInitiatorContext; import org.hibernate.stat.spi.StatisticsFactory; import org.hibernate.stat.spi.StatisticsImplementor; @@ -38,12 +39,24 @@ public Class getServiceInitiated() { return StatisticsImplementor.class; } + @Override + public StatisticsImplementor initiateService(SessionFactoryServiceInitiatorContext context) { + final Object configValue = context.getServiceRegistry() + .getService( ConfigurationService.class ) + .getSettings() + .get( STATS_BUILDER ); + return initiateServiceInternal( context.getSessionFactory(), configValue, context.getServiceRegistry() ); + } + @Override public StatisticsImplementor initiateService( SessionFactoryImplementor sessionFactory, SessionFactoryOptions sessionFactoryOptions, ServiceRegistryImplementor registry) { - final Object configValue = registry.getService( ConfigurationService.class ).getSettings().get( STATS_BUILDER ); + final Object configValue = registry + .getService( ConfigurationService.class ) + .getSettings() + .get( STATS_BUILDER ); return initiateServiceInternal( sessionFactory, configValue, registry ); } @@ -52,9 +65,9 @@ private StatisticsImplementor initiateServiceInternal( Object configValue, ServiceRegistryImplementor registry) { - StatisticsFactory statisticsFactory; + final StatisticsFactory statisticsFactory; if ( configValue == null ) { - statisticsFactory = DEFAULT_STATS_BUILDER; + statisticsFactory = null; //We'll use the default } else if ( StatisticsFactory.class.isInstance( configValue ) ) { statisticsFactory = (StatisticsFactory) configValue; @@ -75,18 +88,18 @@ else if ( StatisticsFactory.class.isInstance( configValue ) ) { ); } } - - StatisticsImplementor statistics = statisticsFactory.buildStatistics( sessionFactory ); + final StatisticsImplementor statistics; + if ( statisticsFactory == null ) { + // Default: + statistics = new StatisticsImpl( sessionFactory ); + } + else { + statistics = statisticsFactory.buildStatistics( sessionFactory ); + } final boolean enabled = sessionFactory.getSettings().isStatisticsEnabled(); statistics.setStatisticsEnabled( enabled ); LOG.debugf( "Statistics initialized [enabled=%s]", enabled ); return statistics; } - private static StatisticsFactory DEFAULT_STATS_BUILDER = new StatisticsFactory() { - @Override - public StatisticsImplementor buildStatistics(SessionFactoryImplementor sessionFactory) { - return new ConcurrentStatisticsImpl( sessionFactory ); - } - }; } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsHelper.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsHelper.java new file mode 100644 index 000000000000..fb2c84f70115 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsHelper.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.stat.internal; + +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Utilities useful when dealing with stats. + * + * @author Steve Ebersole + */ +public class StatsHelper { + /** + * Singleton access + */ + public static final StatsHelper INSTANCE = new StatsHelper(); + + public NavigableRole getRootEntityRole(EntityPersister entityDescriptor) { + final String rootEntityName = entityDescriptor.getRootEntityName(); + if ( entityDescriptor.getEntityName().equals( rootEntityName ) ) { + return entityDescriptor.getNavigableRole(); + } + else { + final EntityPersister rootEntityDescriptor = entityDescriptor.getFactory() + .getMetamodel() + .entityPersister( rootEntityName ); + return rootEntityDescriptor.getNavigableRole(); + } + } + + private StatsHelper() { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsNamedContainer.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsNamedContainer.java new file mode 100644 index 000000000000..9fe926057282 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsNamedContainer.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * Decorates a ConcurrentHashMap implementation to make sure the methods are being + * used correctly for the purpose of Hibernate's statistics. + * In particular, we do like the semantics of ConcurrentHashMap#computeIfAbsent + * but not how it performs. + * See also: + * - http://jsr166-concurrency.10961.n7.nabble.com/Re-ConcurrentHashMap-computeIfAbsent-td11687.html + * - https://hibernate.atlassian.net/browse/HHH-13527 + * + * @author Sanne Grinovero + */ +final class StatsNamedContainer { + + private final ConcurrentHashMap map = new ConcurrentHashMap(); + + public void clear() { + map.clear(); + } + + /** + * This method is inherently racy and expensive. Only use on non-hot paths, and + * only to get a recent snapshot. + * @return all keys in string form. + */ + public String[] keysAsArray() { + return map.keySet().toArray( new String[0] ); + } + + /** + * Similar semantics as you'd get by invoking {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)}, + * but we check for the key existence first. + * This is technically a redundant check, but it has been shown to perform better when the key existing is very likely, + * as in our case. + * Most notably, the ConcurrentHashMap implementation might block other accesses for the sake of making + * sure the function is invoked at most once: we don't need this guarantee, and prefer to reduce risk of blocking. + */ + public V getOrCompute(final String key, final Function function) { + final V v1 = map.get( key ); + if ( v1 != null ) { + return v1; + } + else { + final V v2 = function.apply( key ); + //Occasionally a function might return null. We can't store a null in the CHM, + // so a placeholder would be required to implement that, but we prefer to just keep this + // situation as slightly sub-optimal so to not make the code more complex just to handle the exceptional case: + // null values are assumed to be rare enough for this not being worth it. + if ( v2 == null ) { + return null; + } + else { + final V v3 = map.putIfAbsent( key, v2 ); + if ( v3 == null ) { + return v2; + } + else { + return v3; + } + } + } + } + + public V get(final String key) { + return map.get( key ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java b/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java index 677184bddaa6..6891be2e83da 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java @@ -6,12 +6,13 @@ */ package org.hibernate.stat.spi; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.service.Service; import org.hibernate.stat.Statistics; /** - * Statistics SPI for the Hibernate core. This is essentially the "statistic collector" API, its the contract - * called to collect various stats. + * Statistics SPI for the Hibernate core. This is essentially the + * "statistic collector" API, its the contract called to collect various stats. * * @author Emmanuel Bernard */ @@ -134,55 +135,84 @@ public interface StatisticsImplementor extends Statistics, Service { */ void removeCollection(String role); + + + /** * Callback indicating a put into second level cache. * - * @param regionName The name of the cache region + * @apiNote `entityName` should be the root entity name */ - void secondLevelCachePut(String regionName); + void entityCachePut(NavigableRole entityName, String regionName); /** * Callback indicating a get from second level cache resulted in a hit. * - * @param regionName The name of the cache region + * @apiNote `entityName` should be the root entity name */ - void secondLevelCacheHit(String regionName); + void entityCacheHit(NavigableRole entityName, String regionName); /** * Callback indicating a get from second level cache resulted in a miss. * + * @apiNote `entityName` should be the root entity name + */ + void entityCacheMiss(NavigableRole entityName, String regionName); + + + + + /** + * Callback indicating a put into second level cache. + * + * @param collectionRole The collection's "path" * @param regionName The name of the cache region */ - void secondLevelCacheMiss(String regionName); - + void collectionCachePut(NavigableRole collectionRole, String regionName); + /** - * Callback indicating a put into natural id cache. + * Callback indicating a get from second level cache resulted in a hit. * + * @param collectionRole The collection's "path" * @param regionName The name of the cache region */ - void naturalIdCachePut(String regionName); - + void collectionCacheHit(NavigableRole collectionRole, String regionName); + /** - * Callback indicating a get from natural id cache resulted in a hit. + * Callback indicating a get from second level cache resulted in a miss. * + * @param collectionRole The collection's "path" * @param regionName The name of the cache region */ - void naturalIdCacheHit(String regionName); + void collectionCacheMiss(NavigableRole collectionRole, String regionName); + + + + + + + /** + * Callback indicating a put into natural id cache. + */ + void naturalIdCachePut(NavigableRole rootEntityName, String regionName); + + /** + * Callback indicating a get from natural id cache resulted in a hit. + */ + void naturalIdCacheHit(NavigableRole rootEntityName, String regionName); /** * Callback indicating a get from natural id cache resulted in a miss. - * - * @param regionName The name of the cache region */ - void naturalIdCacheMiss(String regionName); + void naturalIdCacheMiss(NavigableRole rootEntityName, String regionName); /** * Callback indicating execution of a natural id query - * - * @param regionName The name of the cache region - * @param time execution time */ - void naturalIdQueryExecuted(String regionName, long time); + void naturalIdQueryExecuted(String rootEntityName, long executionTime); + + + /** * Callback indicating a put into the query cache. diff --git a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java index 7455c03465c1..cfddf1500bbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java @@ -6,69 +6,193 @@ */ package org.hibernate.tool.enhance; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; +import org.hibernate.cfg.Environment; + import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileFilter; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; -import org.hibernate.bytecode.enhance.spi.Enhancer; -import org.hibernate.cfg.Environment; - -import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.DirectoryScanner; -import org.apache.tools.ant.Project; -import org.apache.tools.ant.Task; -import org.apache.tools.ant.types.FileSet; - /** - * Ant task for performing build-time enhancement of entities and component/embeddable classes. - *

    - * IMPL NOTE : currently makes numerous assumptions, the most "horrific" being that all entities are - * annotated @Entity which precludes {@code hbm.xml} mappings as well as complete {@code orm.xml} mappings. This is - * just a PoC though... + * Ant task for performing build-time enhancement of entity objects. + * + * Code based on from: + * https://github.com/hibernate/hibernate-orm/blob/159bc99a36d86988b61b88ba91eec82cac044e1c/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java + * https://github.com/hibernate/hibernate-orm/blob/159bc99a36d86988b61b88ba91eec82cac044e1c/tooling/hibernate-enhance-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/MavenEnhancePlugin.java + * + * + * + * + * + * + * + * * - * @author Steve Ebersole + * @author Luis Barreiro + * @author Taro App * @see org.hibernate.engine.spi.Managed */ public class EnhancementTask extends Task { - private List filesets = new ArrayList(); - private final Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( new DefaultEnhancementContext() ); + private String base; + private String dir; + + private boolean failOnError = true; + private boolean enableLazyInitialization = false; + private boolean enableDirtyTracking = false; + private boolean enableAssociationManagement = false; + private boolean enableExtendedEnhancement = false; + private List sourceSet = new ArrayList<>(); + + public void setBase(String base) { + this.base = base; + } + + public void setDir(String dir) { + this.dir = dir; + } - public void addFileset(FileSet set) { - this.filesets.add( set ); + public void setFailOnError(boolean failOnError) { + this.failOnError = failOnError; + } + + public void setEnableLazyInitialization(boolean enableLazyInitialization) { + this.enableLazyInitialization = enableLazyInitialization; + } + + public void setEnableDirtyTracking(boolean enableDirtyTracking) { + this.enableDirtyTracking = enableDirtyTracking; + } + + public void setEnableAssociationManagement(boolean enableAssociationManagement) { + this.enableAssociationManagement = enableAssociationManagement; + } + + public void setEnableExtendedEnhancement(boolean enableExtendedEnhancement) { + this.enableExtendedEnhancement = enableExtendedEnhancement; + } + + private boolean shouldApply() { + return enableLazyInitialization || enableDirtyTracking || enableAssociationManagement || enableExtendedEnhancement; } @Override public void execute() throws BuildException { - log( "Starting Hibernate EnhancementTask execution", Project.MSG_INFO ); - - // we use the CtClass stuff here just as a simple vehicle for obtaining low level information about - // the class(es) contained in a file while still maintaining easy access to the underlying byte[] - final Project project = getProject(); - - for ( FileSet fileSet : filesets ) { - final File fileSetBaseDir = fileSet.getDir( project ); - final DirectoryScanner directoryScanner = fileSet.getDirectoryScanner( project ); - for ( String relativeIncludedFileName : directoryScanner.getIncludedFiles() ) { - final File javaClassFile = new File( fileSetBaseDir, relativeIncludedFileName ); - if ( !javaClassFile.exists() ) { - continue; - } + if ( !shouldApply() ) { + log( "Skipping Hibernate bytecode enhancement task execution since no feature is enabled", Project.MSG_WARN ); + return; + } + + if ( !dir.startsWith( base ) ) { + throw new BuildException( "The enhancement directory 'dir' (" + dir + ") is no subdirectory of 'base' (" + base + ")" ); + } + + // Perform a depth first search for sourceSet + File root = new File( dir ); + if ( !root.exists() ) { + log( "Skipping Hibernate enhancement task execution since there is no classes dir " + dir, Project.MSG_INFO ); + return; + } + walkDir( root ); + if ( sourceSet.isEmpty() ) { + log( "Skipping Hibernate enhancement task execution since there are no classes to enhance on " + dir, Project.MSG_INFO ); + return; + } + + log( "Starting Hibernate enhancement task for classes on " + dir, Project.MSG_INFO ); + ClassLoader classLoader = toClassLoader( Collections.singletonList( new File( base ) ) ); - processClassFile( relativeIncludedFileName, javaClassFile ); + EnhancementContext enhancementContext = new DefaultEnhancementContext() { + @Override + public ClassLoader getLoadingClassLoader() { + return classLoader; } + + @Override + public boolean doBiDirectionalAssociationManagement(UnloadedField field) { + return enableAssociationManagement; + } + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return enableDirtyTracking; + } + + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return enableLazyInitialization; + } + + @Override + public boolean isLazyLoadable(UnloadedField field) { + return enableLazyInitialization; + } + + @Override + public boolean doExtendedEnhancement(UnloadedClass classDescriptor) { + return enableExtendedEnhancement; + } + }; + + if ( enableExtendedEnhancement ) { + log( "Extended enhancement is enabled. Classes other than entities may be modified. You should consider access the entities using getter/setter methods and disable this property. Use at your own risk.", Project.MSG_WARN ); } + + Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( enhancementContext ); + + for ( File file : sourceSet ) { + byte[] enhancedBytecode = doEnhancement( file, enhancer ); + if ( enhancedBytecode == null ) { + continue; + } + writeOutEnhancedClass( enhancedBytecode, file ); + + log( "Successfully enhanced class [" + file + "]", Project.MSG_INFO ); + } + } + + private ClassLoader toClassLoader(List runtimeClasspath) throws BuildException { + List urls = new ArrayList<>(); + for ( File file : runtimeClasspath ) { + try { + urls.add( file.toURI().toURL() ); + log( "Adding classpath entry for classes root " + file.getAbsolutePath(), Project.MSG_DEBUG ); + } + catch ( MalformedURLException e ) { + String msg = "Unable to resolve classpath entry to URL: " + file.getAbsolutePath(); + if ( failOnError ) { + throw new BuildException( msg, e ); + } + log( msg, Project.MSG_WARN ); + } + } + + return new URLClassLoader( urls.toArray( new URL[urls.size()] ), Enhancer.class.getClassLoader() ); } - private void processClassFile(String relativeIncludedFileName, File javaClassFile) { + private byte[] doEnhancement(File javaClassFile, Enhancer enhancer) throws BuildException { try { - String className = relativeIncludedFileName.substring( 0, ".class".length() ).replace( File.separatorChar, '.' ); + String className = javaClassFile.getAbsolutePath().substring( + base.length() + 1, + javaClassFile.getAbsolutePath().length() - ".class".length() + ).replace( File.separatorChar, '.' ); ByteArrayOutputStream originalBytes = new ByteArrayOutputStream(); FileInputStream fileInputStream = new FileInputStream( javaClassFile ); try { @@ -81,48 +205,85 @@ private void processClassFile(String relativeIncludedFileName, File javaClassFil finally { fileInputStream.close(); } - byte[] result = enhancer.enhance( className, originalBytes.toByteArray() ); - if ( result != null ) { - writeEnhancedClass( javaClassFile, result ); - } + return enhancer.enhance( className, originalBytes.toByteArray() ); } catch (Exception e) { - log( "Unable to enhance class file [" + javaClassFile.getAbsolutePath() + "]", e, Project.MSG_WARN ); + String msg = "Unable to enhance class: " + javaClassFile.getName(); + if ( failOnError ) { + throw new BuildException( msg, e ); + } + log( msg, e, Project.MSG_WARN ); + return null; } } - private void writeEnhancedClass(File javaClassFile, byte[] result) { + /** + * Expects a directory. + */ + private void walkDir(File dir) { + walkDir( + dir, + new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile() && pathname.getName().endsWith( ".class" ); + } + }, + new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + } + ); + } + + private void walkDir(File dir, FileFilter classesFilter, FileFilter dirFilter) { + File[] dirs = dir.listFiles( dirFilter ); + for ( File dir1 : dirs ) { + walkDir( dir1, classesFilter, dirFilter ); + } + File[] files = dir.listFiles( classesFilter ); + Collections.addAll( sourceSet, files ); + } + + private void writeOutEnhancedClass(byte[] enhancedBytecode, File file) throws BuildException { try { - if ( javaClassFile.delete() ) { - if ( !javaClassFile.createNewFile() ) { - log( "Unable to recreate class file [" + javaClassFile.getName() + "]", Project.MSG_INFO ); + if ( file.delete() ) { + if ( !file.createNewFile() ) { + log( "Unable to recreate class file", Project.MSG_ERR ); } } else { - log( "Unable to delete class file [" + javaClassFile.getName() + "]", Project.MSG_INFO ); + log( "Unable to delete class file", Project.MSG_ERR ); } + } + catch ( IOException e ) { + log( "Problem preparing class file for writing out enhancements", e, Project.MSG_WARN ); + } - FileOutputStream outputStream = new FileOutputStream( javaClassFile, false ); - try { - outputStream.write( result ); - outputStream.flush(); + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream( file, false ); + outputStream.write( enhancedBytecode ); + outputStream.flush(); + } + catch ( IOException e ) { + String msg = String.format( "Error writing to enhanced class [%s] to file [%s]", file.getName(), file.getAbsolutePath() ); + if ( failOnError ) { + throw new BuildException( msg, e ); } - finally { - try { + log( msg, e, Project.MSG_WARN ); + } + finally { + try { + if ( outputStream != null ) { outputStream.close(); } - catch (IOException ignore) { - } } - } - catch (FileNotFoundException ignore) { - // should not ever happen because of explicit checks - } - catch (IOException e) { - throw new BuildException( - String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), e - ); + catch ( IOException ignore ) { + // ignore + } } } - } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java index 89f43ecfb06a..c7e4b21eb1cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java @@ -198,7 +198,7 @@ public SchemaExport setFormat(boolean format) { /** * Should we stop once an error occurs? * - * @param haltOnError True if export should stop afterQuery error. + * @param haltOnError True if export should stop after error. * * @return this */ @@ -356,7 +356,6 @@ public static void main(String[] args) { } catch (Exception e) { LOG.unableToCreateSchema( e ); - e.printStackTrace(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExportTask.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExportTask.java index ce71c031fdfe..73cbfaed31ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExportTask.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExportTask.java @@ -23,6 +23,7 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.internal.build.AllowSysOut; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.tool.schema.Action; @@ -193,6 +194,7 @@ public void execute() throws BuildException { } } + @AllowSysOut private void doExecution() throws Exception { final BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder().build(); final StandardServiceRegistryBuilder ssrBuilder = new StandardServiceRegistryBuilder( bsr ); diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdate.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdate.java index 87884b8793e9..ea00c72c51d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdate.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdate.java @@ -34,6 +34,8 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.tool.schema.TargetType; import org.hibernate.tool.schema.internal.ExceptionHandlerCollectingImpl; +import org.hibernate.tool.schema.internal.ExceptionHandlerHaltImpl; +import org.hibernate.tool.schema.spi.ExceptionHandler; import org.hibernate.tool.schema.spi.ExecutionOptions; import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; @@ -50,6 +52,8 @@ public class SchemaUpdate { private final List exceptions = new ArrayList(); + boolean haltOnError = false; + private String outputFile; private String delimiter; private boolean format; @@ -75,7 +79,10 @@ public void execute(EnumSet targetTypes, Metadata metadata, ServiceR final SchemaManagementTool tool = serviceRegistry.getService( SchemaManagementTool.class ); - final ExceptionHandlerCollectingImpl exceptionHandler = new ExceptionHandlerCollectingImpl(); + final ExceptionHandler exceptionHandler = haltOnError + ? ExceptionHandlerHaltImpl.INSTANCE + : new ExceptionHandlerCollectingImpl(); + final ExecutionOptions executionOptions = SchemaManagementToolCoordinator.buildExecutionOptions( config, exceptionHandler @@ -87,7 +94,9 @@ public void execute(EnumSet targetTypes, Metadata metadata, ServiceR tool.getSchemaMigrator( config ).doMigration( metadata, executionOptions, targetDescriptor ); } finally { - exceptions.addAll( exceptionHandler.getExceptions() ); + if ( exceptionHandler instanceof ExceptionHandlerCollectingImpl ) { + exceptions.addAll( ( (ExceptionHandlerCollectingImpl) exceptionHandler ).getExceptions() ); + } } } @@ -101,6 +110,7 @@ public List getExceptions() { } public SchemaUpdate setHaltOnError(boolean haltOnError) { + this.haltOnError = haltOnError; return this; } @@ -144,7 +154,6 @@ public static void main(String[] args) { } catch (Exception e) { LOG.unableToRunSchemaUpdate( e ); - e.printStackTrace(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdateTask.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdateTask.java index cd7c8d5440de..17d73b18ecf1 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdateTask.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaUpdateTask.java @@ -215,7 +215,9 @@ private void configure(StandardServiceRegistryBuilder registryBuilder) throws IO properties.putAll( getProject().getProperties() ); } else { - properties.load( new FileInputStream( propertiesFile ) ); + try (FileInputStream fip = new FileInputStream( propertiesFile )){ + properties.load( fip ); + } } registryBuilder.applySettings( properties ); diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaValidator.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaValidator.java index 63772e2790ca..66fc318c4795 100755 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaValidator.java @@ -80,7 +80,6 @@ public static void main(String[] args) { } catch (Exception e) { LOG.unableToRunSchemaUpdate( e ); - e.printStackTrace(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ScriptExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ScriptExporter.java index 7dd0addf70a2..1bcadba584c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ScriptExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ScriptExporter.java @@ -6,6 +6,8 @@ */ package org.hibernate.tool.hbm2ddl; +import org.hibernate.internal.build.AllowSysOut; + /** * @author Steve Ebersole * @@ -20,6 +22,7 @@ public boolean acceptsImportScripts() { } @Override + @AllowSysOut public void export(String string) throws Exception { System.out.println( string ); } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/JdbcMetadaAccessStrategy.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/JdbcMetadaAccessStrategy.java index 6ce5049d9a5f..a25c53ed54ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/JdbcMetadaAccessStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/JdbcMetadaAccessStrategy.java @@ -6,8 +6,11 @@ */ package org.hibernate.tool.schema; +import java.util.Map; + import org.hibernate.cfg.AvailableSettings; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; /** * @author Andrea Boriero @@ -24,6 +27,9 @@ public enum JdbcMetadaAccessStrategy { * The {@link org.hibernate.tool.schema.spi.SchemaMigrator} and {@link org.hibernate.tool.schema.spi.SchemaValidator} * execute a single {@link java.sql.DatabaseMetaData#getTables(String, String, String, String[])} call * to retrieve all the database table in order to determine all the {@link javax.persistence.Entity} have a mapped database tables. + *

    + * This strategy is the default one and it may require {@link AvailableSettings#DEFAULT_CATALOG} and/or + * {@link AvailableSettings#DEFAULT_SCHEMA} values to be provided. */ GROUPED( "grouped" ); @@ -38,20 +44,34 @@ public String toString() { return strategy; } - public static JdbcMetadaAccessStrategy interpretHbm2ddlSetting(Object value) { - if(value == null){ - return GROUPED; + public static JdbcMetadaAccessStrategy interpretSetting(Map options) { + if ( options == null ) { + return interpretHbm2ddlSetting( null ); } - String name = value.toString(); - if ( StringHelper.isEmpty( name ) || GROUPED.strategy.equals( name ) ) { - return GROUPED; - } - else if ( INDIVIDUALLY.strategy.equals( name ) ) { + else if ( ConfigurationHelper.getBoolean( AvailableSettings.ENABLE_SYNONYMS, options, false ) ) { + // Use of synonyms can cause issues during schema validation or schema update when GROUPED strategy is used (especially in Oracle) return INDIVIDUALLY; } else { - throw new IllegalArgumentException( "Unrecognized `" + AvailableSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY + "` value : " + name ); + return interpretHbm2ddlSetting( options.get( AvailableSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY ) ); } + } + public static JdbcMetadaAccessStrategy interpretHbm2ddlSetting(Object value) { + if ( value == null ) { + return GROUPED; + } + else { + final String name = value.toString(); + if ( StringHelper.isEmpty( name ) || GROUPED.strategy.equals( name ) ) { + return GROUPED; + } + else if ( INDIVIDUALLY.strategy.equals( name ) ) { + return INDIVIDUALLY; + } + else { + throw new IllegalArgumentException( "Unrecognized `" + AvailableSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY + "` value : " + name ); + } + } } } 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 f4189f5ea8ce..0721ffedd00d 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 @@ -15,6 +15,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.StringTokenizer; import org.hibernate.JDBCException; @@ -29,7 +30,6 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.compare.EqualsHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.tool.schema.extract.spi.ColumnInformation; import org.hibernate.tool.schema.extract.spi.ExtractionContext; @@ -418,7 +418,15 @@ private TableInformation locateTableInNamespace( if ( extractionContext.getJdbcEnvironment().getNameQualifierSupport().supportsCatalogs() ) { if ( catalog == null ) { - catalogFilter = ""; + String defaultCatalog = ""; + if ( extractionContext.getJdbcEnvironment().getNameQualifierSupport().supportsCatalogs() ) { + try { + defaultCatalog = extractionContext.getJdbcConnection().getCatalog(); + } + catch (SQLException ignore) { + } + } + catalogFilter = defaultCatalog; } else { catalogToUse = catalog; @@ -639,7 +647,7 @@ public PrimaryKeyInformation getPrimaryKey(TableInformationImpl tableInformation firstPass = false; } else { - if ( !EqualsHelper.equals( pkIdentifier, currentPkIdentifier ) ) { + if ( !Objects.equals( pkIdentifier, currentPkIdentifier ) ) { throw new SchemaExtractionException( String.format( "Encountered primary keys differing name on table %s", diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/TableInformation.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/TableInformation.java index 89d87d81ce3b..a89bbf0ad879 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/TableInformation.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/TableInformation.java @@ -79,9 +79,9 @@ public interface TableInformation { /** * Retrieve the named IndexInformation - * + * * @param indexName The index identifier (simple name) - * + * * @return The matching index information. May return {@code null} */ public IndexInformation getIndex(Identifier indexName); 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 49d9b9e29c5c..84769b2ab480 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 @@ -11,6 +11,9 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.hibernate.boot.Metadata; import org.hibernate.boot.model.naming.Identifier; @@ -35,6 +38,7 @@ import org.hibernate.tool.hbm2ddl.UniqueConstraintSchemaUpdateStrategy; import org.hibernate.tool.schema.extract.spi.DatabaseInformation; import org.hibernate.tool.schema.extract.spi.ForeignKeyInformation; +import org.hibernate.tool.schema.extract.spi.ForeignKeyInformation.ColumnReferenceMapping; import org.hibernate.tool.schema.extract.spi.IndexInformation; import org.hibernate.tool.schema.extract.spi.NameSpaceTablesInformation; import org.hibernate.tool.schema.extract.spi.SequenceInformation; @@ -174,7 +178,7 @@ private void performMigration( } } - // Create beforeQuery-table AuxiliaryDatabaseObjects + // Create before-table AuxiliaryDatabaseObjects for ( AuxiliaryDatabaseObject auxiliaryDatabaseObject : database.getAuxiliaryDatabaseObjects() ) { if ( !auxiliaryDatabaseObject.beforeTablesOnCreation() && auxiliaryDatabaseObject.appliesToDialect( dialect ) ) { applySqlStrings( @@ -234,7 +238,7 @@ private void performMigration( } } - //NOTE : Foreign keys must be created *afterQuery* all tables of all namespaces for cross namespace fks. see HHH-10420 + //NOTE : Foreign keys must be created *after* all tables of all namespaces for cross namespace fks. see HHH-10420 for ( Namespace namespace : database.getNamespaces() ) { if ( schemaFilter.includeNamespace( namespace ) ) { final NameSpaceTablesInformation nameSpaceTablesInformation = tablesInformation.get( namespace ); @@ -249,7 +253,7 @@ private void performMigration( } } - // Create afterQuery-table AuxiliaryDatabaseObjects + // Create after-table AuxiliaryDatabaseObjects for ( AuxiliaryDatabaseObject auxiliaryDatabaseObject : database.getAuxiliaryDatabaseObjects() ) { if ( auxiliaryDatabaseObject.beforeTablesOnCreation() && auxiliaryDatabaseObject.appliesToDialect( dialect )) { applySqlStrings( @@ -296,8 +300,8 @@ protected void migrateTable( dialect, metadata, tableInformation, - getDefaultCatalogName( database, dialect ), - getDefaultSchemaName( database, dialect ) + database.getDefaultNamespace().getPhysicalName().getCatalog(), + database.getDefaultNamespace().getPhysicalName().getSchema() ), formatter, options, @@ -414,14 +418,14 @@ protected void applyForeignKeys( while ( fkItr.hasNext() ) { final ForeignKey foreignKey = fkItr.next(); if ( foreignKey.isPhysicalConstraint() && foreignKey.isCreationEnabled() ) { - ForeignKeyInformation existingForeignKey = null; + boolean existingForeignKeyFound = false; if ( tableInformation != null ) { - existingForeignKey = findMatchingForeignKey( + existingForeignKeyFound = checkForExistingForeignKey( foreignKey, tableInformation ); } - if ( existingForeignKey == null ) { + if ( !existingForeignKeyFound ) { // todo : shouldn't we just drop+recreate if FK exists? // this follows the existing code from legacy SchemaUpdate which just skipped @@ -439,11 +443,41 @@ protected void applyForeignKeys( } } - private ForeignKeyInformation findMatchingForeignKey(ForeignKey foreignKey, TableInformation tableInformation) { - if ( foreignKey.getName() == null ) { - return null; + /** + * Check if the ForeignKey already exists. First check based on definition and if that is not matched check if a key + * with the exact same name exists. Keys with the same name are presumed to be functional equal. + * + * @param foreignKey - ForeignKey, new key to be created + * @param tableInformation - TableInformation, information of existing keys + * @return boolean, true if key already exists + */ + private boolean checkForExistingForeignKey(ForeignKey foreignKey, TableInformation tableInformation) { + if ( foreignKey.getName() == null || tableInformation == null ) { + return false; } - return tableInformation.getForeignKey( Identifier.toIdentifier( foreignKey.getName() ) ); + + final String referencingColumn = foreignKey.getColumn( 0 ).getName(); + final String referencedTable = foreignKey.getReferencedTable().getName(); + + /* + * Find existing keys based on referencing column and referencedTable. "referencedColumnName" is not checked + * because that always is the primary key of the "referencedTable". + */ + Predicate mappingPredicate = m -> { + String existingReferencingColumn = m.getReferencingColumnMetadata().getColumnIdentifier().getText(); + String existingReferencedTable = m.getReferencedColumnMetadata().getContainingTableInformation().getName().getTableName().getCanonicalName(); + return referencingColumn.equals( existingReferencingColumn ) && referencedTable.equals( existingReferencedTable ); + }; + Stream keyStream = StreamSupport.stream( tableInformation.getForeignKeys().spliterator(), false ); + Stream mappingStream = keyStream.flatMap( k -> StreamSupport.stream( k.getColumnReferenceMappings().spliterator(), false ) ); + boolean found = mappingStream.anyMatch( mappingPredicate ); + if ( found ) { + return true; + } + + // And at the end just compare the name of the key. If a key with the same name exists we assume the function is + // also the same... + return tableInformation.getForeignKey( Identifier.toIdentifier( foreignKey.getName() ) ) != null; } protected void checkExportIdentifier(Exportable exportable, Set exportIdentifiers) { @@ -547,14 +581,4 @@ private static void applySqlStrings( } } } - - private String getDefaultCatalogName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getCatalog(); - return identifier == null ? null : identifier.render( dialect ); - } - - private String getDefaultSchemaName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getSchema(); - return identifier == null ? null : identifier.render( dialect ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/DdlTransactionIsolatorProvidedConnectionImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/DdlTransactionIsolatorProvidedConnectionImpl.java index f96e5bec9c1f..2606dcc53326 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/DdlTransactionIsolatorProvidedConnectionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/DdlTransactionIsolatorProvidedConnectionImpl.java @@ -9,6 +9,9 @@ import java.sql.Connection; import java.sql.SQLException; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.hibernate.tool.schema.internal.exec.JdbcConnectionAccessProvidedConnectionImpl; import org.hibernate.tool.schema.internal.exec.JdbcContext; @@ -20,6 +23,9 @@ * @author Steve Ebersole */ class DdlTransactionIsolatorProvidedConnectionImpl implements DdlTransactionIsolator { + + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DdlTransactionIsolatorProvidedConnectionImpl.class ); + private final JdbcContext jdbcContext; public DdlTransactionIsolatorProvidedConnectionImpl(JdbcContext jdbcContext) { @@ -49,5 +55,20 @@ public void prepare() { @Override public void release() { + JdbcConnectionAccess connectionAccess = jdbcContext.getJdbcConnectionAccess(); + if( !( connectionAccess instanceof JdbcConnectionAccessProvidedConnectionImpl ) ) { + throw new IllegalStateException( + "DdlTransactionIsolatorProvidedConnectionImpl should always use a JdbcConnectionAccessProvidedConnectionImpl" + ); + } + try { + // While passing the connection to the releaseConnection method might be suitable for other `JdbcConnectionAccess` implementations, + // it has no meaning for JdbcConnectionAccessProvidedConnectionImpl because, in this case, the connection is wrapped + // and we don't have access to it upon releasing via the DdlTransactionIsolatorProvidedConnectionImpl. + connectionAccess.releaseConnection( null ); + } + catch (SQLException ignore) { + LOG.unableToReleaseIsolatedConnection( ignore ); + } } } 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 eb169b9dbf80..7ba2e053f719 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 @@ -104,10 +104,7 @@ private SchemaFilterProvider getSchemaFilterProvider(Map options) { } private JdbcMetadaAccessStrategy determineJdbcMetadaAccessStrategy(Map options) { - if ( options == null ) { - return JdbcMetadaAccessStrategy.interpretHbm2ddlSetting( null ); - } - return JdbcMetadaAccessStrategy.interpretHbm2ddlSetting( options.get( AvailableSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY ) ); + return JdbcMetadaAccessStrategy.interpretSetting( options ); } GenerationTarget[] buildGenerationTargets( 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 0de5e13f6063..6c689328c398 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 @@ -254,7 +254,7 @@ public void createFromMetadata( } } - // next, create all "beforeQuery table" auxiliary objects + // next, create all "before table" auxiliary objects for ( AuxiliaryDatabaseObject auxiliaryDatabaseObject : database.getAuxiliaryDatabaseObjects() ) { if ( !auxiliaryDatabaseObject.beforeTablesOnCreation() ) { continue; @@ -356,9 +356,9 @@ public void createFromMetadata( } } - //NOTE : Foreign keys must be created *afterQuery* all tables of all namespaces for cross namespace fks. see HHH-10420 + //NOTE : Foreign keys must be created *after* all tables of all namespaces for cross namespace fks. see HHH-10420 for ( Namespace namespace : database.getNamespaces() ) { - // NOTE : Foreign keys must be created *afterQuery* unique keys for numerous DBs. See HHH-8390 + // NOTE : Foreign keys must be created *after* unique keys for numerous DBs. See HHH-8390 if ( !schemaFilter.includeNamespace( namespace ) ) { continue; @@ -382,7 +382,7 @@ public void createFromMetadata( } } - // next, create all "afterQuery table" auxiliary objects + // next, create all "after table" auxiliary objects for ( AuxiliaryDatabaseObject auxiliaryDatabaseObject : database.getAuxiliaryDatabaseObjects() ) { if ( auxiliaryDatabaseObject.appliesToDialect( dialect ) && !auxiliaryDatabaseObject.beforeTablesOnCreation() ) { 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 c48c7d4eb80d..b6a61e7dc41d 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 @@ -21,7 +21,7 @@ * @author Steve Ebersole */ public class StandardForeignKeyExporter implements Exporter { - private static final String COLUMN_MISMATCH_MSG = "Number of referencing columns [%s] did not " + + private static final String COLUMN_MISMATCH_MSG = "Number of referencing columns [%s] did not " + "match number of referenced columns [%s] in foreign-key [%s] from [%s] to [%s]"; private final Dialect dialect; @@ -32,11 +32,11 @@ public StandardForeignKeyExporter(Dialect dialect) { @Override public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata) { - if ( ! dialect.hasAlterTable() ) { + if ( !dialect.hasAlterTable() ) { return NO_COMMANDS; } - - if ( ! foreignKey.isCreationEnabled() ) { + + if ( !foreignKey.isCreationEnabled() ) { return NO_COMMANDS; } @@ -45,8 +45,8 @@ public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata) { } final int numberOfColumns = foreignKey.getColumnSpan(); - final String[] columnNames = new String[ numberOfColumns ]; - final String[] targetColumnNames = new String[ numberOfColumns ]; + final String[] columnNames = new String[numberOfColumns]; + final String[] targetColumnNames = new String[numberOfColumns]; final Iterator targetItr; if ( foreignKey.isReferenceToPrimaryKey() ) { @@ -100,8 +100,7 @@ public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata) { dialect ); - final StringBuilder buffer = new StringBuilder( "alter table " ) - .append( sourceTableName ) + final StringBuilder buffer = new StringBuilder( dialect.getAlterTableString( sourceTableName ) ) .append( foreignKey.getKeyDefinition() != null ? dialect.getAddForeignKeyConstraintString( @@ -128,11 +127,11 @@ public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata) { @Override public String[] getSqlDropStrings(ForeignKey foreignKey, Metadata metadata) { - if ( ! dialect.hasAlterTable() ) { + if ( !dialect.hasAlterTable() ) { return NO_COMMANDS; } - if ( ! foreignKey.isCreationEnabled() ) { + if ( !foreignKey.isCreationEnabled() ) { return NO_COMMANDS; } @@ -146,7 +145,21 @@ public String[] getSqlDropStrings(ForeignKey foreignKey, Metadata metadata) { dialect ); return new String[] { - "alter table " + sourceTableName + dialect.getDropForeignKeyString() + foreignKey.getName() + getSqlDropStrings( sourceTableName, foreignKey, dialect ) }; } + + private String getSqlDropStrings(String tableName, ForeignKey foreignKey, Dialect dialect) { + final StringBuilder buf = new StringBuilder( dialect.getAlterTableString( tableName ) ); + buf.append( dialect.getDropForeignKeyString() ); + if ( dialect.supportsIfExistsBeforeConstraintName() ) { + buf.append( "if exists " ); + } + buf.append( dialect.quote( foreignKey.getName() ) ); + if ( dialect.supportsIfExistsAfterConstraintName() ) { + buf.append( " if exists" ); + } + return buf.toString(); + } + } 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 f0f335e2a060..426e6e1163a3 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 @@ -7,6 +7,7 @@ package org.hibernate.tool.schema.internal; import java.util.Iterator; +import java.util.Map; import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.QualifiedNameImpl; @@ -57,7 +58,8 @@ public String[] getSqlCreateStrings(Index index, Metadata metadata) { .append( " (" ); boolean first = true; - Iterator columnItr = index.getColumnIterator(); + final Iterator columnItr = index.getColumnIterator(); + final Map columnOrderMap = index.getColumnOrderMap(); while ( columnItr.hasNext() ) { final Column column = columnItr.next(); if ( first ) { @@ -67,6 +69,9 @@ public String[] getSqlCreateStrings(Index index, Metadata metadata) { buf.append( ", " ); } buf.append( ( column.getQuotedName( dialect ) ) ); + if ( columnOrderMap.containsKey( column ) ) { + buf.append( " " ).append( columnOrderMap.get( column ) ); + } } buf.append( ")" ); return new String[] { buf.toString() }; @@ -74,7 +79,7 @@ public String[] getSqlCreateStrings(Index index, Metadata metadata) { @Override public String[] getSqlDropStrings(Index index, Metadata metadata) { - if ( ! dialect.dropConstraints() ) { + if ( !dialect.dropConstraints() ) { return NO_COMMANDS; } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/AbstractScriptTargetOutput.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/AbstractScriptTargetOutput.java index 2bb63eee8757..4bbc41120654 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/AbstractScriptTargetOutput.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/AbstractScriptTargetOutput.java @@ -31,7 +31,7 @@ public void accept(String command) { writer().flush(); } catch (IOException e) { - throw new CommandAcceptanceException( "Could not write to target script file", e ); + throw new CommandAcceptanceException( "Could not write \"" + command + "\" to target script file", e ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java index 46f77847ae53..885fbeb10df3 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java @@ -65,7 +65,7 @@ public void accept(String command) { } catch (SQLException e) { throw new CommandAcceptanceException( - "Error executing DDL via JDBC Statement", + "Error executing DDL \"" + command + "\" via JDBC Statement", e ); } @@ -86,6 +86,15 @@ private Statement jdbcStatement() { @Override public void release() { + if ( jdbcStatement != null ) { + try { + jdbcStatement.close(); + jdbcStatement = null; + } + catch (SQLException e) { + throw ddlTransactionIsolator.getJdbcContext().getSqlExceptionHelper().convert( e, "Unable to close JDBC Statement after DDL execution" ); + } + } if ( releaseAfterUse ) { ddlTransactionIsolator.release(); } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToStdout.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToStdout.java index b42e17fcf762..0014c55a2853 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToStdout.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToStdout.java @@ -6,6 +6,8 @@ */ package org.hibernate.tool.schema.internal.exec; +import org.hibernate.internal.build.AllowSysOut; + /** * GenerationTarget implementation for handling generation to System.out * @@ -28,6 +30,7 @@ public void prepare() { } @Override + @AllowSysOut public void accept(String command) { if ( delimiter != null ) { command += delimiter; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ScriptTargetOutputToStdout.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ScriptTargetOutputToStdout.java index 83fe2cdd23a4..67d6b5151676 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ScriptTargetOutputToStdout.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ScriptTargetOutputToStdout.java @@ -10,6 +10,7 @@ import java.io.OutputStreamWriter; import java.io.Writer; +import org.hibernate.internal.build.AllowSysOut; import org.hibernate.tool.schema.spi.SchemaManagementException; /** @@ -27,6 +28,7 @@ protected Writer writer() { } @Override + @AllowSysOut public void prepare() { super.prepare(); this.writer = new OutputStreamWriter( System.out ); diff --git a/hibernate-core/src/main/java/org/hibernate/transform/CacheableResultTransformer.java b/hibernate-core/src/main/java/org/hibernate/transform/CacheableResultTransformer.java index 22c04353e57a..bdf98e7c1309 100644 --- a/hibernate-core/src/main/java/org/hibernate/transform/CacheableResultTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/transform/CacheableResultTransformer.java @@ -47,7 +47,7 @@ public class CacheableResultTransformer implements ResultTransformer { * tuples to a value(s) that can be cached. * * @param transformer - result transformer that will ultimately be - * be used (afterQuery caching results) + * be used (after caching results) * @param aliases - the aliases that correspond to the tuple; * if it is non-null, its length must equal the number * of true elements in includeInTuple[] diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/AbstractNonIdentifierAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/AbstractNonIdentifierAttribute.java index e6bbf2f230fd..8b4d21e91a69 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/AbstractNonIdentifierAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/AbstractNonIdentifierAttribute.java @@ -86,7 +86,7 @@ public boolean isDirtyCheckable() { @Override public boolean isDirtyCheckable(boolean hasUninitializedProperties) { - return isDirtyCheckable() && ( !hasUninitializedProperties || !isLazy() ); + return isDirtyCheckable(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/NonIdentifierAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/NonIdentifierAttribute.java index d39ab4b899a5..1efd0706fcc9 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/NonIdentifierAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/NonIdentifierAttribute.java @@ -24,6 +24,10 @@ public interface NonIdentifierAttribute extends Attribute, AttributeDefinition { public boolean isNullable(); + /** + * @deprecated Use {@link org.hibernate.tuple.NonIdentifierAttribute#isDirtyCheckable()} instead + */ + @Deprecated public boolean isDirtyCheckable(boolean hasUninitializedProperties); public boolean isDirtyCheckable(); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/PojoInstantiator.java b/hibernate-core/src/main/java/org/hibernate/tuple/PojoInstantiator.java index c218881e8855..d4343fa9580a 100755 --- a/hibernate-core/src/main/java/org/hibernate/tuple/PojoInstantiator.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/PojoInstantiator.java @@ -50,7 +50,11 @@ public PojoInstantiator( } public PojoInstantiator(Component component, ReflectionOptimizer.InstantiationOptimizer optimizer) { - this.mappedClass = component.getComponentClass(); + this( component.getComponentClass(), optimizer ); + } + + public PojoInstantiator(Class componentClass, ReflectionOptimizer.InstantiationOptimizer optimizer) { + this.mappedClass = componentClass; this.isAbstract = ReflectHelper.isAbstractClass( mappedClass ); this.optimizer = optimizer; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java index 4b770edf44a2..a307ccfcb471 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java @@ -10,6 +10,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.internal.UnsavedValueFactory; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -168,6 +169,12 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( boolean alwaysDirtyCheck = type.isAssociationType() && ( (AssociationType) type ).isAlwaysDirtyChecked(); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + lazyAvailable, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); + switch ( nature ) { case BASIC: { return new EntityBasedBasicAttribute( @@ -177,7 +184,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -197,7 +204,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), (CompositeType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -219,7 +226,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), (AssociationType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -279,7 +286,9 @@ public static StandardProperty buildStandardProperty(Property property, boolean return new StandardProperty( property.getName(), type, - lazyAvailable && property.isLazy(), + // only called for embeddable sub-attributes which are never (yet) lazy + //lazyAvailable && property.isLazy(), + false, property.isInsertable(), property.isUpdateable(), property.getValueGenerationStrategy(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractComponentTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractComponentTuplizer.java index 74043909160a..d18b8bfe8962 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractComponentTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractComponentTuplizer.java @@ -34,6 +34,7 @@ public abstract class AbstractComponentTuplizer implements ComponentTuplizer { protected abstract Setter buildSetter(Component component, Property prop); protected AbstractComponentTuplizer(Component component) { + setComponentClass( component ); propertySpan = component.getPropertySpan(); getters = new Getter[propertySpan]; setters = new Setter[propertySpan]; @@ -102,4 +103,8 @@ public void setParent(Object component, Object parent, SessionFactoryImplementor public Getter getGetter(int i) { return getters[i]; } + + // It should be an abstract method but not sure if this can break any customer extension + protected void setComponentClass(Component component){ + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java index a20af0e7b133..f712fa4d6fc0 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java @@ -83,11 +83,15 @@ public AttributeDefinition next() { int columnPosition = currentColumnPosition; currentColumnPosition += type.getColumnSpan( sessionFactory() ); + final CompositeType cType = getType(); + final boolean nullable = + cType.getPropertyNullability() == null || + cType.getPropertyNullability()[subAttributeNumber]; + if ( type.isAssociationType() ) { // we build the association-key here because of the "goofiness" with 'currentColumnPosition' final AssociationKey associationKey; final AssociationType aType = (AssociationType) type; - final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); if ( aType.isAnyType() ) { associationKey = new AssociationKey( @@ -106,6 +110,8 @@ public AttributeDefinition next() { ); } else if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FROM_PARENT ) { + final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); + final String lhsTableName; final String[] lhsColumnNames; @@ -128,17 +134,14 @@ else if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FROM_PARENT ) { associationKey = new AssociationKey( lhsTableName, lhsColumnNames ); } else { + final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); + associationKey = new AssociationKey( joinable.getTableName(), getRHSColumnNames( aType, sessionFactory() ) ); } - final CompositeType cType = getType(); - final boolean nullable = - cType.getPropertyNullability() == null || - cType.getPropertyNullability()[subAttributeNumber]; - return new CompositeBasedAssociationAttribute( AbstractCompositionAttribute.this, sessionFactory(), @@ -173,7 +176,7 @@ else if ( type.isComponentType() ) { .setUpdateable( AbstractCompositionAttribute.this.isUpdateable() ) // todo : handle nested ValueGeneration strategies... // disallow if our strategy != NEVER - .setNullable( getType().getPropertyNullability()[subAttributeNumber] ) + .setNullable( nullable ) .setDirtyCheckable( true ) .setVersionable( AbstractCompositionAttribute.this.isVersionable() ) .setCascadeStyle( getType().getCascadeStyle( subAttributeNumber ) ) @@ -182,9 +185,6 @@ else if ( type.isComponentType() ) { ); } else { - final CompositeType cType = getType(); - final boolean nullable = cType.getPropertyNullability() == null || cType.getPropertyNullability()[subAttributeNumber]; - return new CompositeBasedBasicAttribute( AbstractCompositionAttribute.this, sessionFactory(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentMetamodel.java index 40fdbbce1a10..e5081878af82 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentMetamodel.java @@ -13,6 +13,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.cfg.Environment; import org.hibernate.engine.config.spi.ConfigurationService; @@ -31,7 +32,6 @@ public class ComponentMetamodel implements Serializable { // TODO : will need reference to session factory to fully complete HHH-1907 -// private final SessionFactoryImplementor sessionFactory; private final String role; private final boolean isKey; private final StandardProperty[] properties; @@ -44,9 +44,19 @@ public class ComponentMetamodel implements Serializable { private final Map propertyIndexes = new HashMap(); private final boolean createEmptyCompositesEnabled; -// public ComponentMetamodel(Component component, SessionFactoryImplementor sessionFactory) { + /** + * @deprecated Use {@link ComponentMetamodel#ComponentMetamodel(Component, BootstrapContext)} instead. + */ + @Deprecated public ComponentMetamodel(Component component, MetadataBuildingOptions metadataBuildingOptions) { -// this.sessionFactory = sessionFactory; + this( component, new ComponentTuplizerFactory( metadataBuildingOptions ) ); + } + + public ComponentMetamodel(Component component, BootstrapContext bootstrapContext) { + this( component, new ComponentTuplizerFactory( bootstrapContext ) ); + } + + private ComponentMetamodel(Component component, ComponentTuplizerFactory componentTuplizerFactory){ this.role = component.getRoleName(); this.isKey = component.isKey(); propertySpan = component.getPropertySpan(); @@ -63,7 +73,6 @@ public ComponentMetamodel(Component component, MetadataBuildingOptions metadataB entityMode = component.hasPojoRepresentation() ? EntityMode.POJO : EntityMode.MAP; // todo : move this to SF per HHH-3517; also see HHH-1907 and ComponentMetamodel - final ComponentTuplizerFactory componentTuplizerFactory = new ComponentTuplizerFactory( metadataBuildingOptions ); final String tuplizerClassName = component.getTuplizerImplClassName( entityMode ); this.componentTuplizer = tuplizerClassName == null ? componentTuplizerFactory.constructDefaultTuplizer( entityMode, diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentTuplizerFactory.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentTuplizerFactory.java index 2f5845996049..2d471379420e 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentTuplizerFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/ComponentTuplizerFactory.java @@ -16,6 +16,7 @@ import org.hibernate.boot.internal.ClassLoaderAccessImpl; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.internal.util.ReflectHelper; @@ -33,6 +34,10 @@ public class ComponentTuplizerFactory implements Serializable { private final ClassLoaderAccess classLoaderAccess; + /** + * @deprecated Use {@link ComponentTuplizerFactory#ComponentTuplizerFactory(BootstrapContext)} instead. + */ + @Deprecated public ComponentTuplizerFactory(MetadataBuildingOptions metadataBuildingOptions) { classLoaderAccess = new ClassLoaderAccessImpl( metadataBuildingOptions.getTempClassLoader(), @@ -40,6 +45,10 @@ public ComponentTuplizerFactory(MetadataBuildingOptions metadataBuildingOptions) ); } + public ComponentTuplizerFactory(BootstrapContext bootstrapContext) { + classLoaderAccess = bootstrapContext.getClassLoaderAccess(); + } + /** * Method allowing registration of the tuplizer class to use as default for a particular entity-mode. * @@ -134,7 +143,7 @@ private Constructor getProperConstructor(Class persistEventListeners(SharedSessionContractImplementor session) { - if ( session == null ) { - return Collections.emptyList(); - } - return session - .getFactory() - .getServiceRegistry() - .getService( EventListenerRegistry.class ) - .getEventListenerGroup( EventType.PERSIST ) - .listeners(); - } - - private static Serializable determineEntityIdPersistIfNecessary( + private static Serializable determineEntityId( Object entity, AssociationType associationType, SharedSessionContractImplementor session, @@ -436,9 +430,6 @@ private static Serializable determineEntityIdPersistIfNecessary( return null; } - // NOTE : persist if necessary for proper merge support (HHH-11328) - // but only allow persist if a Session is passed (HHH-11274) - if ( HibernateProxy.class.isInstance( entity ) ) { // entity is a proxy, so we know it is not transient; just return ID from proxy return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier(); @@ -459,36 +450,7 @@ private static Serializable determineEntityIdPersistIfNecessary( sessionFactory ); - Serializable entityId = persister.getIdentifier( entity, session ); - - if ( entityId == null ) { - if ( session != null ) { - // if we have a session, then follow the HHH-11328 requirements - entityId = persistTransientEntity( entity, session ); - } - // otherwise just let it be null HHH-11274 - } - else { - if ( session != null ) { - // if the entity is in the process of being merged, it may be stored in the - // PC already, but doesn't have an EntityEntry yet. If this is the case, - // then don't persist even if it is transient because doing so can lead - // to having 2 entities in the PC with the same ID (HHH-11328). - final EntityKey entityKey = session.generateEntityKey( entityId, persister ); - if ( session.getPersistenceContext().getEntity( entityKey ) == null && - ForeignKeys.isTransient( - persister.getEntityName(), - entity, - null, - session - ) ) { - // entity is transient and it is not in the PersistenceContext. - // entity needs to be persisted. - persistTransientEntity( entity, session ); - } - } - } - return entityId; + return persister.getIdentifier( entity, session ); } private static EntityPersister resolveEntityPersister( @@ -500,13 +462,14 @@ private static EntityPersister resolveEntityPersister( if ( session != null ) { return session.getEntityPersister( - associationType.getAssociatedEntityName( session.getFactory() ), + associationType.getAssociatedEntityName( sessionFactory ), entity ); } String entityName = null; - for ( EntityNameResolver entityNameResolver : sessionFactory.getMetamodel().getEntityNameResolvers() ) { + final MetamodelImplementor metamodel = sessionFactory.getMetamodel(); + for ( EntityNameResolver entityNameResolver : metamodel.getEntityNameResolvers() ) { entityName = entityNameResolver.resolveEntityName( entity ); if ( entityName != null ) { break; @@ -517,29 +480,7 @@ private static EntityPersister resolveEntityPersister( entityName = entity.getClass().getName(); } - return sessionFactory.getMetamodel().entityPersister( entityName ); - } - - private static Serializable persistTransientEntity( - Object entity, - SharedSessionContractImplementor session) { - assert session != null; - - LOG.debug( "Performing implicit derived identity cascade" ); - final PersistEvent event = new PersistEvent( - null, - entity, - (EventSource) session - ); - - for ( PersistEventListener listener : persistEventListeners( session ) ) { - listener.onPersist( event ); - } - final EntityEntry pcEntry = session.getPersistenceContext().getEntry( entity ); - if ( pcEntry == null || pcEntry.getId() == null ) { - throw new HibernateException( "Unable to process implicit derived identity cascade" ); - } - return pcEntry.getId(); + return metamodel.entityPersister( entityName ); } @Override @@ -556,11 +497,12 @@ public void resetIdentifier( Object currentVersion, SharedSessionContractImplementor session) { //noinspection StatementWithEmptyBody - if ( entityMetamodel.getIdentifierProperty().getIdentifierGenerator() instanceof Assigned ) { + final IdentifierProperty identifierProperty = entityMetamodel.getIdentifierProperty(); + if ( identifierProperty.getIdentifierGenerator() instanceof Assigned ) { } else { //reset the id - Serializable result = entityMetamodel.getIdentifierProperty() + Serializable result = identifierProperty .getUnsavedValue() .getDefaultValue( currentId ); setIdentifier( entity, result, session ); @@ -585,28 +527,37 @@ public Object getVersion(Object entity) throws HibernateException { } protected boolean shouldGetAllProperties(Object entity) { - if ( !getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = getEntityMetamodel().getBytecodeEnhancementMetadata(); + if ( !bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { return true; } - return !getEntityMetamodel().getBytecodeEnhancementMetadata().hasUnFetchedAttributes( entity ); + return !bytecodeEnhancementMetadata.hasUnFetchedAttributes( entity ); } @Override public Object[] getPropertyValues(Object entity) { final BytecodeEnhancementMetadata enhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata(); + final LazyAttributesMetadata lazyAttributesMetadata = enhancementMetadata.getLazyAttributesMetadata(); + final int span = entityMetamodel.getPropertySpan(); + final String[] propertyNames = entityMetamodel.getPropertyNames(); final Object[] result = new Object[span]; for ( int j = 0; j < span; j++ ) { - NonIdentifierAttribute property = entityMetamodel.getProperties()[j]; - if ( !property.isLazy() || enhancementMetadata.isAttributeLoaded( entity, property.getName() ) ) { + final String propertyName = propertyNames[j]; + // if the attribute is not lazy (bytecode sense), we can just use the value from the instance + // if the attribute is lazy but has been initialized we can just use the value from the instance + // todo : there should be a third case here when we merge transient instances + if ( ! lazyAttributesMetadata.isLazyAttribute( propertyName ) + || enhancementMetadata.isAttributeLoaded( entity, propertyName) ) { result[j] = getters[j].get( entity ); } else { result[j] = LazyPropertyInitializer.UNFETCHED_PROPERTY; } } + return result; } @@ -704,9 +655,10 @@ private int findSubPropertyIndex(ComponentType type, String subPropertyName) { public void setPropertyValues(Object entity, Object[] values) throws HibernateException { boolean setAll = !entityMetamodel.hasLazyProperties(); + final SessionFactoryImplementor factory = getFactory(); for ( int j = 0; j < entityMetamodel.getPropertySpan(); j++ ) { if ( setAll || values[j] != LazyPropertyInitializer.UNFETCHED_PROPERTY ) { - setters[j].set( entity, values[j], getFactory() ); + setters[j].set( entity, values[j], factory ); } } } @@ -778,7 +730,8 @@ protected final Instantiator getInstantiator() { return instantiator; } - protected final ProxyFactory getProxyFactory() { + @Override + public final ProxyFactory getProxyFactory() { return proxyFactory; } @@ -794,8 +747,9 @@ public Getter getIdentifierGetter() { @Override public Getter getVersionGetter() { - if ( getEntityMetamodel().isVersioned() ) { - return getGetter( getEntityMetamodel().getVersionPropertyIndex() ); + final EntityMetamodel entityMetamodel = getEntityMetamodel(); + if ( entityMetamodel.isVersioned() ) { + return getGetter( entityMetamodel.getVersionPropertyIndex() ); } return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java index 10286c23a615..4d444391051d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java @@ -6,10 +6,14 @@ */ package org.hibernate.tuple.entity; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -44,15 +48,42 @@ public LazyAttributesMetadata getLazyAttributesMetadata() { @Override public LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + + @Override + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + + @Override + public PersistentAttributeInterceptable createEnhancedProxy(EntityKey keyToLoad, boolean addEmptyEntry, SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { + throw new NotInstrumentedException( errorMsg ); + } + @Override public boolean hasUnFetchedAttributes(Object entity) { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java index 48134c0ceeb4..cad2b64c9fad 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java @@ -6,29 +6,49 @@ */ package org.hibernate.tuple.entity; +import java.io.Serializable; +import java.util.Set; + +import org.hibernate.LockMode; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; import org.hibernate.mapping.PersistentClass; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.CompositeType; /** * @author Steve Ebersole */ -public class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementMetadata { - public static BytecodeEnhancementMetadata from(PersistentClass persistentClass) { +public final class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementMetadata { + /** + * Static constructor + */ + public static BytecodeEnhancementMetadata from( + PersistentClass persistentClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean allowEnhancementAsProxy) { final Class mappedClass = persistentClass.getMappedClass(); final boolean enhancedForLazyLoading = PersistentAttributeInterceptable.class.isAssignableFrom( mappedClass ); final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading - ? LazyAttributesMetadata.from( persistentClass ) + ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy ) : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); return new BytecodeEnhancementMetadataPojoImpl( persistentClass.getEntityName(), mappedClass, + identifierAttributeNames, + nonAggregatedCidMapper, enhancedForLazyLoading, lazyAttributesMetadata ); @@ -36,16 +56,26 @@ public static BytecodeEnhancementMetadata from(PersistentClass persistentClass) private final String entityName; private final Class entityClass; + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; private final boolean enhancedForLazyLoading; private final LazyAttributesMetadata lazyAttributesMetadata; - public BytecodeEnhancementMetadataPojoImpl( + @SuppressWarnings("WeakerAccess") + protected BytecodeEnhancementMetadataPojoImpl( String entityName, Class entityClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, boolean enhancedForLazyLoading, LazyAttributesMetadata lazyAttributesMetadata) { + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert identifierAttributeNames != null; + assert !identifierAttributeNames.isEmpty(); + this.entityName = entityName; this.entityClass = entityClass; + this.identifierAttributeNames = identifierAttributeNames; this.enhancedForLazyLoading = enhancedForLazyLoading; this.lazyAttributesMetadata = lazyAttributesMetadata; } @@ -67,18 +97,87 @@ public LazyAttributesMetadata getLazyAttributesMetadata() { @Override public boolean hasUnFetchedAttributes(Object entity) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor != null && interceptor.hasAnyUninitializedAttributes(); + if ( ! enhancedForLazyLoading ) { + return false; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).hasAnyUninitializedAttributes(); + } + + //noinspection RedundantIfStatement + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return true; + } + + return false; } @Override public boolean isAttributeLoaded(Object entity, String attributeName) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor == null || interceptor.isAttributeLoaded( attributeName ); + if ( ! enhancedForLazyLoading ) { + return true; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( attributeName ); + } + + return true; } @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { + return (LazyAttributeLoadingInterceptor) extractLazyInterceptor( entity ); + } + + @Override + public PersistentAttributeInterceptable createEnhancedProxy(EntityKey entityKey, boolean addEmptyEntry, SharedSessionContractImplementor session) { + final EntityPersister persister = entityKey.getPersister(); + final Serializable identifier = entityKey.getIdentifier(); + final PersistenceContext persistenceContext = session.getPersistenceContext(); + + // first, instantiate the entity instance to use as the proxy + final PersistentAttributeInterceptable entity = (PersistentAttributeInterceptable) persister.getEntityTuplizer().instantiate( identifier, session ); + + // add the entity (proxy) instance to the PC + persistenceContext.addEnhancedProxy( entityKey, entity ); + + // if requested, add the "holder entry" to the PC + if ( addEmptyEntry ) { + persistenceContext.addEntry( + entity, + Status.MANAGED, + // loaded state + null, + // row-id + null, + identifier, + // version + null, + LockMode.NONE, + // we assume it exists in db + true, + persister, + true + ); + } + + // inject the interceptor + persister.getEntityMetamodel() + .getBytecodeEnhancementMetadata() + .injectEnhancedEntityAsProxyInterceptor( entity, entityKey, session ); + + return entity; + } + + @Override + public LazyAttributeLoadingInterceptor injectInterceptor( + Object entity, + Object identifier, + SharedSessionContractImplementor session) { if ( !enhancedForLazyLoading ) { throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); } @@ -92,17 +191,41 @@ public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws ) ); } + final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( + getEntityName(), + identifier, + lazyAttributesMetadata.getLazyAttributeNames(), + session + ); - final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor == null ) { - return null; - } + injectInterceptor( entity, interceptor, session ); - return (LazyAttributeLoadingInterceptor) interceptor; + return interceptor; } @Override - public LazyAttributeLoadingInterceptor injectInterceptor(Object entity, SharedSessionContractImplementor session) { + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + injectInterceptor( + entity, + new EnhancementAsProxyLazinessInterceptor( + entityName, + identifierAttributeNames, + nonAggregatedCidMapper, + entityKey, + session + ), + session + ); + } + + @Override + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { if ( !enhancedForLazyLoading ) { throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); } @@ -117,12 +240,31 @@ public LazyAttributeLoadingInterceptor injectInterceptor(Object entity, SharedSe ); } - final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( - getEntityName(), - lazyAttributesMetadata.getLazyAttributeNames(), - session - ); ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( interceptor ); - return interceptor; } + + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { + if ( !enhancedForLazyLoading ) { + throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); + } + + if ( !entityClass.isInstance( entity ) ) { + throw new IllegalArgumentException( + String.format( + "Passed entity instance [%s] is not of expected type [%s]", + entity, + getEntityName() + ) + ); + } + + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + if ( interceptor == null ) { + return null; + } + + return (BytecodeLazyAttributeInterceptor) interceptor; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index ca32f4aabd52..22b62c4ff3eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -18,13 +19,13 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.ValueInclusion; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.ArrayHelper; @@ -92,7 +93,7 @@ public class EntityMetamodel implements Serializable { private final InDatabaseValueGenerationStrategy[] inDatabaseValueGenerationStrategies; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - private final Map propertyIndexes = new HashMap(); + private final Map propertyIndexes = new HashMap<>(); private final boolean hasCollections; private final boolean hasMutableProperties; private final boolean hasLazyProperties; @@ -140,7 +141,30 @@ public EntityMetamodel( versioned = persistentClass.isVersioned(); if ( persistentClass.hasPojoRepresentation() ) { - bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( persistentClass ); + final Component identifierMapperComponent = persistentClass.getIdentifierMapper(); + final CompositeType nonAggregatedCidMapper; + final Set idAttributeNames; + + if ( identifierMapperComponent != null ) { + nonAggregatedCidMapper = (CompositeType) identifierMapperComponent.getType(); + idAttributeNames = new HashSet<>( ); + //noinspection unchecked + final Iterator propertyItr = identifierMapperComponent.getPropertyIterator(); + while ( propertyItr.hasNext() ) { + idAttributeNames.add( propertyItr.next().getName() ); + } + } + else { + nonAggregatedCidMapper = null; + idAttributeNames = Collections.singleton( identifierAttribute.getName() ); + } + + bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( + persistentClass, + idAttributeNames, + nonAggregatedCidMapper, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); } else { bytecodeEnhancementMetadata = new BytecodeEnhancementMetadataNonPojoImpl( persistentClass.getEntityName() ); @@ -150,7 +174,7 @@ public EntityMetamodel( propertySpan = persistentClass.getPropertyClosureSpan(); properties = new NonIdentifierAttribute[propertySpan]; - List naturalIdNumbers = new ArrayList(); + List naturalIdNumbers = new ArrayList<>(); // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ propertyNames = new String[propertySpan]; propertyTypes = new Type[propertySpan]; @@ -181,8 +205,6 @@ public EntityMetamodel( boolean foundCollection = false; boolean foundMutable = false; boolean foundNonIdentifierPropertyNamedId = false; - boolean foundInsertGeneratedValue = false; - boolean foundUpdateGeneratedValue = false; boolean foundUpdateableNaturalIdProperty = false; while ( iter.hasNext() ) { @@ -220,10 +242,16 @@ public EntityMetamodel( } // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - boolean lazy = prop.isLazy() && bytecodeEnhancementMetadata.isEnhancedForLazyLoading(); + boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); + if ( lazy ) { hasLazy = true; } + propertyLaziness[i] = lazy; propertyNames[i] = properties[i].getName(); @@ -379,7 +407,7 @@ else if ( timing == GenerationTiming.ALWAYS ) { } entityMode = persistentClass.hasPojoRepresentation() ? EntityMode.POJO : EntityMode.MAP; - final EntityTuplizerFactory entityTuplizerFactory = sessionFactory.getSettings().getEntityTuplizerFactory(); + final EntityTuplizerFactory entityTuplizerFactory = sessionFactory.getSessionFactoryOptions().getEntityTuplizerFactory(); final String tuplizerClassName = persistentClass.getTuplizerImplClassName( entityMode ); if ( tuplizerClassName == null ) { entityTuplizer = entityTuplizerFactory.constructDefaultTuplizer( entityMode, this, persistentClass ); @@ -514,10 +542,6 @@ public static class ValueGenerationStrategyException extends HibernateException public ValueGenerationStrategyException(String message) { super( message ); } - - public ValueGenerationStrategyException(String message, Throwable cause) { - super( message, cause ); - } } private static class CompositeGenerationStrategyPairBuilder { @@ -540,7 +564,7 @@ public void addPair(GenerationStrategyPair generationStrategyPair) { private void add(InMemoryValueGenerationStrategy inMemoryStrategy) { if ( inMemoryStrategies == null ) { - inMemoryStrategies = new ArrayList(); + inMemoryStrategies = new ArrayList<>(); } inMemoryStrategies.add( inMemoryStrategy ); @@ -551,7 +575,7 @@ private void add(InMemoryValueGenerationStrategy inMemoryStrategy) { private void add(InDatabaseValueGenerationStrategy inDatabaseStrategy) { if ( inDatabaseStrategies == null ) { - inDatabaseStrategies = new ArrayList(); + inDatabaseStrategies = new ArrayList<>(); } inDatabaseStrategies.add( inDatabaseStrategy ); @@ -730,81 +754,6 @@ public String[] getReferencedColumnValues() { } } - private ValueInclusion determineInsertValueGenerationType(Property mappingProperty, NonIdentifierAttribute runtimeProperty) { - if ( isInsertGenerated( runtimeProperty ) ) { - return ValueInclusion.FULL; - } - else if ( mappingProperty.getValue() instanceof Component ) { - if ( hasPartialInsertComponentGeneration( ( Component ) mappingProperty.getValue() ) ) { - return ValueInclusion.PARTIAL; - } - } - return ValueInclusion.NONE; - } - - private boolean isInsertGenerated(NonIdentifierAttribute property) { - return property.getValueGenerationStrategy() != null - && property.getValueGenerationStrategy().getGenerationTiming() != GenerationTiming.NEVER; - } - - private boolean isInsertGenerated(Property property) { - return property.getValueGenerationStrategy() != null - && property.getValueGenerationStrategy().getGenerationTiming() != GenerationTiming.NEVER; - } - - private boolean hasPartialInsertComponentGeneration(Component component) { - Iterator subProperties = component.getPropertyIterator(); - while ( subProperties.hasNext() ) { - final Property prop = ( Property ) subProperties.next(); - if ( isInsertGenerated( prop ) ) { - return true; - } - else if ( prop.getValue() instanceof Component ) { - if ( hasPartialInsertComponentGeneration( (Component) prop.getValue() ) ) { - return true; - } - } - } - return false; - } - - private ValueInclusion determineUpdateValueGenerationType(Property mappingProperty, NonIdentifierAttribute runtimeProperty) { - if ( isUpdateGenerated( runtimeProperty ) ) { - return ValueInclusion.FULL; - } - else if ( mappingProperty.getValue() instanceof Component ) { - if ( hasPartialUpdateComponentGeneration( ( Component ) mappingProperty.getValue() ) ) { - return ValueInclusion.PARTIAL; - } - } - return ValueInclusion.NONE; - } - - private static boolean isUpdateGenerated(Property property) { - return property.getValueGenerationStrategy() != null - && property.getValueGenerationStrategy().getGenerationTiming() == GenerationTiming.ALWAYS; - } - - private static boolean isUpdateGenerated(NonIdentifierAttribute property) { - return property.getValueGenerationStrategy() != null - && property.getValueGenerationStrategy().getGenerationTiming() == GenerationTiming.ALWAYS; - } - - private boolean hasPartialUpdateComponentGeneration(Component component) { - Iterator subProperties = component.getPropertyIterator(); - while ( subProperties.hasNext() ) { - Property prop = (Property) subProperties.next(); - if ( isUpdateGenerated( prop ) ) { - return true; - } - else if ( prop.getValue() instanceof Component ) { - if ( hasPartialUpdateComponentGeneration( ( Component ) prop.getValue() ) ) { - return true; - } - } - } - return false; - } private void mapPropertyToIndex(Property prop, int i) { propertyIndexes.put( prop.getName(), i ); @@ -830,7 +779,7 @@ public boolean isNaturalIdentifierInsertGenerated() { // insert-generated identifier. That wont work if the natural-id is also insert-generated. // // Assumptions: - // * That code checks that there is a natural identifier beforeQuery making this call, so we assume the same here + // * That code checks that there is a natural identifier before making this call, so we assume the same here // * That code assumes a non-composite natural-id, so we assume the same here final InDatabaseValueGenerationStrategy strategy = inDatabaseValueGenerationStrategies[ naturalIdPropertyNumbers[0] ]; return strategy != null && strategy.getGenerationTiming() != GenerationTiming.NEVER; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java index 2765ebd1f109..f9a79e2e8c1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java @@ -15,6 +15,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.property.access.spi.Getter; +import org.hibernate.proxy.ProxyFactory; import org.hibernate.tuple.Tuplizer; /** @@ -186,7 +187,7 @@ Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionCon Object getPropertyValue(Object entity, String propertyName) throws HibernateException; /** - * Called just afterQuery the entities properties have been initialized. + * Called just after the entities properties have been initialized. * * @param entity The entity being initialized. * @param session The session initializing this entity. @@ -273,4 +274,8 @@ Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionCon * @return The getter for the version property. */ Getter getVersionGetter(); + + default ProxyFactory getProxyFactory() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizerFactory.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizerFactory.java index f4deaa6dbbcb..ea0e52ebf774 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizerFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizerFactory.java @@ -132,7 +132,7 @@ private Constructor getProperConstructor( try { constructor = clazz.getDeclaredConstructor( constructorArgs ); try { - constructor.setAccessible( true ); + ReflectHelper.ensureAccessibility( constructor ); } catch ( SecurityException e ) { constructor = 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 47bd979d6612..0910b6d9aea3 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java @@ -46,6 +46,7 @@ protected Object applyInterception(Object entity) { PersistentAttributeInterceptor interceptor = new LazyAttributeLoadingInterceptor( entityMetamodel.getName(), + null, entityMetamodel.getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() .getLazyAttributeNames(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java index 4916a54e1824..a8885c95cd60 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java @@ -7,16 +7,14 @@ package org.hibernate.tuple.entity; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Iterator; import java.util.Map; import java.util.Set; import org.hibernate.EntityMode; import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; -import org.hibernate.MappingException; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; import org.hibernate.classic.Lifecycle; @@ -26,14 +24,12 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.ReflectHelper; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; -import org.hibernate.mapping.Subclass; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.Setter; -import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.ProxyFactory; +import org.hibernate.proxy.pojo.ProxyFactoryHelper; import org.hibernate.tuple.Instantiator; import org.hibernate.type.CompositeType; @@ -51,15 +47,11 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer { private final boolean lifecycleImplementor; private final ReflectionOptimizer optimizer; - private final boolean isBytecodeEnhanced; - - public PojoEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) { super( entityMetamodel, mappedEntity ); this.mappedClass = mappedEntity.getMappedClass(); this.proxyInterface = mappedEntity.getProxyInterface(); this.lifecycleImplementor = Lifecycle.class.isAssignableFrom( mappedClass ); - this.isBytecodeEnhanced = entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); String[] getterNames = new String[propertySpan]; String[] setterNames = new String[propertySpan]; @@ -91,76 +83,25 @@ public PojoEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappe protected ProxyFactory buildProxyFactory(PersistentClass persistentClass, Getter idGetter, Setter idSetter) { // determine the id getter and setter methods from the proxy interface (if any) // determine all interfaces needed by the resulting proxy - - /* - * We need to preserve the order of the interfaces they were put into the set, since javassist will choose the - * first one's class-loader to construct the proxy class with. This is also the reason why HibernateProxy.class - * should be the last one in the order (on JBossAS7 its class-loader will be org.hibernate module's class- - * loader, which will not see the classes inside deployed apps. See HHH-3078 - */ - Set proxyInterfaces = new java.util.LinkedHashSet(); - - Class mappedClass = persistentClass.getMappedClass(); - Class proxyInterface = persistentClass.getProxyInterface(); - - if ( proxyInterface != null && !mappedClass.equals( proxyInterface ) ) { - if ( !proxyInterface.isInterface() ) { - throw new MappingException( - "proxy must be either an interface, or the class itself: " + getEntityName() - ); - } - proxyInterfaces.add( proxyInterface ); - } - - if ( mappedClass.isInterface() ) { - proxyInterfaces.add( mappedClass ); - } + final String entityName = getEntityName(); + final Class mappedClass = persistentClass.getMappedClass(); + final Class proxyInterface = persistentClass.getProxyInterface(); - Iterator subclasses = persistentClass.getSubclassIterator(); - while ( subclasses.hasNext() ) { - final Subclass subclass = subclasses.next(); - final Class subclassProxy = subclass.getProxyInterface(); - final Class subclassClass = subclass.getMappedClass(); - if ( subclassProxy != null && !subclassClass.equals( subclassProxy ) ) { - if ( !subclassProxy.isInterface() ) { - throw new MappingException( - "proxy must be either an interface, or the class itself: " + subclass.getEntityName() - ); - } - proxyInterfaces.add( subclassProxy ); - } - } + final Set proxyInterfaces = ProxyFactoryHelper.extractProxyInterfaces( persistentClass, entityName ); - proxyInterfaces.add( HibernateProxy.class ); + Method proxyGetIdentifierMethod = ProxyFactoryHelper.extractProxyGetIdentifierMethod( idGetter, proxyInterface ); + Method proxySetIdentifierMethod = ProxyFactoryHelper.extractProxySetIdentifierMethod( idSetter, proxyInterface ); - Iterator properties = persistentClass.getPropertyIterator(); - Class clazz = persistentClass.getMappedClass(); - while ( properties.hasNext() ) { - Property property = (Property) properties.next(); - Method method = property.getGetter( clazz ).getMethod(); - if ( method != null && Modifier.isFinal( method.getModifiers() ) ) { - LOG.gettersOfLazyClassesCannotBeFinal( persistentClass.getEntityName(), property.getName() ); - } - method = property.getSetter( clazz ).getMethod(); - if ( method != null && Modifier.isFinal( method.getModifiers() ) ) { - LOG.settersOfLazyClassesCannotBeFinal( persistentClass.getEntityName(), property.getName() ); - } - } + ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); + try { - Method idGetterMethod = idGetter == null ? null : idGetter.getMethod(); - Method idSetterMethod = idSetter == null ? null : idSetter.getMethod(); + ProxyFactoryHelper.validateGetterSetterMethodProxyability( "Getter", proxyGetIdentifierMethod ); + ProxyFactoryHelper.validateGetterSetterMethodProxyability( "Setter", proxySetIdentifierMethod ); - Method proxyGetIdentifierMethod = idGetterMethod == null || proxyInterface == null ? - null : - ReflectHelper.getMethod( proxyInterface, idGetterMethod ); - Method proxySetIdentifierMethod = idSetterMethod == null || proxyInterface == null ? - null : - ReflectHelper.getMethod( proxyInterface, idSetterMethod ); + ProxyFactoryHelper.validateProxyability( persistentClass ); - ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); - try { pf.postInstantiate( - getEntityName(), + entityName, mappedClass, proxyInterfaces, proxyGetIdentifierMethod, @@ -171,7 +112,7 @@ protected ProxyFactory buildProxyFactory(PersistentClass persistentClass, Getter ); } catch (HibernateException he) { - LOG.unableToCreateProxyFactory( getEntityName(), he ); + LOG.unableToCreateProxyFactory( entityName, he ); pf = null; } return pf; @@ -181,7 +122,7 @@ protected ProxyFactory buildProxyFactoryInternal( PersistentClass persistentClass, Getter idGetter, Setter idSetter) { - // TODO : YUCK!!! fix afterQuery HHH-1907 is complete + // TODO : YUCK!!! fix after HHH-1907 is complete return Environment.getBytecodeProvider().getProxyFactoryFactory().buildProxyFactory( getFactory() ); // return getFactory().getSettings().getBytecodeProvider().getProxyFactoryFactory().buildProxyFactory(); } @@ -268,22 +209,14 @@ public Class getConcreteProxyClass() { @Override public void afterInitialize(Object entity, SharedSessionContractImplementor session) { - - // moving to multiple fetch groups, the idea of `lazyPropertiesAreUnfetched` really - // needs to become either: - // 1) the names of all un-fetched fetch groups - // 2) the names of all fetched fetch groups - // probably (2) is best - // - // ultimately this comes from EntityEntry, although usage-search seems to show it is never updated there. - // - // also org.hibernate.persister.entity.AbstractEntityPersister.initializeLazyPropertiesFromDatastore() - // needs to be re-worked - if ( entity instanceof PersistentAttributeInterceptable ) { - final LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); - if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractLazyInterceptor( entity ); + if ( interceptor == null || interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { if ( interceptor.getLinkedSession() == null ) { @@ -300,6 +233,10 @@ public void afterInitialize(Object entity, SharedSessionContractImplementor sess @Override public String determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) { + if ( entityInstance == null ) { + return getEntityName(); + } + final Class concreteEntityClass = entityInstance.getClass(); if ( concreteEntityClass == getMappedClass() ) { return getEntityName(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index e9b65bdd34f5..962545ef860c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -39,7 +39,7 @@ public abstract class AbstractStandardBasicType private static final Size DEFAULT_SIZE = new Size( 19, 2, 255, Size.LobMultiplier.NONE ); // to match legacy behavior private final Size dictatedSize = new Size(); - // Don't use final here. Need to initialize afterQuery-the-fact + // Don't use final here. Need to initialize after-the-fact // by DynamicParameterizedTypes. private SqlTypeDescriptor sqlTypeDescriptor; private JavaTypeDescriptor javaTypeDescriptor; @@ -71,10 +71,7 @@ protected MutabilityPlan getMutabilityPlan() { } protected T getReplacement(T original, T target, SharedSessionContractImplementor session) { - if ( !isMutable() ) { - return original; - } - else if ( isEqual( original, target ) ) { + if ( !isMutable() || ( target != null && isEqual( original, target ) ) ) { return original; } else { @@ -90,7 +87,7 @@ public boolean[] toColumnNullness(Object value, Mapping mapping) { @Override public String[] getRegistrationKeys() { return registerUnderJavaType() - ? new String[] { getName(), javaTypeDescriptor.getJavaTypeClass().getName() } + ? new String[] { getName(), javaTypeDescriptor.getJavaType().getName() } : new String[] { getName() }; } @@ -105,13 +102,13 @@ protected static Size getDefaultSize() { protected Size getDictatedSize() { return dictatedSize; } - + // final implementations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public final JavaTypeDescriptor getJavaTypeDescriptor() { return javaTypeDescriptor; } - + public final void setJavaTypeDescriptor( JavaTypeDescriptor javaTypeDescriptor ) { this.javaTypeDescriptor = javaTypeDescriptor; } @@ -127,7 +124,7 @@ public final void setSqlTypeDescriptor( SqlTypeDescriptor sqlTypeDescriptor ) { @Override public final Class getReturnedClass() { - return javaTypeDescriptor.getJavaTypeClass(); + return javaTypeDescriptor.getJavaType(); } @Override @@ -350,6 +347,10 @@ public final Type getSemiResolvedType(SessionFactoryImplementor factory) { @Override @SuppressWarnings({ "unchecked" }) public final Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) { + if ( original == null && target == null ) { + return null; + } + return getReplacement( (T) original, (T) target, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java index 7cc30824925b..c61dd34f418b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java @@ -10,42 +10,48 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; +import java.util.Objects; import org.hibernate.HibernateException; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.compare.EqualsHelper; /** * Abstract superclass of the built in Type hierarchy. - * + * * @author Gavin King */ public abstract class AbstractType implements Type { protected static final Size LEGACY_DICTATED_SIZE = new Size(); protected static final Size LEGACY_DEFAULT_SIZE = new Size( 19, 2, 255, Size.LobMultiplier.NONE ); // to match legacy behavior + @Override public boolean isAssociationType() { return false; } + @Override public boolean isCollectionType() { return false; } + @Override public boolean isComponentType() { return false; } + @Override public boolean isEntityType() { return false; } - + + @Override public int compare(Object x, Object y) { return ( (Comparable) x ).compareTo(y); } + @Override public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { @@ -57,6 +63,7 @@ public Serializable disassemble(Object value, SharedSessionContractImplementor s } } + @Override public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException { if ( cached==null ) { @@ -67,10 +74,12 @@ public Object assemble(Serializable cached, SharedSessionContractImplementor ses } } + @Override public boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException { return !isSame( old, current ); } + @Override public Object hydrate( ResultSet rs, String[] names, @@ -82,56 +91,67 @@ public Object hydrate( return nullSafeGet(rs, names, session, owner); } + @Override public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { return value; } - public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) + @Override + public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { return value; } - + + @Override public boolean isAnyType() { return false; } + @Override public boolean isModified(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException { return isDirty(old, current, session); } - + + @Override public boolean isSame(Object x, Object y) throws HibernateException { return isEqual(x, y ); } + @Override public boolean isEqual(Object x, Object y) { - return EqualsHelper.equals(x, y); + return Objects.equals( x, y ); } - + + @Override public int getHashCode(Object x) { return x.hashCode(); } + @Override public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) { return isEqual(x, y ); } - + + @Override public int getHashCode(Object x, SessionFactoryImplementor factory) { return getHashCode(x ); } - + + @Override public Type getSemiResolvedType(SessionFactoryImplementor factory) { return this; } + @Override public Object replace( - Object original, - Object target, - SharedSessionContractImplementor session, - Object owner, - Map copyCache, - ForeignKeyDirection foreignKeyDirection) + Object original, + Object target, + SharedSessionContractImplementor session, + Object owner, + Map copyCache, + ForeignKeyDirection foreignKeyDirection) throws HibernateException { boolean include; if ( isAssociationType() ) { @@ -144,6 +164,7 @@ public Object replace( return include ? replace(original, target, session, owner, copyCache) : target; } + @Override public void beforeAssemble(Serializable cached, SharedSessionContractImplementor session) {} /*public Object copy(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index cbdd59c4115c..2a0af5373bf0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -180,7 +180,7 @@ private EntityPersister guessEntityPersister(Object object) { } if ( entityName == null ) { - for ( EntityNameResolver resolver : scope.resolveFactory().getMetamodel().getEntityNameResolvers() ) { + for ( EntityNameResolver resolver : scope.getTypeConfiguration().getSessionFactory().getMetamodel().getEntityNameResolvers() ) { entityName = resolver.resolveEntityName( entity ); if ( entityName != null ) { break; @@ -193,7 +193,7 @@ private EntityPersister guessEntityPersister(Object object) { entityName = object.getClass().getName(); } - return scope.resolveFactory().getMetamodel().entityPersister( entityName ); + return scope.getTypeConfiguration().getSessionFactory().getMetamodel().entityPersister( entityName ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java index 72f1a6fe01b4..cead4fd3155a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java @@ -13,6 +13,7 @@ import org.hibernate.HibernateException; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserType; @@ -25,8 +26,14 @@ public class BasicTypeRegistry implements Serializable { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( BasicTypeRegistry.class ); // TODO : analyze these sizing params; unfortunately this seems to be the only way to give a "concurrencyLevel" - private Map registry = new ConcurrentHashMap( 100, .75f, 1 ); + private Map registry = new ConcurrentHashMap<>( 100, .75f, 1 ); private boolean locked; + private TypeConfiguration typeConfiguration; + + public BasicTypeRegistry(TypeConfiguration typeConfiguration){ + this(); + this.typeConfiguration = typeConfiguration; + } public BasicTypeRegistry() { register( BooleanType.INSTANCE ); @@ -142,6 +149,10 @@ public void register(BasicType type, String[] keys) { if ( key == null ) { continue; } + //Use String#intern here as there's high chances of duplicates combined with long term usage: + //just running our testsuite would generate 210,000 instances for the String "java.lang.Class" alone. + //Incidentally this might help with map lookup efficiency too. + key = key.intern(); LOG.debugf( "Adding type registration %s -> %s", key, type ); final Type old = registry.put( key, type ); if ( old != null && old != type ) { @@ -158,6 +169,12 @@ public void register(CompositeUserType type, String[] keys) { register( new CompositeCustomType( type, keys ) ); } + public void unregister(String... keys) { + for ( String key : keys ) { + registry.remove( key ); + } + } + public BasicType getRegisteredType(String key) { return registry.get( key ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java index c6caf5934921..e731c47b8c5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java @@ -36,7 +36,6 @@ protected boolean registerUnderJavaType() { @Override protected Clob getReplacement(Clob original, Clob target, SharedSessionContractImplementor session) { - return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( original, target, session ); + return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( (Clob) original, (Clob) target, session ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index 643cc1780067..2828314c4fea 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -20,7 +20,6 @@ import java.util.SortedMap; import java.util.TreeMap; -import org.hibernate.EntityMode; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -51,7 +50,7 @@ /** * A type that handles Hibernate PersistentCollections (including arrays). - * + * * @author Gavin King */ public abstract class CollectionType extends AbstractType implements AssociationType { @@ -107,8 +106,12 @@ public boolean isCollectionType() { @Override public final boolean isEqual(Object x, Object y) { return x == y - || ( x instanceof PersistentCollection && ( (PersistentCollection) x ).wasInitialized() && ( (PersistentCollection) x ).isWrapper( y ) ) - || ( y instanceof PersistentCollection && ( (PersistentCollection) y ).wasInitialized() && ( (PersistentCollection) y ).isWrapper( x ) ); + || ( x instanceof PersistentCollection && isEqual( (PersistentCollection) x, y ) ) + || ( y instanceof PersistentCollection && isEqual( (PersistentCollection) y, x ) ); + } + + private boolean isEqual(PersistentCollection x, Object y) { + return x.wasInitialized() && ( x.isWrapper( y ) || x.isDirectlyProvidedCollection( y ) ); } @Override @@ -203,7 +206,7 @@ protected String renderLoggableString(Object value, SessionFactoryImplementor fa return ""; } - final List list = new ArrayList(); + final List list = new ArrayList<>(); Type elemType = getElementType( factory ); Iterator itr = getElementsIterator( value ); while ( itr.hasNext() ) { @@ -259,11 +262,11 @@ public boolean isMutable() { public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { //remember the uk value - + //This solution would allow us to eliminate the owner arg to disassemble(), but //what if the collection was null, and then later had elements added? seems unsafe //session.getPersistenceContext().getCollectionEntry( (PersistentCollection) value ).getKey(); - + final Serializable key = getKeyOfOwner(owner, session); if (key==null) { return null; @@ -278,7 +281,7 @@ public Serializable disassemble(Object value, SharedSessionContractImplementor s @Override public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException { - //we must use the "remembered" uk value, since it is + //we must use the "remembered" uk value, since it is //not available from the EntityEntry during assembly if (cached==null) { return null; @@ -287,7 +290,7 @@ public Object assemble(Serializable cached, SharedSessionContractImplementor ses final Serializable key = (Serializable) getPersister(session) .getKeyType() .assemble( cached, session, owner); - return resolveKey( key, session, owner ); + return resolveKey( key, session, owner, null ); } } @@ -369,14 +372,14 @@ public ForeignKeyDirection getForeignKeyDirection() { * @return The collection owner's key */ public Serializable getKeyOfOwner(Object owner, SharedSessionContractImplementor session) { - + EntityEntry entityEntry = session.getPersistenceContext().getEntry( owner ); if ( entityEntry == null ) { // This just handles a particular case of component // projection, perhaps get rid of it and throw an exception return null; } - + if ( foreignKeyPropertyName == null ) { return entityEntry.getId(); } @@ -397,11 +400,13 @@ public Serializable getKeyOfOwner(Object owner, SharedSessionContractImplementor // NOTE VERY HACKISH WORKAROUND!! // TODO: Fix this so it will work for non-POJO entity mode Type keyType = getPersister( session ).getKeyType(); - if ( !keyType.getReturnedClass().isInstance( id ) ) { + Class returnedClass = keyType.getReturnedClass(); + + if ( !returnedClass.isInstance( id ) ) { id = keyType.semiResolve( entityEntry.getLoadedValue( foreignKeyPropertyName ), session, - owner + owner ); } @@ -450,15 +455,20 @@ public Object hydrate(ResultSet rs, String[] name, SharedSessionContractImplemen @Override public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { - - return resolveKey( getKeyOfOwner( owner, session ), session, owner ); + + return resolve( value, session, owner, null ); + } + + @Override + public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException { + return resolveKey( getKeyOfOwner( owner, session ), session, owner, overridingEager ); } - - private Object resolveKey(Serializable key, SharedSessionContractImplementor session, Object owner) { + + private Object resolveKey(Serializable key, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) { // if (key==null) throw new AssertionFailure("owner identifier unknown when re-assembling // collection reference"); return key == null ? null : // TODO: can this case really occur?? - getCollection( key, session, owner ); + getCollection( key, session, owner, overridingEager ); } @Override @@ -497,19 +507,19 @@ public boolean isModified(Object old, Object current, boolean[] checkable, Share public String getAssociatedEntityName(SessionFactoryImplementor factory) throws MappingException { try { - + QueryableCollection collectionPersister = (QueryableCollection) factory .getCollectionPersister( role ); - + if ( !collectionPersister.getElementType().isEntityType() ) { - throw new MappingException( - "collection was not an association: " + - collectionPersister.getRole() + throw new MappingException( + "collection was not an association: " + + collectionPersister.getRole() ); } - + return collectionPersister.getElementPersister().getEntityName(); - + } catch (ClassCastException cce) { throw new MappingException( "collection role is not queryable " + role ); @@ -545,7 +555,7 @@ public Object replaceElements( // if the original is a PersistentCollection, and that original // was not flagged as dirty, then reset the target's dirty flag - // here afterQuery the copy operation. + // here after the copy operation. //

    // One thing to be careful of here is a "bare" original collection // in which case we should never ever ever reset the dirty flag @@ -656,7 +666,7 @@ protected Object instantiateResult(Object original) { * and perhaps load factor). * * @param anticipatedSize The anticipated size of the instaniated collection - * afterQuery we are done populating it. + * after we are done populating it. * @return A newly instantiated collection to be wrapped. */ public abstract Object instantiate(int anticipatedSize); @@ -673,16 +683,34 @@ public Object replace( } if ( !Hibernate.isInitialized( original ) ) { if ( ( (PersistentCollection) original ).hasQueuedOperations() ) { - final AbstractPersistentCollection pc = (AbstractPersistentCollection) original; - pc.replaceQueuedOperationValues( getPersister( session ), copyCache ); + if ( original == target ) { + // A managed entity with an uninitialized collection is being merged, + // We need to replace any detached entities in the queued operations + // with managed copies. + final AbstractPersistentCollection pc = (AbstractPersistentCollection) original; + pc.replaceQueuedOperationValues( getPersister( session ), copyCache ); + } + else { + // original is a detached copy of the collection; + // it contains queued operations, which will be ignored + LOG.ignoreQueuedOperationsOnMerge( + MessageHelper.collectionInfoString( + getRole(), + ( (PersistentCollection) original ).getKey() + ) + ); + } } return target; } // for a null target, or a target which is the same as the original, we // need to put the merged elements in a new collection - Object result = target == null || target == original ? instantiateResult( original ) : target; - + Object result = ( target == null || + target == original || + target == LazyPropertyInitializer.UNFETCHED_PROPERTY ) ? + instantiateResult( original ) : target; + //for arrays, replaceElements() may return a different reference, since //the array length might not match result = replaceElements( original, result, owner, copyCache, session ); @@ -741,23 +769,24 @@ public String getOnCondition( * @param owner The collection owner * @return The collection */ - public Object getCollection(Serializable key, SharedSessionContractImplementor session, Object owner) { + public Object getCollection(Serializable key, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) { - CollectionPersister persister = getPersister( session ); + final CollectionPersister persister = getPersister( session ); final PersistenceContext persistenceContext = session.getPersistenceContext(); - final EntityMode entityMode = persister.getOwnerEntityPersister().getEntityMode(); + final CollectionKey collectionKey = new CollectionKey( persister, key ); // check if collection is currently being loaded - PersistentCollection collection = persistenceContext.getLoadContexts().locateLoadingCollection( persister, key ); - + PersistentCollection collection = persistenceContext.getLoadContexts() + .locateLoadingCollection( persister, collectionKey ); + if ( collection == null ) { - + // check if it is already completely loaded, but unowned - collection = persistenceContext.useUnownedCollection( new CollectionKey(persister, key, entityMode) ); - + collection = persistenceContext.useUnownedCollection( collectionKey ); + if ( collection == null ) { - collection = persistenceContext.getCollection( new CollectionKey(persister, key, entityMode) ); + collection = persistenceContext.getCollection( collectionKey ); if ( collection == null ) { // create a new collection wrapper, to be initialized later @@ -768,29 +797,30 @@ public Object getCollection(Serializable key, SharedSessionContractImplementor s persistenceContext.addUninitializedCollection( persister, collection, key ); // some collections are not lazy: + boolean eager = overridingEager != null ? overridingEager : !persister.isLazy(); if ( initializeImmediately() ) { session.initializeCollection( collection, false ); } - else if ( !persister.isLazy() ) { + else if ( eager ) { persistenceContext.addNonLazyCollection( collection ); } if ( hasHolder() ) { - session.getPersistenceContext().addCollectionHolder( collection ); + persistenceContext.addCollectionHolder( collection ); } - } + if ( LOG.isTraceEnabled() ) { + LOG.tracef( "Created collection wrapper: %s", + MessageHelper.collectionInfoString( persister, collection, + key, session ) ); + } + // we have already set the owner so we can just return the value + return collection.getValue(); + } } - - if ( LOG.isTraceEnabled() ) { - LOG.tracef( "Created collection wrapper: %s", - MessageHelper.collectionInfoString( persister, collection, - key, session ) ); - } - } - - collection.setOwner(owner); + + collection.setOwner( owner ); return collection.getValue(); } @@ -809,13 +839,13 @@ public String getLHSPropertyName() { } /** - * We always need to dirty check the collection because we sometimes - * need to incremement version number of owner and also because of + * We always need to dirty check the collection because we sometimes + * need to incremement version number of owner and also because of * how assemble/disassemble is implemented for uks */ @Override public boolean isAlwaysDirtyChecked() { - return true; + return true; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/CustomCollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CustomCollectionType.java index 4f2d71f7a7d6..54f7da39ebe1 100755 --- a/hibernate-core/src/main/java/org/hibernate/type/CustomCollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CustomCollectionType.java @@ -16,6 +16,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.LoggableUserType; import org.hibernate.usertype.UserCollectionType; diff --git a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java index 6cb907e3c510..e8c0c36dcb40 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java @@ -97,17 +97,17 @@ public int getColumnSpan(Mapping session) { @Override public Class getReturnedClass() { - return userType.returnedClass(); + return getUserType().returnedClass(); } @Override public boolean isEqual(Object x, Object y) throws HibernateException { - return userType.equals( x, y ); + return getUserType().equals( x, y ); } @Override public int getHashCode(Object x) { - return userType.hashCode(x); + return getUserType().hashCode( x); } @Override @@ -116,7 +116,7 @@ public Object nullSafeGet( String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException { - return userType.nullSafeGet(rs, names, session, owner); + return getUserType().nullSafeGet( rs, names, session, owner); } @Override @@ -131,12 +131,12 @@ public Object nullSafeGet( @Override public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) { - return userType.assemble(cached, owner); + return getUserType().assemble( cached, owner); } @Override public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) { - return userType.disassemble(value); + return getUserType().disassemble( value); } @Override @@ -146,7 +146,7 @@ public Object replace( SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException { - return userType.replace( original, target, owner ); + return getUserType().replace( original, target, owner ); } @Override @@ -157,7 +157,7 @@ public void nullSafeSet( boolean[] settable, SharedSessionContractImplementor session) throws SQLException { if ( settable[0] ) { - userType.nullSafeSet( st, value, index, session ); + getUserType().nullSafeSet( st, value, index, session ); } } @@ -167,7 +167,7 @@ public void nullSafeSet( Object value, int index, SharedSessionContractImplementor session) throws SQLException { - userType.nullSafeSet( st, value, index, session ); + getUserType().nullSafeSet( st, value, index, session ); } @SuppressWarnings({ "UnusedDeclaration" }) @@ -187,12 +187,12 @@ public String getName() { @Override public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException { - return userType.deepCopy(value); + return getUserType().deepCopy( value); } @Override public boolean isMutable() { - return userType.isMutable(); + return getUserType().isMutable(); } @Override @@ -202,22 +202,22 @@ public Object stringToObject(String xml) { @Override public String objectToSQLString(Object value, Dialect dialect) throws Exception { - return ( (EnhancedUserType) userType ).objectToSQLString(value); + return ( (EnhancedUserType) getUserType() ).objectToSQLString( value); } @Override public Comparator getComparator() { - return (Comparator) userType; + return (Comparator) getUserType(); } @Override public Object next(Object current, SharedSessionContractImplementor session) { - return ( (UserVersionType) userType ).next( current, session ); + return ( (UserVersionType) getUserType() ).next( current, session ); } @Override public Object seed(SharedSessionContractImplementor session) { - return ( (UserVersionType) userType ).seed( session ); + return ( (UserVersionType) getUserType() ).seed( session ); } @Override @@ -227,7 +227,7 @@ public String toLoggableString(Object value, SessionFactoryImplementor factory) return "null"; } else if ( customLogging ) { - return ( ( LoggableUserType ) userType ).toLoggableString( value, factory ); + return ( ( LoggableUserType ) getUserType() ).toLoggableString( value, factory ); } else { return toXMLString( value, factory ); @@ -252,27 +252,27 @@ public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSe @Override @SuppressWarnings("unchecked") public String toString(Object value) throws HibernateException { - if ( StringRepresentableType.class.isInstance( userType ) ) { - return ( (StringRepresentableType) userType ).toString( value ); + if ( StringRepresentableType.class.isInstance( getUserType() ) ) { + return ( (StringRepresentableType) getUserType() ).toString( value ); } if ( value == null ) { return null; } - if ( EnhancedUserType.class.isInstance( userType ) ) { + if ( EnhancedUserType.class.isInstance( getUserType() ) ) { //noinspection deprecation - return ( (EnhancedUserType) userType ).toXMLString( value ); + return ( (EnhancedUserType) getUserType() ).toXMLString( value ); } return value.toString(); } @Override public Object fromStringValue(String string) throws HibernateException { - if ( StringRepresentableType.class.isInstance( userType ) ) { - return ( (StringRepresentableType) userType ).fromStringValue( string ); + if ( StringRepresentableType.class.isInstance( getUserType() ) ) { + return ( (StringRepresentableType) getUserType() ).fromStringValue( string ); } - if ( EnhancedUserType.class.isInstance( userType ) ) { + if ( EnhancedUserType.class.isInstance( getUserType() ) ) { //noinspection deprecation - return ( (EnhancedUserType) userType ).fromXMLString( string ); + return ( (EnhancedUserType) getUserType() ).fromXMLString( string ); } throw new HibernateException( String.format( @@ -286,8 +286,8 @@ public Object fromStringValue(String string) throws HibernateException { @Override public boolean canDoSetting() { - if ( ProcedureParameterNamedBinder.class.isInstance( userType ) ) { - return ((ProcedureParameterNamedBinder) userType).canDoSetting(); + if ( ProcedureParameterNamedBinder.class.isInstance( getUserType() ) ) { + return ((ProcedureParameterNamedBinder) getUserType() ).canDoSetting(); } return false; } @@ -296,19 +296,19 @@ public boolean canDoSetting() { public void nullSafeSet( CallableStatement statement, Object value, String name, SharedSessionContractImplementor session) throws SQLException { if ( canDoSetting() ) { - ((ProcedureParameterNamedBinder) userType).nullSafeSet( statement, value, name, session ); + ((ProcedureParameterNamedBinder) getUserType() ).nullSafeSet( statement, value, name, session ); } else { throw new UnsupportedOperationException( - "Type [" + userType + "] does support parameter binding by name" + "Type [" + getUserType() + "] does support parameter binding by name" ); } } @Override public boolean canDoExtraction() { - if ( ProcedureParameterExtractionAware.class.isInstance( userType ) ) { - return ((ProcedureParameterExtractionAware) userType).canDoExtraction(); + if ( ProcedureParameterExtractionAware.class.isInstance( getUserType() ) ) { + return ((ProcedureParameterExtractionAware) getUserType() ).canDoExtraction(); } return false; } @@ -316,11 +316,11 @@ public boolean canDoExtraction() { @Override public Object extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session) throws SQLException { if ( canDoExtraction() ) { - return ((ProcedureParameterExtractionAware) userType).extract( statement, startIndex, session ); + return ((ProcedureParameterExtractionAware) getUserType() ).extract( statement, startIndex, session ); } else { throw new UnsupportedOperationException( - "Type [" + userType + "] does support parameter value extraction" + "Type [" + getUserType() + "] does support parameter value extraction" ); } } @@ -329,12 +329,22 @@ public Object extract(CallableStatement statement, int startIndex, SharedSession public Object extract(CallableStatement statement, String[] paramNames, SharedSessionContractImplementor session) throws SQLException { if ( canDoExtraction() ) { - return ((ProcedureParameterExtractionAware) userType).extract( statement, paramNames, session ); + return ((ProcedureParameterExtractionAware) getUserType() ).extract( statement, paramNames, session ); } else { throw new UnsupportedOperationException( - "Type [" + userType + "] does support parameter value extraction" + "Type [" + getUserType() + "] does support parameter value extraction" ); } } + + @Override + public int hashCode() { + return getUserType().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return ( obj instanceof CustomType ) && getUserType().equals( ( (CustomType) obj ).getUserType() ); + } } 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 262bd6e09a50..bb078f511fc6 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -7,6 +7,7 @@ package org.hibernate.type; import java.io.Serializable; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; @@ -110,6 +111,15 @@ protected EntityType( this.referenceToPrimaryKey = referenceToPrimaryKey; } + protected EntityType(EntityType original, String superTypeEntityName) { + this.scope = original.scope; + this.associatedEntityName = superTypeEntityName; + this.uniqueKeyPropertyName = original.uniqueKeyPropertyName; + this.eager = original.eager; + this.unwrapProxy = original.unwrapProxy; + this.referenceToPrimaryKey = original.referenceToPrimaryKey; + } + protected TypeFactory.TypeScope scope() { return scope; } @@ -242,7 +252,7 @@ private Class determineAssociatedEntityClass() { return ReflectHelper.classForName( entityName ); } catch (ClassNotFoundException cnfe) { - return this.scope.resolveFactory().getMetamodel().entityPersister( entityName ). + return this.scope.getTypeConfiguration().getSessionFactory().getMetamodel().entityPersister( entityName ). getEntityTuplizer().getMappedClass(); } } @@ -262,6 +272,22 @@ public final Object nullSafeGet( return resolve( hydrate( rs, names, session, owner ), session, owner ); } + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session) + throws SQLException { + if ( settable.length > 0 ) { + requireIdentifierOrUniqueKeyType( session.getFactory() ) + .nullSafeSet( st, getIdentifier( value, session ), index, settable, session ); + } + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) + throws SQLException { + requireIdentifierOrUniqueKeyType( session.getFactory() ) + .nullSafeSet( st, getIdentifier( value, session ), index, session ); + } + /** * Two entities are considered the same when their instances are the same. * @@ -296,13 +322,6 @@ public Object replace( return null; } Object cached = copyCache.get( original ); - if ( cached == null ) { - // Avoid creation of invalid managed -> managed mapping in copyCache when traversing - // cascade loop (@OneToMany(cascade=ALL) with associated @ManyToOne(cascade=ALL)) in entity graph - if ( copyCache.containsValue( original ) ) { - cached = original; - } - } if ( cached != null ) { return cached; } @@ -312,10 +331,19 @@ public Object replace( } if ( session.getContextEntityIdentifier( original ) == null && ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) { - final Object copy = session.getEntityPersister( associatedEntityName, original ) - .instantiate( null, session ); - copyCache.put( original, copy ); - return copy; + // original is transient; it is possible that original is a "managed" entity that has + // not been made persistent yet, so check if copyCache contains original as a "managed" value + // that corresponds with some "merge" value. + if ( copyCache.containsValue( original ) ) { + return original; + } + else { + // the transient entity is not "managed"; add the merge/managed pair to copyCache + final Object copy = session.getEntityPersister( associatedEntityName, original ) + .instantiate( null, session ); + copyCache.put( original, copy ); + return copy; + } } else { Object id = getIdentifier( original, session ); @@ -426,9 +454,14 @@ public String getOnCondition( */ @Override public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { + return resolve(value, session, owner, null); + } + + @Override + public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException { if ( value != null && !isNull( owner, session ) ) { if ( isReferenceToPrimaryKey() ) { - return resolveIdentifier( (Serializable) value, session ); + return resolveIdentifier( (Serializable) value, session, overridingEager ); } else if ( uniqueKeyPropertyName != null ) { return loadByUniqueKey( getAssociatedEntityName(), uniqueKeyPropertyName, value, session ); @@ -638,16 +671,19 @@ public final String getIdentifierOrUniqueKeyPropertyName(Mapping factory) * * @throws org.hibernate.HibernateException Indicates problems performing the load. */ - protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session) throws HibernateException { + protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session, Boolean overridingEager) throws HibernateException { + boolean isProxyUnwrapEnabled = unwrapProxy && getAssociatedEntityPersister( session.getFactory() ) .isInstrumented(); + boolean eager = overridingEager != null ? overridingEager : this.eager; + Object proxyOrEntity = session.internalLoad( getAssociatedEntityName(), id, eager, - isNullable() && !isProxyUnwrapEnabled + isNullable() ); if ( proxyOrEntity instanceof HibernateProxy ) { @@ -658,6 +694,10 @@ protected final Object resolveIdentifier(Serializable id, SharedSessionContractI return proxyOrEntity; } + protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session) throws HibernateException { + return resolveIdentifier( id, session, null ); + } + protected boolean isNull(Object owner, SharedSessionContractImplementor session) { return false; } @@ -682,7 +722,7 @@ public Object loadByUniqueKey( final SessionFactoryImplementor factory = session.getFactory(); UniqueKeyLoadable persister = (UniqueKeyLoadable) factory.getMetamodel().entityPersister( entityName ); - //TODO: implement caching?! proxies?! + //TODO: implement 2nd level caching?! natural id caching ?! proxies?! EntityUniqueKey euk = new EntityUniqueKey( entityName, @@ -697,8 +737,26 @@ public Object loadByUniqueKey( Object result = persistenceContext.getEntity( euk ); if ( result == null ) { result = persister.loadByUniqueKey( uniqueKeyPropertyName, key, session ); + + // If the entity was not in the Persistence Context, but was found now, + // add it to the Persistence Context + if (result != null) { + persistenceContext.addEntity(euk, result); + } } + return result == null ? null : persistenceContext.proxyFor( result ); } + protected Type requireIdentifierOrUniqueKeyType(Mapping mapping) { + final Type fkTargetType = getIdentifierOrUniqueKeyType( mapping ); + if ( fkTargetType == null ) { + throw new MappingException( + "Unable to determine FK target Type for many-to-one or one-to-one mapping: " + + "referenced-entity-name=[" + getAssociatedEntityName() + + "], referenced-entity-attribute-name=[" + getLHSPropertyName() + "]" + ); + } + return fkTargetType; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java index 7aa54b37b24f..940ae2a1c079 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java @@ -24,6 +24,12 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter; +import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter; +import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; +import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; +import org.hibernate.type.spi.TypeConfigurationAware; import org.hibernate.usertype.DynamicParameterizedType; import org.hibernate.usertype.EnhancedUserType; import org.hibernate.usertype.LoggableUserType; @@ -53,16 +59,19 @@ * @author Steve Ebersole */ @SuppressWarnings("unchecked") -public class EnumType implements EnhancedUserType, DynamicParameterizedType,LoggableUserType, Serializable { +public class EnumType + implements EnhancedUserType, DynamicParameterizedType, LoggableUserType, TypeConfigurationAware, Serializable { private static final Logger LOG = CoreLogging.logger( EnumType.class ); public static final String ENUM = "enumClass"; public static final String NAMED = "useNamed"; public static final String TYPE = "type"; - private Class enumClass; - private EnumValueMapper enumValueMapper; - private int sqlType; + private Class enumClass; + + private EnumValueConverter enumValueConverter; + + private TypeConfiguration typeConfiguration; @Override public void setParameterValues(Properties parameters) { @@ -91,13 +100,16 @@ else if ( javax.persistence.EnumType.STRING.equals( enumType ) ) { throw new AssertionFailure( "Unknown EnumType: " + enumType ); } + final EnumJavaTypeDescriptor enumJavaDescriptor = (EnumJavaTypeDescriptor) typeConfiguration + .getJavaTypeDescriptorRegistry() + .getDescriptor( enumClass ); + if ( isOrdinal ) { - this.enumValueMapper = new OrdinalEnumValueMapper(); + this.enumValueConverter = new OrdinalEnumValueConverter( enumJavaDescriptor ); } else { - this.enumValueMapper = new NamedEnumValueMapper(); + this.enumValueConverter = new NamedEnumValueConverter( enumJavaDescriptor ); } - sqlType = enumValueMapper.getSqlType(); } else { final String enumClassName = (String) parameters.get( ENUM ); @@ -108,9 +120,14 @@ else if ( javax.persistence.EnumType.STRING.equals( enumType ) ) { throw new HibernateException( "Enum class not found: " + enumClassName, exception ); } - this.enumValueMapper = interpretParameters( parameters ); - this.sqlType = enumValueMapper.getSqlType(); + this.enumValueConverter = interpretParameters( parameters ); } + + LOG.debugf( + "Using %s-based conversion for Enum %s", + isOrdinal() ? "ORDINAL" : "NAMED", + enumClass.getName() + ); } private javax.persistence.EnumType getEnumType(ParameterType reader) { @@ -130,33 +147,36 @@ private javax.persistence.EnumType getEnumType(ParameterType reader) { return enumType; } - private T getAnnotation(Annotation[] annotations, Class anClass) { + private A getAnnotation(Annotation[] annotations, Class anClass) { for ( Annotation annotation : annotations ) { if ( anClass.isInstance( annotation ) ) { - return (T) annotation; + return (A) annotation; } } return null; } - private EnumValueMapper interpretParameters(Properties parameters) { + private EnumValueConverter interpretParameters(Properties parameters) { + final EnumJavaTypeDescriptor javaTypeDescriptor = (EnumJavaTypeDescriptor) typeConfiguration + .getJavaTypeDescriptorRegistry() + .getDescriptor( enumClass ); if ( parameters.containsKey( NAMED ) ) { final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters ); if ( useNamed ) { - return new NamedEnumValueMapper(); + return new NamedEnumValueConverter( javaTypeDescriptor ); } else { - return new OrdinalEnumValueMapper(); + return new OrdinalEnumValueConverter( javaTypeDescriptor ); } } if ( parameters.containsKey( TYPE ) ) { final int type = Integer.decode( (String) parameters.get( TYPE ) ); if ( isNumericType( type ) ) { - return new OrdinalEnumValueMapper(); + return new OrdinalEnumValueConverter( javaTypeDescriptor ); } else if ( isCharacterType( type ) ) { - return new NamedEnumValueMapper(); + return new NamedEnumValueConverter( javaTypeDescriptor ); } else { throw new HibernateException( @@ -170,7 +190,7 @@ else if ( isCharacterType( type ) ) { } // the fallback - return new OrdinalEnumValueMapper(); + return new OrdinalEnumValueConverter( javaTypeDescriptor ); } private boolean isCharacterType(int jdbcTypeCode) { @@ -205,7 +225,8 @@ private boolean isNumericType(int jdbcTypeCode) { @Override public int[] sqlTypes() { - return new int[] { sqlType }; + verifyConfigured(); + return new int[] { enumValueConverter.getJdbcTypeCode() }; } @Override @@ -225,18 +246,20 @@ public int hashCode(Object x) throws HibernateException { @Override public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException { - if ( enumValueMapper == null ) { + verifyConfigured(); + return enumValueConverter.readValue( rs, names[0] ); + } + + private void verifyConfigured() { + if ( enumValueConverter == null ) { throw new AssertionFailure( "EnumType (" + enumClass.getName() + ") not properly, fully configured" ); } - return enumValueMapper.getValue( rs, names ); } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { - if ( enumValueMapper == null ) { - throw new AssertionFailure( "EnumType (" + enumClass.getName() + ") not properly, fully configured" ); - } - enumValueMapper.setValue( st, (Enum) value, index ); + verifyConfigured(); + enumValueConverter.writeValue( st, (Enum) value, index ); } @Override @@ -264,200 +287,43 @@ public Object replace(Object original, Object target, Object owner) throws Hiber return original; } + @Override + public TypeConfiguration getTypeConfiguration() { + return typeConfiguration; + } + + @Override + public void setTypeConfiguration(TypeConfiguration typeConfiguration) { + this.typeConfiguration = typeConfiguration; + } + @Override public String objectToSQLString(Object value) { - return enumValueMapper.objectToSQLString( (Enum) value ); + verifyConfigured(); + return enumValueConverter.toSqlLiteral( value ); } @Override public String toXMLString(Object value) { - return enumValueMapper.toXMLString( (Enum) value ); + verifyConfigured(); + return (String) enumValueConverter.getJavaDescriptor().unwrap( (Enum) value, String.class, null ); } @Override + @SuppressWarnings("RedundantCast") public Object fromXMLString(String xmlValue) { - return enumValueMapper.fromXMLString( xmlValue ); + verifyConfigured(); + return (T) enumValueConverter.getJavaDescriptor().wrap( xmlValue, null ); } @Override public String toLoggableString(Object value, SessionFactoryImplementor factory) { - if ( enumValueMapper != null ) { - return enumValueMapper.toXMLString( (Enum) value ); - } - return value.toString(); + verifyConfigured(); + return enumValueConverter.getJavaDescriptor().toString( (Enum) value ); } public boolean isOrdinal() { - return enumValueMapper instanceof OrdinalEnumValueMapper; - } - - private interface EnumValueMapper extends Serializable { - int getSqlType(); - Enum getValue(ResultSet rs, String[] names) throws SQLException; - void setValue(PreparedStatement st, Enum value, int index) throws SQLException; - - String objectToSQLString(Enum value); - String toXMLString(Enum value); - Enum fromXMLString(String xml); - } - - public abstract class EnumValueMapperSupport implements EnumValueMapper { - protected abstract Object extractJdbcValue(Enum value); - - @Override - public void setValue(PreparedStatement st, Enum value, int index) throws SQLException { - final Object jdbcValue = value == null ? null : extractJdbcValue( value ); - - final boolean traceEnabled = LOG.isTraceEnabled(); - if ( jdbcValue == null ) { - if ( traceEnabled ) { - LOG.trace(String.format("Binding null to parameter: [%s]", index)); - } - st.setNull( index, getSqlType() ); - return; - } - - if ( traceEnabled ) { - LOG.trace(String.format("Binding [%s] to parameter: [%s]", jdbcValue, index)); - } - st.setObject( index, jdbcValue, EnumType.this.sqlType ); - } + verifyConfigured(); + return enumValueConverter instanceof OrdinalEnumValueConverter; } - - private class OrdinalEnumValueMapper extends EnumValueMapperSupport implements EnumValueMapper, Serializable { - private transient Enum[] enumsByOrdinal; - - @Override - public int getSqlType() { - return Types.INTEGER; - } - - @Override - public Enum getValue(ResultSet rs, String[] names) throws SQLException { - final int ordinal = rs.getInt( names[0] ); - final boolean traceEnabled = LOG.isTraceEnabled(); - if ( rs.wasNull() ) { - if ( traceEnabled ) { - LOG.trace(String.format("Returning null as column [%s]", names[0])); - } - return null; - } - - final Enum enumValue = fromOrdinal( ordinal ); - if ( traceEnabled ) { - LOG.trace(String.format("Returning [%s] as column [%s]", enumValue, names[0])); - } - return enumValue; - } - - private Enum fromOrdinal(int ordinal) { - final Enum[] enumsByOrdinal = enumsByOrdinal(); - if ( ordinal < 0 || ordinal >= enumsByOrdinal.length ) { - throw new IllegalArgumentException( - String.format( - "Unknown ordinal value [%s] for enum class [%s]", - ordinal, - enumClass.getName() - ) - ); - } - return enumsByOrdinal[ordinal]; - - } - - private Enum[] enumsByOrdinal() { - if ( enumsByOrdinal == null ) { - enumsByOrdinal = enumClass.getEnumConstants(); - if ( enumsByOrdinal == null ) { - throw new HibernateException( "Failed to init enum values" ); - } - } - return enumsByOrdinal; - } - - @Override - public String objectToSQLString(Enum value) { - return toXMLString( value ); - } - - @Override - public String toXMLString(Enum value) { - return Integer.toString( value.ordinal() ); - } - - @Override - public Enum fromXMLString(String xml) { - return fromOrdinal( Integer.parseInt( xml ) ); - } - - @Override - protected Object extractJdbcValue(Enum value) { - return value.ordinal(); - } - } - - private class NamedEnumValueMapper extends EnumValueMapperSupport implements EnumValueMapper, Serializable { - @Override - public int getSqlType() { - return Types.VARCHAR; - } - - @Override - public Enum getValue(ResultSet rs, String[] names) throws SQLException { - final String value = rs.getString( names[0] ); - - final boolean traceEnabled = LOG.isTraceEnabled(); - if ( rs.wasNull() ) { - if ( traceEnabled ) { - LOG.trace(String.format("Returning null as column [%s]", names[0])); - } - return null; - } - - final Enum enumValue = fromName( value ); - if ( traceEnabled ) { - LOG.trace(String.format("Returning [%s] as column [%s]", enumValue, names[0])); - } - return enumValue; - } - - private Enum fromName(String name) { - try { - if (name == null) { - return null; - } - return Enum.valueOf( enumClass, name.trim() ); - } - catch ( IllegalArgumentException iae ) { - throw new IllegalArgumentException( - String.format( - "Unknown name value [%s] for enum class [%s]", - name, - enumClass.getName() - ) - ); - } - } - - @Override - public String objectToSQLString(Enum value) { - return '\'' + toXMLString( value ) + '\''; - } - - @Override - public String toXMLString(Enum value) { - return value.name(); - } - - @Override - public Enum fromXMLString(String xml) { - return fromName( xml ); - } - - @Override - protected Object extractJdbcValue(Enum value) { - return value.name(); - } - } - } 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 257e9293551a..d0b162f0d5d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -7,7 +7,6 @@ package org.hibernate.type; import java.io.Serializable; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; @@ -17,10 +16,9 @@ import org.hibernate.MappingException; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.jdbc.Size; -import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.Mapping; -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.*; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Loadable; /** * A many-to-one association to an entity. @@ -28,6 +26,7 @@ * @author Gavin King */ public class ManyToOneType extends EntityType { + private final String propertyName; private final boolean ignoreNotFound; private boolean isLogicalOneToOne; @@ -55,7 +54,7 @@ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName, b /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, boolean, boolean, boolean, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -70,6 +69,10 @@ public ManyToOneType( this( scope, referencedEntityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne ); } + /** + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, boolean ) } instead. + */ + @Deprecated public ManyToOneType( TypeFactory.TypeScope scope, String referencedEntityName, @@ -79,16 +82,42 @@ public ManyToOneType( boolean unwrapProxy, boolean ignoreNotFound, boolean isLogicalOneToOne) { + this( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, null, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne ); + } + + public ManyToOneType( + TypeFactory.TypeScope scope, + String referencedEntityName, + boolean referenceToPrimaryKey, + String uniqueKeyPropertyName, + String propertyName, + boolean lazy, + boolean unwrapProxy, + boolean ignoreNotFound, + boolean isLogicalOneToOne) { super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy ); + this.propertyName = propertyName; this.ignoreNotFound = ignoreNotFound; this.isLogicalOneToOne = isLogicalOneToOne; } + public ManyToOneType(ManyToOneType original, String superTypeEntityName) { + super( original, superTypeEntityName ); + this.propertyName = original.propertyName; + this.ignoreNotFound = original.ignoreNotFound; + this.isLogicalOneToOne = original.isLogicalOneToOne; + } + @Override protected boolean isNullable() { return ignoreNotFound; } + @Override + public String getPropertyName() { + return propertyName; + } + @Override public boolean isAlwaysDirtyChecked() { // always need to dirty-check, even when non-updateable; @@ -113,18 +142,6 @@ public int getColumnSpan(Mapping mapping) throws MappingException { return requireIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping ); } - private Type requireIdentifierOrUniqueKeyType(Mapping mapping) { - final Type fkTargetType = getIdentifierOrUniqueKeyType( mapping ); - if ( fkTargetType == null ) { - throw new MappingException( - "Unable to determine FK target Type for many-to-one mapping: " + - "referenced-entity-name=[" + getAssociatedEntityName() + - "], referenced-entity-attribute-name=[" + getLHSPropertyName() + "]" - ); - } - return fkTargetType; - } - @Override public int[] sqlTypes(Mapping mapping) throws MappingException { return requireIdentifierOrUniqueKeyType( mapping ).sqlTypes( mapping ); @@ -140,27 +157,6 @@ public Size[] defaultSizes(Mapping mapping) throws MappingException { return requireIdentifierOrUniqueKeyType( mapping ).defaultSizes( mapping ); } - @Override - public void nullSafeSet( - PreparedStatement st, - Object value, - int index, - boolean[] settable, - SharedSessionContractImplementor session) throws HibernateException, SQLException { - requireIdentifierOrUniqueKeyType( session.getFactory() ) - .nullSafeSet( st, getIdentifier( value, session ), index, settable, session ); - } - - @Override - public void nullSafeSet( - PreparedStatement st, - Object value, - int index, - SharedSessionContractImplementor session) throws HibernateException, SQLException { - requireIdentifierOrUniqueKeyType( session.getFactory() ) - .nullSafeSet( st, getIdentifier( value, session ), index, session ); - } - @Override public ForeignKeyDirection getForeignKeyDirection() { return ForeignKeyDirection.FROM_PARENT; @@ -175,8 +171,25 @@ public Object hydrate( // return the (fully resolved) identifier value, but do not resolve // to the actual referenced entity instance // NOTE: the owner of the association is not really the owner of the id! - final Serializable id = (Serializable) getIdentifierOrUniqueKeyType( session.getFactory() ) - .nullSafeGet( rs, names, session, null ); + + // First hydrate the ID to check if it is null. + // Don't bother resolving the ID if hydratedKeyState[i] is null. + + // Implementation note: if id is a composite ID, then resolving a null value will + // result in instantiating an empty composite if AvailableSettings#CREATE_EMPTY_COMPOSITES_ENABLED + // is true. By not resolving a null value for a composite ID, we avoid the overhead of instantiating + // an empty composite, checking if it is equivalent to null (it should be), then ultimately throwing + // out the empty value. + final Object hydratedId = getIdentifierOrUniqueKeyType( session.getFactory() ) + .hydrate( rs, names, session, null ); + final Serializable id; + if ( hydratedId != null ) { + id = (Serializable) getIdentifierOrUniqueKeyType( session.getFactory() ) + .resolve( hydratedId, session, null ); + } + else { + id = null; + } scheduleBatchLoadIfNeeded( id, session ); return id; } @@ -221,6 +234,27 @@ public boolean isModified( .isDirty( old, getIdentifier( current, session ), session ); } + @Override + public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException { + Object resolvedValue = super.resolve(value, session, owner, overridingEager); + if ( isLogicalOneToOne && value != null && getPropertyName() != null ) { + EntityEntry entry = session.getPersistenceContext().getEntry( owner ); + if ( entry != null ) { + final Loadable ownerPersister = (Loadable) session.getFactory().getMetamodel().entityPersister( entry.getEntityName() ); + EntityUniqueKey entityKey = new EntityUniqueKey( + ownerPersister.getEntityName(), + getPropertyName(), + value, + this, + ownerPersister.getEntityMode(), + session.getFactory() + ); + session.getPersistenceContext().addEntity( entityKey, owner ); + } + } + return resolvedValue; + } + @Override public Serializable disassemble( Object value, diff --git a/hibernate-core/src/main/java/org/hibernate/type/NClobType.java b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java index 71f5921f9761..bf736ce37376 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/NClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java @@ -12,7 +12,7 @@ import org.hibernate.type.descriptor.java.NClobTypeDescriptor; /** - * A type that maps between {@link java.sql.Types#CLOB CLOB} and {@link java.sql.Clob} + * A type that maps between {@link java.sql.Types#NCLOB NCLOB} and {@link java.sql.NClob} * * @author Gavin King * @author Steve Ebersole @@ -38,5 +38,4 @@ protected boolean registerUnderJavaType() { protected NClob getReplacement(NClob original, NClob target, SharedSessionContractImplementor session) { return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeNClob( original, target, session ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java index c1090e8c9c2a..511416994d3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -63,6 +63,13 @@ public OneToOneType( this.entityName = entityName; } + public OneToOneType(OneToOneType original, String superTypeEntityName) { + super( original, superTypeEntityName ); + this.foreignKeyType = original.foreignKeyType; + this.propertyName = original.propertyName; + this.entityName = original.entityName; + } + @Override public String getPropertyName() { return propertyName; @@ -113,11 +120,6 @@ public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] //nothing to do } - @Override - public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) { - //nothing to do - } - @Override public boolean isOneToOne() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java b/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java index d04bf5e26c94..3b7ee81fa417 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java @@ -16,11 +16,13 @@ import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.UUIDTypeDescriptor; import org.hibernate.type.descriptor.sql.BasicBinder; import org.hibernate.type.descriptor.sql.BasicExtractor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; /** * Specialized type mapping for {@link UUID} and the Postgres UUID data type (which is mapped as OTHER in its @@ -59,6 +61,12 @@ public boolean canBeRemapped() { return true; } + @Override + @SuppressWarnings("unchecked") + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( UUID.class ); + } + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayNClobType.java b/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayNClobType.java index 6b0dc7864491..a907e9303876 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayNClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayNClobType.java @@ -15,7 +15,7 @@ * @author Emmanuel Bernard */ public class PrimitiveCharacterArrayNClobType extends AbstractSingleColumnStandardBasicType { - public static final CharacterArrayClobType INSTANCE = new CharacterArrayClobType(); + public static final CharacterArrayNClobType INSTANCE = new CharacterArrayNClobType(); public PrimitiveCharacterArrayNClobType() { super( NClobTypeDescriptor.DEFAULT, PrimitiveCharacterArrayTypeDescriptor.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java b/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java index 84ab2c14314e..dbf8ee4ca9cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java @@ -56,7 +56,7 @@ public interface SingleColumnType extends Type { /** * Set a parameter value without worrying about the possibility of null - * values. Called from {@link #nullSafeSet} afterQuery nullness checks have + * values. Called from {@link #nullSafeSet} after nullness checks have * been performed. * * @param st The statement into which to bind the parameter value. diff --git a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java index 4a53600528fd..bd66bfa280d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java @@ -64,6 +64,10 @@ public SpecialOneToOneType( propertyName ); } + + public SpecialOneToOneType(SpecialOneToOneType original, String superTypeEntityName) { + super( original, superTypeEntityName ); + } public int getColumnSpan(Mapping mapping) throws MappingException { return super.getIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypeTemplate.java b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypeTemplate.java new file mode 100644 index 000000000000..fdfa849f6e23 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypeTemplate.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.type; + +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +/** + * A BasicType adapter targeting partial portability to 6.0's type + * system changes. In 6.0 the notion of a BasicType is just a + * combination of JavaTypeDescriptor/SqlTypeDescriptor. + * + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class StandardBasicTypeTemplate extends AbstractSingleColumnStandardBasicType { + private final String name; + private final String[] registrationKeys; + + public StandardBasicTypeTemplate( + SqlTypeDescriptor sqlTypeDescriptor, + JavaTypeDescriptor javaTypeDescriptor, + String... registrationKeys) { + super( sqlTypeDescriptor, javaTypeDescriptor ); + this.registrationKeys = registrationKeys; + + this.name = javaTypeDescriptor.getJavaType() == null ? "(map-mode)" : javaTypeDescriptor.getJavaType().getName() + + " -> " + sqlTypeDescriptor.getSqlType(); + } + + @Override + public String getName() { + return name; + } + + @Override + public String[] getRegistrationKeys() { + return registrationKeys; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/Type.java b/hibernate-core/src/main/java/org/hibernate/type/Type.java index 2c68e3c3303b..0008c0cc7e6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/type/Type.java @@ -51,7 +51,7 @@ public interface Type extends Serializable { * A {@link CollectionType} is additionally an {@link AssociationType}; so if this method returns true, * {@link #isAssociationType()} should also return true. * - * @return True if this type is also an {@link CollectionType} implementor; false otherwise. + * @return True if this type is also a {@link CollectionType} implementor; false otherwise. */ boolean isCollectionType(); @@ -82,7 +82,7 @@ public interface Type extends Serializable { * version of {@code (type instanceof CompositeType.class)}. A component type may own collections or * associations and hence must provide certain extra functionality. * - * @return True if this type is also an {@link CompositeType} implementor; false otherwise. + * @return True if this type is also a {@link CompositeType} implementor; false otherwise. */ boolean isComponentType(); @@ -222,7 +222,7 @@ public interface Type extends Serializable { * @throws HibernateException A problem occurred calculating the hash code */ int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException; - + /** * Perform a {@link java.util.Comparator} style comparison between values * @@ -235,7 +235,7 @@ public interface Type extends Serializable { /** * Should the parent be considered dirty, given both the old and current value? - * + * * @param old the old value * @param current the current value * @param session The session from which the request originated. @@ -427,9 +427,9 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) * @throws HibernateException An error from Hibernate */ Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException; - + /** - * Called beforeQuery assembling a query result set from the query cache, to allow batch fetching + * Called before assembling a query result set from the query cache, to allow batch fetching * of entities missing from the second-level cache. * * @param cached The key @@ -444,7 +444,7 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) *
  • in the case of an entity or collection type, the key
  • *
  • otherwise, the value itself
  • * - * + * * @param rs The JDBC result set * @param names the column names making up this type value (use to read from result set) * @param session The originating session @@ -460,23 +460,33 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) Object hydrate(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; + /** + * @see #resolve(Object, SharedSessionContractImplementor, Object, Boolean) + */ + Object resolve(Object value, SharedSessionContractImplementor session, Object owner) + throws HibernateException; + /** * The second phase of 2-phase loading. Only really pertinent for entities and collections. Here we resolve the * identifier to an entity or collection instance - * + * * @param value an identifier or value returned by hydrate() * @param owner the parent entity * @param session the session - * + * @param overridingEager can override eager from the mapping. For example because of {@link org.hibernate.engine.spi.LoadQueryInfluencers} + * If null, then it does not override. If true or false then it overrides the mapping value. + * * @return the given value, or the value associated with the identifier * * @throws HibernateException An error from Hibernate * * @see #hydrate */ - Object resolve(Object value, SharedSessionContractImplementor session, Object owner) - throws HibernateException; - + default Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) + throws HibernateException { + return resolve(value, session, owner); + } + /** * Given a hydrated, but unresolved value, return a value that may be used to reconstruct property-ref * associations. @@ -491,7 +501,7 @@ Object resolve(Object value, SharedSessionContractImplementor session, Object ow */ Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; - + /** * As part of 2-phase loading, when we perform resolving what is the resolved type for this type? Generally * speaking the type and its semi-resolved type will be the same. The main deviation from this is in the @@ -526,7 +536,7 @@ Object replace( SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException; - + /** * During merge, replace the existing (target) value in the entity we are merging to * with a new (original) value from the detached entity we are merging. For immutable @@ -552,16 +562,16 @@ Object replace( Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException; - + /** * Given an instance of the type, return an array of boolean, indicating * which mapped columns would be null. - * + * * @param value an instance of the type * @param mapping The mapping abstraction * * @return array indicating column nullness for a value instance */ boolean[] toColumnNullness(Object value, Mapping mapping); - + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index 17c2882dc7df..d00817a858da 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -10,15 +10,14 @@ import java.util.Comparator; import java.util.Properties; -import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.tuple.component.ComponentMetamodel; +import org.hibernate.type.spi.TypeConfiguration; +import org.hibernate.type.spi.TypeConfigurationAware; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.ParameterizedType; import org.hibernate.usertype.UserType; @@ -34,64 +33,32 @@ * * @author Gavin King * @author Steve Ebersole + * + * @deprecated Use {@link TypeConfiguration} instead */ +@Deprecated @SuppressWarnings({"unchecked"}) public final class TypeFactory implements Serializable { private static final CoreMessageLogger LOG = messageLogger( TypeFactory.class ); - private final TypeScopeImpl typeScope = new TypeScopeImpl(); - - public static interface TypeScope extends Serializable { - public SessionFactoryImplementor resolveFactory(); + /** + * @deprecated Use {@link TypeConfiguration}/{@link TypeConfiguration.Scope} instead + */ + @Deprecated + public interface TypeScope extends Serializable { + TypeConfiguration getTypeConfiguration(); } - private static class TypeScopeImpl implements TypeFactory.TypeScope { - private transient SessionFactoryImplementor factory; - private String sessionFactoryName; - private String sessionFactoryUuid; - - public void injectSessionFactory(SessionFactoryImplementor factory) { - if ( this.factory != null ) { - LOG.scopingTypesToSessionFactoryAfterAlreadyScoped( this.factory, factory ); - } - else { - LOG.tracev( "Scoping types to session factory {0}", factory ); - sessionFactoryUuid = factory.getUuid(); - String sfName = factory.getSettings().getSessionFactoryName(); - if ( sfName == null ) { - final CfgXmlAccessService cfgXmlAccessService = factory.getServiceRegistry() - .getService( CfgXmlAccessService.class ); - if ( cfgXmlAccessService.getAggregatedConfig() != null ) { - sfName = cfgXmlAccessService.getAggregatedConfig().getSessionFactoryName(); - } - } - sessionFactoryName = sfName; - } - this.factory = factory; - } + private final TypeConfiguration typeConfiguration; + private final TypeScope typeScope; - public SessionFactoryImplementor resolveFactory() { - if ( factory == null ) { - factory = (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory( - sessionFactoryUuid, - sessionFactoryName - ); - if ( factory == null ) { - throw new HibernateException( - "Could not find a SessionFactory [uuid=" + sessionFactoryUuid + ",name=" + sessionFactoryName + "]" - ); - } - } - return factory; - } - } - - public void injectSessionFactory(SessionFactoryImplementor factory) { - typeScope.injectSessionFactory( factory ); + public TypeFactory(TypeConfiguration typeConfiguration) { + this.typeConfiguration = typeConfiguration; + this.typeScope = (TypeScope) () -> typeConfiguration; } public SessionFactoryImplementor resolveSessionFactory() { - return typeScope.resolveFactory(); + return typeConfiguration.getSessionFactory(); } public Type byClass(Class clazz, Properties parameters) { @@ -190,7 +157,17 @@ public CollectionType customCollection( } public CustomType custom(Class typeClass, Properties parameters) { - return custom( typeClass, parameters, typeScope ); + try { + UserType userType = typeClass.newInstance(); + if ( TypeConfigurationAware.class.isInstance( userType ) ) { + ( (TypeConfigurationAware) userType ).setTypeConfiguration( typeConfiguration ); + } + injectParameters( userType, parameters ); + return new CustomType( userType ); + } + catch (Exception e) { + throw new MappingException( "Unable to instantiate custom type: " + typeClass.getName(), e ); + } } /** @@ -286,10 +263,35 @@ public EntityType manyToOne( ); } + /** + * @deprecated Use {@link #manyToOne(String, boolean, String, String, boolean, boolean, boolean, boolean)} instead. + */ + @Deprecated + public EntityType manyToOne( + String persistentClass, + boolean referenceToPrimaryKey, + String uniqueKeyPropertyName, + boolean lazy, + boolean unwrapProxy, + boolean ignoreNotFound, + boolean isLogicalOneToOne) { + return manyToOne( + persistentClass, + referenceToPrimaryKey, + uniqueKeyPropertyName, + null, + lazy, + unwrapProxy, + ignoreNotFound, + isLogicalOneToOne + ); + } + public EntityType manyToOne( String persistentClass, boolean referenceToPrimaryKey, String uniqueKeyPropertyName, + String propertyName, boolean lazy, boolean unwrapProxy, boolean ignoreNotFound, @@ -299,6 +301,7 @@ public EntityType manyToOne( persistentClass, referenceToPrimaryKey, uniqueKeyPropertyName, + propertyName, lazy, unwrapProxy, ignoreNotFound, 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 cef4709595a2..295cfba36d26 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java @@ -6,22 +6,22 @@ */ package org.hibernate.type; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Map; - -import org.hibernate.Hibernate; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl; import org.hibernate.tuple.NonIdentifierAttribute; +import java.io.Serializable; +import java.util.Map; + /** * Collection of convenience methods relating to operations across arrays of types... * * @author Steve Ebersole + * + * @deprecated with no real replacement. this was always intended as an internal class */ +@Deprecated public class TypeHelper { /** * Disallow instantiation @@ -156,25 +156,11 @@ public static Object[] replace( final Map copyCache) { Object[] copied = new Object[original.length]; for ( int i = 0; i < types.length; i++ ) { - if ( original[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY - || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { + if ( original[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { copied[i] = target[i]; } else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { - // Should be no need to check for target[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN - // because PropertyAccessStrategyBackRefImpl.get( object ) returns - // PropertyAccessStrategyBackRefImpl.UNKNOWN, so target[i] == original[i]. - // - // We know from above that original[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY && - // original[i] != PropertyAccessStrategyBackRefImpl.UNKNOWN; - // This is a case where the entity being merged has a lazy property - // that has been initialized. Copy the initialized value from original. - if ( types[i].isMutable() ) { - copied[i] = types[i].deepCopy( original[i], session.getFactory() ); - } - else { - copied[i] = original[i]; - } + copied[i] = types[i].replace( original[i], null, session, owner, copyCache ); } else { copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache ); @@ -210,6 +196,9 @@ public static Object[] replace( || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { copied[i] = target[i]; } + else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + copied[i] = types[i].replace( original[i], null, session, owner, copyCache, foreignKeyDirection ); + } else { copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache, foreignKeyDirection ); } @@ -279,9 +268,12 @@ else if ( !types[i].isAssociationType() ) { * @param includeColumns Columns to be included in the dirty checking, per property * @param anyUninitializedProperties Does the entity currently hold any uninitialized property values? * @param session The session from which the dirty check request originated. - * + * * @return Array containing indices of the dirty properties, or null if no properties considered dirty. + * + * @deprecated Use {org.hibernate.type.TypeHelper{@link #findDirty(NonIdentifierAttribute[], Object[], Object[], boolean[][], SharedSessionContractImplementor)} indtead */ + @Deprecated public static int[] findDirty( final NonIdentifierAttribute[] properties, final Object[] currentState, @@ -289,14 +281,38 @@ public static int[] findDirty( final boolean[][] includeColumns, final boolean anyUninitializedProperties, final SharedSessionContractImplementor session) { + return findDirty( properties, currentState, previousState, includeColumns, session ); + } + + /** + * Determine if any of the given field values are dirty, returning an array containing + * indices of the dirty fields. + *

    + * If it is determined that no fields are dirty, null is returned. + * + * @param properties The property definitions + * @param currentState The current state of the entity + * @param previousState The baseline state of the entity + * @param includeColumns Columns to be included in the dirty checking, per property + * @param session The session from which the dirty check request originated. + * + * @return Array containing indices of the dirty properties, or null if no properties considered dirty. + */ + public static int[] findDirty( + final NonIdentifierAttribute[] properties, + final Object[] currentState, + final Object[] previousState, + final boolean[][] includeColumns, + final SharedSessionContractImplementor session) { int[] results = null; int count = 0; int span = properties.length; for ( int i = 0; i < span; i++ ) { - final boolean dirty = currentState[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY - && properties[i].isDirtyCheckable( anyUninitializedProperties ) - && properties[i].getType().isDirty( previousState[i], currentState[i], includeColumns[i], session ); + final boolean dirty = currentState[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY && + ( previousState[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY || + ( properties[i].isDirtyCheckable() + && properties[i].getType().isDirty( previousState[i], currentState[i], includeColumns[i], session ) ) ); if ( dirty ) { if ( results == null ) { results = new int[span]; @@ -330,7 +346,11 @@ public static int[] findDirty( * @param session The session from which the dirty check request originated. * * @return Array containing indices of the modified properties, or null if no properties considered modified. + * + * @deprecated Use {@link #findModified(NonIdentifierAttribute[], Object[], Object[], boolean[][], boolean[], boolean, SharedSessionContractImplementor)} + * instead. */ + @Deprecated public static int[] findModified( final NonIdentifierAttribute[] properties, final Object[] currentState, @@ -339,6 +359,31 @@ public static int[] findModified( final boolean[] includeProperties, final boolean anyUninitializedProperties, final SharedSessionContractImplementor session) { + return findModified( properties, currentState, previousState, includeColumns, includeProperties, session ); + } + + /** + * Determine if any of the given field values are modified, returning an array containing + * indices of the modified fields. + *

    + * If it is determined that no fields are dirty, null is returned. + * + * @param properties The property definitions + * @param currentState The current state of the entity + * @param previousState The baseline state of the entity + * @param includeColumns Columns to be included in the mod checking, per property + * @param includeProperties Array of property indices that identify which properties participate in check + * @param session The session from which the dirty check request originated. + * + * @return Array containing indices of the modified properties, or null if no properties considered modified. + **/ + public static int[] findModified( + final NonIdentifierAttribute[] properties, + final Object[] currentState, + final Object[] previousState, + final boolean[][] includeColumns, + final boolean[] includeProperties, + final SharedSessionContractImplementor session) { int[] results = null; int count = 0; int span = properties.length; @@ -346,7 +391,7 @@ public static int[] findModified( for ( int i = 0; i < span; i++ ) { final boolean modified = currentState[ i ] != LazyPropertyInitializer.UNFETCHED_PROPERTY && includeProperties[ i ] - && properties[ i ].isDirtyCheckable( anyUninitializedProperties ) + && properties[ i ].isDirtyCheckable() && properties[ i ].getType().isModified( previousState[ i ], currentState[ i ], includeColumns[ i ], session ); if ( modified ) { if ( results == null ) { @@ -366,24 +411,4 @@ public static int[] findModified( } } - public static String toLoggableString( - Object[] state, - Type[] types, - SessionFactoryImplementor factory) { - final StringBuilder buff = new StringBuilder(); - for ( int i = 0; i < state.length; i++ ) { - if ( i > 0 ) { - buff.append( ", " ); - } - - // HHH-11173 - Instead of having to account for unfectched lazy properties in all types, it's done here - if ( state[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY || !Hibernate.isInitialized( state[i] ) ) { - buff.append( "" ); - } - else { - buff.append( types[i].toLoggableString( state[i], factory ) ); - } - } - return buff.toString(); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java b/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java index f99556bb9f01..12b6e3069872 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java @@ -10,8 +10,9 @@ import java.util.Properties; import org.hibernate.MappingException; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserType; @@ -19,35 +20,47 @@ * Acts as the contract for getting types and as the mediator between {@link BasicTypeRegistry} and {@link TypeFactory}. * * @author Steve Ebersole + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 */ +@Deprecated public class TypeResolver implements Serializable { - private final BasicTypeRegistry basicTypeRegistry; private final TypeFactory typeFactory; + private final TypeConfiguration typeConfiguration; - public TypeResolver() { - this( new BasicTypeRegistry(), new TypeFactory() ); - } - - public TypeResolver(BasicTypeRegistry basicTypeRegistry, TypeFactory typeFactory) { - this.basicTypeRegistry = basicTypeRegistry; + public TypeResolver(TypeConfiguration typeConfiguration, TypeFactory typeFactory){ + this.typeConfiguration = typeConfiguration; this.typeFactory = typeFactory; } - public TypeResolver scope(SessionFactoryImplementor factory) { - typeFactory.injectSessionFactory( factory ); - return new TypeResolver( basicTypeRegistry.shallowCopy(), typeFactory ); - } +// public TypeResolver() { +// this( new BasicTypeRegistry(), new TypeFactory() ); +// } +// +// /** +// * @deprecated (since 5.3) +// */ +// @Deprecated +// public TypeResolver(BasicTypeRegistry basicTypeRegistry, TypeFactory typeFactory) { +// this.basicTypeRegistry = basicTypeRegistry; +// this.typeFactory = typeFactory; +// } + +// public TypeResolver scope(SessionFactoryImplementor factory) { +// typeFactory.injectSessionFactory( factory ); +// return new TypeResolver( basicTypeRegistry.shallowCopy(), typeFactory ); +// } public void registerTypeOverride(BasicType type) { - basicTypeRegistry.register( type ); + typeConfiguration.getBasicTypeRegistry().register( type ); } public void registerTypeOverride(UserType type, String[] keys) { - basicTypeRegistry.register( type, keys ); + typeConfiguration.getBasicTypeRegistry().register( type, keys ); } public void registerTypeOverride(CompositeUserType type, String[] keys) { - basicTypeRegistry.register( type, keys ); + typeConfiguration.getBasicTypeRegistry().register( type, keys ); } public TypeFactory getTypeFactory() { @@ -62,7 +75,7 @@ public TypeFactory getTypeFactory() { * @return The registered type */ public BasicType basic(String name) { - return basicTypeRegistry.getRegisteredType( name ); + return typeConfiguration.getBasicTypeRegistry().getRegisteredType( name ); } /** @@ -107,12 +120,13 @@ public Type heuristicType(String typeName, Properties parameters) throws Mapping } try { - Class typeClass = ReflectHelper.classForName( typeName ); + final ClassLoaderService classLoaderService = typeConfiguration.getServiceRegistry().getService( ClassLoaderService.class ); + Class typeClass = classLoaderService.classForName( typeName ); if ( typeClass != null ) { return typeFactory.byClass( typeClass, parameters ); } } - catch ( ClassNotFoundException ignore ) { + catch ( ClassLoadingException ignore ) { } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/type/WrapperBinaryType.java b/hibernate-core/src/main/java/org/hibernate/type/WrapperBinaryType.java index 88a8be5ffeb5..79a34f0830f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/WrapperBinaryType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/WrapperBinaryType.java @@ -27,7 +27,7 @@ public String[] getRegistrationKeys() { } public String getName() { - //TODO find a decent name beforeQuery documenting + //TODO find a decent name before documenting return "wrapper-binary"; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterMutabilityPlanImpl.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterMutabilityPlanImpl.java index ffe997972aef..f3fc0da38566 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterMutabilityPlanImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterMutabilityPlanImpl.java @@ -6,26 +6,30 @@ */ package org.hibernate.type.descriptor.converter; -import javax.persistence.AttributeConverter; - +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.type.descriptor.java.MutableMutabilityPlan; /** - * For now we need to treat attributes to which a converter has been applied as mutable. - * See Jira HHH-10111 for details. + * The standard aproach for defining a MutabilityPlan for converted (AttributeConverter) + * values is to always assume that they are immutable to make sure that dirty checking, + * deep copying and second-level caching all work properly no matter what. That was work + * done under https://hibernate.atlassian.net/browse/HHH-10111 + * + * However a series of approaches to tell Hibernate that the values are immutable were + * documented as part of https://hibernate.atlassian.net/browse/HHH-10127 * * @author Steve Ebersole */ public class AttributeConverterMutabilityPlanImpl extends MutableMutabilityPlan { - private final AttributeConverter attributeConverter; + private final JpaAttributeConverter converter; - public AttributeConverterMutabilityPlanImpl(AttributeConverter attributeConverter) { - this.attributeConverter = attributeConverter; + public AttributeConverterMutabilityPlanImpl(JpaAttributeConverter converter) { + this.converter = converter; } @Override @SuppressWarnings("unchecked") protected T deepCopyNotNull(T value) { - return (T) attributeConverter.convertToEntityAttribute( attributeConverter.convertToDatabaseColumn( value ) ); + return (T) converter.toDomainValue( converter.toRelationalValue( value ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterSqlTypeDescriptorAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterSqlTypeDescriptorAdapter.java index 7af451f4d4cc..1a5bcf1827ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterSqlTypeDescriptorAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterSqlTypeDescriptorAdapter.java @@ -13,6 +13,7 @@ import javax.persistence.AttributeConverter; import javax.persistence.PersistenceException; +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; @@ -35,12 +36,12 @@ public class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescriptor { private static final Logger log = Logger.getLogger( AttributeConverterSqlTypeDescriptorAdapter.class ); - private final AttributeConverter converter; + private final JpaAttributeConverter converter; private final SqlTypeDescriptor delegate; private final JavaTypeDescriptor intermediateJavaTypeDescriptor; public AttributeConverterSqlTypeDescriptorAdapter( - AttributeConverter converter, + JpaAttributeConverter converter, SqlTypeDescriptor delegate, JavaTypeDescriptor intermediateJavaTypeDescriptor) { this.converter = converter; @@ -74,7 +75,7 @@ public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { final Object convertedValue; try { - convertedValue = converter.convertToDatabaseColumn( value ); + convertedValue = converter.toRelationalValue( value ); } catch (PersistenceException pe) { throw pe; @@ -91,7 +92,7 @@ public void bind(PreparedStatement st, X value, int index, WrapperOptions option public void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { final Object convertedValue; try { - convertedValue = converter.convertToDatabaseColumn( value ); + convertedValue = converter.toRelationalValue( value ); } catch (PersistenceException pe) { throw pe; @@ -136,7 +137,7 @@ public X extract(CallableStatement statement, String[] paramNames, WrapperOption @SuppressWarnings("unchecked") private X doConversion(Object extractedValue) { try { - X convertedValue = (X) converter.convertToEntityAttribute( extractedValue ); + X convertedValue = (X) converter.toDomainValue( extractedValue ); log.debugf( "Converted value on extraction: %s -> %s", extractedValue, convertedValue ); return convertedValue; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java index c30569aa103e..21c7c931cc00 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java @@ -6,8 +6,7 @@ */ package org.hibernate.type.descriptor.converter; -import javax.persistence.AttributeConverter; - +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -31,7 +30,7 @@ public class AttributeConverterTypeAdapter extends AbstractSingleColumnStanda private final Class modelType; private final Class jdbcType; - private final AttributeConverter attributeConverter; + private final JpaAttributeConverter attributeConverter; private final MutabilityPlan mutabilityPlan; @@ -39,7 +38,7 @@ public class AttributeConverterTypeAdapter extends AbstractSingleColumnStanda public AttributeConverterTypeAdapter( String name, String description, - AttributeConverter attributeConverter, + JpaAttributeConverter attributeConverter, SqlTypeDescriptor sqlTypeDescriptorAdapter, Class modelType, Class jdbcType, @@ -51,10 +50,9 @@ public AttributeConverterTypeAdapter( this.jdbcType = jdbcType; this.attributeConverter = attributeConverter; - this.mutabilityPlan = - entityAttributeJavaTypeDescriptor.getMutabilityPlan().isMutable() ? - new AttributeConverterMutabilityPlanImpl( attributeConverter ) : - ImmutableMutabilityPlan.INSTANCE; + this.mutabilityPlan = entityAttributeJavaTypeDescriptor.getMutabilityPlan().isMutable() + ? new AttributeConverterMutabilityPlanImpl( attributeConverter ) + : ImmutableMutabilityPlan.INSTANCE; log.debug( "Created AttributeConverterTypeAdapter -> " + name ); } @@ -72,7 +70,7 @@ public Class getJdbcType() { return jdbcType; } - public AttributeConverter getAttributeConverter() { + public JpaAttributeConverter getAttributeConverter() { return attributeConverter; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java index 6c2a8ef31262..4881e09d73fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java @@ -8,17 +8,22 @@ import java.io.Serializable; import java.util.Comparator; +import java.util.Objects; import org.hibernate.HibernateException; import org.hibernate.internal.util.compare.ComparableComparator; -import org.hibernate.internal.util.compare.EqualsHelper; /** * Abstract adapter for Java type descriptors. * + * @apiNote This abstract descriptor implements BasicJavaDescriptor + * because we currently only categorize "basic" JavaTypeDescriptors, + * as in the {@link javax.persistence.metamodel.Type.PersistenceType#BASIC} + * sense + * * @author Steve Ebersole */ -public abstract class AbstractTypeDescriptor implements JavaTypeDescriptor, Serializable { +public abstract class AbstractTypeDescriptor implements BasicJavaDescriptor, Serializable { private final Class type; private final MutabilityPlan mutabilityPlan; private final Comparator comparator; @@ -55,9 +60,17 @@ public MutabilityPlan getMutabilityPlan() { return mutabilityPlan; } + public Class getJavaType() { + return type; + } + + /** + * @deprecated Use {@link #getJavaType()} instead + */ @Override + @Deprecated public Class getJavaTypeClass() { - return type; + return getJavaType(); } @Override @@ -67,7 +80,7 @@ public int extractHashCode(T value) { @Override public boolean areEqual(T one, T another) { - return EqualsHelper.equals( one, another ); + return Objects.equals( one, another ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BasicJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BasicJavaDescriptor.java new file mode 100644 index 000000000000..7bca9ce77388 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BasicJavaDescriptor.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.type.descriptor.java; + +import org.hibernate.type.descriptor.spi.JdbcRecommendedSqlTypeMappingContext; +import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +/** + * @apiNote Currently this is the only high-level categorization of + * JavaTypeDescriptor, but 6.0 will have specific JavaTypeDescriptor + * categorizations for managed-type, mapped-superclass, identifiable-type, entity, embeddable, + * collections. + * + * @author Steve Ebersole + */ +public interface BasicJavaDescriptor extends JavaTypeDescriptor { + /** + * Obtain the "recommended" SQL type descriptor for this Java type. The recommended + * aspect comes from the JDBC spec (mostly). + * + * @param context Contextual information + * + * @return The recommended SQL type descriptor + */ + default SqlTypeDescriptor getJdbcRecommendedSqlType(JdbcRecommendedSqlTypeMappingContext context) { + // match legacy behavior + return context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( + JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( getJavaType() ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateTypeDescriptor.java index d0e4ea671821..be9e7dfe1a2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateTypeDescriptor.java @@ -11,7 +11,6 @@ import java.util.Date; import java.util.GregorianCalendar; -import org.hibernate.cfg.Environment; import org.hibernate.internal.util.compare.CalendarComparator; import org.hibernate.type.descriptor.WrapperOptions; @@ -101,16 +100,7 @@ public Calendar wrap(X value, WrapperOptions options) { } Calendar cal = new GregorianCalendar(); - if ( Environment.jvmHasTimestampBug() ) { - final long milliseconds = ( (Date) value ).getTime(); - final long nanoseconds = java.sql.Timestamp.class.isInstance( value ) - ? ( (java.sql.Timestamp) value ).getNanos() - : 0; - cal.setTime( new Date( milliseconds + nanoseconds / 1000000 ) ); - } - else { - cal.setTime( (Date) value ); - } + cal.setTime( (Date) value ); return cal; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeTypeDescriptor.java index 6c0299a4a79f..82cf0690b9ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeTypeDescriptor.java @@ -101,16 +101,7 @@ public Calendar wrap(X value, WrapperOptions options) { } Calendar cal = new GregorianCalendar(); - if ( Environment.jvmHasTimestampBug() ) { - final long milliseconds = ( (Date) value ).getTime(); - final long nanoseconds = java.sql.Timestamp.class.isInstance( value ) - ? ( (java.sql.Timestamp) value ).getNanos() - : 0; - cal.setTime( new Date( milliseconds + nanoseconds / 1000000 ) ); - } - else { - cal.setTime( (Date) value ); - } + cal.setTime( (Date) value ); return cal; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTypeDescriptor.java index c71baedb3947..1731abff70e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTypeDescriptor.java @@ -117,16 +117,7 @@ public Calendar wrap(X value, WrapperOptions options) { } Calendar cal = new GregorianCalendar(); - if ( Environment.jvmHasTimestampBug() ) { - final long milliseconds = ( (java.util.Date) value ).getTime(); - final long nanoseconds = java.sql.Timestamp.class.isInstance( value ) - ? ( (java.sql.Timestamp) value ).getNanos() - : 0; - cal.setTime( new Date( milliseconds + nanoseconds / 1000000 ) ); - } - else { - cal.setTime( (java.util.Date) value ); - } + cal.setTime( (java.util.Date) value ); return cal; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java index 5f4ddc103420..1b1039968fff 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java @@ -15,10 +15,9 @@ */ public class EnumJavaTypeDescriptor extends AbstractTypeDescriptor { @SuppressWarnings("unchecked") - protected EnumJavaTypeDescriptor(Class type) { + public EnumJavaTypeDescriptor(Class type) { super( type, ImmutableMutabilityPlan.INSTANCE ); - - JavaTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + //JavaTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); } @Override @@ -27,19 +26,68 @@ public String toString(T value) { } @Override + @SuppressWarnings("unchecked") public T fromString(String string) { - return string == null ? null : (T) Enum.valueOf( getJavaTypeClass(), string ); + return string == null ? null : (T) Enum.valueOf( getJavaType(), string ); } @Override @SuppressWarnings("unchecked") public X unwrap(T value, Class type, WrapperOptions options) { + if ( String.class.equals( type ) ) { + return (X) toName( value ); + } + else if ( Integer.class.isInstance( type ) ) { + return (X) toOrdinal( value ); + } + return (X) value; } @Override @SuppressWarnings("unchecked") public T wrap(X value, WrapperOptions options) { + if ( value == null ) { + return null; + } + else if ( String.class.isInstance( value ) ) { + return fromName( (String) value ); + } + else if ( Integer.class.isInstance( value ) ) { + return fromOrdinal( (Integer) value ); + } + return (T) value; } + + + public Integer toOrdinal(E domainForm) { + if ( domainForm == null ) { + return null; + } + return domainForm.ordinal(); + } + + @SuppressWarnings("unchecked") + public E fromOrdinal(Integer relationalForm) { + if ( relationalForm == null ) { + return null; + } + return (E) getJavaType().getEnumConstants()[ relationalForm ]; + } + + @SuppressWarnings("unchecked") + public T fromName(String relationalForm) { + if ( relationalForm == null ) { + return null; + } + return (T) Enum.valueOf( getJavaType(), relationalForm.trim() ); + } + + public String toName(T domainForm) { + if ( domainForm == null ) { + return null; + } + return domainForm.name(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java index bdd0659b4a39..9bc9a52e0a03 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java @@ -61,7 +61,23 @@ public X unwrap(Instant instant, Class type, WrapperOptions options) { } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( instant ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + ZonedDateTime zonedDateTime = instant.atZone( ZoneId.systemDefault() ); + if ( zonedDateTime.getYear() < 1905 ) { + return (X) Timestamp.valueOf( zonedDateTime.toLocalDateTime() ); + } + else { + return (X) Timestamp.from( instant ); + } } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -95,7 +111,22 @@ public Instant wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return ts.toInstant(); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( ts.getYear() < 5 ) { // Timestamp year 0 is 1900 + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ).toInstant(); + } + else { + return ts.toInstant(); + } } if ( Long.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptor.java index 50512598cd86..b0c407c0d179 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptor.java @@ -8,7 +8,9 @@ import java.io.Serializable; import java.util.Comparator; +import java.util.Objects; +import org.hibernate.internal.util.compare.ComparableComparator; import org.hibernate.type.descriptor.WrapperOptions; /** @@ -21,22 +23,34 @@ public interface JavaTypeDescriptor extends Serializable { * Retrieve the Java type handled here. * * @return The Java type. + * + * @deprecated Use {@link #getJavaType()} instead + */ + @Deprecated + Class getJavaTypeClass(); + + /** + * Get the Java type described */ - public Class getJavaTypeClass(); + default Class getJavaType() { + // default on this side since #getJavaTypeClass is the currently implemented method + return getJavaTypeClass(); + } /** * Retrieve the mutability plan for this Java type. - * - * @return The mutability plan */ - public MutabilityPlan getMutabilityPlan(); + @SuppressWarnings("unchecked") + default MutabilityPlan getMutabilityPlan() { + return ImmutableMutabilityPlan.INSTANCE; + } /** * Retrieve the natural comparator for this type. - * - * @return The natural comparator. */ - public Comparator getComparator(); + default Comparator getComparator() { + return Comparable.class.isAssignableFrom( Comparable.class ) ? ComparableComparator.INSTANCE : null; + } /** * Extract a proper hash code for this value. @@ -45,7 +59,12 @@ public interface JavaTypeDescriptor extends Serializable { * * @return The extracted hash code. */ - public int extractHashCode(T value); + default int extractHashCode(T value) { + if ( value == null ) { + throw new IllegalArgumentException( "Value to extract hashCode from cannot be null" ); + } + return value.hashCode(); + } /** * Determine if two instances are equal @@ -55,7 +74,9 @@ public interface JavaTypeDescriptor extends Serializable { * * @return True if the two are considered equal; false otherwise. */ - public boolean areEqual(T one, T another); + default boolean areEqual(T one, T another) { + return Objects.deepEquals( one, another ); + } /** * Extract a loggable representation of the value. @@ -64,11 +85,15 @@ public interface JavaTypeDescriptor extends Serializable { * * @return The loggable representation */ - public String extractLoggableRepresentation(T value); + default String extractLoggableRepresentation(T value) { + return toString( value ); + } - public String toString(T value); + default String toString(T value) { + return value == null ? "null" : value.toString(); + } - public T fromString(String string); + T fromString(String string); /** * Unwrap an instance of our handled Java type into the requested type. @@ -86,7 +111,7 @@ public interface JavaTypeDescriptor extends Serializable { * * @return The unwrapped value. */ - public X unwrap(T value, Class type, WrapperOptions options); + X unwrap(T value, Class type, WrapperOptions options); /** * Wrap a value as our handled Java type. @@ -99,5 +124,5 @@ public interface JavaTypeDescriptor extends Serializable { * * @return The wrapped value. */ - public T wrap(X value, WrapperOptions options); + T wrap(X value, WrapperOptions options); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java index 8767a3abddac..bc421c9c19e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java @@ -7,29 +7,37 @@ package org.hibernate.type.descriptor.java; import java.io.Serializable; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.persistence.AttributeConverter; + import org.hibernate.HibernateException; import org.hibernate.annotations.Immutable; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.type.descriptor.WrapperOptions; - -import org.jboss.logging.Logger; +import org.hibernate.type.descriptor.java.spi.RegistryHelper; +import org.hibernate.type.spi.TypeConfiguration; /** * Basically a map from {@link Class} -> {@link JavaTypeDescriptor} * * @author Steve Ebersole + * + * @deprecated Use (5.3) Use {@link org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry} instead */ -public class JavaTypeDescriptorRegistry { +@Deprecated +public class JavaTypeDescriptorRegistry implements Serializable { private static final CoreMessageLogger log = CoreLogging.messageLogger( JavaTypeDescriptorRegistry.class ); + /** + * @deprecated (5.3) Use {@link TypeConfiguration#getJavaTypeDescriptorRegistry()} instead. + */ + @Deprecated public static final JavaTypeDescriptorRegistry INSTANCE = new JavaTypeDescriptorRegistry(); - private ConcurrentHashMap descriptorsByClass = new ConcurrentHashMap<>(); + private ConcurrentHashMap descriptorsByClass = new ConcurrentHashMap<>(); public JavaTypeDescriptorRegistry() { addDescriptorInternal( ByteTypeDescriptor.INSTANCE ); @@ -78,64 +86,56 @@ public JavaTypeDescriptorRegistry() { } private JavaTypeDescriptor addDescriptorInternal(JavaTypeDescriptor descriptor) { - return descriptorsByClass.put( descriptor.getJavaTypeClass(), descriptor ); + return descriptorsByClass.put( descriptor.getJavaType(), descriptor ); } /** * Adds the given descriptor to this registry * * @param descriptor The descriptor to add. + * + * @deprecated (5.3) Use {@link org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry#addDescriptor(JavaTypeDescriptor)} instead. */ + @Deprecated public void addDescriptor(JavaTypeDescriptor descriptor) { JavaTypeDescriptor old = addDescriptorInternal( descriptor ); if ( old != null ) { log.debugf( "JavaTypeDescriptorRegistry entry replaced : %s -> %s (was %s)", - descriptor.getJavaTypeClass(), + descriptor.getJavaType(), descriptor, old ); } } + /** + * @deprecated (5.3) Use {@link org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry#getDescriptor(Class)} instead. + */ + @Deprecated @SuppressWarnings("unchecked") - public JavaTypeDescriptor getDescriptor(Class cls) { - if ( cls == null ) { - throw new IllegalArgumentException( "Class passed to locate Java type descriptor cannot be null" ); - } - - JavaTypeDescriptor descriptor = descriptorsByClass.get( cls ); - if ( descriptor != null ) { - return descriptor; - } - - if ( cls.isEnum() ) { - descriptor = new EnumJavaTypeDescriptor( cls ); - descriptorsByClass.put( cls, descriptor ); - return descriptor; - } - - // find the first "assignable" match - for ( Map.Entry entry : descriptorsByClass.entrySet() ) { - if ( entry.getKey().isAssignableFrom( cls ) ) { - log.debugf( "Using cached JavaTypeDescriptor instance for Java class [%s]", cls.getName() ); - return entry.getValue(); - } - } - - if ( Serializable.class.isAssignableFrom( cls ) ) { - return new SerializableTypeDescriptor( cls ); - } - - log.debugf( - "Could not find matching JavaTypeDescriptor for requested Java class [%s]; using fallback. " + - "This means Hibernate does not know how to perform certain basic operations in relation to this Java type." + - "", - cls.getName() + public JavaTypeDescriptor getDescriptor(Class cls) { + return RegistryHelper.INSTANCE.resolveDescriptor( + descriptorsByClass, + cls, + () -> { + if ( Serializable.class.isAssignableFrom( cls ) ) { + return new SerializableTypeDescriptor( cls ); + } + + if ( !AttributeConverter.class.isAssignableFrom( cls ) ) { + log.debugf( + "Could not find matching JavaTypeDescriptor for requested Java class [%s]; using fallback. " + + "This means Hibernate does not know how to perform certain basic operations in relation to this Java type." + + "", + cls.getName() + ); + checkEqualsAndHashCode( cls ); + } + + return new FallbackJavaTypeDescriptor<>( cls ); + } ); - checkEqualsAndHashCode( cls ); - - return new FallbackJavaTypeDescriptor( cls ); } @SuppressWarnings("unchecked") @@ -148,7 +148,7 @@ private void checkEqualsAndHashCode(Class javaType) { public static class FallbackJavaTypeDescriptor extends AbstractTypeDescriptor { protected FallbackJavaTypeDescriptor(final Class type) { - super(type, createMutabilityPlan(type)); + super( type, createMutabilityPlan( type ) ); } @SuppressWarnings("unchecked") @@ -176,7 +176,7 @@ public String toString(T value) { @Override public T fromString(String string) { throw new HibernateException( - "Not known how to convert String to given type [" + getJavaTypeClass().getName() + "]" + "Not known how to convert String to given type [" + getJavaType().getName() + "]" ); } @@ -192,5 +192,4 @@ public T wrap(X value, WrapperOptions options) { return (T) value; } } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java index a266fbace1cd..bd3ebe3866fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java @@ -127,7 +127,7 @@ public Date wrap(X value, WrapperOptions options) { if ( value == null ) { return null; } - if ( Date.class.isInstance( value ) ) { + if ( java.sql.Date.class.isInstance( value ) ) { return (Date) value; } @@ -139,8 +139,8 @@ public Date wrap(X value, WrapperOptions options) { return new java.sql.Date( ( (Calendar) value ).getTimeInMillis() ); } - if ( java.util.Date.class.isInstance( value ) ) { - return new java.sql.Date( ( (java.util.Date) value ).getTime() ); + if ( Date.class.isInstance( value ) ) { + return new java.sql.Date( ( (Date) value ).getTime() ); } throw unknownWrap( value.getClass() ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java index fcd583484586..2d52984bc949 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java @@ -143,7 +143,7 @@ public Date wrap(X value, WrapperOptions options) { } if ( Date.class.isInstance( value ) ) { - return (Date) value; + return new Time( ( (Date) value ).getTime() ); } throw unknownWrap( value.getClass() ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java index 18e96f29ab58..1f2fa8e809e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java @@ -152,7 +152,7 @@ public Date wrap(X value, WrapperOptions options) { } if ( Date.class.isInstance( value ) ) { - return (Date) value; + return new Timestamp( ( (Date) value ).getTime() ); } throw unknownWrap( value.getClass() ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java index 61f9c3458dc1..44018d2a92f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java @@ -63,6 +63,13 @@ public X unwrap(LocalDate value, Class type, WrapperOptions options) { final LocalDateTime localDateTime = value.atStartOfDay(); if ( Timestamp.class.isAssignableFrom( type ) ) { + /* + * Workaround for HHH-13266 (JDK-8061577). + * We could have done Timestamp.from( localDateTime.atZone( ZoneId.systemDefault() ).toInstant() ), + * but on top of being more complex than the line below, it won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ return (X) Timestamp.valueOf( localDateTime ); } @@ -97,7 +104,14 @@ public LocalDate wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalDate(); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalDate(), + * but on top of being more complex than the line below, it won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().toLocalDate(); } if ( Long.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java index 1760393c2532..aa3c760fef29 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java @@ -55,8 +55,14 @@ public X unwrap(LocalDateTime value, Class type, WrapperOptions options) } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant(); - return (X) java.sql.Timestamp.from( instant ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do Timestamp.from( value.atZone( ZoneId.systemDefault() ).toInstant() ), + * but on top of being more complex than the line below, it won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return (X) Timestamp.valueOf( value ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -98,7 +104,14 @@ public LocalDateTime wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ), + * but on top of being more complex than the line below, it won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime(); } if ( Long.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java index df3698429563..c9f624f1050e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java @@ -59,7 +59,24 @@ public X unwrap(OffsetDateTime offsetDateTime, Class type, WrapperOptions } if ( Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( offsetDateTime.toInstant() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( offsetDateTime.getYear() < 1905 ) { + return (X) Timestamp.valueOf( + offsetDateTime.atZoneSameInstant( ZoneId.systemDefault() ).toLocalDateTime() + ); + } + else { + return (X) Timestamp.from( offsetDateTime.toInstant() ); + } } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -93,7 +110,22 @@ public OffsetDateTime wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return OffsetDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( ts.getYear() < 5 ) { // Timestamp year 0 is 1900 + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ).toOffsetDateTime(); + } + else { + return OffsetDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + } } if ( Date.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java index 2e3947f57e8a..65f0099a0c4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java @@ -12,7 +12,7 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Date; @@ -65,6 +65,12 @@ public X unwrap(OffsetTime offsetTime, Class type, WrapperOptions options final ZonedDateTime zonedDateTime = offsetTime.atDate( LocalDate.of( 1970, 1, 1 ) ).toZonedDateTime(); if ( Timestamp.class.isAssignableFrom( type ) ) { + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use Timestamp.from( offsetDateTime.toInstant() ), but this won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ return (X) Timestamp.valueOf( zonedDateTime.toLocalDateTime() ); } @@ -95,22 +101,44 @@ public OffsetTime wrap(X value, WrapperOptions options) { return (OffsetTime) value; } + /* + * Also, in order to fix HHH-13357, and to be consistent with the conversion to Time (see above), + * we set the offset to the current offset of the JVM (OffsetDateTime.now().getOffset()). + * This is different from setting the *zone* to the current *zone* of the JVM (ZoneId.systemDefault()), + * since a zone has a varying offset over time, + * thus the zone might have a different offset for the given timezone than it has for the current date/time. + * For example, if the timestamp represents 1970-01-01TXX:YY, + * and the JVM is set to use Europe/Paris as a timezone, and the current time is 2019-04-16-08:53, + * then applying the JVM timezone to the timestamp would result in the offset +01:00, + * but applying the JVM offset would result in the offset +02:00, since DST is in effect at 2019-04-16-08:53. + * + * Of course none of this would be a problem if we just stored the offset in the database, + * but I guess there are historical reasons that explain why we don't. + */ + ZoneOffset offset = OffsetDateTime.now().getOffset(); + if ( Time.class.isInstance( value ) ) { - return ( (Time) value ).toLocalTime().atOffset( OffsetDateTime.now().getOffset() ); + return ( (Time) value ).toLocalTime().atOffset( offset ); } if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return OffsetTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use OffsetDateTime.ofInstant( ts.toInstant(), ... ), but this won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().toLocalTime().atOffset( offset ); } if ( Date.class.isInstance( value ) ) { final Date date = (Date) value; - return OffsetTime.ofInstant( date.toInstant(), ZoneId.systemDefault() ); + return OffsetTime.ofInstant( date.toInstant(), offset ); } if ( Long.class.isInstance( value ) ) { - return OffsetTime.ofInstant( Instant.ofEpochMilli( (Long) value ), ZoneId.systemDefault() ); + return OffsetTime.ofInstant( Instant.ofEpochMilli( (Long) value ), offset ); } if ( Calendar.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java index 08a914703e72..8e0ecae98b62 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java @@ -11,6 +11,7 @@ import java.io.Serializable; import java.sql.Blob; import java.sql.SQLException; +import java.util.Arrays; import org.hibernate.HibernateException; import org.hibernate.annotations.Immutable; @@ -29,11 +30,9 @@ public class SerializableTypeDescriptor extends Abstract // unfortunately the param types cannot be the same so use something other than 'T' here to make that obvious public static class SerializableMutabilityPlan extends MutableMutabilityPlan { + public static final SerializableMutabilityPlan INSTANCE = new SerializableMutabilityPlan<>(); - public static final SerializableMutabilityPlan INSTANCE - = new SerializableMutabilityPlan( ); - - public SerializableMutabilityPlan() { + private SerializableMutabilityPlan() { } @Override @@ -73,7 +72,7 @@ public boolean areEqual(T one, T another) { return false; } return one.equals( another ) - || PrimitiveByteArrayTypeDescriptor.INSTANCE.areEqual( toBytes( one ), toBytes( another ) ); + || Arrays.equals( toBytes( one ), toBytes( another ) ); } @Override @@ -124,7 +123,7 @@ else if ( Blob.class.isInstance( value ) ) { throw new HibernateException( e ); } } - else if ( getJavaTypeClass().isInstance( value ) ) { + else if ( getJavaType().isInstance( value ) ) { return (T) value; } throw unknownWrap( value.getClass() ); @@ -136,6 +135,6 @@ protected byte[] toBytes(T value) { @SuppressWarnings({ "unchecked" }) protected T fromBytes(byte[] bytes) { - return (T) SerializationHelper.deserialize( bytes, getJavaTypeClass().getClassLoader() ); + return (T) SerializationHelper.deserialize( bytes, getJavaType().getClassLoader() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringTypeDescriptor.java index a72100e6b63e..0ccaab15105c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringTypeDescriptor.java @@ -48,12 +48,15 @@ public X unwrap(String value, Class type, WrapperOptions options) { if ( CharacterStream.class.isAssignableFrom( type ) ) { return (X) new CharacterStreamImpl( value ); } - if ( Clob.class.isAssignableFrom( type ) ) { - return (X) options.getLobCreator().createClob( value ); - } + // Since NClob extends Clob, we need to check if type is an NClob + // before checking if type is a Clob. That will ensure that + // the correct type is returned. if ( DataHelper.isNClob( type ) ) { return (X) options.getLobCreator().createNClob( value ); } + if ( Clob.class.isAssignableFrom( type ) ) { + return (X) options.getLobCreator().createClob( value ); + } throw unknownUnwrap( type ); } 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 f1153855ab20..694a5012b76c 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 @@ -99,17 +99,14 @@ public static class ToBytesTransformer implements ValueTransformer { public byte[] transform(UUID uuid) { byte[] bytes = new byte[16]; - System.arraycopy( BytesHelper.fromLong( uuid.getMostSignificantBits() ), 0, bytes, 0, 8 ); - System.arraycopy( BytesHelper.fromLong( uuid.getLeastSignificantBits() ), 0, bytes, 8, 8 ); + BytesHelper.fromLong( uuid.getMostSignificantBits(), bytes, 0); + BytesHelper.fromLong( uuid.getLeastSignificantBits(), bytes, 8 ); return bytes; } public UUID parse(Object value) { - byte[] msb = new byte[8]; - byte[] lsb = new byte[8]; - System.arraycopy( value, 0, msb, 0, 8 ); - System.arraycopy( value, 8, lsb, 0, 8 ); - return new UUID( BytesHelper.asLong( msb ), BytesHelper.asLong( lsb ) ); + byte[] bytea = (byte[]) value; + return new UUID( BytesHelper.asLong( bytea, 0 ), BytesHelper.asLong( bytea, 8 ) ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java index 26cdd1e410b1..a10bb47a8ecb 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java @@ -59,7 +59,24 @@ public X unwrap(ZonedDateTime zonedDateTime, Class type, WrapperOptions o } if ( Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( zonedDateTime.toInstant() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( zonedDateTime.getYear() < 1905 ) { + return (X) Timestamp.valueOf( + zonedDateTime.withZoneSameInstant( ZoneId.systemDefault() ).toLocalDateTime() + ); + } + else { + return (X) Timestamp.from( zonedDateTime.toInstant() ); + } } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -93,7 +110,22 @@ public ZonedDateTime wrap(X value, WrapperOptions options) { if ( java.sql.Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return ZonedDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( ts.getYear() < 5 ) { // Timestamp year 0 is 1900 + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ); + } + else { + return ts.toInstant().atZone( ZoneId.systemDefault() ); + } } if ( java.util.Date.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeDescriptorRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeDescriptorRegistry.java new file mode 100644 index 000000000000..a71b4529a1f0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeDescriptorRegistry.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 . + */ +package org.hibernate.type.descriptor.java.spi; + +import java.io.Serializable; +import java.util.concurrent.ConcurrentHashMap; + +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; + +import org.jboss.logging.Logger; + +/** + * Basically a map from {@link Class} -> {@link JavaTypeDescriptor} + * + * @author Steve Ebersole + * @author Andrea Boriero + * + * @since 5.3 + */ +public class JavaTypeDescriptorRegistry implements Serializable { + private static final Logger log = Logger.getLogger( JavaTypeDescriptorRegistry.class ); + + + private ConcurrentHashMap descriptorsByClass = new ConcurrentHashMap<>(); + + @SuppressWarnings("unused") + public JavaTypeDescriptorRegistry(TypeConfiguration typeConfiguration) { + } + + public JavaTypeDescriptor getDescriptor(Class javaType) { + return RegistryHelper.INSTANCE.resolveDescriptor( + descriptorsByClass, + javaType, + () -> { + log.debugf( + "Could not find matching scoped JavaTypeDescriptor for requested Java class [%s]; " + + "falling back to static registry", + javaType.getName() + ); + + return org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry.INSTANCE.getDescriptor( javaType ); + } + ); + } + + public void addDescriptor(JavaTypeDescriptor descriptor) { + JavaTypeDescriptor old = descriptorsByClass.put( descriptor.getJavaType(), descriptor ); + if ( old != null ) { + log.debugf( + "JavaTypeDescriptorRegistry entry replaced : %s -> %s (was %s)", + descriptor.getJavaType(), + descriptor, + old + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/RegistryHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/RegistryHelper.java new file mode 100644 index 000000000000..da449213eb25 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/RegistryHelper.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.type.descriptor.java.spi; + +import java.io.Serializable; +import java.util.Map; +import java.util.function.Supplier; + +import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.java.SerializableTypeDescriptor; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public class RegistryHelper { + private static final Logger log = Logger.getLogger( RegistryHelper.class ); + + /** + * Singleton access + */ + public static final RegistryHelper INSTANCE = new RegistryHelper(); + + private RegistryHelper() { + } + + @SuppressWarnings("unchecked") + public JavaTypeDescriptor resolveDescriptor( + Map descriptorsByClass, + Class cls, + Supplier> defaultValueSupplier) { + if ( cls == null ) { + throw new IllegalArgumentException( "Class passed to locate JavaTypeDescriptor cannot be null" ); + } + + JavaTypeDescriptor descriptor = descriptorsByClass.get( cls ); + if ( descriptor != null ) { + return descriptor; + } + + if ( cls.isEnum() ) { + descriptor = new EnumJavaTypeDescriptor( cls ); + descriptorsByClass.put( cls, descriptor ); + return descriptor; + } + + // find the first "assignable" match + for ( Map.Entry entry : descriptorsByClass.entrySet() ) { + if ( entry.getKey().isAssignableFrom( cls ) ) { + log.debugf( "Using cached JavaTypeDescriptor instance for Java class [%s]", cls.getName() ); + return entry.getValue(); + } + } + + return defaultValueSupplier.get(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/spi/JdbcRecommendedSqlTypeMappingContext.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/spi/JdbcRecommendedSqlTypeMappingContext.java new file mode 100644 index 000000000000..79803b64f6db --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/spi/JdbcRecommendedSqlTypeMappingContext.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.type.descriptor.spi; + +import java.sql.Types; +import javax.persistence.EnumType; + +import org.hibernate.type.spi.TypeConfiguration; + +/** + * More-or-less a parameter-object intended for use in determining the SQL/JDBC type recommended + * by the JDBC spec (explicitly or implicitly) for a given Java type. + * + * @see org.hibernate.type.descriptor.java.BasicJavaDescriptor#getJdbcRecommendedSqlType + * + * @author Steve Ebersole + */ +public interface JdbcRecommendedSqlTypeMappingContext { + /** + * Was nationalized character datatype requested for the given Java type? + * + * @return {@code true} if nationalized character datatype should be used; {@code false} otherwise. + */ + default boolean isNationalized() { + return false; + } + + /** + * Was LOB datatype requested for the given Java type? + * + * @return {@code true} if LOB datatype should be used; {@code false} otherwise. + */ + default boolean isLob() { + return false; + } + + /** + * For enum mappings, what style of storage was requested (name vs. ordinal)? + * + * @return The enum type. + */ + default EnumType getEnumeratedType() { + return EnumType.ORDINAL; + } + + /** + * When mapping a boolean type to the database what is the preferred SQL type code to use? + *

    + * Specifically names the key into the + * {@link org.hibernate.type.descriptor.sql.spi.SqlTypeDescriptorRegistry}. + */ + default int getPreferredSqlTypeCodeForBoolean() { + return Types.BOOLEAN; + } + + /** + * Provides access to the TypeConfiguration for access to various type-system registries. + */ + TypeConfiguration getTypeConfiguration(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java index 1bb11378f25a..febd37218e54 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java @@ -7,15 +7,18 @@ package org.hibernate.type.descriptor.sql; import java.math.BigDecimal; +import java.math.BigInteger; import java.sql.Blob; import java.sql.Clob; +import java.sql.NClob; import java.sql.Ref; +import java.sql.RowId; +import java.sql.SQLXML; import java.sql.Struct; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.util.Calendar; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.hibernate.mapping.Array; @@ -23,15 +26,15 @@ import org.jboss.logging.Logger; /** - * Presents recommended {@literal JDCB typecode <-> Java Class} mappings. Currently the mappings contained here come - * from the recommendations defined by the JDBC spec itself, as outlined at - * . - *

    + * Maintains the JDBC recommended mappings for JDBC type-code to/from Java Class + * as defined in _Appendix B : Data Type Conversion Tables_ of the _JDBC 4.0 Specification_ + * * Eventually, the plan is to have {@link org.hibernate.dialect.Dialect} and * {@link java.sql.DatabaseMetaData#getTypeInfo()} contribute this information. * * @author Steve Ebersole */ +@SuppressWarnings("WeakerAccess") public class JdbcTypeJavaClassMappings { private static final Logger log = Logger.getLogger( JdbcTypeJavaClassMappings.class ); @@ -41,10 +44,17 @@ public class JdbcTypeJavaClassMappings { private final ConcurrentHashMap jdbcTypeCodeToJavaClassMap; private JdbcTypeJavaClassMappings() { - javaClassToJdbcTypeCodeMap = buildJdbcJavaClassMappings(); - jdbcTypeCodeToJavaClassMap = transpose( javaClassToJdbcTypeCodeMap ); + javaClassToJdbcTypeCodeMap = buildJavaClassToJdbcTypeCodeMappings(); + jdbcTypeCodeToJavaClassMap = buildJdbcTypeCodeToJavaClassMappings(); } + /** + * For the given Java type, determine the JDBC recommended JDBC type. + * + * This includes the mappings defined in TABLE B-2 - Java Types Mapped to JDBC Types + * as well as some additional "common sense" mappings for things like BigDecimal, BigInteger, + * etc. + */ public int determineJdbcTypeCodeForJavaClass(Class cls) { Integer typeCode = javaClassToJdbcTypeCodeMap.get( cls ); if ( typeCode != null ) { @@ -58,6 +68,10 @@ public int determineJdbcTypeCodeForJavaClass(Class cls) { return specialCode; } + /** + * For the given JDBC type, determine the JDBC recommended Java type. These mappings + * are defined by TABLE B-1 - JDBC Types Mapped to Java Types + */ public Class determineJavaClassForJdbcTypeCode(Integer typeCode) { Class cls = jdbcTypeCodeToJavaClassMap.get( typeCode ); if ( cls != null ) { @@ -71,6 +85,9 @@ public Class determineJavaClassForJdbcTypeCode(Integer typeCode) { return Object.class; } + /** + * @see #determineJavaClassForJdbcTypeCode(Integer) + */ public Class determineJavaClassForJdbcTypeCode(int typeCode) { return determineJavaClassForJdbcTypeCode( Integer.valueOf( typeCode ) ); } @@ -78,46 +95,80 @@ public Class determineJavaClassForJdbcTypeCode(int typeCode) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - private static ConcurrentHashMap buildJdbcJavaClassMappings() { - ConcurrentHashMap jdbcJavaClassMappings = new ConcurrentHashMap(); + private static ConcurrentHashMap buildJavaClassToJdbcTypeCodeMappings() { + final ConcurrentHashMap workMap = new ConcurrentHashMap<>(); // these mappings are the ones outlined specifically in the spec - jdbcJavaClassMappings.put( String.class, Types.VARCHAR ); - jdbcJavaClassMappings.put( BigDecimal.class, Types.NUMERIC ); - jdbcJavaClassMappings.put( Boolean.class, Types.BIT ); - jdbcJavaClassMappings.put( Integer.class, Types.INTEGER ); - jdbcJavaClassMappings.put( Long.class, Types.BIGINT ); - jdbcJavaClassMappings.put( Float.class, Types.REAL ); - jdbcJavaClassMappings.put( Double.class, Types.DOUBLE ); - jdbcJavaClassMappings.put( byte[].class, Types.LONGVARBINARY ); - jdbcJavaClassMappings.put( java.sql.Date.class, Types.DATE ); - jdbcJavaClassMappings.put( Time.class, Types.TIME ); - jdbcJavaClassMappings.put( Timestamp.class, Types.TIMESTAMP ); - jdbcJavaClassMappings.put( Blob.class, Types.BLOB ); - jdbcJavaClassMappings.put( Clob.class, Types.CLOB ); - jdbcJavaClassMappings.put( Array.class, Types.ARRAY ); - jdbcJavaClassMappings.put( Struct.class, Types.STRUCT ); - jdbcJavaClassMappings.put( Ref.class, Types.REF ); - jdbcJavaClassMappings.put( Class.class, Types.JAVA_OBJECT ); + workMap.put( String.class, Types.VARCHAR ); + workMap.put( BigDecimal.class, Types.NUMERIC ); + workMap.put( BigInteger.class, Types.NUMERIC ); + workMap.put( Boolean.class, Types.BIT ); + workMap.put( Short.class, Types.SMALLINT ); + workMap.put( Integer.class, Types.INTEGER ); + workMap.put( Long.class, Types.BIGINT ); + workMap.put( Float.class, Types.REAL ); + workMap.put( Double.class, Types.DOUBLE ); + workMap.put( byte[].class, Types.LONGVARBINARY ); + workMap.put( java.sql.Date.class, Types.DATE ); + workMap.put( Time.class, Types.TIME ); + workMap.put( Timestamp.class, Types.TIMESTAMP ); + workMap.put( Blob.class, Types.BLOB ); + workMap.put( Clob.class, Types.CLOB ); + workMap.put( Array.class, Types.ARRAY ); + workMap.put( Struct.class, Types.STRUCT ); + workMap.put( Ref.class, Types.REF ); + workMap.put( Class.class, Types.JAVA_OBJECT ); + workMap.put( RowId.class, Types.ROWID ); + workMap.put( SQLXML.class, Types.SQLXML ); + // additional "common sense" registrations - jdbcJavaClassMappings.put( Character.class, Types.CHAR ); - jdbcJavaClassMappings.put( char[].class, Types.VARCHAR ); - jdbcJavaClassMappings.put( Character[].class, Types.VARCHAR ); - jdbcJavaClassMappings.put( Byte[].class, Types.LONGVARBINARY ); - jdbcJavaClassMappings.put( java.util.Date.class, Types.TIMESTAMP ); - jdbcJavaClassMappings.put( Calendar.class, Types.TIMESTAMP ); - - return jdbcJavaClassMappings; + workMap.put( Character.class, Types.CHAR ); + workMap.put( char[].class, Types.VARCHAR ); + workMap.put( Character[].class, Types.VARCHAR ); + workMap.put( Byte[].class, Types.LONGVARBINARY ); + workMap.put( java.util.Date.class, Types.TIMESTAMP ); + workMap.put( Calendar.class, Types.TIMESTAMP ); + + return workMap; } - private static ConcurrentHashMap transpose(ConcurrentHashMap javaClassToJdbcTypeCodeMap) { - final ConcurrentHashMap transposed = new ConcurrentHashMap(); - - for ( Map.Entry entry : javaClassToJdbcTypeCodeMap.entrySet() ) { - transposed.put( entry.getValue(), entry.getKey() ); - } - - return transposed; + private static ConcurrentHashMap buildJdbcTypeCodeToJavaClassMappings() { + final ConcurrentHashMap workMap = new ConcurrentHashMap<>(); + + workMap.put( Types.CHAR, String.class ); + workMap.put( Types.VARCHAR, String.class ); + workMap.put( Types.LONGVARCHAR, String.class ); + workMap.put( Types.NCHAR, String.class ); + workMap.put( Types.NVARCHAR, String.class ); + workMap.put( Types.LONGNVARCHAR, String.class ); + workMap.put( Types.NUMERIC, BigDecimal.class ); + workMap.put( Types.DECIMAL, BigDecimal.class ); + workMap.put( Types.BIT, Boolean.class ); + workMap.put( Types.BOOLEAN, Boolean.class ); + workMap.put( Types.TINYINT, Byte.class ); + workMap.put( Types.SMALLINT, Short.class ); + workMap.put( Types.INTEGER, Integer.class ); + workMap.put( Types.BIGINT, Long.class ); + workMap.put( Types.REAL, Float.class ); + workMap.put( Types.DOUBLE, Double.class ); + workMap.put( Types.FLOAT, Double.class ); + workMap.put( Types.BINARY, byte[].class ); + workMap.put( Types.VARBINARY, byte[].class ); + workMap.put( Types.LONGVARBINARY, byte[].class ); + workMap.put( Types.DATE, java.sql.Date.class ); + workMap.put( Types.TIME, Time.class ); + workMap.put( Types.TIMESTAMP, Timestamp.class ); + workMap.put( Types.BLOB, Blob.class ); + workMap.put( Types.CLOB, Clob.class ); + workMap.put( Types.NCLOB, NClob.class ); + workMap.put( Types.ARRAY, Array.class ); + workMap.put( Types.STRUCT, Struct.class ); + workMap.put( Types.REF, Ref.class ); + workMap.put( Types.JAVA_OBJECT, Class.class ); + workMap.put( Types.ROWID, RowId.class ); + workMap.put( Types.SQLXML, SQLXML.class ); + + return workMap; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java index ce5694cf3961..d68a7099bd80 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java @@ -10,62 +10,98 @@ import java.util.Locale; import java.util.Map; -import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; import org.hibernate.type.descriptor.JdbcTypeNameMapper; -import org.jboss.logging.Logger; - /** * @author Steve Ebersole + * @author Sanne Grinovero */ -public class LobTypeMappings { - private static final Logger log = Logger.getLogger( LobTypeMappings.class ); +public final class LobTypeMappings { /** * Singleton access + * @deprecated use the static method helpers instead. */ + @Deprecated public static final LobTypeMappings INSTANCE = new LobTypeMappings(); - private final Map lobCodeByNonLobCode; - private LobTypeMappings() { - this.lobCodeByNonLobCode = new BoundedConcurrentHashMap(); - - // BLOB mappings - this.lobCodeByNonLobCode.put( Types.BLOB, Types.BLOB ); - this.lobCodeByNonLobCode.put( Types.BINARY, Types.BLOB ); - this.lobCodeByNonLobCode.put( Types.VARBINARY, Types.BLOB ); - this.lobCodeByNonLobCode.put( Types.LONGVARBINARY, Types.BLOB ); - - // CLOB mappings - this.lobCodeByNonLobCode.put( Types.CLOB, Types.CLOB ); - this.lobCodeByNonLobCode.put( Types.CHAR, Types.CLOB ); - this.lobCodeByNonLobCode.put( Types.VARCHAR, Types.CLOB ); - this.lobCodeByNonLobCode.put( Types.LONGVARCHAR, Types.CLOB ); - - // NCLOB mappings - this.lobCodeByNonLobCode.put( Types.NCLOB, Types.NCLOB ); - this.lobCodeByNonLobCode.put( Types.NCHAR, Types.NCLOB ); - this.lobCodeByNonLobCode.put( Types.NVARCHAR, Types.NCLOB ); - this.lobCodeByNonLobCode.put( Types.LONGNVARCHAR, Types.NCLOB ); } - public boolean hasCorrespondingLobCode(int jdbcTypeCode) { - return lobCodeByNonLobCode.containsKey( jdbcTypeCode ); + /** + * + * @param jdbcTypeCode + * @return + * @deprecated use {@link #isMappedToKnownLobCode(int)} + */ + @Deprecated + public boolean hasCorrespondingLobCode(final int jdbcTypeCode) { + return isMappedToKnownLobCode( jdbcTypeCode ); } - public int getCorrespondingLobCode(int jdbcTypeCode) { - Integer lobTypeCode = lobCodeByNonLobCode.get( jdbcTypeCode ); - if ( lobTypeCode == null ) { - throw new IllegalArgumentException( - String.format( - Locale.ROOT, - "JDBC type-code [%s (%s)] not known to have a corresponding LOB equivalent", - jdbcTypeCode, - JdbcTypeNameMapper.getTypeName( jdbcTypeCode ) - ) - ); + /** + * + * @param jdbcTypeCode + * @return + * @deprecated use {@link #getLobCodeTypeMapping(int)} + */ + @Deprecated + public int getCorrespondingLobCode(final int jdbcTypeCode) { + return getLobCodeTypeMapping( jdbcTypeCode ); + } + + public static boolean isMappedToKnownLobCode(final int jdbcTypeCode) { + return + // BLOB mappings + jdbcTypeCode == Types.BLOB || + jdbcTypeCode == Types.BINARY || + jdbcTypeCode == Types.VARBINARY || + jdbcTypeCode == Types.LONGVARBINARY || + + // CLOB mappings + jdbcTypeCode == Types.CLOB || + jdbcTypeCode == Types.CHAR || + jdbcTypeCode == Types.VARCHAR || + jdbcTypeCode == Types.LONGVARCHAR || + + // NCLOB mappings + jdbcTypeCode == Types.NCLOB || + jdbcTypeCode == Types.NCHAR || + jdbcTypeCode == Types.NVARCHAR || + jdbcTypeCode == Types.LONGNVARCHAR; + } + + public static int getLobCodeTypeMapping(final int jdbcTypeCode) { + switch ( jdbcTypeCode ) { + + // BLOB mappings + case Types.BLOB : + case Types.BINARY : + case Types.VARBINARY : + case Types.LONGVARBINARY : return Types.BLOB; + + // CLOB mappings + case Types.CLOB : + case Types.CHAR : + case Types.VARCHAR : + case Types.LONGVARCHAR : return Types.CLOB; + + // NCLOB mappings + case Types.NCLOB : + case Types.NCHAR : + case Types.NVARCHAR : + case Types.LONGNVARCHAR : return Types.NCLOB; + + // Anything else: + default: + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "JDBC type-code [%s (%s)] not known to have a corresponding LOB equivalent", + jdbcTypeCode, + JdbcTypeNameMapper.getTypeName( jdbcTypeCode ) + ) ); } - return lobTypeCode; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java index eedea2e25763..9dd7c21ffa33 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java @@ -7,9 +7,6 @@ package org.hibernate.type.descriptor.sql; import java.sql.Types; -import java.util.Map; - -import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; import org.jboss.logging.Logger; @@ -20,35 +17,44 @@ * corresponding nationalized equivalent, so that's all we implement for now * * @author Steve Ebersole + * @author Sanne Grinovero */ -public class NationalizedTypeMappings { +public final class NationalizedTypeMappings { + private static final Logger log = Logger.getLogger( NationalizedTypeMappings.class ); /** * Singleton access + * @deprecated use the static methods instead */ + @Deprecated public static final NationalizedTypeMappings INSTANCE = new NationalizedTypeMappings(); - private final Map nationalizedCodeByNonNationalized; - - public NationalizedTypeMappings() { - this.nationalizedCodeByNonNationalized = new BoundedConcurrentHashMap(); - map( Types.CHAR, Types.NCHAR ); - map( Types.CLOB, Types.NCLOB ); - map( Types.LONGVARCHAR, Types.LONGNVARCHAR ); - map( Types.VARCHAR, Types.NVARCHAR ); + private NationalizedTypeMappings() { } - private void map(int nonNationalizedCode, int nationalizedCode) { - nationalizedCodeByNonNationalized.put( nonNationalizedCode, nationalizedCode ); + public static int toNationalizedTypeCode(final int jdbcCode) { + switch ( jdbcCode ) { + case Types.CHAR: return Types.NCHAR; + case Types.CLOB: return Types.NCLOB; + case Types.LONGVARCHAR: return Types.LONGNVARCHAR; + case Types.VARCHAR: return Types.NVARCHAR; + default: + if ( log.isDebugEnabled() ) { + log.debug( "Unable to locate nationalized jdbc-code equivalent for given jdbc code : " + jdbcCode ); + } + return jdbcCode; + } } + /** + * @deprecated use {@link #toNationalizedTypeCode(int)} + * @param jdbcCode + * @return + */ + @Deprecated public int getCorrespondingNationalizedCode(int jdbcCode) { - Integer nationalizedCode = nationalizedCodeByNonNationalized.get( jdbcCode ); - if ( nationalizedCode == null ) { - log.debug( "Unable to locate nationalized jdbc-code equivalent for given jdbc code : " + jdbcCode ); - return jdbcCode; - } - return nationalizedCode; + return toNationalizedTypeCode( jdbcCode ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java index 49d6404ce94e..3b5431f84c7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java @@ -10,7 +10,9 @@ import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for the SQL/JDBC side of a value mapping. @@ -26,7 +28,7 @@ public interface SqlTypeDescriptor extends Serializable { * * @return typeCode The JDBC type-code */ - public int getSqlType(); + int getSqlType(); /** * Is this descriptor available for remapping? @@ -36,7 +38,16 @@ public interface SqlTypeDescriptor extends Serializable { * @see org.hibernate.type.descriptor.WrapperOptions#remapSqlTypeDescriptor * @see org.hibernate.dialect.Dialect#remapSqlTypeDescriptor */ - public boolean canBeRemapped(); + boolean canBeRemapped(); + + @SuppressWarnings("unchecked") + default BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + // match legacy behavior + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( + JdbcTypeJavaClassMappings.INSTANCE.determineJavaClassForJdbcTypeCode( getSqlType() ) + ); + + } /** * Get the binder (setting JDBC in-going parameter values) capable of handling values of the type described by the @@ -46,7 +57,7 @@ public interface SqlTypeDescriptor extends Serializable { * * @return The appropriate binder. */ - public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor); + ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor); /** * Get the extractor (pulling out-going values from JDBC objects) capable of handling values of the type described @@ -56,5 +67,5 @@ public interface SqlTypeDescriptor extends Serializable { * * @return The appropriate extractor */ - public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor); + ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java index e252e18e68ed..71ad7f64503a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java @@ -18,6 +18,7 @@ import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; @@ -25,52 +26,72 @@ * Basically a map from JDBC type code (int) -> {@link SqlTypeDescriptor} * * @author Steve Ebersole + * + * @deprecated (5.3) Use {@link org.hibernate.type.descriptor.sql.spi.SqlTypeDescriptorRegistry} instead. */ -public class SqlTypeDescriptorRegistry { +@Deprecated +public class SqlTypeDescriptorRegistry implements Serializable { + + /** + * @deprecated (5.3) Use {@link TypeConfiguration#getSqlTypeDescriptorRegistry()} instead. + */ + @Deprecated public static final SqlTypeDescriptorRegistry INSTANCE = new SqlTypeDescriptorRegistry(); private static final Logger log = Logger.getLogger( SqlTypeDescriptorRegistry.class ); private ConcurrentHashMap descriptorMap = new ConcurrentHashMap(); - private SqlTypeDescriptorRegistry() { - addDescriptor( BooleanTypeDescriptor.INSTANCE ); - - addDescriptor( BitTypeDescriptor.INSTANCE ); - addDescriptor( BigIntTypeDescriptor.INSTANCE ); - addDescriptor( DecimalTypeDescriptor.INSTANCE ); - addDescriptor( DoubleTypeDescriptor.INSTANCE ); - addDescriptor( FloatTypeDescriptor.INSTANCE ); - addDescriptor( IntegerTypeDescriptor.INSTANCE ); - addDescriptor( NumericTypeDescriptor.INSTANCE ); - addDescriptor( RealTypeDescriptor.INSTANCE ); - addDescriptor( SmallIntTypeDescriptor.INSTANCE ); - addDescriptor( TinyIntTypeDescriptor.INSTANCE ); - - addDescriptor( DateTypeDescriptor.INSTANCE ); - addDescriptor( TimestampTypeDescriptor.INSTANCE ); - addDescriptor( TimeTypeDescriptor.INSTANCE ); - - addDescriptor( BinaryTypeDescriptor.INSTANCE ); - addDescriptor( VarbinaryTypeDescriptor.INSTANCE ); - addDescriptor( LongVarbinaryTypeDescriptor.INSTANCE ); - addDescriptor( BlobTypeDescriptor.DEFAULT ); - - addDescriptor( CharTypeDescriptor.INSTANCE ); - addDescriptor( VarcharTypeDescriptor.INSTANCE ); - addDescriptor( LongVarcharTypeDescriptor.INSTANCE ); - addDescriptor( ClobTypeDescriptor.DEFAULT ); - - addDescriptor( NCharTypeDescriptor.INSTANCE ); - addDescriptor( NVarcharTypeDescriptor.INSTANCE ); - addDescriptor( LongNVarcharTypeDescriptor.INSTANCE ); - addDescriptor( NClobTypeDescriptor.DEFAULT ); + protected SqlTypeDescriptorRegistry() { + addDescriptorInternal( BooleanTypeDescriptor.INSTANCE ); + + addDescriptorInternal( BitTypeDescriptor.INSTANCE ); + addDescriptorInternal( BigIntTypeDescriptor.INSTANCE ); + addDescriptorInternal( DecimalTypeDescriptor.INSTANCE ); + addDescriptorInternal( DoubleTypeDescriptor.INSTANCE ); + addDescriptorInternal( FloatTypeDescriptor.INSTANCE ); + addDescriptorInternal( IntegerTypeDescriptor.INSTANCE ); + addDescriptorInternal( NumericTypeDescriptor.INSTANCE ); + addDescriptorInternal( RealTypeDescriptor.INSTANCE ); + addDescriptorInternal( SmallIntTypeDescriptor.INSTANCE ); + addDescriptorInternal( TinyIntTypeDescriptor.INSTANCE ); + + addDescriptorInternal( DateTypeDescriptor.INSTANCE ); + addDescriptorInternal( TimestampTypeDescriptor.INSTANCE ); + addDescriptorInternal( TimeTypeDescriptor.INSTANCE ); + + addDescriptorInternal( BinaryTypeDescriptor.INSTANCE ); + addDescriptorInternal( VarbinaryTypeDescriptor.INSTANCE ); + addDescriptorInternal( LongVarbinaryTypeDescriptor.INSTANCE ); + addDescriptorInternal( BlobTypeDescriptor.DEFAULT ); + + addDescriptorInternal( CharTypeDescriptor.INSTANCE ); + addDescriptorInternal( VarcharTypeDescriptor.INSTANCE ); + addDescriptorInternal( LongVarcharTypeDescriptor.INSTANCE ); + addDescriptorInternal( ClobTypeDescriptor.DEFAULT ); + + addDescriptorInternal( NCharTypeDescriptor.INSTANCE ); + addDescriptorInternal( NVarcharTypeDescriptor.INSTANCE ); + addDescriptorInternal( LongNVarcharTypeDescriptor.INSTANCE ); + addDescriptorInternal( NClobTypeDescriptor.DEFAULT ); } + /** + * @deprecated (5.3) Use {@link org.hibernate.type.descriptor.sql.spi.SqlTypeDescriptorRegistry#addDescriptor(SqlTypeDescriptor)} instead. + */ + @Deprecated public void addDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { descriptorMap.put( sqlTypeDescriptor.getSqlType(), sqlTypeDescriptor ); } + private void addDescriptorInternal(SqlTypeDescriptor sqlTypeDescriptor){ + descriptorMap.put( sqlTypeDescriptor.getSqlType(), sqlTypeDescriptor ); + } + + /** + * @deprecated (5.3) Use {@link org.hibernate.type.descriptor.sql.spi.SqlTypeDescriptorRegistry#getDescriptor(int)} instead. + */ + @Deprecated public SqlTypeDescriptor getDescriptor(int jdbcTypeCode) { SqlTypeDescriptor descriptor = descriptorMap.get( Integer.valueOf( jdbcTypeCode ) ); if ( descriptor != null ) { @@ -130,7 +151,7 @@ public boolean canBeRemapped() { @Override public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { - if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaTypeClass() ) ) { + if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaType() ) ) { return VarbinaryTypeDescriptor.INSTANCE.getBinder( javaTypeDescriptor ); } @@ -152,7 +173,7 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions @Override @SuppressWarnings("unchecked") public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) { - if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaTypeClass() ) ) { + if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaType() ) ) { return VarbinaryTypeDescriptor.INSTANCE.getExtractor( javaTypeDescriptor ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/SqlTypeDescriptorRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/SqlTypeDescriptorRegistry.java new file mode 100644 index 000000000000..4e62eb9d2baa --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/SqlTypeDescriptorRegistry.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.type.descriptor.sql.spi; + +import java.io.Serializable; + +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Basically a map from JDBC type code (int) -> {@link SqlTypeDescriptor} + * + * @author Steve Ebersole + * @author Andrea Boriero + * + * @since 5.3 + */ +public class SqlTypeDescriptorRegistry + extends org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry + implements Serializable { + + private final TypeConfiguration typeConfiguration; + private final org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry sqlTypeDescriptorRegistry; + + public SqlTypeDescriptorRegistry(TypeConfiguration typeConfiguration) { + this.typeConfiguration = typeConfiguration; + sqlTypeDescriptorRegistry = org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry.INSTANCE; + } + + @Override + public void addDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + sqlTypeDescriptorRegistry.addDescriptor( sqlTypeDescriptor ); + } + + @Override + public SqlTypeDescriptor getDescriptor(int jdbcTypeCode) { + return sqlTypeDescriptorRegistry.getDescriptor( jdbcTypeCode ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/internal/TypeConfigurationRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/internal/TypeConfigurationRegistry.java new file mode 100644 index 000000000000..7d8e039aeda0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/internal/TypeConfigurationRegistry.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.type.internal; + +import java.util.concurrent.ConcurrentHashMap; + +import org.hibernate.type.spi.TypeConfiguration; + +import org.jboss.logging.Logger; + +/** + * A Registry of TypeConfiguration references based on the + * TypeConfiguration's UUID. + * + * @author Steve Ebersole + */ +public class TypeConfigurationRegistry { + private static final Logger LOG = Logger.getLogger( TypeConfigurationRegistry.class ); + + /** + * Singleton access + */ + public static final TypeConfigurationRegistry INSTANCE = new TypeConfigurationRegistry(); + + private TypeConfigurationRegistry() { + } + + private ConcurrentHashMap configurationMap; + + public void registerTypeConfiguration(TypeConfiguration typeConfiguration) { + if ( configurationMap == null ) { + configurationMap = new ConcurrentHashMap<>(); + } + configurationMap.put( typeConfiguration.getUuid(), typeConfiguration ); + } + + public TypeConfiguration findTypeConfiguration(String uuid) { + if ( configurationMap == null ) { + return null; + } + + return configurationMap.get( uuid ); + } + + public void deregisterTypeConfiguration(TypeConfiguration typeConfiguration) { + final TypeConfiguration existing = configurationMap.remove( typeConfiguration.getUuid() ); + if ( existing != typeConfiguration ) { + LOG.debugf( + "Different TypeConfiguration [%s] passed to #deregisterTypeConfiguration than previously registered [%s] under that UUID [%s]", + typeConfiguration, + existing, + typeConfiguration.getUuid() + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java new file mode 100644 index 000000000000..a27fcf1b5938 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -0,0 +1,347 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.type.spi; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.hibernate.HibernateException; +import org.hibernate.Incubating; +import org.hibernate.SessionFactory; +import org.hibernate.SessionFactoryObserver; +import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.id.uuid.LocalObjectUuidHelper; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.SessionFactoryRegistry; +import org.hibernate.metamodel.internal.MetamodelImpl; +import org.hibernate.metamodel.spi.MetamodelImplementor; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.BasicTypeRegistry; +import org.hibernate.type.Type; +import org.hibernate.type.TypeFactory; +import org.hibernate.type.TypeResolver; +import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry; +import org.hibernate.type.descriptor.sql.spi.SqlTypeDescriptorRegistry; +import org.hibernate.type.internal.TypeConfigurationRegistry; + +import static org.hibernate.internal.CoreLogging.messageLogger; + +/** + * Defines a set of available Type instances as isolated from other configurations. The + * isolation is defined by each instance of a TypeConfiguration. + *

    + * Note that each Type is inherently "scoped" to a TypeConfiguration. We only ever access + * a Type through a TypeConfiguration - specifically the TypeConfiguration in effect for + * the current persistence unit. + *

    + * Even though each Type instance is scoped to a TypeConfiguration, Types do not inherently + * have access to that TypeConfiguration (mainly because Type is an extension contract - meaning + * that Hibernate does not manage the full set of Types available in ever TypeConfiguration). + * However Types will often want access to the TypeConfiguration, which can be achieved by the + * Type simply implementing the {@link TypeConfigurationAware} interface. + * + * @author Steve Ebersole + * + * @since 5.3 + */ +@Incubating +public class TypeConfiguration implements SessionFactoryObserver, Serializable { + private static final CoreMessageLogger log = messageLogger( Scope.class ); + + private final String uuid = LocalObjectUuidHelper.generateLocalObjectUuid(); + + private final Scope scope; + private final transient TypeFactory typeFactory; + + // things available during both boot and runtime ("active") lifecycle phases + private final transient JavaTypeDescriptorRegistry javaTypeDescriptorRegistry; + private final transient SqlTypeDescriptorRegistry sqlTypeDescriptorRegistry; + private final transient BasicTypeRegistry basicTypeRegistry; + + private final transient Map importMap = new ConcurrentHashMap<>(); + + + // temporarily needed to support deprecations + private final transient TypeResolver typeResolver; + + public TypeConfiguration() { + this.scope = new Scope(); + this.javaTypeDescriptorRegistry = new JavaTypeDescriptorRegistry( this ); + this.sqlTypeDescriptorRegistry = new SqlTypeDescriptorRegistry( this ); + + this.basicTypeRegistry = new BasicTypeRegistry(); + this.typeFactory = new TypeFactory( this ); + this.typeResolver = new TypeResolver( this, typeFactory ); + + TypeConfigurationRegistry.INSTANCE.registerTypeConfiguration( this ); + } + + public String getUuid() { + return uuid; + } + + /** + * Temporarily needed to support deprecations + * + * Retrieve the {@link Type} resolver associated with this factory. + * + * @return The type resolver + * + * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0 + */ + @Deprecated + public TypeResolver getTypeResolver(){ + return typeResolver; + } + + public BasicTypeRegistry getBasicTypeRegistry() { + return basicTypeRegistry; + } + + + public JavaTypeDescriptorRegistry getJavaTypeDescriptorRegistry() { + return javaTypeDescriptorRegistry; + } + + public SqlTypeDescriptorRegistry getSqlTypeDescriptorRegistry() { + return sqlTypeDescriptorRegistry; + } + + public Map getImportMap() { + return Collections.unmodifiableMap( importMap ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Scoping + + /** + * Obtain the MetadataBuildingContext currently scoping the + * TypeConfiguration. + * + * @apiNote This will throw an exception if the SessionFactory is not yet + * bound here. See {@link Scope} for more details regarding the stages + * a TypeConfiguration goes through + * + * @return + */ + public MetadataBuildingContext getMetadataBuildingContext() { + return scope.getMetadataBuildingContext(); + } + + public void scope(MetadataBuildingContext metadataBuildingContext) { + log.debugf( "Scoping TypeConfiguration [%s] to MetadataBuildingContext [%s]", this, metadataBuildingContext ); + scope.setMetadataBuildingContext( metadataBuildingContext ); + } + + public MetamodelImplementor scope(SessionFactoryImplementor sessionFactory, BootstrapContext bootstrapContext) { + log.debugf( "Scoping TypeConfiguration [%s] to SessionFactoryImpl [%s]", this, sessionFactory ); + + for ( Map.Entry importEntry : scope.metadataBuildingContext.getMetadataCollector().getImports().entrySet() ) { + if ( importMap.containsKey( importEntry.getKey() ) ) { + continue; + } + + importMap.put( importEntry.getKey(), importEntry.getValue() ); + } + + scope.setSessionFactory( sessionFactory ); + sessionFactory.addObserver( this ); + return new MetamodelImpl( sessionFactory, this ); + } + + /** + * Obtain the SessionFactory currently scoping the TypeConfiguration. + * + * @apiNote This will throw an exception if the SessionFactory is not yet + * bound here. See {@link Scope} for more details regarding the stages + * a TypeConfiguration goes through (this is "runtime stage") + * + * @return The SessionFactory + * + * @throws IllegalStateException if the TypeConfiguration is currently not + * associated with a SessionFactory (in "runtime stage"). + */ + public SessionFactoryImplementor getSessionFactory() { + return scope.getSessionFactory(); + } + + /** + * Obtain the ServiceRegistry scoped to the TypeConfiguration. + * + * @apiNote Depending on what the {@link Scope} is currently scoped to will determine where the + * {@link ServiceRegistry} is obtained from. + * + * @return The ServiceRegistry + */ + public ServiceRegistry getServiceRegistry() { + return scope.getServiceRegistry(); + } + + @Override + public void sessionFactoryCreated(SessionFactory factory) { + // Instead of allowing scope#setSessionFactory to influence this, we use the SessionFactoryObserver callback + // to handle this, allowing any SessionFactory constructor code to be able to continue to have access to the + // MetadataBuildingContext through TypeConfiguration until this callback is fired. + log.tracef( "Handling #sessionFactoryCreated from [%s] for TypeConfiguration", factory ); + scope.setMetadataBuildingContext( null ); + } + + @Override + public void sessionFactoryClosed(SessionFactory factory) { + log.tracef( "Handling #sessionFactoryClosed from [%s] for TypeConfiguration", factory ); + + TypeConfigurationRegistry.INSTANCE.deregisterTypeConfiguration( this ); + + scope.unsetSessionFactory( factory ); + + // todo (6.0) : finish this + // release Database, descriptor Maps, etc... things that are only + // valid while the TypeConfiguration is scoped to SessionFactory + } + + /** + * Encapsulation of lifecycle concerns for a TypeConfiguration, mainly in + * regards to eventually being associated with a SessionFactory. Goes + * 3 "lifecycle" stages, pertaining to {@link #getMetadataBuildingContext()} + * and {@link #getSessionFactory()}: + * + * * "Initialization" is where the {@link TypeConfiguration} is first + * built as the "boot model" ({@link org.hibernate.boot.model}) of + * the user's domain model is converted into the "runtime model" + * ({@link org.hibernate.metamodel.model}). During this phase, + * {@link #getMetadataBuildingContext()} will be accessible but + * {@link #getSessionFactory} will throw an exception. + * * "Runtime" is where the "runtime model" is accessible while the + * SessionFactory is still unclosed. During this phase + * {@link #getSessionFactory()} is accessible while + * {@link #getMetadataBuildingContext()} will now throw an + * exception + * * "Sunset" is after the SessionFactory has been closed. During this + * phase both {@link #getSessionFactory()} and + * {@link #getMetadataBuildingContext()} will now throw an exception + * + * Each stage or phase is consider a "scope" for the TypeConfiguration. + */ + private static class Scope implements Serializable { + + // todo (6.0) : consider a proper contract implemented by both SessionFactory (or its metamodel) and boot's MetadataImplementor + // 1) type-related info from MetadataBuildingOptions + // 2) ServiceRegistry + private transient MetadataBuildingContext metadataBuildingContext; + private transient SessionFactoryImplementor sessionFactory; + + private String sessionFactoryName; + private String sessionFactoryUuid; + + public MetadataBuildingContext getMetadataBuildingContext() { + if ( metadataBuildingContext == null ) { + throw new HibernateException( "TypeConfiguration is not currently scoped to MetadataBuildingContext" ); + } + return metadataBuildingContext; + } + + public ServiceRegistry getServiceRegistry() { + if ( metadataBuildingContext != null ) { + return metadataBuildingContext.getBootstrapContext().getServiceRegistry(); + } + else if ( sessionFactory != null ) { + return sessionFactory.getServiceRegistry(); + } + return null; + } + + public void setMetadataBuildingContext(MetadataBuildingContext metadataBuildingContext) { + this.metadataBuildingContext = metadataBuildingContext; + } + + public SessionFactoryImplementor getSessionFactory() { + if ( sessionFactory == null ) { + if ( sessionFactoryName == null && sessionFactoryUuid == null ) { + throw new HibernateException( "TypeConfiguration was not yet scoped to SessionFactory" ); + } + sessionFactory = (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory( + sessionFactoryUuid, + sessionFactoryName + ); + if ( sessionFactory == null ) { + throw new HibernateException( + "Could not find a SessionFactory [uuid=" + sessionFactoryUuid + ",name=" + sessionFactoryName + "]" + ); + } + + } + + return sessionFactory; + } + + /** + * Used by TypeFactory scoping. + * + * @param factory The SessionFactory that the TypeFactory is being bound to + */ + void setSessionFactory(SessionFactoryImplementor factory) { + if ( this.sessionFactory != null ) { + log.scopingTypesToSessionFactoryAfterAlreadyScoped( this.sessionFactory, factory ); + } + else { + this.sessionFactoryUuid = factory.getUuid(); + String sfName = factory.getSessionFactoryOptions().getSessionFactoryName(); + if ( sfName == null ) { + final CfgXmlAccessService cfgXmlAccessService = factory.getServiceRegistry() + .getService( CfgXmlAccessService.class ); + if ( cfgXmlAccessService.getAggregatedConfig() != null ) { + sfName = cfgXmlAccessService.getAggregatedConfig().getSessionFactoryName(); + } + } + this.sessionFactoryName = sfName; + } + this.sessionFactory = factory; + } + + public void unsetSessionFactory(SessionFactory factory) { + log.debugf( "Un-scoping TypeConfiguration [%s] from SessionFactory [%s]", this, factory ); + this.sessionFactory = null; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Custom serialization hook + + private Object readResolve() throws InvalidObjectException { + if ( sessionFactory == null ) { + if ( sessionFactoryName != null || sessionFactoryUuid != null ) { + sessionFactory = (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory( + sessionFactoryUuid, + sessionFactoryName + ); + + if ( sessionFactory == null ) { + throw new HibernateException( + "Could not find a SessionFactory [uuid=" + sessionFactoryUuid + ",name=" + sessionFactoryName + "]" + ); + } + } + } + + return this; + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Custom serialization hook + + private Object readResolve() throws InvalidObjectException { + log.trace( "Resolving serialized TypeConfiguration - readResolve" ); + return TypeConfigurationRegistry.INSTANCE.findTypeConfiguration( getUuid() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfigurationAware.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfigurationAware.java new file mode 100644 index 000000000000..b35cb2b11f40 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfigurationAware.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.type.spi; + +/** + * Optional contract for Types that would like to be part of the scoping process of the + * TypeConfiguration, specifically to receive access to the TypeConfiguration it is scoped + * to. For additional information on TypeConfiguration scoping, see {@link TypeConfiguration} + *

    + * Note that it is illegal for a Type to implement TypeConfigurationAware and at the same time + * be scoped to more than one TypeConfiguration. Hibernate will enforce this internally + * which is why {@link #getTypeConfiguration()} is exposed here. + * + * @author Steve Ebersole + */ +public interface TypeConfigurationAware { + TypeConfiguration getTypeConfiguration(); + void setTypeConfiguration(TypeConfiguration typeConfiguration); +} diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java index 2b24ff4a653c..85130a0bc025 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java @@ -87,7 +87,7 @@ public interface CompositeUserType { * @throws HibernateException */ boolean equals(Object x, Object y) throws HibernateException; - + /** * Get a hashcode for the instance, consistent with persistence "equality" */ diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java b/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java index 1d0638d25020..f6539a12f10d 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java @@ -19,32 +19,32 @@ * @author Janario Oliveira */ public interface DynamicParameterizedType extends ParameterizedType { - public static final String PARAMETER_TYPE = "org.hibernate.type.ParameterType"; + String PARAMETER_TYPE = "org.hibernate.type.ParameterType"; - public static final String IS_DYNAMIC = "org.hibernate.type.ParameterType.dynamic"; + String IS_DYNAMIC = "org.hibernate.type.ParameterType.dynamic"; - public static final String RETURNED_CLASS = "org.hibernate.type.ParameterType.returnedClass"; - public static final String IS_PRIMARY_KEY = "org.hibernate.type.ParameterType.primaryKey"; - public static final String ENTITY = "org.hibernate.type.ParameterType.entityClass"; - public static final String PROPERTY = "org.hibernate.type.ParameterType.propertyName"; - public static final String ACCESS_TYPE = "org.hibernate.type.ParameterType.accessType"; - public static final String XPROPERTY = "org.hibernate.type.ParameterType.xproperty"; + String RETURNED_CLASS = "org.hibernate.type.ParameterType.returnedClass"; + String IS_PRIMARY_KEY = "org.hibernate.type.ParameterType.primaryKey"; + String ENTITY = "org.hibernate.type.ParameterType.entityClass"; + String PROPERTY = "org.hibernate.type.ParameterType.propertyName"; + String ACCESS_TYPE = "org.hibernate.type.ParameterType.accessType"; + String XPROPERTY = "org.hibernate.type.ParameterType.xproperty"; - public static interface ParameterType { + interface ParameterType { - public Class getReturnedClass(); + Class getReturnedClass(); - public Annotation[] getAnnotationsMethod(); + Annotation[] getAnnotationsMethod(); - public String getCatalog(); + String getCatalog(); - public String getSchema(); + String getSchema(); - public String getTable(); + String getTable(); - public boolean isPrimaryKey(); + boolean isPrimaryKey(); - public String[] getColumns(); + String[] getColumns(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/EnhancedUserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/EnhancedUserType.java index 48e75454e667..cee6af7afd8b 100755 --- a/hibernate-core/src/main/java/org/hibernate/usertype/EnhancedUserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/EnhancedUserType.java @@ -15,7 +15,7 @@ public interface EnhancedUserType extends UserType { /** * Return an SQL literal representation of the value */ - public String objectToSQLString(Object value); + String objectToSQLString(Object value); /** * Return a string representation of this value, as it should appear in an XML document @@ -24,7 +24,7 @@ public interface EnhancedUserType extends UserType { * instead. See HHH-7776 for details */ @Deprecated - public String toXMLString(Object value); + String toXMLString(Object value); /** * Parse a string representation of this value, as it appears in an XML document @@ -34,5 +34,5 @@ public interface EnhancedUserType extends UserType { * See HHH-7776 for details */ @Deprecated - public Object fromXMLString(String xmlValue); + Object fromXMLString(String xmlValue); } diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java old mode 100755 new mode 100644 index f4ac7315126e..5856c3534ddc --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java @@ -66,7 +66,7 @@ Object replaceElements( * and perhaps load factor). * * @param anticipatedSize The anticipated size of the instaniated collection - * afterQuery we are done populating it. Note, may be negative to indicate that + * after we are done populating it. Note, may be negative to indicate that * we not yet know anything about the anticipated size (i.e., when initializing * from a result set row by row). */ diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java index 7ae9d9c73d04..abd55a59efd8 100755 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java @@ -12,7 +12,7 @@ /** * A user type that may be used for a version property - * + * * @author Gavin King */ public interface UserVersionType extends UserType, Comparator { @@ -34,5 +34,4 @@ public interface UserVersionType extends UserType, Comparator { * @return an instance of the type */ Object next(Object current, SharedSessionContractImplementor session); - } diff --git a/hibernate-core/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider b/hibernate-core/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider index 2961f55a4c5f..8cebd687981a 100644 --- a/hibernate-core/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider +++ b/hibernate-core/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider @@ -4,10 +4,4 @@ # License: GNU Lesser General Public License (LGPL), version 2.1 or later. # See the lgpl.txt file in the root directory or . # -# -# Hibernate, Relational Persistence for Idiomatic Java -# -# License: GNU Lesser General Public License (LGPL), version 2.1 or later. -# See the lgpl.txt file in the root directory or . -# org.hibernate.jpa.HibernatePersistenceProvider \ No newline at end of file diff --git a/hibernate-core/src/main/resources/org/hibernate/jpa/orm_1_0.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_1_0.xsd index fbde2656d4c3..17388ee71609 100644 --- a/hibernate-core/src/main/resources/org/hibernate/jpa/orm_1_0.xsd +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_1_0.xsd @@ -46,7 +46,7 @@ - The entity-mappings element is the root element of an mapping + The entity-mappings element is the root element of a mapping file. It contains the following four types of elements: 1. The persistence-unit-metadata element contains metadata diff --git a/hibernate-core/src/main/resources/org/hibernate/jpa/orm_2_2.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_2_2.xsd new file mode 100644 index 000000000000..e30b195fc8cb --- /dev/null +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_2_2.xsd @@ -0,0 +1,2319 @@ + + + + + + + + @(#)orm_2_2.xsd 2.2 July 7 2017 + + + + + + ... + + + + ]]> + + + + + + + + + + + + + + + + + + The entity-mappings element is the root element of a mapping + file. It contains the following four types of elements: + + 1. The persistence-unit-metadata element contains metadata + for the entire persistence unit. It is undefined if this element + occurs in multiple mapping files within the same persistence unit. + + 2. The package, schema, catalog and access elements apply to all of + the entity, mapped-superclass and embeddable elements defined in + the same file in which they occur. + + 3. The sequence-generator, table-generator, converter, named-query, + named-native-query, named-stored-procedure-query, and + sql-result-set-mapping elements are global to the persistence + unit. It is undefined to have more than one sequence-generator + or table-generator of the same name in the same or different + mapping files in a persistence unit. It is undefined to have + more than one named-query, named-native-query, sql-result-set-mapping, + or named-stored-procedure-query of the same name in the same + or different mapping files in a persistence unit. It is also + undefined to have more than one converter for the same target + type in the same or different mapping files in a persistence unit. + + 4. The entity, mapped-superclass and embeddable elements each define + the mapping information for a managed persistent class. The mapping + information contained in these elements may be complete or it may + be partial. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Metadata that applies to the persistence unit and not just to + the mapping file in which it is contained. + + If the xml-mapping-metadata-complete element is specified, + the complete set of mapping metadata for the persistence unit + is contained in the XML mapping files for the persistence unit. + + + + + + + + + + + + + + + + + These defaults are applied to the persistence unit as a whole + unless they are overridden by local annotation or XML + element settings. + + schema - Used as the schema for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + catalog - Used as the catalog for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + delimited-identifiers - Used to treat database identifiers as + delimited identifiers. + access - Used as the access type for all managed classes in + the persistence unit + cascade-persist - Adds cascade-persist to the set of cascade options + in all entity relationships of the persistence unit + entity-listeners - List of default entity listeners to be invoked + on each entity in the persistence unit. + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for an entity. Is allowed to be + sparsely populated and used in conjunction with the annotations. + Alternatively, the metadata-complete attribute can be used to + indicate that no annotations on the entity class (and its fields + or properties) are to be processed. If this is the case then + the defaulting rules for the entity and its subelements will + be recursively applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface Entity { + String name() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This element determines how the persistence provider accesses the + state of an entity or embedded object. + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AssociationOverride { + String name(); + JoinColumn[] joinColumns() default{}; + JoinTable joinTable() default @JoinTable; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AttributeOverride { + String name(); + Column column(); + } + + + + + + + + + + + + + + + + + This element contains the entity field or property mappings. + It may be sparsely populated to include only a subset of the + fields or properties. If metadata-complete for the entity is true + then the remainder of the attributes will be defaulted according + to the default rules. + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Basic { + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH}; + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface CollectionTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Column { + String name() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ColumnResult { + String name(); + Class type() default void.class; + } + + + + + + + + + + + + + public enum ConstraintMode {CONSTRAINT, NO_CONSTRAINT, PROVIDER_DEFAULT}; + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ConstructorResult { + Class targetClass(); + ColumnResult[] columns(); + } + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Convert { + Class converter() default void.class; + String attributeName() default ""; + boolean disableConversion() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Converter { + boolean autoApply() default false; + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorColumn { + String name() default "DTYPE"; + DiscriminatorType discriminatorType() default STRING; + String columnDefinition() default ""; + int length() default 31; + } + + + + + + + + + + + + + + + + public enum DiscriminatorType { STRING, CHAR, INTEGER }; + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorValue { + String value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ElementCollection { + Class targetClass() default void.class; + FetchType fetch() default LAZY; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for embeddable objects. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + in the class. If this is the case then the defaulting rules will + be recursively applied. + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Embeddable {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Embedded {} + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface EmbeddedId {} + + + + + + + + + + + + + + + + + Defines an entity listener to be invoked at lifecycle events + for the entities that list this listener. + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface EntityListeners { + Class[] value(); + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface EntityResult { + Class entityClass(); + FieldResult[] fields() default {}; + String discriminatorColumn() default ""; + } + + + + + + + + + + + + + + + + + public enum EnumType { + ORDINAL, + STRING + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Enumerated { + EnumType value() default ORDINAL; + } + + + + + + + + + + + + + public enum FetchType { LAZY, EAGER }; + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface FieldResult { + String name(); + String column(); + } + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ForeignKey { + String name() default ""; + ConstraintMode value() defalut CONSTRAINT + String foreign-key-definition() default ""; + + Note that the elements that embed the use of the annotation + default this use as @ForeignKey(PROVIDER_DEFAULT) + } + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface GeneratedValue { + GenerationType strategy() default AUTO; + String generator() default ""; + } + + + + + + + + + + + + + + public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO }; + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Id {} + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface IdClass { + Class value(); + } + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface Index { + String name() default ""; + String columnList(); + boolean unique() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Inheritance { + InheritanceType strategy() default SINGLE_TABLE; + } + + + + + + + + + + + + + public enum InheritanceType + { SINGLE_TABLE, JOINED, TABLE_PER_CLASS}; + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + ForeignKey foreignKey() default @ForeignKey(); + } + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + JoinColumn[] inverseJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Lob {} + + + + + + + + + + + + public enum LockModeType { READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT, PESSIMISTIC_READ, PESSIMISTIC_WRITE, PESSIMISTIC_FORCE_INCREMENT, NONE}; + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKey { + String name() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyClass { + Class value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyColumn { + String name() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + } + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for a mapped superclass. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + If this is the case then the defaulting rules will be recursively + applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface MappedSuperclass{} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedAttributeNode { + String value(); + String subgraph() default ""; + String keySubgraph() default ""; + } + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedEntityGraph { + String name() default ""; + NamedAttributeNode[] attributeNodes() default {}; + boolean includeAllAttributes() default false; + NamedSubgraph[] subgraphs() default {}; + NamedSubGraph[] subclassSubgraphs() default {}; + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedNativeQuery { + String name(); + String query(); + QueryHint[] hints() default {}; + Class resultClass() default void.class; + String resultSetMapping() default ""; //named SqlResultSetMapping + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedQuery { + String name(); + String query(); + LockModeType lockMode() default NONE; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedStoredProcedureQuery { + String name(); + String procedureName(); + StoredProcedureParameter[] parameters() default {}; + Class[] resultClasses() default {}; + String[] resultSetMappings() default{}; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedSubgraph { + String name(); + Class type() default void.class; + NamedAttributeNode[] attributeNodes(); + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + String mappedBy() default ""; + boolean orphanRemoval() default false; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderBy { + String value() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderColumn { + String name() default ""; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + + + public enum ParameterMode { IN, INOUT, OUT, REF_CURSOR}; + + + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostLoad {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostPersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostUpdate {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PrePersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreUpdate {} + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface PrimaryKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface QueryHint { + String name(); + String value(); + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SecondaryTable { + String name(); + String catalog() default ""; + String schema() default ""; + PrimaryKeyJoinColumn[] pkJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface SequenceGenerator { + String name(); + String sequenceName() default ""; + String catalog() default ""; + String schema() default ""; + int initialValue() default 1; + int allocationSize() default 50; + } + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SqlResultSetMapping { + String name(); + EntityResult[] entities() default {}; + ConstructorResult[] classes() default{}; + ColumnResult[] columns() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface StoredProcedureParameter { + String name() default ""; + ParameterMode mode() default ParameterMode.IN; + Class type(); + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Table { + String name() default ""; + String catalog() default ""; + String schema() default ""; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface TableGenerator { + String name(); + String table() default ""; + String catalog() default ""; + String schema() default ""; + String pkColumnName() default ""; + String valueColumnName() default ""; + String pkColumnValue() default ""; + int initialValue() default 0; + int allocationSize() default 50; + UniqueConstraint[] uniqueConstraints() default {}; + Indexes[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Temporal { + TemporalType value(); + } + + + + + + + + + + + + + public enum TemporalType { + DATE, // java.sql.Date + TIME, // java.sql.Time + TIMESTAMP // java.sql.Timestamp + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Transient {} + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface UniqueConstraint { + String name() default ""; + String[] columnNames(); + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Version {} + + + + + + + + + + + + diff --git a/hibernate-core/src/main/resources/org/hibernate/jpa/orm_3_0.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_3_0.xsd new file mode 100644 index 000000000000..233d98f6eaa9 --- /dev/null +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_3_0.xsd @@ -0,0 +1,2324 @@ + + + + + + + + + ... + + + + ]]> + + + + + + + + + + + + + + + + + + The entity-mappings element is the root element of a mapping + file. It contains the following four types of elements: + + 1. The persistence-unit-metadata element contains metadata + for the entire persistence unit. It is undefined if this element + occurs in multiple mapping files within the same persistence unit. + + 2. The package, schema, catalog and access elements apply to all of + the entity, mapped-superclass and embeddable elements defined in + the same file in which they occur. + + 3. The sequence-generator, table-generator, converter, named-query, + named-native-query, named-stored-procedure-query, and + sql-result-set-mapping elements are global to the persistence + unit. It is undefined to have more than one sequence-generator + or table-generator of the same name in the same or different + mapping files in a persistence unit. It is undefined to have + more than one named-query, named-native-query, sql-result-set-mapping, + or named-stored-procedure-query of the same name in the same + or different mapping files in a persistence unit. It is also + undefined to have more than one converter for the same target + type in the same or different mapping files in a persistence unit. + + 4. The entity, mapped-superclass and embeddable elements each define + the mapping information for a managed persistent class. The mapping + information contained in these elements may be complete or it may + be partial. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Metadata that applies to the persistence unit and not just to + the mapping file in which it is contained. + + If the xml-mapping-metadata-complete element is specified, + the complete set of mapping metadata for the persistence unit + is contained in the XML mapping files for the persistence unit. + + + + + + + + + + + + + + + + + These defaults are applied to the persistence unit as a whole + unless they are overridden by local annotation or XML + element settings. + + schema - Used as the schema for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + catalog - Used as the catalog for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + delimited-identifiers - Used to treat database identifiers as + delimited identifiers. + access - Used as the access type for all managed classes in + the persistence unit + cascade-persist - Adds cascade-persist to the set of cascade options + in all entity relationships of the persistence unit + entity-listeners - List of default entity listeners to be invoked + on each entity in the persistence unit. + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for an entity. Is allowed to be + sparsely populated and used in conjunction with the annotations. + Alternatively, the metadata-complete attribute can be used to + indicate that no annotations on the entity class (and its fields + or properties) are to be processed. If this is the case then + the defaulting rules for the entity and its subelements will + be recursively applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface Entity { + String name() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This element determines how the persistence provider accesses the + state of an entity or embedded object. + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AssociationOverride { + String name(); + JoinColumn[] joinColumns() default{}; + JoinTable joinTable() default @JoinTable; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AttributeOverride { + String name(); + Column column(); + } + + + + + + + + + + + + + + + + + This element contains the entity field or property mappings. + It may be sparsely populated to include only a subset of the + fields or properties. If metadata-complete for the entity is true + then the remainder of the attributes will be defaulted according + to the default rules. + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Basic { + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH}; + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface CollectionTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Column { + String name() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ColumnResult { + String name(); + Class type() default void.class; + } + + + + + + + + + + + + + + public enum ConstraintMode {CONSTRAINT, NO_CONSTRAINT, PROVIDER_DEFAULT}; + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ConstructorResult { + Class targetClass(); + ColumnResult[] columns(); + } + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Convert { + Class converter() default void.class; + String attributeName() default ""; + boolean disableConversion() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Converter { + boolean autoApply() default false; + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorColumn { + String name() default "DTYPE"; + DiscriminatorType discriminatorType() default STRING; + String columnDefinition() default ""; + int length() default 31; + } + + + + + + + + + + + + + + + + public enum DiscriminatorType { STRING, CHAR, INTEGER }; + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorValue { + String value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ElementCollection { + Class targetClass() default void.class; + FetchType fetch() default LAZY; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for embeddable objects. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + in the class. If this is the case then the defaulting rules will + be recursively applied. + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Embeddable {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Embedded {} + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface EmbeddedId {} + + + + + + + + + + + + + + + + + Defines an entity listener to be invoked at lifecycle events + for the entities that list this listener. + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface EntityListeners { + Class[] value(); + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface EntityResult { + Class entityClass(); + FieldResult[] fields() default {}; + String discriminatorColumn() default ""; + } + + + + + + + + + + + + + + + + + public enum EnumType { + ORDINAL, + STRING + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Enumerated { + EnumType value() default ORDINAL; + } + + + + + + + + + + + + + public enum FetchType { LAZY, EAGER }; + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface FieldResult { + String name(); + String column(); + } + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ForeignKey { + String name() default ""; + ConstraintMode value() default CONSTRAINT; + String foreign-key-definition() default ""; + + Note that the elements that embed the use of the annotation + default this use as @ForeignKey(PROVIDER_DEFAULT). + + } + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface GeneratedValue { + GenerationType strategy() default AUTO; + String generator() default ""; + } + + + + + + + + + + + + + + public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO }; + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Id {} + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface IdClass { + Class value(); + } + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface Index { + String name() default ""; + String columnList(); + boolean unique() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Inheritance { + InheritanceType strategy() default SINGLE_TABLE; + } + + + + + + + + + + + + + public enum InheritanceType + { SINGLE_TABLE, JOINED, TABLE_PER_CLASS}; + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + ForeignKey foreignKey() default @ForeignKey(); + } + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + JoinColumn[] inverseJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Lob {} + + + + + + + + + + + + public enum LockModeType { READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT, PESSIMISTIC_READ, PESSIMISTIC_WRITE, PESSIMISTIC_FORCE_INCREMENT, NONE}; + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKey { + String name() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyClass { + Class value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyColumn { + String name() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + } + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for a mapped superclass. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + If this is the case then the defaulting rules will be recursively + applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface MappedSuperclass{} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedAttributeNode { + String value(); + String subgraph() default ""; + String keySubgraph() default ""; + } + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedEntityGraph { + String name() default ""; + NamedAttributeNode[] attributeNodes() default {}; + boolean includeAllAttributes() default false; + NamedSubgraph[] subgraphs() default {}; + NamedSubGraph[] subclassSubgraphs() default {}; + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedNativeQuery { + String name(); + String query(); + QueryHint[] hints() default {}; + Class resultClass() default void.class; + String resultSetMapping() default ""; //named SqlResultSetMapping + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedQuery { + String name(); + String query(); + LockModeType lockMode() default NONE; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedStoredProcedureQuery { + String name(); + String procedureName(); + StoredProcedureParameter[] parameters() default {}; + Class[] resultClasses() default {}; + String[] resultSetMappings() default{}; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedSubgraph { + String name(); + Class type() default void.class; + NamedAttributeNode[] attributeNodes(); + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + String mappedBy() default ""; + boolean orphanRemoval() default false; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderBy { + String value() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderColumn { + String name() default ""; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + + + public enum ParameterMode { IN, INOUT, OUT, REF_CURSOR}; + + + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostLoad {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostPersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostUpdate {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PrePersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreUpdate {} + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface PrimaryKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface QueryHint { + String name(); + String value(); + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SecondaryTable { + String name(); + String catalog() default ""; + String schema() default ""; + PrimaryKeyJoinColumn[] pkJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface SequenceGenerator { + String name(); + String sequenceName() default ""; + String catalog() default ""; + String schema() default ""; + int initialValue() default 1; + int allocationSize() default 50; + } + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SqlResultSetMapping { + String name(); + EntityResult[] entities() default {}; + ConstructorResult[] classes() default{}; + ColumnResult[] columns() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface StoredProcedureParameter { + String name() default ""; + ParameterMode mode() default ParameterMode.IN; + Class type(); + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Table { + String name() default ""; + String catalog() default ""; + String schema() default ""; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface TableGenerator { + String name(); + String table() default ""; + String catalog() default ""; + String schema() default ""; + String pkColumnName() default ""; + String valueColumnName() default ""; + String pkColumnValue() default ""; + int initialValue() default 0; + int allocationSize() default 50; + UniqueConstraint[] uniqueConstraints() default {}; + Indexes[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Temporal { + TemporalType value(); + } + + + + + + + + + + + + + public enum TemporalType { + DATE, // java.sql.Date + TIME, // java.sql.Time + TIMESTAMP // java.sql.Timestamp + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Transient {} + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface UniqueConstraint { + String name() default ""; + String[] columnNames(); + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Version {} + + + + + + + + + + + + diff --git a/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_2_2.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_2_2.xsd new file mode 100644 index 000000000000..2301f70877af --- /dev/null +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_2_2.xsd @@ -0,0 +1,341 @@ + + + + + + + + @(#)persistence_2_2.xsd 2.2 July 17, 2017 + + + + + + ... + + + ]]> + + + + + + + + + + + + + + + + + + + + + + Configuration of a persistence unit. + + + + + + + + + + + + Description of this persistence unit. + + + + + + + + + + + + Provider class that supplies EntityManagers for this + persistence unit. + + + + + + + + + + + + The container-specific name of the JTA datasource to use. + + + + + + + + + + + + The container-specific name of a non-JTA datasource to use. + + + + + + + + + + + + File containing mapping information. Loaded as a resource + by the persistence provider. + + + + + + + + + + + + Jar file that is to be scanned for managed classes. + + + + + + + + + + + + Managed class to be included in the persistence unit and + to scan for annotations. It should be annotated + with either @Entity, @Embeddable or @MappedSuperclass. + + + + + + + + + + + + When set to true then only listed classes and jars will + be scanned for persistent classes, otherwise the + enclosing jar or directory will also be scanned. + Not applicable to Java SE persistence units. + + + + + + + + + + + + Defines whether caching is enabled for the + persistence unit if caching is supported by the + persistence provider. When set to ALL, all entities + will be cached. When set to NONE, no entities will + be cached. When set to ENABLE_SELECTIVE, only entities + specified as cacheable will be cached. When set to + DISABLE_SELECTIVE, entities specified as not cacheable + will not be cached. When not specified or when set to + UNSPECIFIED, provider defaults may apply. + + + + + + + + + + + + The validation mode to be used for the persistence unit. + + + + + + + + + + + + + A list of standard and vendor-specific properties + and hints. + + + + + + + + + A name-value pair. + + + + + + + + + + + + + + + + + + + + Name used in code to reference this persistence unit. + + + + + + + + + + + + Type of transactions used by EntityManagers from this + persistence unit. + + + + + + + + + + + + + + + + + + + public enum PersistenceUnitTransactionType {JTA, RESOURCE_LOCAL}; + + + + + + + + + + + + + + + + public enum SharedCacheMode { ALL, NONE, ENABLE_SELECTIVE, DISABLE_SELECTIVE, UNSPECIFIED}; + + + + + + + + + + + + + + + + + + + public enum ValidationMode { AUTO, CALLBACK, NONE}; + + + + + + + + + + + diff --git a/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_3_0.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_3_0.xsd new file mode 100644 index 000000000000..e2a294393704 --- /dev/null +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_3_0.xsd @@ -0,0 +1,342 @@ + + + + + + + + + ... + + + ]]> + + + + + + + + + + + + + + + + + + + + + + Configuration of a persistence unit. + + + + + + + + + + + + Description of this persistence unit. + + + + + + + + + + + + Provider class that supplies EntityManagers for this + persistence unit. + + + + + + + + + + + + The container-specific name of the JTA datasource to use. + + + + + + + + + + + + The container-specific name of a non-JTA datasource to use. + + + + + + + + + + + + File containing mapping information. Loaded as a resource + by the persistence provider. + + + + + + + + + + + + Jar file that is to be scanned for managed classes. + + + + + + + + + + + + Managed class to be included in the persistence unit and + to scan for annotations. It should be annotated + with either @Entity, @Embeddable or @MappedSuperclass. + + + + + + + + + + + + When set to true then only listed classes and jars will + be scanned for persistent classes, otherwise the + enclosing jar or directory will also be scanned. + Not applicable to Java SE persistence units. + + + + + + + + + + + + Defines whether caching is enabled for the + persistence unit if caching is supported by the + persistence provider. When set to ALL, all entities + will be cached. When set to NONE, no entities will + be cached. When set to ENABLE_SELECTIVE, only entities + specified as cacheable will be cached. When set to + DISABLE_SELECTIVE, entities specified as not cacheable + will not be cached. When not specified or when set to + UNSPECIFIED, provider defaults may apply. + + + + + + + + + + + + The validation mode to be used for the persistence unit. + + + + + + + + + + + + + A list of standard and vendor-specific properties + and hints. + + + + + + + + + A name-value pair. + + + + + + + + + + + + + + + + + + + + Name used in code to reference this persistence unit. + + + + + + + + + + + + Type of transactions used by EntityManagers from this + persistence unit. + + + + + + + + + + + + + + + + + + + public enum PersistenceUnitTransactionType {JTA, RESOURCE_LOCAL}; + + + + + + + + + + + + + + + + public enum SharedCacheMode { ALL, NONE, ENABLE_SELECTIVE, DISABLE_SELECTIVE, UNSPECIFIED}; + + + + + + + + + + + + + + + + + + + public enum ValidationMode { AUTO, CALLBACK, NONE}; + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java new file mode 100644 index 000000000000..819356a0a1b4 --- /dev/null +++ b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity(name = "annotationnopackage") +public class AnnotationMappedNoPackageEntity { + @Id + private Integer id; + + @Basic + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/NoPackageTest.java b/hibernate-core/src/test/java/NoPackageTest.java new file mode 100644 index 000000000000..5c8e20aef34c --- /dev/null +++ b/hibernate-core/src/test/java/NoPackageTest.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test using an entity which is in no package. + * We had problems with ByteBuddy in the past. + */ +@TestForIssue(jiraKey = "HHH-13112") +public class NoPackageTest extends BaseCoreFunctionalTestCase { + + @Test + public void testNoException() { + inTransaction( session -> { + AnnotationMappedNoPackageEntity box = new AnnotationMappedNoPackageEntity(); + box.setId( 42 ); + box.setName( "This feels dirty" ); + session.persist( box ); + } ); + + inTransaction( session -> { + Query query = session.createQuery( + "select e from " + AnnotationMappedNoPackageEntity.class.getSimpleName() + " e", + AnnotationMappedNoPackageEntity.class + ); + AnnotationMappedNoPackageEntity box = query.getSingleResult(); + assertEquals( (Integer) 42, box.getId() ); + } ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + AnnotationMappedNoPackageEntity.class + }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/model/naming/NamingHelperTest.java b/hibernate-core/src/test/java/org/hibernate/boot/model/naming/NamingHelperTest.java new file mode 100644 index 000000000000..d31768c00f9b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/model/naming/NamingHelperTest.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.boot.model.naming; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.util.ReflectionUtil; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; + +import static org.junit.Assert.*; + +public class NamingHelperTest extends BaseUnitTestCase { + + @Rule + public DefaultCharset defaultCharset = new DefaultCharset(); + + @Test + @TestForIssue(jiraKey = "HHH-12357") + public void generateHashedFkName() { + Identifier booksDe = new Identifier( "Bücher", false ); + Identifier authorsDe = new Identifier( "Autoren", false ); + Identifier authorId = new Identifier( "autor_id", false ); + + defaultCharset.set( StandardCharsets.ISO_8859_1 ); + + String fkNameLatin1 = NamingHelper.INSTANCE.generateHashedFkName( "FK", booksDe, authorsDe, authorId ); + + assertEquals( "FKpvm24wh1qwbmx6xjcbc7uv5f7", fkNameLatin1 ); + + defaultCharset.set( StandardCharsets.UTF_8 ); + + String fkNameUtf8 = NamingHelper.INSTANCE.generateHashedFkName( "FK", booksDe, authorsDe, authorId ); + + assertEquals( "FKdgopp1hqnm8c1o6sfbb3tbeh", fkNameUtf8 ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12357") + public void generateHashedFkNameUSingUtf8() { + Identifier booksDe = new Identifier( "Bücher", false ); + Identifier authorsDe = new Identifier( "Autoren", false ); + Identifier authorId = new Identifier( "autor_id", false ); + + defaultCharset.set( StandardCharsets.ISO_8859_1 ); + + String fkNameLatin1 = NamingHelper.withCharset( "UTF8" ).generateHashedFkName( "FK", booksDe, authorsDe, authorId ); + + assertEquals( "FKdgopp1hqnm8c1o6sfbb3tbeh", fkNameLatin1 ); + + defaultCharset.set( StandardCharsets.UTF_8 ); + + String fkNameUtf8 = NamingHelper.withCharset( "UTF8" ).generateHashedFkName( "FK", booksDe, authorsDe, authorId ); + + assertEquals( "FKdgopp1hqnm8c1o6sfbb3tbeh", fkNameUtf8 ); + } + + public static class DefaultCharset extends ExternalResource { + + private Charset prev; + + @Override + protected void before() { + prev = ReflectionUtil.getStaticFieldValue( Charset.class, "defaultCharset" ); + } + + @Override + protected void after() { + set( prev ); + } + + public void set(Charset charset) { + ReflectionUtil.setStaticField( Charset.class, "defaultCharset", charset ); + } + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/model/process/internal/ScanningCoordinatorTest.java b/hibernate-core/src/test/java/org/hibernate/boot/model/process/internal/ScanningCoordinatorTest.java new file mode 100644 index 000000000000..0f5789c9f36f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/model/process/internal/ScanningCoordinatorTest.java @@ -0,0 +1,155 @@ +package org.hibernate.boot.model.process.internal; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.archive.internal.ByteArrayInputStreamAccess; +import org.hibernate.boot.archive.scan.internal.ClassDescriptorImpl; +import org.hibernate.boot.archive.scan.internal.DisabledScanner; +import org.hibernate.boot.archive.scan.internal.MappingFileDescriptorImpl; +import org.hibernate.boot.archive.scan.internal.PackageDescriptorImpl; +import org.hibernate.boot.archive.scan.internal.ScanResultImpl; +import org.hibernate.boot.archive.scan.spi.ClassDescriptor; +import org.hibernate.boot.archive.scan.spi.MappingFileDescriptor; +import org.hibernate.boot.archive.scan.spi.PackageDescriptor; +import org.hibernate.boot.archive.scan.spi.ScanEnvironment; +import org.hibernate.boot.archive.scan.spi.ScanOptions; +import org.hibernate.boot.archive.scan.spi.ScanParameters; +import org.hibernate.boot.archive.scan.spi.ScanResult; +import org.hibernate.boot.archive.scan.spi.Scanner; +import org.hibernate.boot.archive.spi.InputStreamAccess; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.XmlMappingBinderAccess; +import org.hibernate.internal.CoreMessageLogger; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +/** + * @author Vlad Mihalcea + * @author Petteri Pitkanen + */ +public class ScanningCoordinatorTest extends BaseUnitTestCase { + + private ManagedResourcesImpl managedResources = Mockito.mock( ManagedResourcesImpl.class ); + private ScanResult scanResult = Mockito.mock( ScanResult.class ); + private BootstrapContext bootstrapContext = Mockito.mock( BootstrapContext.class ); + private XmlMappingBinderAccess xmlMappingBinderAccess = Mockito.mock( XmlMappingBinderAccess.class ); + + private ScanEnvironment scanEnvironment = Mockito.mock( ScanEnvironment.class ); + private StandardServiceRegistry serviceRegistry = Mockito.mock( StandardServiceRegistry.class ); + + private ClassLoaderService classLoaderService = Mockito.mock( ClassLoaderService.class ); + + private Triggerable triggerable; + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, ScanningCoordinator.class.getName() ) ); + + @Before + public void init(){ + Mockito.reset( scanResult ); + Mockito.reset( bootstrapContext ); + Mockito.reset( scanEnvironment ); + + when( bootstrapContext.getScanEnvironment() ).thenReturn( scanEnvironment ); + when( bootstrapContext.getServiceRegistry() ).thenReturn( serviceRegistry ); + when( serviceRegistry.getService( ClassLoaderService.class ) ).thenReturn( classLoaderService ); + + when( scanEnvironment.getExplicitlyListedClassNames() ).thenReturn( + Arrays.asList( "a.b.C" ) ); + + when( classLoaderService.classForName( "a.b.C" ) ).thenReturn( Object.class ); + + triggerable = logInspection.watchForLogMessages( "Unable" ); + triggerable.reset(); + } + + @Test + public void testApplyScanResultsToManagedResourcesWithNullRootUrl() { + + ScanningCoordinator.INSTANCE.applyScanResultsToManagedResources( + managedResources, + scanResult, + bootstrapContext, + xmlMappingBinderAccess + ); + assertEquals( "Unable to resolve class [a.b.C] named in persistence unit [null]", triggerable.triggerMessage() ); + } + + @Test + public void testApplyScanResultsToManagedResourcesWithNotNullRootUrl() + throws MalformedURLException { + when( scanEnvironment.getRootUrl() ).thenReturn( new URL( "http://http://hibernate.org/" ) ); + + ScanningCoordinator.INSTANCE.applyScanResultsToManagedResources( + managedResources, + scanResult, + bootstrapContext, + xmlMappingBinderAccess + ); + assertEquals( "Unable to resolve class [a.b.C] named in persistence unit [http://http://hibernate.org/]", triggerable.triggerMessage() ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12505" ) + public void testManagedResourcesAfterCoordinateScanWithDisabledScanner() { + assertManagedResourcesAfterCoordinateScanWithScanner( new DisabledScanner(), true ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12505" ) + public void testManagedResourcesAfterCoordinateScanWithCustomEnabledScanner() { + final Scanner scanner = new Scanner() { + @Override + public ScanResult scan(final ScanEnvironment environment, final ScanOptions options, final ScanParameters parameters) { + final InputStreamAccess dummyInputStreamAccess = new ByteArrayInputStreamAccess( "dummy", new byte[0] ); + return new ScanResultImpl( + Collections.singleton( new PackageDescriptorImpl( "dummy", dummyInputStreamAccess ) ), + Collections.singleton( new ClassDescriptorImpl( "dummy", ClassDescriptor.Categorization.MODEL, dummyInputStreamAccess ) ), + Collections.singleton( new MappingFileDescriptorImpl( "dummy", dummyInputStreamAccess ) ) + ); + } + }; + assertManagedResourcesAfterCoordinateScanWithScanner( scanner, false ); + } + + /** + * Run coordinateScan() with the given Scanner and assert the emptiness + * of ManagedResources. + */ + private void assertManagedResourcesAfterCoordinateScanWithScanner(final Scanner scanner, final boolean expectedIsManagedResourcesEmpty) { + when( bootstrapContext.getScanner() ).thenReturn( scanner ); + + final ManagedResourcesImpl managedResources = ManagedResourcesImpl.baseline( new MetadataSources(), bootstrapContext ); + + ScanningCoordinator.INSTANCE.coordinateScan( managedResources, bootstrapContext, xmlMappingBinderAccess ); + + assertEquals( 1, scanEnvironment.getExplicitlyListedClassNames().size() ); + assertEquals( "a.b.C", scanEnvironment.getExplicitlyListedClassNames().get(0) ); + + assertEquals( true, managedResources.getAttributeConverterDefinitions().isEmpty() ); + assertEquals( true, managedResources.getAnnotatedClassReferences().isEmpty() ); + assertEquals( expectedIsManagedResourcesEmpty, managedResources.getAnnotatedClassNames().isEmpty() ); + assertEquals( expectedIsManagedResourcesEmpty, managedResources.getAnnotatedPackageNames().isEmpty() ); + assertEquals( expectedIsManagedResourcesEmpty, managedResources.getXmlMappingBindings().isEmpty() ); + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/AnnotationEntity.java b/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/AnnotationEntity.java new file mode 100644 index 000000000000..a675e87eadfb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/AnnotationEntity.java @@ -0,0 +1,25 @@ +package org.hibernate.boot.model.source.internal.hbm; + +import javax.persistence.*; + +@Entity +public class AnnotationEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "annotationentity_id_seq") + @SequenceGenerator( + name = "annotationentity_id_seq", + sequenceName = "annotationentity_id_seq" + ) + private Long _id; + + /** + * Get the identifier. + * + * @return the id. + */ + public Long getId() + { + return _id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/HBMEntity.java b/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/HBMEntity.java new file mode 100644 index 000000000000..40546468cddd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/HBMEntity.java @@ -0,0 +1,23 @@ +package org.hibernate.boot.model.source.internal.hbm; + +public class HBMEntity { + + private long _id; + private AnnotationEntity _association; + + public long getId() { + return _id; + } + + public void setId(long id) { + _id = id; + } + + public AnnotationEntity getAssociation() { + return _association; + } + + public void setAssociation(AnnotationEntity association) { + _association = association; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/HBMManyToOneAnnotationMissingPrimaryKeyTest.java b/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/HBMManyToOneAnnotationMissingPrimaryKeyTest.java new file mode 100644 index 000000000000..d194e07f33aa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/model/source/internal/hbm/HBMManyToOneAnnotationMissingPrimaryKeyTest.java @@ -0,0 +1,41 @@ +package org.hibernate.boot.model.source.internal.hbm; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Test; + +/** + * https://hibernate.atlassian.net/browse/HHH-11502 + * + * @author Russ Tennant (russ@venturetech.net) + */ +public class HBMManyToOneAnnotationMissingPrimaryKeyTest extends BaseNonConfigCoreFunctionalTestCase +{ + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + AnnotationEntity.class + }; + } + + @Override + protected String[] getMappings() { + return new String[]{ + "HBMEntity.hbm.xml" + }; + } + + @Override + protected String getBaseForMappings() { + return "/org/hibernate/boot/model/source/internal/hbm/"; + } + + /** + * Test to trigger the NullPointerException in the ModelBinder. + * @throws Exception on error. + */ + @Test + public void hhh11502() throws Exception { + Assert.assertTrue(true); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadata.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadata.java new file mode 100644 index 000000000000..4a3288f102c4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadata.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.delegation; + +import org.hibernate.boot.spi.AbstractDelegatingMetadata; +import org.hibernate.boot.spi.MetadataImplementor; + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingMetadata extends AbstractDelegatingMetadata { + + public TestDelegatingMetadata(MetadataImplementor delegate) { + super( delegate ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadataBuilderImplementor.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadataBuilderImplementor.java new file mode 100644 index 000000000000..bca2fe15387f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadataBuilderImplementor.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.delegation; + +import org.hibernate.boot.spi.AbstractDelegatingMetadataBuilderImplementor; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.MetadataBuilderImplementor; + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingMetadataBuilderImplementor extends AbstractDelegatingMetadataBuilderImplementor { + + public TestDelegatingMetadataBuilderImplementor(MetadataBuilderImplementor delegate) { + super( delegate ); + } + + @Override + protected TestDelegatingMetadataBuilderImplementor getThis() { + return this; + } + + @Override + public BootstrapContext getBootstrapContext() { + return delegate().getBootstrapContext(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadataBuildingOptions.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadataBuildingOptions.java new file mode 100644 index 000000000000..58cc81306f3c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingMetadataBuildingOptions.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.delegation; + +import org.hibernate.boot.spi.AbstractDelegatingMetadataBuildingOptions; +import org.hibernate.boot.spi.MetadataBuildingOptions; + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingMetadataBuildingOptions extends AbstractDelegatingMetadataBuildingOptions { + + public TestDelegatingMetadataBuildingOptions(MetadataBuildingOptions delegate) { + super( delegate ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryBuilder.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryBuilder.java new file mode 100644 index 000000000000..7c43bc26f440 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryBuilder.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.boot.spi.delegation; + +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.spi.AbstractDelegatingSessionFactoryBuilder; + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingSessionFactoryBuilder extends AbstractDelegatingSessionFactoryBuilder { + + public TestDelegatingSessionFactoryBuilder(SessionFactoryBuilder delegate) { + super( delegate ); + } + + @Override + protected TestDelegatingSessionFactoryBuilder getThis() { + return this; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryBuilderImplementor.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryBuilderImplementor.java new file mode 100644 index 000000000000..2182f2aa4d5a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryBuilderImplementor.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 . + */ +package org.hibernate.boot.spi.delegation; + +import org.hibernate.boot.spi.AbstractDelegatingSessionFactoryBuilderImplementor; +import org.hibernate.boot.spi.SessionFactoryBuilderImplementor; + + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingSessionFactoryBuilderImplementor extends AbstractDelegatingSessionFactoryBuilderImplementor { + + public TestDelegatingSessionFactoryBuilderImplementor(SessionFactoryBuilderImplementor delegate) { + super( delegate ); + } + + @Override + protected TestDelegatingSessionFactoryBuilderImplementor getThis() { + return this; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryOptions.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryOptions.java new file mode 100644 index 000000000000..d05b6d40acef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/delegation/TestDelegatingSessionFactoryOptions.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.delegation; + +import org.hibernate.boot.spi.AbstractDelegatingSessionFactoryOptions; +import org.hibernate.boot.spi.SessionFactoryOptions; + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingSessionFactoryOptions extends AbstractDelegatingSessionFactoryOptions { + + public TestDelegatingSessionFactoryOptions(SessionFactoryOptions delegate) { + super( delegate ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/AbstractSqlFunctionMetadataBuilderContributorTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/AbstractSqlFunctionMetadataBuilderContributorTest.java new file mode 100644 index 000000000000..1e1f468f709c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/AbstractSqlFunctionMetadataBuilderContributorTest.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.metadatabuildercontributor; + +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue( jiraKey = "HHH-12589" ) +public abstract class AbstractSqlFunctionMetadataBuilderContributorTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( + EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR, + matadataBuilderContributor() + ); + } + + protected abstract Object matadataBuilderContributor(); + + final Employee employee = new Employee(); + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + employee.id = 1L; + employee.username = "user@acme.com"; + + entityManager.persist( employee ); + } ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + String result = (String) entityManager.createQuery( + "select INSTR(e.username,'@acme.com') " + + "from Employee e " + + "where " + + " e.id = :employeeId") + .setParameter( "employeeId", employee.id ) + .getSingleResult(); + + assertEquals( "5", result ); + } ); + } + + @Entity(name = "Employee") + public static class Employee { + + @Id + private Long id; + + @NaturalId + private String username; + + private String password; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributor.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributor.java new file mode 100644 index 000000000000..ba4cea8e625f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributor.java @@ -0,0 +1,22 @@ +package org.hibernate.boot.spi.metadatabuildercontributor; + +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.spi.MetadataBuilderContributor; +import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.type.StandardBasicTypes; + +/** + * @author Vlad Mihalcea + */ +//tag::bootstrap-jpa-compliant-MetadataBuilderContributor-example[] +public class SqlFunctionMetadataBuilderContributor + implements MetadataBuilderContributor { + + @Override + public void contribute(MetadataBuilder metadataBuilder) { + metadataBuilder.applySqlFunction( + "instr", new StandardSQLFunction( "instr", StandardBasicTypes.STRING ) + ); + } +} +//end::bootstrap-jpa-compliant-MetadataBuilderContributor-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorClassNameTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorClassNameTest.java new file mode 100644 index 000000000000..228bf00fd4e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorClassNameTest.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.metadatabuildercontributor; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue( jiraKey = "HHH-12589" ) +public class SqlFunctionMetadataBuilderContributorClassNameTest + extends AbstractSqlFunctionMetadataBuilderContributorTest { + + @Override + protected Object matadataBuilderContributor() { + return SqlFunctionMetadataBuilderContributor.class.getName(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorClassTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorClassTest.java new file mode 100644 index 000000000000..1ceac696690e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorClassTest.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 . + */ +package org.hibernate.boot.spi.metadatabuildercontributor; + +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue( jiraKey = "HHH-12589" ) +public class SqlFunctionMetadataBuilderContributorClassTest + extends AbstractSqlFunctionMetadataBuilderContributorTest { + + @Override + protected Object matadataBuilderContributor() { + return SqlFunctionMetadataBuilderContributor.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalArgumentTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalArgumentTest.java new file mode 100644 index 000000000000..5aeaf44ba760 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalArgumentTest.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.metadatabuildercontributor; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue( jiraKey = "HHH-12589" ) +public class SqlFunctionMetadataBuilderContributorIllegalArgumentTest + extends AbstractSqlFunctionMetadataBuilderContributorTest { + + @Override + protected Object matadataBuilderContributor() { + return new Object(); + } + + @Override + public void buildEntityManagerFactory() { + try { + super.buildEntityManagerFactory(); + + fail("Should throw exception!"); + } + catch (IllegalArgumentException e) { + assertTrue( e.getMessage().startsWith( "The provided hibernate.metadata_builder_contributor setting value" ) ); + } + } + + @Override + public void test() { + try { + super.test(); + + fail("Should throw exception!"); + } + catch (Exception expected) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java new file mode 100644 index 000000000000..8c1c331119be --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.metadatabuildercontributor; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue( jiraKey = "HHH-12589" ) +public class SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest + extends AbstractSqlFunctionMetadataBuilderContributorTest { + + @Override + protected Object matadataBuilderContributor() { + return this.getClass(); + } + + @Override + public void buildEntityManagerFactory() { + try { + super.buildEntityManagerFactory(); + + fail("Should throw exception!"); + } + catch (ClassCastException e) { + assertTrue( e.getMessage().contains( "cannot be cast to" ) ); + assertTrue( e.getMessage().contains( "org.hibernate.boot.spi.MetadataBuilderContributor" ) ); + } + } + + @Override + public void test() { + try { + super.test(); + + fail("Should throw exception!"); + } + catch (Exception expected) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorInstanceTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorInstanceTest.java new file mode 100644 index 000000000000..9bbe917152dd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorInstanceTest.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.spi.metadatabuildercontributor; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue( jiraKey = "HHH-12589" ) +public class SqlFunctionMetadataBuilderContributorInstanceTest + extends AbstractSqlFunctionMetadataBuilderContributorTest { + + @Override + protected Object matadataBuilderContributor() { + return new SqlFunctionMetadataBuilderContributor(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMissingTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMissingTest.java new file mode 100644 index 000000000000..b8697eec7b41 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMissingTest.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.boot.spi.metadatabuildercontributor; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.util.ExceptionUtil; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue( jiraKey = "HHH-12589" ) +public class SqlFunctionMissingTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + }; + } + + final Employee employee = new Employee(); + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + employee.id = 1L; + employee.username = "user@acme.com"; + + entityManager.persist( employee ); + } ); + } + + @Test + public void test() { + try { + doInJPA( this::entityManagerFactory, entityManager -> { + Number result = (Number) entityManager.createQuery( + "select INSTR(e.username,'@') " + + "from Employee e " + + "where " + + " e.id = :employeeId") + .setParameter( "employeeId", employee.id ) + .getSingleResult(); + + fail("Should throw exception!"); + } ); + } + catch (Exception expected) { + assertTrue( ExceptionUtil.rootCause( expected ).getMessage().contains( "No data type for node: org.hibernate.hql.internal.ast.tree.MethodNod" ) ); + } + } + + @Entity(name = "Employee") + public static class Employee { + + @Id + private Long id; + + @NaturalId + private String username; + + private String password; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java new file mode 100644 index 000000000000..afc655116b2c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java @@ -0,0 +1,92 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.hibernate.bytecode.internal.bytebuddy.BasicProxyFactoryImpl; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12786") +public class ByteBuddyBasicProxyFactoryTest { + + private static final BasicProxyFactoryImpl BASIC_PROXY_FACTORY = new BasicProxyFactoryImpl( Entity.class, new Class[0], new ByteBuddyState() ); + + @Test + public void testEqualsHashCode() { + Object entityProxy = BASIC_PROXY_FACTORY.getProxy(); + + assertTrue( entityProxy.equals( entityProxy ) ); + assertNotNull( entityProxy.hashCode() ); + + Object otherEntityProxy = BASIC_PROXY_FACTORY.getProxy(); + assertFalse( entityProxy.equals( otherEntityProxy ) ); + } + + @Test + public void testToString() { + Object entityProxy = BASIC_PROXY_FACTORY.getProxy(); + + assertTrue( entityProxy.toString().contains( "HibernateBasicProxy" ) ); + } + + @Test + public void testGetterSetter() { + Entity entityProxy = (Entity) BASIC_PROXY_FACTORY.getProxy(); + + entityProxy.setBool( true ); + assertTrue( entityProxy.isBool() ); + entityProxy.setBool( false ); + assertFalse( entityProxy.isBool() ); + + entityProxy.setString( "John Irving" ); + assertEquals( "John Irving", entityProxy.getString() ); + } + + @Test + public void testNonGetterSetterMethod() { + Entity entityProxy = (Entity) BASIC_PROXY_FACTORY.getProxy(); + + assertNull( entityProxy.otherMethod() ); + } + + public static class Entity { + + private String string; + + private boolean bool; + + public Entity() { + } + + public boolean isBool() { + return bool; + } + + public void setBool(boolean bool) { + this.bool = bool; + } + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } + + public String otherMethod() { + return "a string"; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java new file mode 100644 index 000000000000..8189554a17d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests that bytecode can be enhanced when the original class cannot be loaded from + * the ClassLoader provided to ByteBuddy. + */ +public class EnhanceByteCodeNotInProvidedClassLoaderTest { + + @Test + @TestForIssue( jiraKey = "HHH-13343" ) + public void test() { + Enhancer enhancer = createByteBuddyEnhancer(); + byte[] buffer = readResource( SimpleEntity.class ); + // Now use a fake class name so it won't be found in the ClassLoader + // provided by DefaultEnhancementContext + byte[] enhanced = enhancer.enhance( SimpleEntity.class.getName() + "Fake", buffer ); + Assert.assertNotNull( "This is null when there have been swallowed exceptions during enhancement. Check Logs!", enhanced ); + // Make sure enhanced bytecode is different from original bytecode. + Assert.assertFalse( Arrays.equals( buffer, enhanced ) ); + } + + private byte[] readResource(Class clazz) { + String internalName = clazz.getName().replace( '.', '/' ); + String resourceName = internalName + ".class"; + + final int BUF_SIZE = 256; + byte[] buffer = new byte[BUF_SIZE]; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int readSize = 0; + try ( InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream( resourceName ) ) { + while ( ( readSize = inputStream.read( buffer ) ) != -1 ) { + os.write( buffer, 0, readSize ); + } + os.flush(); + os.close(); + } + catch (IOException ex) { + Assert.fail( "Should not have an IOException here" ); + } + return os.toByteArray(); + } + + private Enhancer createByteBuddyEnhancer() { + ByteBuddyState bytebuddy = new ByteBuddyState(); + DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext(); + EnhancerImpl impl = new EnhancerImpl( enhancementContext, bytebuddy ); + return impl; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java new file mode 100644 index 000000000000..42de28c88cf3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.test.bytecode.Bean; +import org.junit.Assert; +import org.junit.Test; + +/** + * WildFly will use class names in "internal JVM format" when invoking the enhancer, + * meaning the package separator is '/' rather than '.'. + * We need to make sure this is handled. + */ +public class EnhancerWildFlyNamesTest { + + @Test + @TestForIssue( jiraKey = "HHH-12545" ) + public void test() { + Enhancer enhancer = createByteBuddyEnhancer(); + String internalName = SimpleEntity.class.getName().replace( '.', '/' ); + String resourceName = internalName + ".class"; + byte[] buffer = new byte[0]; + try { + buffer = readResource( resourceName ); + } + catch (IOException e) { + Assert.fail( "Should not have an IOException here" ); + } + byte[] enhanced = enhancer.enhance( internalName, buffer ); + Assert.assertNotNull( "This is null when there have been swallowed exceptions during enhancement. Check Logs!", enhanced ); + } + + private byte[] readResource(String resourceName) throws IOException { + final int BUF_SIZE = 256; + byte[] buffer = new byte[BUF_SIZE]; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int readSize = 0; + try ( InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream( resourceName ) ) { + while ( ( readSize = inputStream.read( buffer ) ) != -1 ) { + os.write( buffer, 0, readSize ); + } + os.flush(); + os.close(); + } + return os.toByteArray(); + } + + private Enhancer createByteBuddyEnhancer() { + ByteBuddyState bytebuddy = new ByteBuddyState(); + DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext(); + EnhancerImpl impl = new EnhancerImpl( enhancementContext, bytebuddy ); + return impl; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java new file mode 100644 index 000000000000..3c016a2e949e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java @@ -0,0 +1,53 @@ +package org.hibernate.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.spi.ByteCodeHelper; +import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; +import org.junit.Test; + +public class GenerateProxiesTest { + + @Test + public void generateBasicProxy() { + BasicProxyFactoryImpl basicProxyFactory = new BasicProxyFactoryImpl( SimpleEntity.class, new Class[0], + new ByteBuddyState() ); + assertNotNull( basicProxyFactory.getProxy() ); + } + + @Test + public void generateProxy() throws InstantiationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException, SecurityException { + ByteBuddyProxyHelper byteBuddyProxyHelper = new ByteBuddyProxyHelper( new ByteBuddyState() ); + Class proxyClass = byteBuddyProxyHelper.buildProxy( SimpleEntity.class, new Class[0] ); + assertNotNull( proxyClass ); + assertNotNull( proxyClass.getConstructor().newInstance() ); + } + + @Test + public void generateFastClassAndReflectionOptimizer() { + BytecodeProviderImpl bytecodeProvider = new BytecodeProviderImpl(); + ReflectionOptimizer reflectionOptimizer = bytecodeProvider.getReflectionOptimizer( SimpleEntity.class, + new String[]{ "getId", "getName" }, new String[]{ "setId", "setName" }, + new Class[]{ Long.class, String.class } ); + assertEquals( 2, reflectionOptimizer.getAccessOptimizer().getPropertyNames().length ); + assertNotNull( reflectionOptimizer.getInstantiationOptimizer().newInstance() ); + } + + @Test + public void generateEnhancedClass() throws EnhancementException, IOException { + Enhancer enhancer = new EnhancerImpl( new DefaultEnhancementContext(), new ByteBuddyState() ); + enhancer.enhance( SimpleEntity.class.getName(), + ByteCodeHelper.readByteCode( SimpleEntity.class.getClassLoader() + .getResourceAsStream( SimpleEntity.class.getName().replace( '.', '/' ) + ".class" ) ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java new file mode 100644 index 000000000000..184eca1a6bd6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Method; + +import org.junit.Test; + +public class HibernateMethodLookupDispatcherTest { + + @Test + public void testAuthorizedClass() { + HibernateMethodLookupDispatcher.registerAuthorizedClass( AuthorizedClass.class.getName() ); + + AuthorizedClass authorizedClass = new AuthorizedClass(); + assertNotNull( authorizedClass.declaredMethod ); + assertEquals( "myMethod", authorizedClass.declaredMethod.getName() ); + } + + @Test( expected = SecurityException.class ) + public void testUnauthorizedClass() { + new UnauthorizedClass(); + } + + public static class AuthorizedClass { + + private Method declaredMethod; + + public AuthorizedClass() { + declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod", + new Class[]{ String.class } ); + } + + public void myMethod(String myParameter) { + } + } + + public static class UnauthorizedClass { + + @SuppressWarnings("unused") + private Method declaredMethod; + + public UnauthorizedClass() { + declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod", + new Class[]{ String.class } ); + } + + public void myMethod(String myParameter) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java new file mode 100644 index 000000000000..e402cf1e000c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import java.util.regex.Pattern; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity(name = "SimpleEntity") +public class SimpleEntity { + + private static final Pattern PATTERN = Pattern.compile( "whatever" ); + + @Id + @GeneratedValue + private Long id; + + @Basic(fetch = FetchType.LAZY) + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/cache/internal/CacheKeyImplementationHashCodeTest.java b/hibernate-core/src/test/java/org/hibernate/cache/internal/CacheKeyImplementationHashCodeTest.java new file mode 100644 index 000000000000..89160be926bb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/cache/internal/CacheKeyImplementationHashCodeTest.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.internal; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; + +/** + * @author Gail Badner + */ +public class CacheKeyImplementationHashCodeTest { + + @Test + @TestForIssue( jiraKey = "HHH-12746") + public void test() { + ServiceRegistryImplementor serviceRegistry = ( + ServiceRegistryImplementor) new StandardServiceRegistryBuilder().build(); + MetadataSources ms = new MetadataSources( serviceRegistry ); + ms.addAnnotatedClass( AnEntity.class ).addAnnotatedClass( AnotherEntity.class ); + Metadata metadata = ms.buildMetadata(); + final SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder(); + SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) sfb.build(); + + + CacheKeyImplementation anEntityCacheKey = createCacheKeyImplementation( + 1, sessionFactory.getMetamodel().entityPersister( AnEntity.class ), sessionFactory + ); + CacheKeyImplementation anotherEntityCacheKey = createCacheKeyImplementation( + 1, sessionFactory.getMetamodel().entityPersister( AnotherEntity.class ), sessionFactory + ); + assertFalse( anEntityCacheKey.equals( anotherEntityCacheKey ) ); + } + + private CacheKeyImplementation createCacheKeyImplementation( + int id, + EntityPersister persister, + SessionFactoryImplementor sfi) { + return new CacheKeyImplementation( id, persister.getIdentifierType(), persister.getRootEntityName(), null, sfi ); + } + + @Entity(name = "AnEntity") + @Cache( usage = CacheConcurrencyStrategy.READ_WRITE) + public static class AnEntity { + @Id + @GeneratedValue + private int id; + } + + @Entity(name = "AnotherEntity") + @Cache( usage = CacheConcurrencyStrategy.READ_WRITE) + public static class AnotherEntity { + @Id + @GeneratedValue + private int id; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/cache/spi/QueryKeyTest.java b/hibernate-core/src/test/java/org/hibernate/cache/spi/QueryKeyTest.java index 7d197474993c..becdae8f44e6 100644 --- a/hibernate-core/src/test/java/org/hibernate/cache/spi/QueryKeyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/cache/spi/QueryKeyTest.java @@ -61,7 +61,7 @@ public void setPropAccessedByMethod(String propAccessedByMethod) { public void testSerializedEqualityResultTransformer() throws Exception { // settings are lazily initialized when calling transformTuple(), // so they have not been initialized for the following test - // (it *should* be initialized beforeQuery creating a QueryKey) + // (it *should* be initialized before creating a QueryKey) doResultTransformerTest( new AliasToBeanResultTransformer( AClass.class ), false ); // initialize settings for the next test 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 57204005881a..edc30e455e9f 100644 --- a/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java @@ -6,8 +6,12 @@ */ package org.hibernate.cfg.annotations; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import java.sql.SQLException; -import java.util.Map; +import java.util.HashMap; import org.hibernate.MappingException; import org.hibernate.annotations.common.reflection.XClass; @@ -17,17 +21,11 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; - import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; - import org.mockito.Mockito; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - /** * Test for HHH-10106 * @@ -39,20 +37,18 @@ public class CollectionBinderTest extends BaseUnitTestCase { @TestForIssue(jiraKey = "HHH-10106") public void testAssociatedClassException() throws SQLException { final Collection collection = mock(Collection.class); - final Map persistentClasses = mock(Map.class); final XClass collectionType = mock(XClass.class); final MetadataBuildingContext buildingContext = mock(MetadataBuildingContext.class); final InFlightMetadataCollector inFly = mock(InFlightMetadataCollector.class); final PersistentClass persistentClass = mock(PersistentClass.class); final Table table = mock(Table.class); - + when(buildingContext.getMetadataCollector()).thenReturn(inFly); - when(persistentClasses.get(null)).thenReturn(null); when(collection.getOwner()).thenReturn(persistentClass); when(collectionType.getName()).thenReturn("List"); when(persistentClass.getTable()).thenReturn(table); when(table.getName()).thenReturn("Hibernate"); - + CollectionBinder collectionBinder = new CollectionBinder(false) { @Override protected Collection createCollection(PersistentClass persistentClass) { @@ -69,7 +65,7 @@ protected Collection createCollection(PersistentClass persistentClass) { String expectMessage = "Association [abc] for entity [CollectionBinderTest] references unmapped class [List]"; try { - collectionBinder.bindOneToManySecondPass(collection, persistentClasses, null, collectionType, false, false, buildingContext, null); + collectionBinder.bindOneToManySecondPass(collection, new HashMap(), null, collectionType, false, false, buildingContext, null); } catch (MappingException e) { assertEquals(expectMessage, e.getMessage()); } diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/DB2390DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/DB2390DialectTestCase.java new file mode 100644 index 000000000000..965567044334 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/DB2390DialectTestCase.java @@ -0,0 +1,115 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-11747") +@RequiresDialect(DB2390Dialect.class) +public class DB2390DialectTestCase extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( AvailableSettings.USE_LEGACY_LIMIT_HANDLERS, Boolean.TRUE ); + } + + @Test + public void testLegacyLimitHandlerWithNoOffset() { + doInJPA( this::entityManagerFactory, entityManager -> { + List results = entityManager.createQuery( "FROM SimpleEntity", SimpleEntity.class ) + .setMaxResults( 2 ) + .getResultList(); + assertEquals( Arrays.asList( 0, 1 ), results.stream().map( SimpleEntity::getId ).collect( Collectors.toList() ) ); + } ); + } + + @Test + public void testLegacyLimitHandlerWithOffset() { + doInJPA( this::entityManagerFactory, entityManager -> { + List results = entityManager.createQuery( "FROM SimpleEntity", SimpleEntity.class ) + .setFirstResult( 2 ) + .setMaxResults( 2 ) + .getResultList(); + assertEquals( Arrays.asList( 2, 3 ), results.stream().map( SimpleEntity::getId ).collect( Collectors.toList() ) ); + } ); + } + + @Before + public void populateSchema() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( int i = 0; i < 10; ++i ) { + final SimpleEntity simpleEntity = new SimpleEntity( i, "Entity" + i ); + entityManager.persist( simpleEntity ); + } + } ); + } + + @After + public void cleanSchema() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "DELETE FROM SimpleEntity" ).executeUpdate(); + } ); + } + + @Entity(name = "SimpleEntity") + public static class SimpleEntity { + @Id + private Integer id; + private String name; + + public SimpleEntity() { + + } + + public SimpleEntity(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; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/DB2DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/DB2DialectTestCase.java index 83f983a90bca..80dadb437d04 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/DB2DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/DB2DialectTestCase.java @@ -8,6 +8,7 @@ import java.sql.Types; +import org.hibernate.engine.spi.RowSelection; import org.junit.Test; import org.hibernate.mapping.Column; @@ -15,6 +16,7 @@ import org.hibernate.testing.junit4.BaseUnitTestCase; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * DB2 dialect related test cases @@ -63,4 +65,17 @@ public void testGetExplicitBinaryTypeName() { actual ); } + + @Test + @TestForIssue(jiraKey = "HHH-12369") + public void testIntegerOverflowForMaxResults() { + RowSelection rowSelection = new RowSelection(); + rowSelection.setFirstRow(1); + rowSelection.setMaxRows(Integer.MAX_VALUE); + String sql = dialect.getLimitHandler().processSql( "select a.id from tbl_a a order by a.id", rowSelection ); + assertTrue( + "Integer overflow for max rows in: " + sql, + sql.contains("fetch first 2147483647 rows only") + ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/InformixLimitHandlerTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/InformixLimitHandlerTestCase.java new file mode 100644 index 000000000000..2633fbe14a29 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/InformixLimitHandlerTestCase.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 . + */ +package org.hibernate.dialect; + +import org.hibernate.engine.spi.RowSelection; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; +import org.hibernate.dialect.pagination.Informix10LimitHandler; +import org.junit.Before; +import static org.junit.Assert.assertEquals; + +public class InformixLimitHandlerTestCase extends + BaseNonConfigCoreFunctionalTestCase { + + private Informix10LimitHandler informixLimitHandler; + + private final String TEST_SQL = "SELECT field FROM table"; + + @Before + public void setup() { + informixLimitHandler = Informix10LimitHandler.INSTANCE; + } + + @Test + @TestForIssue(jiraKey = "HHH-11509") + public void testCorrectLimit() { + assertLimitHandlerEquals( "SELECT FIRST 10 field FROM table", 0, 10 ); + assertLimitHandlerEquals( "SELECT SKIP 3 FIRST 5 field FROM table", 3, 5 ); + assertLimitHandlerEquals( "SELECT SKIP 10 FIRST 5 field FROM table", 10, 5 ); + assertLimitHandlerEquals( "SELECT SKIP 55 FIRST 12 field FROM table", 55, 12 ); + } + + private void assertLimitHandlerEquals(String sql, int firstRow, int maxRows) { + assertEquals( sql, informixLimitHandler.processSql( TEST_SQL, toRowSelection( firstRow, maxRows ) ) ); + } + + private RowSelection toRowSelection(int firstRow, int maxRows) { + RowSelection selection = new RowSelection(); + selection.setFirstRow( firstRow ); + selection.setMaxRows( maxRows ); + return selection; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL92DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL92DialectTestCase.java new file mode 100644 index 000000000000..dc46d2d6aedc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL92DialectTestCase.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test case for PostgreSQL 9.2 specific things. + * + * @author Christoph Dreis + */ +@TestForIssue( jiraKey = "HHH-11647") +public class PostgreSQL92DialectTestCase extends BaseUnitTestCase { + + /** + * Tests that getAlterTableString() will make use of IF EXISTS syntax + */ + @Test + @TestForIssue( jiraKey = "HHH-11647" ) + public void testGetAlterTableString() { + PostgreSQL92Dialect dialect = new PostgreSQL92Dialect(); + + assertEquals("alter table if exists table_name", dialect.getAlterTableString( "table_name" )); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java index c2b859c68232..e84df4a5bcc6 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java @@ -386,6 +386,18 @@ public void testGetLimitStringWithSelectClauseNestedQueryUsingParenthesis() { ); } + @Test + @TestForIssue(jiraKey = "HHH-11650") + public void testGetLimitWithStringValueContainingParenthesis() { + final String query = "select t1.c1 as col_0_0 FROM table1 t1 where t1.c1 = '(123' ORDER BY t1.c1 ASC"; + + assertEquals( + "WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " + + "select TOP(?) t1.c1 as col_0_0 FROM table1 t1 where t1.c1 = '(123' ORDER BY t1.c1 ASC ) inner_query ) SELECT col_0_0 FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( query, toRowSelection( 1, 5 ) ) + ); + } + @Test @TestForIssue(jiraKey = "HHH-11324") public void testGetLimitStringWithSelectClauseNestedQueryUsingParenthesisOnlyTop() { @@ -397,6 +409,92 @@ public void testGetLimitStringWithSelectClauseNestedQueryUsingParenthesisOnlyTop ); } + @Test + @TestForIssue(jiraKey = "HHH-8916") + public void testGetLimitStringUsingCTEQueryNoOffset() { + RowSelection selection = toRowSelection( 0, 5 ); + + // test top-based CTE with single CTE query definition with no odd formatting + final String query1 = "WITH a (c1, c2) AS (SELECT c1, c2 FROM t) SELECT c1, c2 FROM a"; + assertEquals( + "WITH a (c1, c2) AS (SELECT c1, c2 FROM t) SELECT TOP(?) c1, c2 FROM a", + dialect.getLimitHandler().processSql( query1, selection ) + ); + + // test top-based CTE with single CTE query definition and various tab, newline spaces + final String query2 = " \n\tWITH a (c1\n\t,c2)\t\nAS (SELECT\n\tc1,c2 FROM t)\t\nSELECT c1, c2 FROM a"; + assertEquals( + " \n\tWITH a (c1\n\t,c2)\t\nAS (SELECT\n\tc1,c2 FROM t)\t\nSELECT TOP(?) c1, c2 FROM a", + dialect.getLimitHandler().processSql( query2, selection ) + ); + + // test top-based CTE with multiple CTE query definitions with no odd formatting + final String query3 = "WITH a (c1, c2) AS (SELECT c1, c2 FROM t1), b (b1, b2) AS (SELECT b1, b2 FROM t2) " + + "SELECT c1, c2, b1, b2 FROM t1, t2 WHERE t1.c1 = t2.b1"; + assertEquals( + "WITH a (c1, c2) AS (SELECT c1, c2 FROM t1), b (b1, b2) AS (SELECT b1, b2 FROM t2) " + + "SELECT TOP(?) c1, c2, b1, b2 FROM t1, t2 WHERE t1.c1 = t2.b1", + dialect.getLimitHandler().processSql( query3, selection ) + ); + + // test top-based CTE with multiple CTE query definitions and various tab, newline spaces + final String query4 = " \n\r\tWITH a (c1, c2) AS\n\r (SELECT c1, c2 FROM t1)\n\r, b (b1, b2)\tAS\t" + + "(SELECT b1, b2 FROM t2) SELECT c1, c2, b1, b2 FROM t1, t2 WHERE t1.c1 = t2.b1"; + assertEquals( + " \n\r\tWITH a (c1, c2) AS\n\r (SELECT c1, c2 FROM t1)\n\r, b (b1, b2)\tAS\t(SELECT b1, b2 FROM t2)" + + " SELECT TOP(?) c1, c2, b1, b2 FROM t1, t2 WHERE t1.c1 = t2.b1", + dialect.getLimitHandler().processSql( query4, selection ) + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-8916") + public void testGetLimitStringUsingCTEQueryWithOffset() { + RowSelection selection = toRowSelection( 1, 5 ); + + // test non-top based CTE with single CTE query definition with no odd formatting + final String query1 = "WITH a (c1, c2) AS (SELECT c1, c2 FROM t) SELECT c1, c2 FROM a"; + assertEquals( + "WITH a (c1, c2) AS (SELECT c1, c2 FROM t), query AS (SELECT inner_query.*, ROW_NUMBER() OVER " + + "(ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( SELECT c1 as page0_, c2 as page1_ " + + "FROM a ) inner_query ) SELECT page0_, page1_ FROM query WHERE __hibernate_row_nr__ >= ? " + + "AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( query1, selection ) + ); + + // test non-top based CTE with single CTE query definition and various tab, newline spaces + final String query2 = " \n\tWITH a (c1\n\t,c2)\t\nAS (SELECT\n\tc1,c2 FROM t)\t\nSELECT c1, c2 FROM a"; + assertEquals( + " \n\tWITH a (c1\n\t,c2)\t\nAS (SELECT\n\tc1,c2 FROM t), query AS (SELECT inner_query.*, ROW_NUMBER()" + + " OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( \t\nSELECT c1 as page0_, c2 " + + "as page1_ FROM a ) inner_query ) SELECT page0_, page1_ FROM query WHERE __hibernate_row_nr__ >= " + + "? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( query2, selection ) + ); + + // test non-top based CTE with multiple CTE query definitions with no odd formatting + final String query3 = "WITH a (c1, c2) AS (SELECT c1, c2 FROM t1), b (b1, b2) AS (SELECT b1, b2 FROM t2) " + + " SELECT c1, c2, b1, b2 FROM t1, t2 WHERE t1.c1 = t2.b1"; + assertEquals( + "WITH a (c1, c2) AS (SELECT c1, c2 FROM t1), b (b1, b2) AS (SELECT b1, b2 FROM t2), query AS (" + + "SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM (" + + " SELECT c1 as page0_, c2 as page1_, b1 as page2_, b2 as page3_ FROM t1, t2 WHERE t1.c1 = t2.b1 ) inner_query )" + + " SELECT page0_, page1_, page2_, page3_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( query3, selection ) + ); + + // test top-based CTE with multiple CTE query definitions and various tab, newline spaces + final String query4 = " \n\r\tWITH a (c1, c2) AS\n\r (SELECT c1, c2 FROM t1)\n\r, b (b1, b2)\tAS\t(SELECT b1, " + + "b2 FROM t2) SELECT c1, c2, b1, b2 FROM t1, t2 WHERE t1.c1 = t2.b1"; + assertEquals( + " \n\r\tWITH a (c1, c2) AS\n\r (SELECT c1, c2 FROM t1)\n\r, b (b1, b2)\tAS\t(SELECT b1, b2 FROM t2), query AS (" + + "SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM (" + + " SELECT c1 as page0_, c2 as page1_, b1 as page2_, b2 as page3_ FROM t1, t2 WHERE t1.c1 = t2.b1 ) inner_query )" + + " SELECT page0_, page1_, page2_, page3_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( query4, selection ) + ); + } + @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintReadPastLocking() { @@ -446,7 +544,7 @@ public void testAppendLockHintPessimisticReadNoTimeOut() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintWrite() { - final String expectedLockHint = "tab1 with (updlock, rowlock)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock)"; LockOptions lockOptions = new LockOptions( LockMode.WRITE ); String lockHint = dialect.appendLockHint( lockOptions, "tab1" ); @@ -457,7 +555,7 @@ public void testAppendLockHintWrite() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintWriteWithNoTimeOut() { - final String expectedLockHint = "tab1 with (updlock, rowlock, nowait)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock, nowait)"; LockOptions lockOptions = new LockOptions( LockMode.WRITE ); lockOptions.setTimeOut( LockOptions.NO_WAIT ); @@ -470,7 +568,7 @@ public void testAppendLockHintWriteWithNoTimeOut() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintUpgradeNoWait() { - final String expectedLockHint = "tab1 with (updlock, rowlock, nowait)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock, nowait)"; LockOptions lockOptions = new LockOptions( LockMode.UPGRADE_NOWAIT ); String lockHint = dialect.appendLockHint( lockOptions, "tab1" ); @@ -481,7 +579,7 @@ public void testAppendLockHintUpgradeNoWait() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintUpgradeNoWaitNoTimeout() { - final String expectedLockHint = "tab1 with (updlock, rowlock, nowait)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock, nowait)"; LockOptions lockOptions = new LockOptions( LockMode.UPGRADE_NOWAIT ); lockOptions.setTimeOut( LockOptions.NO_WAIT ); @@ -493,7 +591,7 @@ public void testAppendLockHintUpgradeNoWaitNoTimeout() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintUpgrade() { - final String expectedLockHint = "tab1 with (updlock, rowlock)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock)"; LockOptions lockOptions = new LockOptions( LockMode.UPGRADE ); String lockHint = dialect.appendLockHint( lockOptions, "tab1" ); @@ -504,7 +602,7 @@ public void testAppendLockHintUpgrade() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintUpgradeNoTimeout() { - final String expectedLockHint = "tab1 with (updlock, rowlock, nowait)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock, nowait)"; LockOptions lockOptions = new LockOptions( LockMode.UPGRADE ); lockOptions.setTimeOut( LockOptions.NO_WAIT ); @@ -516,7 +614,7 @@ public void testAppendLockHintUpgradeNoTimeout() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintPessimisticWrite() { - final String expectedLockHint = "tab1 with (updlock, rowlock)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock)"; LockOptions lockOptions = new LockOptions( LockMode.UPGRADE ); String lockHint = dialect.appendLockHint( lockOptions, "tab1" ); @@ -527,7 +625,7 @@ public void testAppendLockHintPessimisticWrite() { @Test @TestForIssue(jiraKey = "HHH-9635") public void testAppendLockHintPessimisticWriteNoTimeOut() { - final String expectedLockHint = "tab1 with (updlock, rowlock, nowait)"; + final String expectedLockHint = "tab1 with (updlock, holdlock, rowlock, nowait)"; LockOptions lockOptions = new LockOptions( LockMode.UPGRADE ); lockOptions.setTimeOut( LockOptions.NO_WAIT ); diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java index 592d1c3f8ff7..468daf591f42 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java @@ -41,7 +41,7 @@ public void tearDown() { public void testGetLimitStringMaxRowsOnly() { final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc"; assertEquals( - input + " offset 0 rows fetch next 10 rows only", + input + " offset 0 rows fetch next ? rows only", dialect.getLimitHandler().processSql( input, toRowSelection( 0, 10 ) ).toLowerCase( Locale.ROOT ) ); } @@ -51,7 +51,7 @@ public void testGetLimitStringMaxRowsOnly() { public void testGetLimitStringWithOffsetAndMaxRows() { final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc"; assertEquals( - input + " offset 5 rows fetch next 25 rows only", + input + " offset ? rows fetch next ? rows only", dialect.getLimitHandler().processSql( input, toRowSelection( 5, 25 ) ).toLowerCase( Locale.ROOT ) ); } diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java new file mode 100644 index 000000000000..ceb764d59bf2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java @@ -0,0 +1,54 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import static org.junit.Assert.assertEquals; + +import org.hibernate.dialect.pagination.SybaseASE157LimitHandler; +import org.hibernate.engine.spi.RowSelection; +import org.junit.Test; + +public class SybaseASE157LimitHandlerTest { + + @Test + public void testLimitHandler() { + assertEquals( "select * from entity", processSql( "select * from entity", null, null ) ); + assertEquals( "select * from entity", processSql( "select * from entity", 15, null ) ); + assertEquals( "select top 15 * from entity", processSql( "select * from entity", null, 15 ) ); + assertEquals( "select top 18 * from entity", processSql( "select * from entity", 3, 15 ) ); + assertEquals( "SELECT top 18 * FROM entity", processSql( "SELECT * FROM entity", 3, 15 ) ); + assertEquals( " select top 18 * from entity", processSql( " select * from entity", 3, 15 ) ); + assertEquals( "selectand", processSql( "selectand", 3, 15 ) ); + assertEquals( "select distinct top 15 id from entity", + processSql( "select distinct id from entity", null, 15 ) ); + assertEquals( "select distinct top 18 id from entity", processSql( "select distinct id from entity", 3, 15 ) ); + assertEquals( " select distinct top 18 id from entity", + processSql( " select distinct id from entity", 3, 15 ) ); + assertEquals( + "WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'", + processSql( + "WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'", + 3, 15 ) ); + + assertEquals( "select top 5 * from entity", processSql( "select top 5 * from entity", 3, 15 ) ); + assertEquals( "select distinct top 7 * from entity", processSql( "select distinct top 7 * from entity", 3, 15 ) ); + assertEquals( "select distinct top 18 top_column from entity", processSql( "select distinct top_column from entity", 3, 15 ) ); + } + + private String processSql(String sql, Integer offset, Integer limit) { + RowSelection rowSelection = new RowSelection(); + if ( offset != null ) { + rowSelection.setFirstRow( offset ); + } + if (limit != null) { + rowSelection.setMaxRows( limit ); + } + + SybaseASE157LimitHandler limitHandler = new SybaseASE157LimitHandler(); + return limitHandler.processSql( sql, rowSelection ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java index 8856cf4f9f40..a0cb13fccc97 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java @@ -6,11 +6,6 @@ */ package org.hibernate.dialect.resolver; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - import org.hibernate.HibernateException; import org.hibernate.boot.registry.BootstrapServiceRegistry; import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; @@ -18,30 +13,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.selector.spi.StrategySelectionException; import org.hibernate.cfg.Environment; -import org.hibernate.dialect.DB2400Dialect; -import org.hibernate.dialect.DB2Dialect; -import org.hibernate.dialect.DerbyDialect; -import org.hibernate.dialect.DerbyTenFiveDialect; -import org.hibernate.dialect.DerbyTenSevenDialect; -import org.hibernate.dialect.DerbyTenSixDialect; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.HSQLDialect; -import org.hibernate.dialect.InformixDialect; -import org.hibernate.dialect.IngresDialect; -import org.hibernate.dialect.MySQL5Dialect; -import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.Oracle10gDialect; -import org.hibernate.dialect.Oracle8iDialect; -import org.hibernate.dialect.Oracle9iDialect; -import org.hibernate.dialect.PostgreSQL81Dialect; -import org.hibernate.dialect.PostgreSQL82Dialect; -import org.hibernate.dialect.PostgreSQL9Dialect; -import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SQLServerDialect; -import org.hibernate.dialect.SybaseASE15Dialect; -import org.hibernate.dialect.SybaseAnywhereDialect; -import org.hibernate.dialect.TestingDialects; +import org.hibernate.dialect.*; import org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl; import org.hibernate.engine.jdbc.dialect.internal.DialectResolverSet; import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver; @@ -49,14 +21,16 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfoSource; import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; import org.hibernate.service.spi.ServiceRegistryImplementor; - import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.*; /** * @author Steve Ebersole @@ -136,9 +110,26 @@ public void testPreregisteredDialects() { testDetermination( "H2", H2Dialect.class, resolver ); testDetermination( "MySQL", MySQLDialect.class, resolver ); testDetermination( "MySQL", 5, 0, MySQL5Dialect.class, resolver ); + testDetermination( "MySQL", 5, 5, MySQL55Dialect.class, resolver ); + testDetermination( "MySQL", 5, 6, MySQL55Dialect.class, resolver ); + testDetermination( "MySQL", 5, 7, MySQL57Dialect.class, resolver ); + testDetermination( "MySQL", 8, 0, MySQL8Dialect.class, resolver ); + testDetermination( "MariaDB", "MariaDB connector/J", 10, 3, MariaDB103Dialect.class, resolver ); + testDetermination( "MariaDB", "MariaDB connector/J", 10, 2, MariaDB102Dialect.class, resolver ); + testDetermination( "MariaDB", "MariaDB connector/J", 10, 1, MariaDB10Dialect.class, resolver ); + testDetermination( "MariaDB", "MariaDB connector/J", 10, 0, MariaDB10Dialect.class, resolver ); + testDetermination( "MariaDB", "MariaDB connector/J", 5, 5, MariaDB53Dialect.class, resolver ); + testDetermination( "MariaDB", "MariaDB connector/J", 5, 2, MariaDBDialect.class, resolver ); testDetermination( "PostgreSQL", PostgreSQL81Dialect.class, resolver ); testDetermination( "PostgreSQL", 8, 2, PostgreSQL82Dialect.class, resolver ); + testDetermination( "PostgreSQL", 8, 3, PostgreSQL82Dialect.class, resolver ); testDetermination( "PostgreSQL", 9, 0, PostgreSQL9Dialect.class, resolver ); + testDetermination( "PostgreSQL", 9, 1, PostgreSQL9Dialect.class, resolver ); + testDetermination( "PostgreSQL", 9, 2, PostgreSQL92Dialect.class, resolver ); + testDetermination( "PostgreSQL", 9, 3, PostgreSQL92Dialect.class, resolver ); + testDetermination( "PostgreSQL", 9, 4, PostgreSQL94Dialect.class, resolver ); + testDetermination( "PostgreSQL", 9, 5, PostgreSQL95Dialect.class, resolver ); + testDetermination( "PostgreSQL", 10, 0, PostgreSQL95Dialect.class, resolver ); testDetermination( "EnterpriseDB", 9, 2, PostgresPlusDialect.class, resolver ); testDetermination( "Apache Derby", 10, 4, DerbyDialect.class, resolver ); testDetermination( "Apache Derby", 10, 5, DerbyTenFiveDialect.class, resolver ); @@ -152,7 +143,7 @@ public void testPreregisteredDialects() { testDetermination( "Sybase SQL Server", SybaseASE15Dialect.class, resolver ); testDetermination( "Adaptive Server Enterprise", SybaseASE15Dialect.class, resolver ); testDetermination( "Adaptive Server Anywhere", SybaseAnywhereDialect.class, resolver ); - testDetermination( "Informix Dynamic Server", InformixDialect.class, resolver ); + testDetermination( "Informix Dynamic Server", Informix10Dialect.class, resolver ); testDetermination( "DB2/NT", DB2Dialect.class, resolver ); testDetermination( "DB2/LINUX", DB2Dialect.class, resolver ); testDetermination( "DB2/6000", DB2Dialect.class, resolver ); @@ -233,13 +224,23 @@ private void testDetermination( final int minorVersion, Class expected, DialectResolver resolver) { + testDetermination( databaseName, null, majorVersion, minorVersion, expected, resolver ); + } + + private void testDetermination( + final String databaseName, + final String driverName, + final int majorVersion, + final int minorVersion, + Class expected, + DialectResolver resolver) { dialectFactory.setDialectResolver( resolver ); Dialect resolved = dialectFactory.buildDialect( new Properties(), new DialectResolutionInfoSource() { @Override public DialectResolutionInfo getDialectResolutionInfo() { - return TestingDialectResolutionInfo.forDatabaseInfo( databaseName, majorVersion, minorVersion ); + return TestingDialectResolutionInfo.forDatabaseInfo( databaseName, driverName, majorVersion, minorVersion ); } } ); diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/TestingDialectResolutionInfo.java b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/TestingDialectResolutionInfo.java index dc244e626852..dd39bfc2a22e 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/TestingDialectResolutionInfo.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/TestingDialectResolutionInfo.java @@ -47,6 +47,10 @@ public static TestingDialectResolutionInfo forDatabaseInfo(String name, int majo return new TestingDialectResolutionInfo( name, majorVersion, minorVersion, null, NO_VERSION, NO_VERSION ); } + public static TestingDialectResolutionInfo forDatabaseInfo(String databaseName, String driverName, int majorVersion, int minorVersion) { + return new TestingDialectResolutionInfo( databaseName, majorVersion, minorVersion, driverName, NO_VERSION, NO_VERSION ); + } + @Override public String getDatabaseName() { return databaseName; diff --git a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/ResultSetWrapperProxyTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/ResultSetWrapperProxyTest.java new file mode 100644 index 000000000000..bee91c98dc16 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/ResultSetWrapperProxyTest.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.service.ServiceRegistry; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class ResultSetWrapperProxyTest { + + private ResultSet resultSet; + + private ResultSet resultSetProxy; + + @Before + public void initialize() throws SQLException { + ServiceRegistry serviceRegistry = Mockito.mock( ServiceRegistry.class ); + when( serviceRegistry.getService( eq( ClassLoaderService.class ) ) ).thenReturn( new ClassLoaderServiceImpl() ); + + ColumnNameCache columnNameCache = new ColumnNameCache( 2 ); + + resultSet = Mockito.mock( ResultSet.class ); + when( resultSet.findColumn( eq( "myColumn" ) ) ).thenReturn( 1 ); + + resultSetProxy = ResultSetWrapperProxy.generateProxy( resultSet, columnNameCache, serviceRegistry ); + } + + @Test + public void testRedirectedGetMethod() throws SQLException { + resultSetProxy.getBigDecimal( "myColumn" ); + + verify( resultSet, times( 1 ) ).getBigDecimal( 1 ); + } + + @SuppressWarnings("deprecation") + @Test + public void testRedirectedGetMethodWithAdditionalParameters() throws SQLException { + resultSetProxy.getBigDecimal( "myColumn", 8 ); + + verify( resultSet, times( 1 ) ).getBigDecimal( 1, 8 ); + } + + @Test + public void testRedirectedUpdateMethod() throws SQLException { + resultSetProxy.updateInt( "myColumn", 19 ); + + verify( resultSet, times( 1 ) ).updateInt( 1, 19 ); + } + + @SuppressWarnings("deprecation") + @Test + public void testIntMethods() throws SQLException { + resultSetProxy.getBigDecimal( 3 ); + verify( resultSet, times( 1 ) ).getBigDecimal( 3 ); + + resultSetProxy.getBigDecimal( 13, 8 ); + verify( resultSet, times( 1 ) ).getBigDecimal( 13, 8 ); + + resultSetProxy.updateInt( 23, 19 ); + verify( resultSet, times( 1 ) ).updateInt( 23, 19 ); + } + + @Test + public void testStandardMethod() throws SQLException { + resultSetProxy.getFetchSize(); + + verify( resultSet, times( 1 ) ).getFetchSize(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java index b326e1ab0f0d..5858a52e1870 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java @@ -6,24 +6,16 @@ */ package org.hibernate.engine.jdbc.dialect.internal; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.sql.SQLException; - -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; -import org.hibernate.dialect.PostgreSQL82Dialect; -import org.hibernate.dialect.PostgreSQL9Dialect; -import org.hibernate.dialect.SQLServer2005Dialect; -import org.hibernate.dialect.SQLServer2008Dialect; -import org.hibernate.dialect.SQLServer2012Dialect; -import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.*; import org.hibernate.dialect.resolver.TestingDialectResolutionInfo; - import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; +import java.sql.SQLException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + /** * Unit test of the {@link StandardDialectResolver} class. * @@ -52,7 +44,7 @@ public void testResolveDialectInternalForSQLServer2008() @Test public void testResolveDialectInternalForSQLServer2012() throws SQLException { - runSQLServerDialectTest( 11, SQLServer2008Dialect.class ); + runSQLServerDialectTest( 11, SQLServer2012Dialect.class ); } @Test @@ -101,7 +93,70 @@ public void testResolveDialectInternalForPostgres91() throws SQLException { @Test public void testResolveDialectInternalForPostgres92() throws SQLException { - runPostgresDialectTest( 9, 2, PostgreSQL9Dialect.class ); + runPostgresDialectTest( 9, 2, PostgreSQL92Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMariaDB103() throws SQLException { + runMariaDBDialectTest( 10, 3, MariaDB103Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMariaDB102() throws SQLException { + runMariaDBDialectTest( 10, 2, MariaDB102Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMariaDB101() throws SQLException { + runMariaDBDialectTest( 10, 1, MariaDB10Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMariaDB100() throws SQLException { + runMariaDBDialectTest( 10, 0, MariaDB10Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMariaDB55() throws SQLException { + runMariaDBDialectTest( 5, 5, MariaDB53Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMariaDB52() throws SQLException { + runMariaDBDialectTest( 5, 2, MariaDBDialect.class ); + } + + @Test + public void testResolveDialectInternalForMySQL57() throws SQLException { + runMySQLDialectTest( 5, 7, MySQL57Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMySQL6() throws SQLException { + runMySQLDialectTest( 6, 0, MySQL57Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMySQL7() throws SQLException { + runMySQLDialectTest( 7, 0, MySQL57Dialect.class ); + } + + + @Test + public void testResolveDialectInternalForMySQL8() throws SQLException { + runMySQLDialectTest( 8, 0, MySQL8Dialect.class ); + } + + private static void runMariaDBDialectTest( + int majorVersion, int minorVersion, Class expectedDialect) + throws SQLException { + runDialectTest( "MariaDB", "MariaDB connector/J", majorVersion, minorVersion, expectedDialect ); + } + + private static void runMySQLDialectTest( + int majorVersion, int minorVersion, Class expectedDialect) + throws SQLException { + runDialectTest( "MySQL", "MySQL connector/J", majorVersion, minorVersion, expectedDialect ); } private static void runSQLServerDialectTest( @@ -123,7 +178,16 @@ private static void runDialectTest( int majorVersion, int minorVersion, Class expectedDialect) { - TestingDialectResolutionInfo info = TestingDialectResolutionInfo.forDatabaseInfo( productName, majorVersion, minorVersion ); + runDialectTest( productName, null, majorVersion, minorVersion, expectedDialect ); + } + + private static void runDialectTest( + String productName, + String driverName, + int majorVersion, + int minorVersion, + Class expectedDialect) { + TestingDialectResolutionInfo info = TestingDialectResolutionInfo.forDatabaseInfo( productName, driverName, majorVersion, minorVersion ); Dialect dialect = StandardDialectResolver.INSTANCE.resolveDialect( info ); @@ -135,8 +199,11 @@ private static void runDialectTest( String dbms = builder.toString(); assertNotNull( "Dialect for " + dbms + " should not be null", dialect ); - assertTrue( "Dialect for " + dbms + " should be " - + expectedDialect.getSimpleName(), - expectedDialect.isInstance( dialect ) ); + // Make sure to test that the actual dialect class is as expected + // (not just an instance of the expected dialect. + assertEquals( "Dialect for " + dbms + " should be " + expectedDialect.getSimpleName(), + expectedDialect, + dialect.getClass() + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java index 458d9c26689d..df1646aa58ae 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java @@ -4,6 +4,9 @@ import java.sql.Connection; import java.sql.SQLException; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -62,6 +65,12 @@ public void testConnectionClose() when( serviceRegistry.getService( eq( JdbcServices.class ) ) ).thenReturn( jdbcServices ); + ConfigurationService configurationService = Mockito.mock( ConfigurationService.class ); + when( serviceRegistry.getService( eq( ConfigurationService.class ) ) ).thenReturn( + configurationService ); + when( configurationService.getSetting( eq( AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT ), same( StandardConverters.BOOLEAN), eq( false )) ) + .thenReturn( false ); + SqlExceptionHelper sqlExceptionHelper = Mockito.mock( SqlExceptionHelper.class ); when( jdbcServices.getSqlExceptionHelper() ).thenReturn( sqlExceptionHelper ); diff --git a/hibernate-core/src/test/java/org/hibernate/engine/query/ParameterParserTest.java b/hibernate-core/src/test/java/org/hibernate/engine/query/ParameterParserTest.java index a9cc874c59f1..b231059731cb 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/query/ParameterParserTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/query/ParameterParserTest.java @@ -33,7 +33,7 @@ public void testEscapeCallRecognition() { } @Test public void testQuotedTextInComment() { - ParamLocationRecognizer recognizer = new ParamLocationRecognizer(); + ParamLocationRecognizer recognizer = new ParamLocationRecognizer( 0 ); ParameterParser.parse("-- 'This' should not fail the test.\n" + "SELECT column FROM Table WHERE column <> :param", recognizer); @@ -43,35 +43,35 @@ public void testQuotedTextInComment() { @Test public void testContractionInComment() { - ParamLocationRecognizer recognizer = new ParamLocationRecognizer(); + ParamLocationRecognizer recognizer = new ParamLocationRecognizer( 0 ); ParameterParser.parse("-- This shouldn't fail the test.\n" + "SELECT column FROM Table WHERE column <> :param", recognizer); - assertTrue(recognizer.getNamedParameterDescriptionMap().containsKey("param")); + assertTrue( recognizer.getNamedParameterDescriptionMap().containsKey("param")); } @Test public void testDoubleDashInCharLiteral() { - ParamLocationRecognizer recognizer = new ParamLocationRecognizer(); + ParamLocationRecognizer recognizer = new ParamLocationRecognizer( 0 ); ParameterParser.parse("select coalesce(i.name, '--NONE--') as itname from Item i where i.intVal=? ",recognizer); - assertEquals( 1, recognizer.getOrdinalParameterLocationList().size() ); + assertEquals( 1, recognizer.getOrdinalParameterDescriptionMap().size() ); } @Test public void testSlashStarInCharLiteral() { - ParamLocationRecognizer recognizer = new ParamLocationRecognizer(); + ParamLocationRecognizer recognizer = new ParamLocationRecognizer( 0 ); ParameterParser.parse("select coalesce(i.name, '/*NONE') as itname from Item i where i.intVal=? ",recognizer); - assertEquals( 1, recognizer.getOrdinalParameterLocationList().size() ); + assertEquals( 1, recognizer.getOrdinalParameterDescriptionMap().size() ); } @Test public void testApostropheInOracleAlias() { - ParamLocationRecognizer recognizer = new ParamLocationRecognizer(); + ParamLocationRecognizer recognizer = new ParamLocationRecognizer( 0 ); ParameterParser.parse("SELECT column as \"Table's column\" FROM Table WHERE column <> :param", recognizer); @@ -87,34 +87,52 @@ public void testParseColonCharacterEscaped() { public void outParameter(int position) { fail(); } + @Override public void ordinalParameter(int position) { fail(); } + @Override public void namedParameter(String name, int position) { fail(); } + @Override - public void jpaPositionalParameter(String name, int position) { + public void jpaPositionalParameter(int name, int position) { fail(); } + @Override public void other(char character) { captured.append(character); } - }; + + @Override + public void complete() { + } + }; ParameterParser.parse("SELECT @a,(@a::=20) FROM tbl_name", recognizer); assertEquals("SELECT @a,(@a:=20) FROM tbl_name", captured.toString()); } @Test public void testParseNamedParameter() { - ParamLocationRecognizer recognizer = new ParamLocationRecognizer(); + ParamLocationRecognizer recognizer = new ParamLocationRecognizer( 0 ); ParameterParser.parse("from Stock s where s.stockCode = :stockCode and s.xyz = :pxyz", recognizer); assertTrue(recognizer.getNamedParameterDescriptionMap().containsKey("stockCode")); assertTrue(recognizer.getNamedParameterDescriptionMap().containsKey("pxyz")); assertEquals( 2, recognizer.getNamedParameterDescriptionMap().size() ); } + + @Test + public void testParseJPAPositionalParameter() { + ParamLocationRecognizer recognizer = new ParamLocationRecognizer( 0 ); + ParameterParser.parse("from Stock s where s.stockCode = ?1 and s.xyz = ?1", recognizer); + assertEquals( 1, recognizer.getOrdinalParameterDescriptionMap().size() ); + + ParameterParser.parse("from Stock s where s.stockCode = ?1 and s.xyz = ?2", recognizer); + assertEquals( 2, recognizer.getOrdinalParameterDescriptionMap().size() ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/SessionDelegatorBaseImplTest.java b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/SessionDelegatorBaseImplTest.java new file mode 100644 index 000000000000..800762a93271 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/SessionDelegatorBaseImplTest.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.engine.spi.delegation; + +import java.sql.SQLException; +import java.sql.Statement; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.SessionDelegatorBaseImpl; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +@RequiresDialect(H2Dialect.class) +public class SessionDelegatorBaseImplTest extends BaseCoreFunctionalTestCase { + + @Before + public void init() { + inTransaction( session -> { + session.doWork( connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( "DROP ALIAS findOneUser IF EXISTS" ); + statement.executeUpdate( + "CREATE ALIAS findOneUser AS $$\n" + + "import org.h2.tools.SimpleResultSet;\n" + + "import java.sql.*;\n" + + "@CODE\n" + + "ResultSet findOneUser() {\n" + + " SimpleResultSet rs = new SimpleResultSet();\n" + + " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + + " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + + " rs.addRow(1, \"Steve\");\n" + + " return rs;\n" + + "}\n" + + "$$" + ); + } + } ); + } ); + } + + @After + public void tearDown() { + inTransaction( session -> { + session.doWork( connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( "DROP ALIAS findOneUser IF EXISTS" ); + } + catch (SQLException e) { + //Do not ignore as failure to cleanup might lead to other tests to fail: + throw new RuntimeException( e ); + } + } ); + } ); + } + + @Test + public void testcreateStoredProcedureQuery() { + inTransaction( + session -> { + SessionDelegatorBaseImpl delegator = new SessionDelegatorBaseImpl( session ); + delegator.createStoredProcedureQuery( "findOneUser" ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSessionBuilder.java b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSessionBuilder.java new file mode 100644 index 000000000000..aec61f8ed238 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSessionBuilder.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 . + */ +package org.hibernate.engine.spi.delegation; + +import org.hibernate.SessionBuilder; +import org.hibernate.engine.spi.AbstractDelegatingSessionBuilder; + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingSessionBuilder extends AbstractDelegatingSessionBuilder { + + @SuppressWarnings("rawtypes") + public TestDelegatingSessionBuilder(SessionBuilder delegate) { + super( delegate ); + } + + @Override + protected TestDelegatingSessionBuilder getThis() { + return this; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSessionBuilderImplementor.java b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSessionBuilderImplementor.java new file mode 100644 index 000000000000..93ba8875dbfe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSessionBuilderImplementor.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.engine.spi.delegation; + +import org.hibernate.engine.spi.AbstractDelegatingSessionBuilderImplementor; +import org.hibernate.engine.spi.SessionBuilderImplementor; + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingSessionBuilderImplementor extends AbstractDelegatingSessionBuilderImplementor { + + public TestDelegatingSessionBuilderImplementor(SessionBuilderImplementor delegate) { + super( delegate ); + } + + @Override + protected TestDelegatingSessionBuilderImplementor getThis() { + return this; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSharedSessionBuilder.java b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSharedSessionBuilder.java new file mode 100644 index 000000000000..f66c1b67b5d8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/TestDelegatingSharedSessionBuilder.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.engine.spi.delegation; + +import org.hibernate.SharedSessionBuilder; +import org.hibernate.engine.spi.AbstractDelegatingSharedSessionBuilder; + + +/** + * If this class does not compile anymore due to unimplemented methods, you should probably add the corresponding + * methods to the parent class. + * + * @author Guillaume Smet + */ +public class TestDelegatingSharedSessionBuilder extends AbstractDelegatingSharedSessionBuilder { + + @SuppressWarnings("rawtypes") + public TestDelegatingSharedSessionBuilder(SharedSessionBuilder delegate) { + super( delegate ); + } + + @Override + protected TestDelegatingSharedSessionBuilder getThis() { + return this; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/event/EmbeddableCallbackTest.java b/hibernate-core/src/test/java/org/hibernate/event/EmbeddableCallbackTest.java new file mode 100644 index 000000000000..ad2be1ffacaf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/event/EmbeddableCallbackTest.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.event; + +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.PrePersist; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * @author Vlad Mihalcea + */ +public class EmbeddableCallbackTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Employee.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-12326") + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Employee employee = new Employee(); + employee.details = new EmployeeDetails(); + employee.id = 1; + + entityManager.persist( employee ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Employee employee = entityManager.find( Employee.class, 1 ); + + assertEquals( "Vlad", employee.name ); + assertEquals( "Developer Advocate", employee.details.jobTitle ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13110") + public void testNullEmbeddable() { + doInJPA( this::entityManagerFactory, entityManager -> { + Employee employee = new Employee(); + employee.id = 1; + + entityManager.persist( employee ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Employee employee = entityManager.find( Employee.class, 1 ); + + assertEquals( "Vlad", employee.name ); + assertNull( employee.details ); + } ); + } + + @Entity(name = "Employee") + public static class Employee { + + @Id + private Integer id; + + private String name; + + private EmployeeDetails details; + + @PrePersist + public void setUp() { + name = "Vlad"; + } + } + + @Embeddable + public static class EmployeeDetails { + + private String jobTitle; + + @PrePersist + public void setUp() { + jobTitle = "Developer Advocate"; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/event/PreInsertEventListenerVetoBidirectionalTest.java b/hibernate-core/src/test/java/org/hibernate/event/PreInsertEventListenerVetoBidirectionalTest.java new file mode 100644 index 000000000000..c988e8d39888 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/event/PreInsertEventListenerVetoBidirectionalTest.java @@ -0,0 +1,144 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.event; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +import org.hibernate.TransientObjectException; +import org.hibernate.action.internal.EntityActionVetoException; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.PreInsertEventListener; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +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.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Chris Cranford + */ +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +@TestForIssue(jiraKey = "HHH-11721") +public class PreInsertEventListenerVetoBidirectionalTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Child.class, Parent.class }; + } + + @Override + protected void afterSessionFactoryBuilt() { + super.afterSessionFactoryBuilt(); + EventListenerRegistry registry = sessionFactory().getServiceRegistry() + .getService( EventListenerRegistry.class ); + registry.appendListeners( + EventType.PRE_INSERT, + (PreInsertEventListener) event -> event.getEntity() instanceof Parent + ); + } + + @Test(expected = EntityActionVetoException.class) + public void testVeto() { + doInHibernate( this::sessionFactory, session -> { + Parent parent = new Parent(); + parent.setField1( "f1" ); + parent.setfield2( "f2" ); + + Child child = new Child(); + parent.setChild( child ); + + session.save( parent ); + } ); + + fail( "Should have thrown EntityActionVetoException!" ); + } + + @Entity(name = "Child") + public static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @OneToOne + private Parent parent; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private String field1; + + private String field2; + + @OneToOne(cascade = CascadeType.ALL, mappedBy = "parent") + private Child child; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getField1() { + return field1; + } + + public void setField1(String field1) { + this.field1 = field1; + } + + public String getField2() { + return field2; + } + + public void setfield2(String field2) { + this.field2 = field2; + } + + public Child getChild() { + return child; + } + + public void setChild(Child child) { + this.child = child; + child.setParent( this ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/event/PreInsertEventListenerVetoUnidirectionalTest.java b/hibernate-core/src/test/java/org/hibernate/event/PreInsertEventListenerVetoUnidirectionalTest.java new file mode 100644 index 000000000000..cd07370f3bf9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/event/PreInsertEventListenerVetoUnidirectionalTest.java @@ -0,0 +1,128 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.event; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +import org.hibernate.TransientObjectException; +import org.hibernate.action.internal.EntityActionVetoException; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.PreInsertEventListener; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +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.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Chris Cranford + */ +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +@TestForIssue( jiraKey = "HHH-11721" ) +public class PreInsertEventListenerVetoUnidirectionalTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Child.class, Parent.class }; + } + + @Override + protected void afterSessionFactoryBuilt() { + super.afterSessionFactoryBuilt(); + EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class ); + registry.appendListeners( + EventType.PRE_INSERT, + (PreInsertEventListener) event -> event.getEntity() instanceof Parent + ); + } + + @Test(expected = EntityActionVetoException.class) + public void testVeto() { + doInHibernate( this::sessionFactory, session -> { + Parent parent = new Parent(); + parent.setField1( "f1" ); + parent.setfield2( "f2" ); + + Child child = new Child(); + child.setParent( parent ); + + session.save( child ); + } ); + + fail( "Should have thrown EntityActionVetoException!" ); + } + + @Entity(name = "Child") + public static class Child { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @OneToOne(cascade = CascadeType.ALL) + private Parent parent; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + @Entity(name = "Parent") + public static class Parent { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String field1; + private String field2; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getField1() { + return field1; + } + + public void setField1(String field1) { + this.field1 = field1; + } + + public String getField2() { + return field2; + } + + public void setfield2(String field2) { + this.field2 = field2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java new file mode 100644 index 000000000000..468b511c8b3e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.id; + +import org.hibernate.FlushMode; +import org.hibernate.dialect.AbstractHANADialect; + +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.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +@TestForIssue(jiraKey = "HHH-12464") +public class CreateDeleteTest extends BaseCoreFunctionalTestCase { + @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") + public void createAndDeleteAnEntityInTheSameTransactionTest() { + doInHibernate( this::sessionFactory, session -> { + session.setHibernateFlushMode( FlushMode.COMMIT ); + RootEntity entity = new RootEntity(); + session.persist( entity ); + session.delete( entity ); + } ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + RootEntity.class, + RelatedEntity.class, + }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/id/FlushIdGenTest.java b/hibernate-core/src/test/java/org/hibernate/id/FlushIdGenTest.java index 4a4306ca6749..b2b6c989d4bd 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/FlushIdGenTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/FlushIdGenTest.java @@ -9,17 +9,20 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.AbstractHANADialect; 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.junit.Test; @TestForIssue(jiraKey = "HHH-8611") -@RequiresDialectFeature( DialectChecks.SupportsIdentityColumns.class ) +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) public class FlushIdGenTest extends BaseCoreFunctionalTestCase { @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") public void testPersistBeforeTransaction() { Session session = openSession(); RootEntity ent1_0 = new RootEntity(); @@ -29,7 +32,7 @@ public void testPersistBeforeTransaction() { session.persist( ent1_1 ); Transaction tx = session.beginTransaction(); - tx.commit(); //flush + tx.commit(); // flush } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/id/IdClassManyToOneCascadeTest.java b/hibernate-core/src/test/java/org/hibernate/id/IdClassManyToOneCascadeTest.java new file mode 100644 index 000000000000..3cdf7bc3955c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/id/IdClassManyToOneCascadeTest.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.id; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.ManyToOne; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.wildfly.common.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12251" ) +public class IdClassManyToOneCascadeTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SomeEntity.class, + ReferencedEntity.class + }; + } + + @Test + @FailureExpected( jiraKey = "HHH-12251") + public void testMergeCascadesToManyToOne() { + + doInJPA( this::entityManagerFactory, entityManager -> { + ReferencedEntity referencedEntity = new ReferencedEntity(); + referencedEntity.setId( 42L ); + + SomeEntity someEntity = new SomeEntity(); + someEntity.setId( 23L ); + someEntity.setReferencedEntity( referencedEntity ); + + entityManager.merge( someEntity ); + + assertTrue( entityManager.contains( referencedEntity ) ); + } ); + } + + @Test + public void testPersistCascadesToManyToOne() { + + doInJPA( this::entityManagerFactory, entityManager -> { + ReferencedEntity referencedEntity = new ReferencedEntity(); + referencedEntity.setId( 42L ); + + SomeEntity someEntity = new SomeEntity(); + someEntity.setId( 23L ); + someEntity.setReferencedEntity( referencedEntity ); + + entityManager.persist( someEntity ); + + assertTrue( entityManager.contains( referencedEntity ) ); + } ); + } + + @Entity(name = "SomeEntity") + @IdClass(SomeEntityPK.class) + public static class SomeEntity { + + @Id + private long id; + + @Id + @ManyToOne + private ReferencedEntity referencedEntity; + + public ReferencedEntity getReferencedEntity() { + return referencedEntity; + } + + public void setReferencedEntity(ReferencedEntity referencedEntity) { + this.referencedEntity = referencedEntity; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + } + + public static class SomeEntityPK implements Serializable { + + private Long id; + private Long referencedEntity; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getReferencedEntity() { + return referencedEntity; + } + + public void setReferencedEntity(Long referencedEntity) { + this.referencedEntity = referencedEntity; + } + } + + @Entity(name = "ReferencedEntity") + public static class ReferencedEntity { + + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java index 78cb64dbb0f6..ff75830628bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java @@ -13,39 +13,37 @@ import javax.persistence.Id; import javax.persistence.Table; -import org.hibernate.Session; -import org.hibernate.Transaction; +import org.hibernate.dialect.Oracle12cDialect; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertNotNull; /** * @author Vlad Mihalcea */ @RequiresDialectFeature( value = DialectChecks.SupportsIdentityColumns.class, jiraKey = "HHH-9271") +@SkipForDialect(value = Oracle12cDialect.class, comment = "Oracle and identity column: java.sql.Connection#prepareStatement(String sql, int columnIndexes[]) does not work with quoted table names and/or quoted columnIndexes") public class QuotedIdentifierTest extends BaseCoreFunctionalTestCase { @Test - public void testDirectIdPropertyAccess() throws Exception { - Session s = openSession(); - Transaction transaction = s.beginTransaction(); - QuotedIdentifier o = new QuotedIdentifier(); - o.timestamp = System.currentTimeMillis(); - o.from = "HHH-9271"; - s.persist( o ); - transaction.commit(); - s.close(); + public void testDirectIdPropertyAccess() { + QuotedIdentifier quotedIdentifier = new QuotedIdentifier(); + doInHibernate( this::sessionFactory, session -> { + quotedIdentifier.timestamp = System.currentTimeMillis(); + quotedIdentifier.from = "HHH-9271"; + session.persist( quotedIdentifier ); + } ); - s = openSession(); - transaction = s.beginTransaction(); - o = session.get( QuotedIdentifier.class, o.index ); - assertNotNull(o); - transaction.commit(); - s.close(); + doInHibernate( this::sessionFactory, session -> { + QuotedIdentifier result = session.get( QuotedIdentifier.class, quotedIdentifier.index ); + assertNotNull( result ); + } ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java b/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java index 742917dc2afd..fd9cf6ed726f 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java @@ -19,15 +19,18 @@ public class RootEntity implements Serializable { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) - @Column(name = "universalid")// "uid" is a keywork in Oracle + @Column(name = "universalid")// "uid" is a keyword in Oracle private long uid; + public String description; + @javax.persistence.OneToMany(mappedBy = "linkedRoot") private java.util.List linkedEntities = new java.util.ArrayList(); public long getUid() { return uid; } + public void setUid(long uid) { this.uid = uid; } diff --git a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java index 496d6bb1d8da..6f8532764270 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java @@ -106,7 +106,7 @@ public void testHiLoAlgorithm() { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ assertEquals(7L, generateValue()); // unlike the newer strategies, the db value will not get update here. It gets updated on the next invocation - // afterQuery a clock over + // after a clock over assertEquals(1L, extractSequenceValue()); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/test/java/org/hibernate/id/SequenceValueExtractor.java b/hibernate-core/src/test/java/org/hibernate/id/SequenceValueExtractor.java index 0192ee21d1b9..5794f26ede0f 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/SequenceValueExtractor.java +++ b/hibernate-core/src/test/java/org/hibernate/id/SequenceValueExtractor.java @@ -13,6 +13,7 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; @@ -49,6 +50,10 @@ else if ( dialect instanceof HSQLDialect ) { queryString = "call current value for " + sequenceName; } + else if ( dialect instanceof AbstractHANADialect ) { + + queryString = "select " + sequenceName + ".currval from sys.dummy"; + } else { queryString = "select currval('" + sequenceName + "');"; } 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 c9b0ef2239c7..808b3ed1c7fe 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 @@ -33,15 +33,16 @@ public void testBasicNoOptimizerUsage() { assertEquals( 10, sequence.getTimesCalled() ); assertEquals( 10, sequence.getCurrentValue() ); - // test historic table behavior, where the initial values started at 0 (we now force 1 to be the first used id value) + // As of HHH-11709 being fixed, Hibernate will use the value retrieved from the sequence, + // rather than incrementing 1. sequence = new SourceMock( 0 ); optimizer = buildNoneOptimizer( -1, 1 ); for ( int i = 1; i < 11; i++ ) { final Long next = ( Long ) optimizer.generate( sequence ); - assertEquals( i, next.intValue() ); + assertEquals( i-1, next.intValue() ); } - assertEquals( 11, sequence.getTimesCalled() ); // an extra time to get to 1 initially - assertEquals( 10, sequence.getCurrentValue() ); + assertEquals( 10, sequence.getTimesCalled() ); // an extra time to get to 1 initially + assertEquals( 9, sequence.getCurrentValue() ); } @Test public void testBasicNoOptimizerUsageWithNegativeValues() { @@ -55,15 +56,16 @@ public void testBasicNoOptimizerUsageWithNegativeValues() { assertEquals( 10, sequence.getTimesCalled() ); assertEquals( -10, sequence.getCurrentValue() ); - // test historic table behavior, where the initial values started at 0 (we now force 1 to be the first used id value) + // As of HHH-11709 being fixed, Hibernate will use the value retrieved from the sequence, + // rather than incrementing 1. sequence = new SourceMock( 0 ); optimizer = buildNoneOptimizer( -1, 1 ); for ( int i = 1; i < 11; i++ ) { final Long next = ( Long ) optimizer.generate( sequence ); - assertEquals( i, next.intValue() ); + assertEquals( i-1, next.intValue() ); } - assertEquals( 11, sequence.getTimesCalled() ); // an extra time to get to 1 initially - assertEquals( 10, sequence.getCurrentValue() ); + assertEquals( 10, sequence.getTimesCalled() ); // an extra time to get to 1 initially + assertEquals( 9, sequence.getCurrentValue() ); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/internal/MaskSensitiveInformationTest.java b/hibernate-core/src/test/java/org/hibernate/internal/MaskSensitiveInformationTest.java new file mode 100644 index 000000000000..1e0c63982f03 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/internal/MaskSensitiveInformationTest.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.internal; + +import java.util.Map; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test that sensitive information is correctly masked. + * + * @author Bruno P. Kinoshita + */ +public class MaskSensitiveInformationTest extends BaseEntityManagerFunctionalTestCase { + + private EntityManagerFactory entityManagerFactory; + + private static final String EXPECTED_MASKED_VALUE = "****"; + + @Before + public void setUp() { + entityManagerFactory = entityManagerFactory(); + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + } + + @Test + public void testMaskOutSensitiveInformation() { + SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap( SessionFactoryImpl.class ); + Map properties = sessionFactory.getProperties(); + assertEquals( EXPECTED_MASKED_VALUE, properties.get( AvailableSettings.USER ) ); + assertEquals( EXPECTED_MASKED_VALUE, properties.get( AvailableSettings.PASS ) ); + assertEquals( EXPECTED_MASKED_VALUE, properties.get( AvailableSettings.JPA_JDBC_USER ) ); + assertEquals( EXPECTED_MASKED_VALUE, properties.get( AvailableSettings.JPA_JDBC_PASSWORD ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/internal/util/MathHelperTest.java b/hibernate-core/src/test/java/org/hibernate/internal/util/MathHelperTest.java new file mode 100644 index 000000000000..6f44048b22d0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/internal/util/MathHelperTest.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.internal.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Vlad Mihalcea + */ +public class MathHelperTest { + + @Test + public void ceilingPowerOfTwo() { + assertEquals( 1, MathHelper.ceilingPowerOfTwo( 1 ) ); + assertEquals( 2, MathHelper.ceilingPowerOfTwo( 2 ) ); + assertEquals( 4, MathHelper.ceilingPowerOfTwo( 3 ) ); + assertEquals( 4, MathHelper.ceilingPowerOfTwo( 4 ) ); + assertEquals( 8, MathHelper.ceilingPowerOfTwo( 5 ) ); + assertEquals( 8, MathHelper.ceilingPowerOfTwo( 6 ) ); + assertEquals( 8, MathHelper.ceilingPowerOfTwo( 7 ) ); + assertEquals( 8, MathHelper.ceilingPowerOfTwo( 8 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 9 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 10 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 11 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 12 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 13 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 16 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 14 ) ); + assertEquals( 16, MathHelper.ceilingPowerOfTwo( 15 ) ); + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java b/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java index 337540e05be2..78e914a910fe 100644 --- a/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java +++ b/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java @@ -6,6 +6,8 @@ */ package org.hibernate.internal.util; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import javax.persistence.FetchType; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; @@ -14,12 +16,17 @@ import org.hibernate.internal.util.hib3rnat3.C0nst4nts३; import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.testing.TestForIssue; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import static java.lang.Integer.valueOf; +import static org.hibernate.internal.util.ReflectHelperTest.Status.OFF; +import static org.hibernate.internal.util.ReflectHelperTest.Status.ON; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -38,6 +45,61 @@ public enum Status { OFF } + interface A { + + Integer getId(); + void setId(Integer id); + + default Status getStatus(){ + return ON; + } + + default void setId(String id){ + this.setId(valueOf(id)); + } + } + + interface B extends A { + String getName(); + } + + interface C extends B { + String getData(); + } + + class D implements C { + + @Override + public Integer getId() { + return null; + } + + @Override + public void setId(Integer id) { + + } + + @Override + public String getName() { + return null; + } + + @Override + public String getData() { + return null; + } + } + + class E extends D { + + } + + class F extends D { + public Status getStatus(){ + return OFF; + } + } + private SessionFactoryImplementor sessionFactoryImplementorMock; private SessionFactoryOptions sessionFactoryOptionsMock; @@ -121,7 +183,7 @@ public void test_getConstantValue_nestedEnum() { when( sessionFactoryOptionsMock.isConventionalJavaConstants() ).thenReturn( true ); when( classLoaderServiceMock.classForName( "org.hibernate.internal.util.ReflectHelperTest$Status" ) ).thenReturn( (Class) Status.class ); Object value = ReflectHelper.getConstantValue( "org.hibernate.internal.util.ReflectHelperTest$Status.ON", sessionFactoryImplementorMock); - assertEquals( Status.ON, value ); + assertEquals( ON, value ); verify(classLoaderServiceMock, times(1)).classForName( eq("org.hibernate.internal.util.ReflectHelperTest$Status") ); } @@ -134,4 +196,38 @@ public void test_getConstantValue_constant_digits() { assertEquals( C0nst4nts३.ABC_DEF, value ); verify(classLoaderServiceMock, times(1)).classForName( eq("org.hibernate.internal.util.hib3rnat3.C0nst4nts३") ); } + + @Test + public void test_getMethod_nestedInterfaces() { + assertNotNull( ReflectHelper.findGetterMethod( C.class, "id" ) ); + } + + @Test + public void test_getMethod_superclass() { + assertNotNull( ReflectHelper.findGetterMethod( E.class, "id" ) ); + } + + @Test + public void test_setMethod_nestedInterfaces() { + assertNotNull( ReflectHelper.findSetterMethod( C.class, "id", Integer.class ) ); + } + + @TestForIssue(jiraKey = "HHH-12090") + @Test + public void test_getMethod_nestedInterfaces_on_superclasses() + throws InvocationTargetException, IllegalAccessException { + Method statusMethodEClass = ReflectHelper.findGetterMethod( E.class, "status" ); + assertNotNull(statusMethodEClass); + assertEquals( ON, statusMethodEClass.invoke( new E() ) ); + + Method statusMethodFClass = ReflectHelper.findGetterMethod( F.class, "status" ); + assertNotNull(statusMethodFClass); + assertEquals( OFF, statusMethodFClass.invoke( new F() ) ); + } + + @TestForIssue(jiraKey = "HHH-12090") + @Test + public void test_setMethod_nestedInterfaces_on_superclasses() { + assertNotNull( ReflectHelper.findSetterMethod( E.class, "id", String.class ) ); + } } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java b/hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java new file mode 100644 index 000000000000..599a7cd7813e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.internal.util.xml; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.dom4j.DocumentFactory; +import org.dom4j.io.SAXReader; +import org.xml.sax.EntityResolver; + +/** + * Small helper class that lazy loads DOM and SAX reader and keep them for fast use afterwards. + * + * This was part of Hibernate ORM core, but moved into the testsuite helpers to not expose + * access to the dom4j types. It's also used by Hibernate Envers, so we will need two copies + * until Envers is able to remove its reliance on dom4j. + * The rest of Hibernate uses StAX now for XML processing. See {@link org.hibernate.boot.jaxb.internal.stax} + */ +public final class XMLHelper { + private final DocumentFactory documentFactory; + + public XMLHelper() { + PrivilegedAction action = new PrivilegedAction() { + public DocumentFactory run() { + final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); + try { + // We need to make sure we get DocumentFactory + // loaded from the same ClassLoader that loads + // Hibernate classes, to make sure we get the + // proper version of DocumentFactory, This class + // is "internal", and should only be used for XML + // files generated by Envers. + + // Using the (Hibernate) ClassLoader that loads + // this Class will avoid collisions in the case + // that DocumentFactory can be loaded from, + // for example, the application ClassLoader. + Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() ); + return DocumentFactory.getInstance(); + } + finally { + Thread.currentThread().setContextClassLoader( originalTccl ); + } + } + }; + + this.documentFactory = System.getSecurityManager() != null + ? AccessController.doPrivileged( action ) + : action.run(); + } + + public DocumentFactory getDocumentFactory() { + return documentFactory; + } + + public SAXReader createSAXReader(ErrorLogger errorLogger, EntityResolver entityResolver) { + SAXReader saxReader = new SAXReader(); + try { + saxReader.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false ); + saxReader.setFeature( "http://xml.org/sax/features/external-general-entities", false ); + saxReader.setFeature( "http://xml.org/sax/features/external-parameter-entities", false ); + } + catch (Exception e) { + throw new RuntimeException( e ); + } + saxReader.setMergeAdjacentText( true ); + saxReader.setValidation( true ); + saxReader.setErrorHandler( errorLogger ); + saxReader.setEntityResolver( entityResolver ); + + return saxReader; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java b/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java index 60eb93e583ca..e5f244ae06f7 100644 --- a/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java @@ -22,6 +22,7 @@ * @author Steve Ebersole */ public class BasicFormatterTest extends BaseUnitTestCase { + @Test public void testNoLoss() { assertNoLoss( "insert into Address (city, state, zip, \"from\") values (?, ?, ?, 'insert value')" ); @@ -43,6 +44,10 @@ public void testNoLoss() { assertNoLoss( "/* Here we' go! */ select case when p.age > 50 then 'old' when p.age > 18 then 'adult' else 'child' end from Person p where ( case when p.age > 50 then 'old' when p.age > 18 then 'adult' else 'child' end ) like ?" ); + assertNoLoss( + "(select p.pid from Address where city = 'Boston') union (select p.pid from Address where city = 'Taipei')" + ); + assertNoLoss( "select group0.[order] as order0 from [Group] group0 where group0.[order]=?1" ); } private void assertNoLoss(String query) { @@ -50,8 +55,9 @@ private void assertNoLoss(String query) { StringTokenizer formatted = new StringTokenizer( formattedQuery, " \t\n\r\f()" ); StringTokenizer plain = new StringTokenizer( query, " \t\n\r\f()" ); - System.out.println( "Original: " + query ); - System.out.println( "Formatted: " + formattedQuery ); + log.debugf( "Original: {}", query ); + log.debugf( "Formatted: {}", formattedQuery ); + while ( formatted.hasMoreTokens() && plain.hasMoreTokens() ) { String plainToken = plain.nextToken(); String formattedToken = formatted.nextToken(); diff --git a/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java b/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java index f3767224cdc4..7bbfe779c77d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java @@ -13,6 +13,8 @@ import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; +import org.jboss.logging.Logger; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -20,6 +22,7 @@ * @author Vlad Mihalcea */ public class DdlFormatterTest extends BaseUnitTestCase { + @Test public void testNoLoss() { @@ -44,8 +47,9 @@ private void assertNoLoss(String query) { StringTokenizer formatted = new StringTokenizer( formattedQuery, " \t\n\r\f()" ); StringTokenizer plain = new StringTokenizer( query, " \t\n\r\f()" ); - System.out.println( "Original: " + query ); - System.out.println( "Formatted: " + formattedQuery ); + log.debugf( "Original: {}", query ); + log.debugf( "Formatted: {}", formattedQuery ); + while ( formatted.hasMoreTokens() && plain.hasMoreTokens() ) { String plainToken = plain.nextToken(); String formattedToken = formatted.nextToken(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/JpaComplianceTestingImpl.java b/hibernate-core/src/test/java/org/hibernate/jpa/JpaComplianceTestingImpl.java new file mode 100644 index 000000000000..dbdb146436cd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/JpaComplianceTestingImpl.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa; + +import org.hibernate.jpa.spi.JpaCompliance; + +/** + * @author Steve Ebersole + */ +public class JpaComplianceTestingImpl implements JpaCompliance { + public static JpaCompliance normal() { + return new JpaComplianceTestingImpl(); + } + + public static JpaCompliance withTransactionCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.transactionCompliance = true; + return compliance; + } + + public static JpaCompliance withQueryCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.queryCompliance = true; + return compliance; + } + + public static JpaCompliance withListCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.listCompliance = true; + return compliance; + } + + public static JpaCompliance withClosedCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.closedCompliance = true; + return compliance; + } + + private boolean queryCompliance; + private boolean transactionCompliance; + private boolean listCompliance; + private boolean closedCompliance; + private boolean proxyCompliance; + private boolean cacheCompliance; + private boolean idGeneratorNameScopeCompliance; + + @Override + public boolean isJpaQueryComplianceEnabled() { + return queryCompliance; + } + + @Override + public boolean isJpaTransactionComplianceEnabled() { + return transactionCompliance; + } + + @Override + public boolean isJpaListComplianceEnabled() { + return listCompliance; + } + + @Override + public boolean isJpaClosedComplianceEnabled() { + return closedCompliance; + } + + @Override + public boolean isJpaProxyComplianceEnabled() { + return proxyCompliance; + } + + @Override + public boolean isJpaCacheComplianceEnabled() { + return cacheCompliance; + } + + @Override + public boolean isGlobalGeneratorScopeEnabled() { + return idGeneratorNameScopeCompliance; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/spi/NativeQueryTupleTransformerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/spi/NativeQueryTupleTransformerTest.java new file mode 100644 index 000000000000..0def3e165ad5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/spi/NativeQueryTupleTransformerTest.java @@ -0,0 +1,35 @@ +package org.hibernate.jpa.spi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import javax.persistence.Tuple; + +import org.junit.Test; + +/** + * @author Maksym Symonov + */ +public class NativeQueryTupleTransformerTest { + + private final NativeQueryTupleTransformer nativeQueryTupleTransformer = new NativeQueryTupleTransformer(); + + @Test + public void nullValueIsExtractedFromTuple() { + final Tuple tuple = (Tuple) nativeQueryTupleTransformer.transformTuple( + new Object[] { 1L, null }, + new String[] { "id", "value" } + ); + assertEquals(1L, tuple.get("id")); + assertNull(tuple.get("value")); + } + + @Test(expected = IllegalArgumentException.class) + public void missingAliasCausesExceptionWhenIsExtractedFromTuple() { + final Tuple tuple = (Tuple) nativeQueryTupleTransformer.transformTuple( + new Object[] { 1L, null }, + new String[] { "id", "value" } + ); + tuple.get("unknownAlias"); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java index 9af76c4f9b0a..4f0648baac3e 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; + import javax.persistence.EntityManager; import javax.persistence.SharedCacheMode; import javax.persistence.ValidationMode; @@ -21,21 +22,17 @@ import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; - import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; -import org.jboss.logging.Logger; - /** * A base class for all ejb tests. * @@ -43,7 +40,6 @@ * @author Hardy Ferentschik */ public abstract class BaseEntityManagerFunctionalTestCase extends BaseUnitTestCase { - private static final Logger log = Logger.getLogger( BaseEntityManagerFunctionalTestCase.class ); // IMPL NOTE : Here we use @Before and @After (instead of @BeforeClassOnce and @AfterClassOnce like we do in // BaseCoreFunctionalTestCase) because the old HEM test methodology was to create an EMF for each test method. @@ -70,7 +66,7 @@ protected StandardServiceRegistryImpl serviceRegistry() { @Before @SuppressWarnings( {"UnusedDeclaration"}) - public void buildEntityManagerFactory() throws Exception { + public void buildEntityManagerFactory() { log.trace( "Building EntityManagerFactory" ); entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( @@ -197,7 +193,7 @@ protected Map buildSettings() { protected void addMappings(Map settings) { String[] mappings = getMappings(); if ( mappings != null ) { - settings.put( AvailableSettings.HBXML_FILES, StringHelper.join( ",", mappings ) ); + settings.put( AvailableSettings.HBXML_FILES, String.join( ",", mappings ) ); } } @@ -211,6 +207,8 @@ protected Map getConfig() { Map config = Environment.getProperties(); ArrayList classes = new ArrayList(); + config.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() ); + classes.addAll( Arrays.asList( getAnnotatedClasses() ) ); config.put( AvailableSettings.LOADED_CLASSES, classes ); for ( Map.Entry entry : getCachedClasses().entrySet() ) { @@ -293,7 +291,7 @@ private void releaseUnclosedEntityManager(EntityManager em) { log.warn("You left an open transaction! Fix your test case. For now, we are closing it for you."); } if ( em.isOpen() ) { - // as we open an EM beforeQuery the test runs, it will still be open if the test uses a custom EM. + // as we open an EM before the test runs, it will still be open if the test uses a custom EM. // or, the person may have forgotten to close. So, do not raise a "fail", but log the fact. em.close(); log.warn("The EntityManager is not closed. Closing it."); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerClosedTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerClosedTest.java new file mode 100644 index 000000000000..015723d14afd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerClosedTest.java @@ -0,0 +1,674 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test; + +import java.util.Date; +import java.util.GregorianCalendar; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.Parameter; +import javax.persistence.Query; +import javax.persistence.TemporalType; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Gail Badner + */ +public class EntityManagerClosedTest extends BaseEntityManagerFunctionalTestCase { + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetMetamodel() { + EntityManager em = getOrCreateEntityManager(); + em.close(); + try { + em.getMetamodel(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetMetamodelWithTransaction () { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.close(); + try { + em.getMetamodel(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // make sure transaction is set for rollback + assertTrue( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetCriteriaBuilder() { + EntityManager em = getOrCreateEntityManager(); + em.close(); + try { + em.getCriteriaBuilder(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetCriteriaBuilderWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.close(); + try { + em.getCriteriaBuilder(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // make sure transaction is set for rollback + assertTrue( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetEntityManagerFactory() { + EntityManager em = getOrCreateEntityManager(); + em.close(); + try { + em.getEntityManagerFactory(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetEntityManagerFactoryWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.close(); + try { + em.getEntityManagerFactory(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // make sure transaction is set for rollback + assertTrue( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testCreateNamedQuery() { + EntityManager em = getOrCreateEntityManager(); + em.close(); + try { + em.createNamedQuery( "abc" ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testCreateNamedQueryWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.close(); + try { + em.createNamedQuery( "abc" ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // make sure transaction is set for rollback + assertTrue( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetFlushMode() { + EntityManager em = getOrCreateEntityManager(); + em.close(); + try { + em.getFlushMode(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testSetFlushMode() { + EntityManager em = getOrCreateEntityManager(); + em.close(); + try { + em.setFlushMode( FlushModeType.AUTO ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testGetDelegate() { + EntityManager em = getOrCreateEntityManager(); + em.close(); + try { + em.getDelegate(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetParametersWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity where name = :name" ); + query.setParameter( "name", "AName" ); + em.close(); + try { + query.getParameters(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // txn should not be set for rollback + assertFalse( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetParameterByPositionWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity where name = ?1" ); + query.setParameter( 1, "AName" ); + em.close(); + try { + query.getParameter( 1 ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // txn should not be set for rollback + assertFalse( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetParameterByNameWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity where name = :name" ); + em.close(); + try { + query.getParameter( "name" ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // txn should not be set for rollback + assertFalse( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetParameterValueByParameterWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity where name = ?1" ); + query.setParameter( 1, "AName" ); + Parameter p = query.getParameter( 1 ); + em.close(); + try { + query.getParameterValue( p ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // txn should not be set for rollback + assertFalse( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetParameterValueByStringWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity where name = :name" ); + query.setParameter( "name", "AName" ); + em.close(); + try { + query.getParameterValue( "name" ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // txn should not be set for rollback + assertFalse( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryIsBound() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where name = :name" ); + query.setParameter( "name", "AName" ); + Parameter parameter = query.getParameter( "name" ); + em.close(); + try { + query.isBound( parameter ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetFirstResult() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where name = :name" ); + query.setParameter( "name", "AName" ); + em.close(); + try { + query.setFirstResult( 1 ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetFlushMode() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where name = :name" ); + query.setParameter( "name", "AName" ); + em.close(); + try { + query.setFlushMode( FlushModeType.AUTO ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetLockMode() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where name = :name" ); + query.setParameter( "name", "AName" ); + em.close(); + try { + query.setLockMode( LockModeType.OPTIMISTIC ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetCalendarDateParameterByPosition() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where birthDay = ?1" ); + em.close(); + try { + query.setParameter( 1, new GregorianCalendar( 2000, 4, 1 ), TemporalType.DATE ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetDateParameterByPosition() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where birthDay = ?1" ); + em.close(); + try { + query.setParameter( 1, new Date(), TemporalType.DATE ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetIntParameterByPosition() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where intValue = ?1" ); + em.close(); + try { + query.setParameter( 1, 1 ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetCalendarDateParameterByParameter() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where birthDay = ?1" ); + Parameter parameter = query.getParameter( 1 ); + em.close(); + try { + query.setParameter( parameter, new GregorianCalendar( 2000, 4, 1 ), TemporalType.DATE ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetDateParameterByParameter() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where birthDay = ?1" ); + Parameter parameter = query.getParameter( 1 ); + em.close(); + try { + query.setParameter( parameter, new Date(), TemporalType.DATE ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetIntParameterByParameter() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where intValue = ?1" ); + Parameter parameter = query.getParameter( 1 ); + em.close(); + try { + query.setParameter( parameter, 1 ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetCalendarParameterByName() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where birthDay = :bday" ); + em.close(); + try { + query.setParameter( "bday", new GregorianCalendar( 2000, 4, 1 ), TemporalType.DATE ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetSingleResult() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity" ); + em.close(); + try { + query.getSingleResult(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetDateParameterByName() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where birthDay = :bday" ); + em.close(); + try { + query.setParameter( "bday", new Date(), TemporalType.DATE ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQuerySetIntParameterByName() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity where intValue = :ival" ); + em.close(); + try { + query.setParameter( "ival", 1 ); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetFlushMode() { + EntityManager em = getOrCreateEntityManager(); + Query query = em.createQuery( "from AnEntity" ); + em.close(); + try { + query.getFlushMode(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetFlushModeWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity" ); + em.close(); + try { + query.getFlushMode(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + assertTrue( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetLockModeWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity" ); + em.close(); + try { + query.getLockMode(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + // transaction should not be set for rollback + assertFalse( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetMaxResultsWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity" ); + em.close(); + try { + query.getMaxResults(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + assertTrue( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12110") + public void testQueryGetFirstResultWithTransaction() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Query query = em.createQuery( "from AnEntity" ); + em.close(); + try { + query.getFirstResult(); + fail( "should have thrown IllegalStateException" ); + } + catch (IllegalStateException ex) { + // expected + assertTrue( em.getTransaction().getRollbackOnly() ); + } + finally { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + } + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { AnEntity.class }; + } + + @Entity(name = "AnEntity") + private static class AnEntity { + @Id + private long id; + + private String name; + private Date birthDay; + private int intValue; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java index c01a040bd3bb..13d0bebd8670 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java @@ -58,6 +58,7 @@ public Class[] getAnnotatedClasses() { @SuppressWarnings( {"unchecked"}) protected void addConfigOptions(Map options) { options.put( Environment.GENERATE_STATISTICS, "true" ); + options.put( Environment.JPA_CLOSED_COMPLIANCE, "true" ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java new file mode 100644 index 000000000000..a619e6a9e15d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java @@ -0,0 +1,234 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-13244") +public class JpaProxyComplianceWithDebug extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void addConfigOptions(Map options) { + options.put( + AvailableSettings.JPA_PROXY_COMPLIANCE, + Boolean.TRUE); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + MvnoBillingAgreement.class, + MvnoOpcio.class, + }; + } + + @Before + public void setUp() { + List opciok = Arrays.asList(2008, 2010, 2012, 2014, 2015, 2026, 2027, 2103, 2110, 2145, 992068, 992070); + + doInJPA(this::entityManagerFactory, entityManager -> { + + MvnoBillingAgreement ba = new MvnoBillingAgreement(); + ba.setId(1); + ba.setName("1"); + entityManager.persist(ba); + + for (int opcioId : opciok) { + MvnoOpcio o = new MvnoOpcio(); + o.setId(opcioId); + o.setMegnevezes(Integer.toString(opcioId)); + o.getMvnoBillingAgreementekDefaultOpcioja().add(ba); + ba.getMvnoDefaultUniverzalisOpcioi().add(o); + entityManager.persist(o); + } + + ba.setBehajtasEgyiranyusitasOpcio(entityManager.find(MvnoOpcio.class, 2026)); + ba.setBehajtasFelfuggesztesOpcio(entityManager.find(MvnoOpcio.class, 992070)); + ba.setHotlimitEmeltDijasBarOpcio(entityManager.find(MvnoOpcio.class, 2145)); + + }); + } + + + @Test + @TestForIssue(jiraKey = "HHH-13244") + public void testJpaComplianceProxyWithDebug() { + + //This could be replaced with setting the root logger level, or the "org.hibernate" logger to debug. + //These are simply the narrowest log settings that trigger the bug + Logger entityLogger = LogManager.getLogger("org.hibernate.internal.util.EntityPrinter"); + Logger listenerLogger = LogManager.getLogger("org.hibernate.event.internal.AbstractFlushingEventListener"); + + Level oldEntityLogLevel = entityLogger.getLevel(); + Level oldListenerLogLevel = listenerLogger.getLevel(); + + entityLogger.setLevel((Level) Level.DEBUG); + listenerLogger.setLevel((Level) Level.DEBUG); + try { + doInJPA(this::entityManagerFactory, entityManager -> { + entityManager.find(MvnoBillingAgreement.class, 1); + }); + } finally { + entityLogger.setLevel(oldEntityLogLevel); + listenerLogger.setLevel(oldListenerLogLevel); + } + + } + + @Entity + @Table(name = "mvno_billing_agreement") + public static class MvnoBillingAgreement implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + private int id; + + private String name; + + @ManyToMany + @JoinTable( + name = "mvnobillagr_def_univerzalis", joinColumns = { + @JoinColumn(name = "billing_agreement_id") + }, + inverseJoinColumns = { + @JoinColumn(name = "univerzalis_opcio_id") + }) + private Set mvnoDefaultUniverzalisOpcioi = new HashSet<>(); + + @JoinColumn(name = "egyiranyusitas_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio behajtasEgyiranyusitasOpcio; + + @JoinColumn(name = "felfuggesztes_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio behajtasFelfuggesztesOpcio; + + @JoinColumn(name = "emeltdijas_bar_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio hotlimitEmeltDijasBarOpcio; + + public MvnoBillingAgreement() {} + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getMvnoDefaultUniverzalisOpcioi() { + return this.mvnoDefaultUniverzalisOpcioi; + } + + public void setMvnoDefaultUniverzalisOpcioi(Set mvnoDefaultUniverzalisOpcioi) { + this.mvnoDefaultUniverzalisOpcioi = mvnoDefaultUniverzalisOpcioi; + } + + public MvnoOpcio getBehajtasEgyiranyusitasOpcio() { + return this.behajtasEgyiranyusitasOpcio; + } + + public void setBehajtasEgyiranyusitasOpcio(MvnoOpcio behajtasEgyiranyusitasOpcio) { + this.behajtasEgyiranyusitasOpcio = behajtasEgyiranyusitasOpcio; + } + + public MvnoOpcio getBehajtasFelfuggesztesOpcio() { + return this.behajtasFelfuggesztesOpcio; + } + + public void setBehajtasFelfuggesztesOpcio(MvnoOpcio behajtasFelfuggesztesOpcio) { + this.behajtasFelfuggesztesOpcio = behajtasFelfuggesztesOpcio; + } + + public MvnoOpcio getHotlimitEmeltDijasBarOpcio() { + return this.hotlimitEmeltDijasBarOpcio; + } + + public void setHotlimitEmeltDijasBarOpcio(MvnoOpcio hotlimitEmeltDijasBarOpcio) { + this.hotlimitEmeltDijasBarOpcio = hotlimitEmeltDijasBarOpcio; + } + + } + + @Entity + @Table(name = "mvno_opcio") + public static class MvnoOpcio implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + private int id; + + @Column(name = "megnevezes") + private String megnevezes; + + @ManyToMany(mappedBy = "mvnoDefaultUniverzalisOpcioi") + private Set mvnoBillingAgreementekDefaultOpcioja = new HashSet<>(); + + public MvnoOpcio() {} + + public int getId() { + return this.id; + } + + public void setId(int id) { + this.id = id; + } + + public String getMegnevezes() { + return this.megnevezes; + } + + public void setMegnevezes(String megnevezes) { + this.megnevezes = megnevezes; + } + + public Set getMvnoBillingAgreementekDefaultOpcioja() { + return this.mvnoBillingAgreementekDefaultOpcioja; + } + + public void setMvnoBillingAgreementekDefaultOpcioja(Set mvnoBillingAgreementekDefaultOpcioja) { + this.mvnoBillingAgreementekDefaultOpcioja = mvnoBillingAgreementekDefaultOpcioja; + } + + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/NamedQueryTransactionFailureTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/NamedQueryTransactionFailureTest.java new file mode 100644 index 000000000000..5f7d8ea31f1e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/NamedQueryTransactionFailureTest.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +//$Id$ + +package org.hibernate.jpa.test; + +import java.util.Map; +import javax.persistence.Query; + +import org.hibernate.cfg.Environment; +import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; +import org.hibernate.resource.transaction.spi.TransactionCoordinator; +import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import org.mockito.Mockito; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; + +/** + * @author Vlad Mihalcea + */ +public class NamedQueryTransactionFailureTest extends BaseEntityManagerFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Item.class, + Distributor.class, + Wallet.class + }; + } + + private TransactionCoordinator transactionCoordinator; + + @SuppressWarnings( {"unchecked"}) + protected void addConfigOptions(Map options) { + TransactionCoordinatorBuilder transactionCoordinatorBuilder = Mockito.mock( TransactionCoordinatorBuilder.class); + when(transactionCoordinatorBuilder.getDefaultConnectionHandlingMode()) + .thenReturn( PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD ); + + when(transactionCoordinatorBuilder.isJta()) + .thenReturn( false ); + + transactionCoordinator = Mockito.mock( TransactionCoordinator.class); + + when(transactionCoordinatorBuilder.buildTransactionCoordinator(any(), any( TransactionCoordinatorBuilder.Options.class))) + .thenReturn( transactionCoordinator ); + + when( transactionCoordinator.getTransactionCoordinatorBuilder() ).thenReturn( transactionCoordinatorBuilder ); + + TransactionCoordinator.TransactionDriver transactionDriver = Mockito.mock( TransactionCoordinator.TransactionDriver.class); + when( transactionCoordinator.getTransactionDriverControl() ).thenReturn( transactionDriver ); + when( transactionCoordinator.isActive() ).thenReturn( true ); + when( transactionDriver.isActive( anyBoolean() ) ).thenReturn( false ); + + doNothing().when( transactionCoordinator ).pulse(); + + options.put( Environment.TRANSACTION_COORDINATOR_STRATEGY, transactionCoordinatorBuilder ); + } + + @Override + protected boolean createSchema() { + return false; + } + + @Test + @TestForIssue( jiraKey = "HHH-11997" ) + public void testNamedQueryWithTransactionSynchStatus() { + try { + doInJPA( this::entityManagerFactory, entityManager -> { + try { + Mockito.reset( transactionCoordinator ); + doThrow(IllegalStateException.class).when( transactionCoordinator ).pulse(); + + entityManager.createNamedQuery( "NamedQuery" ); + } + catch (Exception e) { + assertEquals(IllegalArgumentException.class, e.getClass()); + assertEquals(IllegalStateException.class, e.getCause().getClass()); + } + }); + } + catch (Exception ignore) { + } + } + + @Test + @TestForIssue( jiraKey = "HHH-11997" ) + public void testNamedQueryWithMarkForRollbackOnlyFailure() { + try { + doInJPA( this::entityManagerFactory, entityManager -> { + try { + Mockito.reset( transactionCoordinator ); + doNothing(). + doThrow(IllegalStateException.class).when( transactionCoordinator ).pulse(); + + entityManager.createNamedQuery( "NamedQuery" ); + } + catch (Exception e) { + assertEquals(IllegalArgumentException.class, e.getClass()); + assertEquals(IllegalStateException.class, e.getCause().getClass()); + } + }); + } + catch (Exception ignore) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/ValidatorFactory2PhaseInjectionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/ValidatorFactory2PhaseInjectionTest.java index 98430fa81da7..d567c9c22f72 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/ValidatorFactory2PhaseInjectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/ValidatorFactory2PhaseInjectionTest.java @@ -17,7 +17,7 @@ import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; -import org.hibernate.jpa.test.jee.OrmVersionTest; +import org.hibernate.test.jpa.xml.versions.JpaXsdVersionsTest; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; @@ -49,7 +49,7 @@ public void after() { @Test public void testInjectionAvailabilityFromEmf() { EntityManagerFactoryBuilder emfb = Bootstrap.getEntityManagerFactoryBuilder( - new OrmVersionTest.PersistenceUnitInfoImpl( "my-test" ) { + new JpaXsdVersionsTest.PersistenceUnitInfoImpl( "my-test" ) { @Override public URL getPersistenceUnitRootUrl() { // just get any known url... diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/BootFailureTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/BootFailureTest.java new file mode 100644 index 000000000000..d0c99a17a44d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/BootFailureTest.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 . + */ +package org.hibernate.jpa.test.boot; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.util.ConfigHelper; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.service.spi.ServiceException; + +import org.junit.Test; + +/** + * Test to verify that a dump configuration error results in an exception being + * thrown even when booting via the standard JPA boostrap API. + * + * @author Andrea Boriero + * @author Sanne Grinovero + */ +public class BootFailureTest extends BaseEntityManagerFunctionalTestCase { + + @Test(expected = ServiceException.class) + public void exceptionOnIllegalPUTest() { + bootstrapPersistenceUnit( "IntentionallyBroken" ); + } + + @Test(expected = ServiceException.class) + public void exceptionOnIllegalPUWithoutProviderTest() { + bootstrapPersistenceUnit( "IntentionallyBrokenWihoutExplicitProvider" ); + } + + private void bootstrapPersistenceUnit(final String puName) { + final Map properties = new HashMap<>(); + properties.put( AvailableSettings.CLASSLOADERS, Arrays.asList( new TestClassLoader() ) ); + EntityManagerFactory broken = Persistence.createEntityManagerFactory( + puName, + properties + ); + if ( broken != null ) { + broken.close(); + } + } + + private static class TestClassLoader extends ClassLoader { + static final List urls = Arrays.asList( ConfigHelper.findAsResource( "org/hibernate/jpa/test/bootstrap/META-INF/persistence.xml" ) ); + + @Override + protected Enumeration findResources(String name) { + return name.equals( "META-INF/persistence.xml" ) ? + Collections.enumeration( urls ) : + Collections.emptyEnumeration(); + } + } + +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java new file mode 100644 index 000000000000..b4f707577505 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.boot; + + +import org.hibernate.internal.HEMLogging; +import org.hibernate.jpa.boot.spi.ProviderChecker; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests that deprecated (and removed) provider, "org.hibernate.ejb.HibernatePersistence", + * is recognized as a Hibernate persistence provider. + * + * @author Gail Badner + */ +public class DeprecatedProviderCheckerTest { + final static String DEPRECATED_PROVIDER_NAME = "org.hibernate.ejb.HibernatePersistence"; + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + HEMLogging.messageLogger( ProviderChecker.class.getName() ) + ); + + @Test + @TestForIssue( jiraKey = "HHH-13027") + public void testDeprecatedProvider() { + Triggerable triggerable = logInspection.watchForLogMessages( "HHH015016" ); + triggerable.reset(); + assertTrue( ProviderChecker.hibernateProviderNamesContain( DEPRECATED_PROVIDER_NAME ) ); + triggerable.wasTriggered(); + assertEquals( + "HHH015016: Encountered a deprecated javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence]; [org.hibernate.jpa.HibernatePersistenceProvider] will be used instead.", + triggerable.triggerMessage() + ); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/NewBootProcessTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/NewBootProcessTest.java index 2daba86012ea..47916e966c47 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/NewBootProcessTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/NewBootProcessTest.java @@ -11,7 +11,7 @@ import java.util.HashMap; import java.util.Map; -import org.hibernate.jpa.test.jee.OrmVersionTest; +import org.hibernate.test.jpa.xml.versions.JpaXsdVersionsTest; import org.hibernate.jpa.HibernatePersistenceProvider; import org.junit.Test; @@ -26,7 +26,7 @@ public void basicNewBootProcessTest() { HibernatePersistenceProvider persistenceProvider = new HibernatePersistenceProvider(); final EntityManagerFactory emf = persistenceProvider.createContainerEntityManagerFactory( - new OrmVersionTest.PersistenceUnitInfoImpl( "my-test" ) { + new JpaXsdVersionsTest.PersistenceUnitInfoImpl( "my-test" ) { @Override public URL getPersistenceUnitRootUrl() { // just get any known url... diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cacheable/annotation/ConfigurationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cacheable/annotation/ConfigurationTest.java index f56752777548..30722b0d6881 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/cacheable/annotation/ConfigurationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cacheable/annotation/ConfigurationTest.java @@ -13,7 +13,6 @@ import javax.persistence.SharedCacheMode; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.cache.internal.NoCachingRegionFactory; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.cfg.Environment; import org.hibernate.jpa.AvailableSettings; @@ -24,12 +23,11 @@ import org.hibernate.testing.cache.CachingRegionFactory; import org.hibernate.testing.junit4.BaseUnitTestCase; - import org.junit.After; import org.junit.Test; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * this is hacky transient step until EMF building is integrated with metamodel @@ -51,13 +49,13 @@ public void testSharedCacheModeNone() { MetadataImplementor metadata = buildMetadata( SharedCacheMode.NONE ); PersistentClass pc = metadata.getEntityBinding( ExplicitlyCacheableEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); pc = metadata.getEntityBinding( ExplicitlyNonCacheableEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); pc = metadata.getEntityBinding( NoCacheableAnnotationEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); } @Test @@ -65,13 +63,13 @@ public void testSharedCacheModeUnspecified() { MetadataImplementor metadata = buildMetadata( SharedCacheMode.UNSPECIFIED ); PersistentClass pc = metadata.getEntityBinding( ExplicitlyCacheableEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); pc = metadata.getEntityBinding( ExplicitlyNonCacheableEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); pc = metadata.getEntityBinding( NoCacheableAnnotationEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); } @Test @@ -79,13 +77,13 @@ public void testSharedCacheModeAll() { MetadataImplementor metadata = buildMetadata( SharedCacheMode.ALL ); PersistentClass pc = metadata.getEntityBinding( ExplicitlyCacheableEntity.class.getName() ); - assertNotNull( pc.getCacheConcurrencyStrategy() ); + assertTrue( pc.isCached() ); pc = metadata.getEntityBinding( ExplicitlyNonCacheableEntity.class.getName() ); - assertNotNull( pc.getCacheConcurrencyStrategy() ); + assertTrue( pc.isCached() ); pc = metadata.getEntityBinding( NoCacheableAnnotationEntity.class.getName() ); - assertNotNull( pc.getCacheConcurrencyStrategy() ); + assertTrue( pc.isCached() ); } @Test @@ -93,13 +91,13 @@ public void testSharedCacheModeEnable() { MetadataImplementor metadata = buildMetadata( SharedCacheMode.ENABLE_SELECTIVE ); PersistentClass pc = metadata.getEntityBinding( ExplicitlyCacheableEntity.class.getName() ); - assertNotNull( pc.getCacheConcurrencyStrategy() ); + assertTrue( pc.isCached() ); pc = metadata.getEntityBinding( ExplicitlyNonCacheableEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); pc = metadata.getEntityBinding( NoCacheableAnnotationEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); } @Test @@ -107,13 +105,13 @@ public void testSharedCacheModeDisable() { MetadataImplementor metadata = buildMetadata( SharedCacheMode.DISABLE_SELECTIVE ); PersistentClass pc = metadata.getEntityBinding( ExplicitlyCacheableEntity.class.getName() ); - assertNotNull( pc.getCacheConcurrencyStrategy() ); + assertTrue( pc.isCached() ); pc = metadata.getEntityBinding( ExplicitlyNonCacheableEntity.class.getName() ); - assertNull( pc.getCacheConcurrencyStrategy() ); + assertFalse( pc.isCached() ); pc = metadata.getEntityBinding( NoCacheableAnnotationEntity.class.getName() ); - assertNotNull( pc.getCacheConcurrencyStrategy() ); + assertTrue( pc.isCached() ); } @SuppressWarnings("unchecked") diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java new file mode 100644 index 000000000000..0e9ab1b32151 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java @@ -0,0 +1,127 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-12718") +@RunWith(BytecodeEnhancerRunner.class) +public class PreUpdateBytecodeEnhancementTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() ); + options.put( AvailableSettings.ENHANCER_ENABLE_LAZY_INITIALIZATION, "true" ); + options.put( AvailableSettings.ENHANCER_ENABLE_DIRTY_TRACKING, "true" ); + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + } + + @Entity(name = "Person") + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java new file mode 100644 index 000000000000..bb45944c8112 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java @@ -0,0 +1,180 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.CustomEntityDirtinessStrategy; +import org.hibernate.Session; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-12718") +public class PreUpdateCustomEntityDirtinessStrategyTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + + assertTrue( DefaultCustomEntityDirtinessStrategy.INSTANCE.isPersonNameChanged() ); + assertTrue( DefaultCustomEntityDirtinessStrategy.INSTANCE.isPersonLastUpdatedAtChanged() ); + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY, DefaultCustomEntityDirtinessStrategy.INSTANCE ); + } + + public static class DefaultCustomEntityDirtinessStrategy + implements CustomEntityDirtinessStrategy { + private static final DefaultCustomEntityDirtinessStrategy INSTANCE = + new DefaultCustomEntityDirtinessStrategy(); + + private boolean personNameChanged = false; + private boolean personLastUpdatedAtChanged = false; + + @Override + public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) { + return true; + } + + @Override + public boolean isDirty(Object entity, EntityPersister persister, Session session) { + Person person = (Person) entity; + if ( !personNameChanged ) { + personNameChanged = person.getName() != null; + return personNameChanged; + } + if ( !personLastUpdatedAtChanged ) { + personLastUpdatedAtChanged = person.getLastUpdatedAt() != null; + return personLastUpdatedAtChanged; + } + return false; + } + + @Override + public void resetDirty(Object entity, EntityPersister persister, Session session) { + } + + @Override + public void findDirty( + Object entity, + EntityPersister persister, + Session session, + DirtyCheckContext dirtyCheckContext) { + } + + public boolean isPersonNameChanged() { + return personNameChanged; + } + + public boolean isPersonLastUpdatedAtChanged() { + return personLastUpdatedAtChanged; + } + } + + @Entity(name = "Person") + @DynamicUpdate + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java new file mode 100644 index 000000000000..93a3f8069721 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java @@ -0,0 +1,155 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.EmptyInterceptor; +import org.hibernate.SessionBuilder; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.type.Type; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-12718") +public class PreUpdateDirtyCheckingInterceptorTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + doInHibernateSessionBuilder( this::sessionWithInterceptor, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + } + + public static class OnFlushDirtyInterceptor extends EmptyInterceptor { + + private static OnFlushDirtyInterceptor INSTANCE = new OnFlushDirtyInterceptor(); + + @Override + public int[] findDirty( + Object entity, + Serializable id, + Object[] currentState, + Object[] previousState, + String[] propertyNames, + Type[] types) { + int[] result = new int[propertyNames.length]; + int span = 0; + + for ( int i = 0; i < previousState.length; i++ ) { + if( !Objects.deepEquals(previousState[i], currentState[i])) { + result[span++] = i; + } + } + + return result; + } + } + + private SessionBuilder sessionWithInterceptor() { + return sessionFactory() + .withOptions() + .interceptor( OnFlushDirtyInterceptor.INSTANCE ); + } + + @Entity(name = "Person") + @DynamicUpdate + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewBidirectionalBagTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewBidirectionalBagTest.java new file mode 100644 index 000000000000..4d7a2ab322d3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewBidirectionalBagTest.java @@ -0,0 +1,120 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.PreUpdate; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-13466") +public class PreUpdateNewBidirectionalBagTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Tag.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + person.id = 1; + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + final Tag tag = new Tag(); + tag.id = 2; + tag.description = "description"; + tag.person = p; + final Set tags = new HashSet(); + tags.add( tag ); + p.tags = tags; + entityManager.merge( p ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertEquals( 1, p.tags.size() ); + assertEquals( "description", p.tags.iterator().next().description ); + assertNotNull( p.getLastUpdatedAt() ); + } ); + } + + @Entity(name = "Person") + @EntityListeners( PersonListener.class ) + private static class Person { + @Id + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Collection tags = new ArrayList(); + } + + @Entity(name = "Tag") + public static class Tag { + + @Id + private int id; + + private String description; + + @ManyToOne + private Person person; + } + + public static class PersonListener { + @PreUpdate + void onPreUpdate(Object o) { + if ( Person.class.isInstance( o ) ) { + ( (Person) o ).setLastUpdatedAt( Instant.now() ); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalBagTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalBagTest.java new file mode 100644 index 000000000000..c7febd65366a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalBagTest.java @@ -0,0 +1,115 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.PreUpdate; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-13466") +public class PreUpdateNewUnidirectionalBagTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Tag.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + person.id = 1; + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + final Tag tag = new Tag(); + tag.id = 2; + tag.description = "description"; + final Set tags = new HashSet(); + tags.add( tag ); + p.tags = tags; + entityManager.merge( p ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertEquals( 1, p.tags.size() ); + assertEquals( "description", p.tags.iterator().next().description ); + assertNotNull( p.getLastUpdatedAt() ); + } ); + } + + @Entity(name = "Person") + @EntityListeners( PersonListener.class ) + private static class Person { + @Id + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Collection tags = new ArrayList(); + } + + @Entity(name = "Tag") + public static class Tag { + + @Id + private int id; + + private String description; + } + + public static class PersonListener { + @PreUpdate + void onPreUpdate(Object o) { + if ( Person.class.isInstance( o ) ) { + ( (Person) o ).setLastUpdatedAt( Instant.now() ); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalIdBagTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalIdBagTest.java new file mode 100644 index 000000000000..be3325a2e6b0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalIdBagTest.java @@ -0,0 +1,124 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.PreUpdate; + +import org.hibernate.annotations.CollectionId; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Type; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-13466") +public class PreUpdateNewUnidirectionalIdBagTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Tag.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + person.id = 1; + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + final Tag tag = new Tag(); + tag.id = 2; + tag.description = "description"; + final Set tags = new HashSet(); + tags.add( tag ); + p.tags = tags; + entityManager.merge( p ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertEquals( 1, p.tags.size() ); + assertEquals( "description", p.tags.iterator().next().description ); + assertNotNull( p.getLastUpdatedAt() ); + } ); + } + + @Entity(name = "Person") + @EntityListeners( PersonListener.class ) + @GenericGenerator(name="increment", strategy = "increment") + private static class Person { + @Id + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @CollectionId( + columns = @Column(name = "n_key_tag"), + type = @Type(type = "long"), + generator = "increment" ) + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Collection tags = new ArrayList(); + } + + @Entity(name = "Tag") + public static class Tag { + + @Id + private int id; + + private String description; + } + + public static class PersonListener { + @PreUpdate + void onPreUpdate(Object o) { + if ( Person.class.isInstance( o ) ) { + ( (Person) o ).setLastUpdatedAt( Instant.now() ); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/MergeWithTransientNonCascadedAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/MergeWithTransientNonCascadedAssociationTest.java index dbc3d15dc486..b42727698b7a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/MergeWithTransientNonCascadedAssociationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/MergeWithTransientNonCascadedAssociationTest.java @@ -79,8 +79,8 @@ public Person() { @Entity( name = "Address" ) public static class Address { @Id - @GeneratedValue( generator = "increment" ) - @GenericGenerator( name = "increment", strategy = "increment" ) + @GeneratedValue( generator = "increment_1" ) + @GenericGenerator( name = "increment_1", strategy = "increment" ) private Integer id; } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeCollectionEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeCollectionEmbeddableTest.java new file mode 100644 index 000000000000..8f496f8f1b19 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeCollectionEmbeddableTest.java @@ -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 . + */ +package org.hibernate.jpa.test.cascade.multilevel; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +public class MultiLevelCascadeCollectionEmbeddableTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( MultiLevelCascadeCollectionEmbeddableTest.class ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + MainEntity.class, + SubEntity.class, + AnotherSubSubEntity.class, + SubSubEntity.class + }; + } + + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "INSERT INTO MAIN_TABLE(ID_NUM) VALUES (99427)" ).executeUpdate(); + entityManager.createNativeQuery( "INSERT INTO SUB_TABLE(ID_NUM, SUB_ID, FAMILY_IDENTIFIER, IND_NUM) VALUES (99427, 1, 'A', '123A')" ).executeUpdate(); + entityManager.createNativeQuery( "INSERT INTO SUB_TABLE(ID_NUM, SUB_ID, FAMILY_IDENTIFIER, IND_NUM) VALUES (99427, 2, 'S', '321A')" ).executeUpdate(); + entityManager.createNativeQuery( "INSERT INTO SUB_SUB_TABLE(ID_NUM, CODE, IND_NUM) VALUES (99427, 'CODE1', '123A')" ).executeUpdate(); + } ); + } + + @Test + @FailureExpected( jiraKey = "HHH-12291" ) + public void testHibernateDeleteEntityWithoutInitializingCollections() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + MainEntity mainEntity = entityManager.find(MainEntity.class, 99427L); + + assertNotNull(mainEntity); + assertFalse(mainEntity.getSubEntities().isEmpty()); + + Optional subEntityToRemove = mainEntity.getSubEntities().stream() + .filter(subEntity -> "123A".equals(subEntity.getIndNum())).findFirst(); + subEntityToRemove.ifPresent( mainEntity::removeSubEntity ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12294" ) + public void testHibernateDeleteEntityInitializeCollections() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + MainEntity mainEntity = entityManager.find(MainEntity.class, 99427L); + + assertNotNull(mainEntity); + assertFalse(mainEntity.getSubEntities().isEmpty()); + + Optional subEntityToRemove = mainEntity.getSubEntities().stream() + .filter(subEntity -> "123A".equals(subEntity.getIndNum())).findFirst(); + if ( subEntityToRemove.isPresent() ) { + SubEntity subEntity = subEntityToRemove.get(); + assertEquals( 1, subEntity.getSubSubEntities().size() ); + assertEquals( 0, subEntity.getAnotherSubSubEntities().size() ); + mainEntity.removeSubEntity( subEntity ); + } + } ); + } + + + @Entity + @Table(name = "MAIN_TABLE") + public static class MainEntity implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idNum") + @SequenceGenerator(name = "idNum", sequenceName = "id_num", allocationSize = 1) + @Column(name="ID_NUM") + private Long idNum; + + @OneToMany(mappedBy = "mainEntity", cascade = CascadeType.ALL, orphanRemoval = true) + private List subEntities = new ArrayList<>(); + + public void addSubEntity(SubEntity subEntity) { + subEntity.setMainEntity(this); + subEntities.add(subEntity); + } + + public void removeSubEntity(SubEntity subEntity) { + subEntity.setMainEntity(null); + subEntities.remove(subEntity); + } + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public List getSubEntities() { + return subEntities; + } + + public void setSubEntities(List subEntities) { + this.subEntities = subEntities; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + MainEntity that = (MainEntity) o; + return Objects.equals(idNum, that.idNum); + } + + @Override + public int hashCode() { + + return Objects.hash(super.hashCode(), idNum); + } + } + + @Entity + @Table(name = "SUB_TABLE") + public static class SubEntity implements Serializable { + + @Id + @Column(name = "SUB_ID") + private Long subIdNum; + + @Column(name = "IND_NUM") + private String indNum; + + @Column(name = "FAMILY_IDENTIFIER") + private String familyIdentifier; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "ID_NUM") + private MainEntity mainEntity; + + @OneToMany(mappedBy = "subEntity", cascade = CascadeType.ALL, orphanRemoval = true) + private List subSubEntities = new ArrayList<>(); + + @OneToMany(mappedBy = "subEntity", cascade = CascadeType.ALL, orphanRemoval = true) + private List anotherSubSubEntities = new ArrayList<>(); + + public Long getSubIdNum() { + return subIdNum; + } + + public void setSubIdNum(Long subIdNum) { + this.subIdNum = subIdNum; + } + + public String getIndNum() { + return indNum; + } + + public void setIndNum(String indNum) { + this.indNum = indNum; + } + + public String getFamilyIdentifier() { + return familyIdentifier; + } + + public void setFamilyIdentifier(String familyIdentifier) { + this.familyIdentifier = familyIdentifier; + } + + public MainEntity getMainEntity() { + return mainEntity; + } + + public void setMainEntity(MainEntity mainEntity) { + this.mainEntity = mainEntity; + } + + public List getSubSubEntities() { + return subSubEntities; + } + + public void setSubSubEntities(List subSubEntities) { + this.subSubEntities = subSubEntities; + } + + public List getAnotherSubSubEntities() { + return anotherSubSubEntities; + } + + public void setAnotherSubSubEntities(List anotherSubSubEntities) { + this.anotherSubSubEntities = anotherSubSubEntities; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SubEntity subEntity = (SubEntity) o; + return Objects.equals(subIdNum, subEntity.subIdNum); + } + + @Override + public int hashCode() { + + return Objects.hash(super.hashCode(), subIdNum); + } + } + + @Entity + @Table(name = "ANOTHER_SUB_SUB_TABLE") + public static class AnotherSubSubEntity implements Serializable { + + @EmbeddedId + private AnotherSubSubEntityId id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "ID_NUM", referencedColumnName = "ID_NUM", insertable = false, updatable = false), + @JoinColumn(name = "PERSON", referencedColumnName = "FAMILY_IDENTIFIER", insertable = false, updatable = false) + }) + private SubEntity subEntity; + } + + @Embeddable + public static class AnotherSubSubEntityId implements Serializable { + + @Column(name = "ID_NUM", insertable = false, updatable = false) + private Long idNum; + + @Column(name = "PERSON", insertable = false, updatable = false) + private String person; + + @Column(name = "SOURCE_CODE") + private String sourceCode; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AnotherSubSubEntityId that = (AnotherSubSubEntityId) o; + return Objects.equals(idNum, that.idNum) && + Objects.equals(person, that.person) && + Objects.equals(sourceCode, that.sourceCode); + } + + @Override + public int hashCode() { + return Objects.hash(idNum, person, sourceCode); + } + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public String getPerson() { + return person; + } + + public void setPerson(String person) { + this.person = person; + } + + public String getSourceCode() { + return sourceCode; + } + + public void setSourceCode(String sourceCode) { + this.sourceCode = sourceCode; + } + } + + @Entity + @Table(name = "SUB_SUB_TABLE") + public static class SubSubEntity { + + @EmbeddedId + private SubSubEntityId id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "ID_NUM", referencedColumnName = "ID_NUM", insertable = false, updatable = false), + @JoinColumn(name = "IND_NUM", referencedColumnName = "IND_NUM", insertable = false, updatable = false) + }) + private SubEntity subEntity; + } + + @Embeddable + public static class SubSubEntityId implements Serializable { + + @Column(name = "ID_NUM") + private Long idNum; + + @Column(name = "CODE") + private String code; + + @Column(name = "IND_NUM") + private String indNum; + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getIndNum() { + return indNum; + } + + public void setIndNum(String indNum) { + this.indNum = indNum; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SubSubEntityId that = (SubSubEntityId) o; + return Objects.equals(idNum, that.idNum) && + Objects.equals(code, that.code) && + Objects.equals(indNum, that.indNum); + } + + @Override + public int hashCode() { + + return Objects.hash(idNum, code, indNum); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeCollectionIdClassTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeCollectionIdClassTest.java new file mode 100644 index 000000000000..abc2384d46fe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeCollectionIdClassTest.java @@ -0,0 +1,510 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.cascade.multilevel; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.persistence.CascadeType; +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.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +public class MultiLevelCascadeCollectionIdClassTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( MultiLevelCascadeCollectionIdClassTest.class ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + MainEntity.class, + SubEntity.class, + AnotherSubSubEntity.class, + SubSubEntity.class + }; + } + + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "INSERT INTO MAIN_TABLE(ID_NUM) VALUES (99427)" ).executeUpdate(); + entityManager.createNativeQuery( "INSERT INTO SUB_TABLE(ID_NUM, SUB_ID, FAMILY_IDENTIFIER, IND_NUM) VALUES (99427, 1, 'A', '123A')" ).executeUpdate(); + entityManager.createNativeQuery( "INSERT INTO SUB_TABLE(ID_NUM, SUB_ID, FAMILY_IDENTIFIER, IND_NUM) VALUES (99427, 2, 'S', '321A')" ).executeUpdate(); + entityManager.createNativeQuery( "INSERT INTO SUB_SUB_TABLE(ID_NUM, CODE, IND_NUM) VALUES (99427, 'CODE1', '123A')" ).executeUpdate(); + } ); + } + + @Test + @FailureExpected( jiraKey = "HHH-12291" ) + public void testHibernateDeleteEntityWithoutInitializingCollections() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + MainEntity mainEntity = entityManager.find(MainEntity.class, 99427L); + + assertNotNull(mainEntity); + assertFalse(mainEntity.getSubEntities().isEmpty()); + + Optional subEntityToRemove = mainEntity.getSubEntities().stream() + .filter(subEntity -> "123A".equals(subEntity.getIndNum())).findFirst(); + subEntityToRemove.ifPresent( mainEntity::removeSubEntity ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12294" ) + public void testHibernateDeleteEntityInitializeCollections() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + MainEntity mainEntity = entityManager.find(MainEntity.class, 99427L); + + assertNotNull(mainEntity); + assertFalse(mainEntity.getSubEntities().isEmpty()); + + Optional subEntityToRemove = mainEntity.getSubEntities().stream() + .filter(subEntity -> "123A".equals(subEntity.getIndNum())).findFirst(); + if ( subEntityToRemove.isPresent() ) { + SubEntity subEntity = subEntityToRemove.get(); + assertEquals( 1, subEntity.getSubSubEntities().size() ); + assertEquals( 0, subEntity.getAnotherSubSubEntities().size() ); + mainEntity.removeSubEntity( subEntity ); + } + } ); + } + + + @Entity + @Table(name = "MAIN_TABLE") + public static class MainEntity implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idNum") + @SequenceGenerator(name = "idNum", sequenceName = "id_num", allocationSize = 1) + @Column(name="ID_NUM") + private Long idNum; + + @OneToMany(mappedBy = "mainEntity", cascade = CascadeType.ALL, orphanRemoval = true) + private List subEntities = new ArrayList<>(); + + public void addSubEntity(SubEntity subEntity) { + subEntity.setMainEntity(this); + subEntities.add(subEntity); + } + + public void removeSubEntity(SubEntity subEntity) { + subEntity.setMainEntity(null); + subEntities.remove(subEntity); + } + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public List getSubEntities() { + return subEntities; + } + + public void setSubEntities(List subEntities) { + this.subEntities = subEntities; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + MainEntity that = (MainEntity) o; + return Objects.equals(idNum, that.idNum); + } + + @Override + public int hashCode() { + + return Objects.hash(super.hashCode(), idNum); + } + } + + @Entity + @Table(name = "SUB_TABLE") + public static class SubEntity implements Serializable { + + @Id + @Column(name = "SUB_ID") + private Long subIdNum; + + @Column(name = "IND_NUM") + private String indNum; + + @Column(name = "FAMILY_IDENTIFIER") + private String familyIdentifier; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "ID_NUM") + private MainEntity mainEntity; + + @OneToMany(mappedBy = "subEntity", cascade = CascadeType.ALL, orphanRemoval = true) + private List subSubEntities = new ArrayList<>(); + + @OneToMany(mappedBy = "subEntity", cascade = CascadeType.ALL, orphanRemoval = true) + private List anotherSubSubEntities = new ArrayList<>(); + + public Long getSubIdNum() { + return subIdNum; + } + + public void setSubIdNum(Long subIdNum) { + this.subIdNum = subIdNum; + } + + public String getIndNum() { + return indNum; + } + + public void setIndNum(String indNum) { + this.indNum = indNum; + } + + public String getFamilyIdentifier() { + return familyIdentifier; + } + + public void setFamilyIdentifier(String familyIdentifier) { + this.familyIdentifier = familyIdentifier; + } + + public MainEntity getMainEntity() { + return mainEntity; + } + + public void setMainEntity(MainEntity mainEntity) { + this.mainEntity = mainEntity; + } + + public List getSubSubEntities() { + return subSubEntities; + } + + public void setSubSubEntities(List subSubEntities) { + this.subSubEntities = subSubEntities; + } + + public List getAnotherSubSubEntities() { + return anotherSubSubEntities; + } + + public void setAnotherSubSubEntities(List anotherSubSubEntities) { + this.anotherSubSubEntities = anotherSubSubEntities; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SubEntity subEntity = (SubEntity) o; + return Objects.equals(subIdNum, subEntity.subIdNum); + } + + @Override + public int hashCode() { + + return Objects.hash(super.hashCode(), subIdNum); + } + } + + @Entity + @Table(name = "ANOTHER_SUB_SUB_TABLE") + @IdClass(AnotherSubSubEntity.AnotherSubSubEntityId.class) + public static class AnotherSubSubEntity implements Serializable { + + @Id + @Column(name = "ID_NUM", insertable = false, updatable = false) + private Long idNum; + + @Id + @Column(name = "PERSON", insertable = false, updatable = false) + private String person; + + @Id + @Column(name = "SOURCE_CODE") + private String sourceCode; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "ID_NUM", referencedColumnName = "ID_NUM", insertable = false, updatable = false), + @JoinColumn(name = "PERSON", referencedColumnName = "FAMILY_IDENTIFIER", insertable = false, updatable = false) + }) + private SubEntity subEntity; + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public String getPerson() { + return person; + } + + public void setPerson(String person) { + this.person = person; + } + + public String getSourceCode() { + return sourceCode; + } + + public void setSourceCode(String sourceCode) { + this.sourceCode = sourceCode; + } + + public SubEntity getSubEntity() { + return subEntity; + } + + public void setSubEntity(SubEntity subEntity) { + idNum = Optional.ofNullable(subEntity).map( SubEntity::getMainEntity).map( MainEntity::getIdNum).orElse( null); + person = Optional.ofNullable(subEntity).map( SubEntity::getFamilyIdentifier).orElse( null); + this.subEntity = subEntity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AnotherSubSubEntity that = (AnotherSubSubEntity) o; + return Objects.equals(idNum, that.idNum) && + Objects.equals(person, that.person) && + Objects.equals(sourceCode, that.sourceCode); + } + + @Override + public int hashCode() { + return Objects.hash(idNum, person, sourceCode); + } + + @Override + public String toString() { + return "AnotherSubSubEntity{" + + "idNum=" + idNum + + ", person='" + person + '\'' + + ", sourceCode='" + sourceCode + '\'' + + '}'; + } + + public static class AnotherSubSubEntityId implements Serializable { + + private Long idNum; + + private String person; + + private String sourceCode; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AnotherSubSubEntityId that = (AnotherSubSubEntityId) o; + return Objects.equals(idNum, that.idNum) && + Objects.equals(person, that.person) && + Objects.equals(sourceCode, that.sourceCode); + } + + @Override + public int hashCode() { + return Objects.hash(idNum, person, sourceCode); + } + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public String getPerson() { + return person; + } + + public void setPerson(String person) { + this.person = person; + } + + public String getSourceCode() { + return sourceCode; + } + + public void setSourceCode(String sourceCode) { + this.sourceCode = sourceCode; + } + } + + } + + @Entity + @Table(name = "SUB_SUB_TABLE") + @IdClass(SubSubEntity.SubSubEntityId.class) + public static class SubSubEntity implements Serializable { + + @Id + @Column(name = "ID_NUM", insertable = false, updatable = false) + private Long idNum; + + @Id + @Column(name = "CODE") + private String code; + + @Id + @Column(name = "IND_NUM", insertable = false, updatable = false) + private String indNum; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "ID_NUM", referencedColumnName = "ID_NUM"), + @JoinColumn(name = "IND_NUM", referencedColumnName = "IND_NUM") + }) + private SubEntity subEntity; + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getIndNum() { + return indNum; + } + + public void setIndNum(String indNum) { + this.indNum = indNum; + } + + public SubEntity getSubEntity() { + return subEntity; + } + + public void setSubEntity(SubEntity subEntity) { + idNum = Optional.ofNullable(subEntity).map( SubEntity::getMainEntity).map( MainEntity::getIdNum).orElse( null); + code = Optional.ofNullable(subEntity).map( SubEntity::getIndNum).orElse( null); + this.subEntity = subEntity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SubSubEntity that = (SubSubEntity) o; + return Objects.equals(idNum, that.idNum) && + Objects.equals(code, that.code) && + Objects.equals(indNum, that.indNum); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), idNum, code, indNum); + } + + @Override + public String toString() { + return "SubSubEntity{" + + "idNum=" + idNum + + ", code='" + code + '\'' + + ", indNum='" + indNum + '\'' + + '}'; + } + + public static class SubSubEntityId implements Serializable { + + private Long idNum; + + private String code; + + private String indNum; + + public Long getIdNum() { + return idNum; + } + + public void setIdNum(Long idNum) { + this.idNum = idNum; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getIndNum() { + return indNum; + } + + public void setIndNum(String indNum) { + this.indNum = indNum; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SubSubEntityId that = (SubSubEntityId) o; + return Objects.equals(idNum, that.idNum) && + Objects.equals(code, that.code) && + Objects.equals(indNum, that.indNum); + } + + @Override + public int hashCode() { + + return Objects.hash(idNum, code, indNum); + } + } + + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeRegularIdBasedParentChildAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeRegularIdBasedParentChildAssociationTest.java new file mode 100644 index 000000000000..5054b8498fec --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cascade/multilevel/MultiLevelCascadeRegularIdBasedParentChildAssociationTest.java @@ -0,0 +1,153 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.cascade.multilevel; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +public class MultiLevelCascadeRegularIdBasedParentChildAssociationTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( MultiLevelCascadeRegularIdBasedParentChildAssociationTest.class ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class, + Skill.class, + Hobby.class + }; + } + + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent parent = new Parent(); + parent.id = 1L; + + Child child1 = new Child(); + child1.id = 1L; + parent.addChild( child1 ); + + Child child2 = new Child(); + child2.id = 2L; + parent.addChild( child2 ); + + Skill skill = new Skill(); + skill.id = 1L; + child1.addSkill( skill ); + + entityManager.persist( parent ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12291" ) + public void testHibernateDeleteEntityWithoutInitializingCollections() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent mainEntity = entityManager.find( Parent.class, 1L); + + assertNotNull(mainEntity); + assertFalse(mainEntity.getChildren().isEmpty()); + + Optional childToRemove = mainEntity.getChildren().stream() + .filter(child -> Long.valueOf( 1L ).equals(child.id)).findFirst(); + childToRemove.ifPresent( mainEntity::removeChild ); + } ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + private Long id; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) + private List children = new ArrayList<>(); + + public List getChildren() { + return children; + } + + public void addChild(Child child) { + child.setParent( this); + children.add(child); + } + + public void removeChild(Child child) { + child.setParent( null); + children.remove(child); + } + } + + @Entity(name = "Child") + public static class Child { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Parent parent; + + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true) + private List hobbies = new ArrayList<>(); + + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true) + private List skills = new ArrayList<>(); + + public Parent getParent() { + return parent; + } + + public void setParent(Parent mainEntity) { + this.parent = mainEntity; + } + + public void addSkill(Skill skill) { + skill.owner = this; + skills.add(skill); + } + } + + @Entity(name = "Skill") + public static class Skill { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Child owner; + } + + @Entity(name = "Hobby") + public static class Hobby { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Child owner; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/BasicCdiTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/BasicCdiTest.java index 56169ef9adf6..8c32446ba340 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/BasicCdiTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/BasicCdiTest.java @@ -10,93 +10,98 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import javax.annotation.Resource; -import javax.inject.Inject; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; import javax.persistence.Entity; import javax.persistence.EntityListeners; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; import javax.persistence.Id; -import javax.persistence.PersistenceUnit; import javax.persistence.PrePersist; import javax.persistence.Table; -import javax.transaction.TransactionManager; -import javax.transaction.UserTransaction; + +import org.hibernate.boot.MetadataSources; +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.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.tool.schema.Action; import org.junit.Test; -import org.junit.runner.RunWith; - -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit.Arquillian; -import org.jboss.shrinkwrap.api.Archive; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.EmptyAsset; -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.jboss.shrinkwrap.descriptor.api.Descriptors; -import org.jboss.shrinkwrap.descriptor.api.persistence21.PersistenceDescriptor; +import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * @author Steve Ebersole */ -@RunWith(Arquillian.class) public class BasicCdiTest { - @Deployment - public static Archive buildDeployment() { - return ShrinkWrap.create( JavaArchive.class, "test.jar" ) - .addClass( MyEntity.class ) - .addClass( EventQueue.class ) - .addClass( Event.class ) - .addClass( Monitor.class ) - .addAsManifestResource( "jboss-deployment-structure.xml" ) - .addAsManifestResource( EmptyAsset.INSTANCE, "beans.xml" ) - .addAsManifestResource( new StringAsset( persistenceXml().exportAsString() ), "persistence.xml" ); - } - - private static PersistenceDescriptor persistenceXml() { - return Descriptors.create( PersistenceDescriptor.class ) - .createPersistenceUnit().name( "pu-cdi-basic" ) - .clazz( MyEntity.class.getName() ) - .excludeUnlistedClasses( true ) - .nonJtaDataSource( "java:jboss/datasources/ExampleDS" ) - .getOrCreateProperties().createProperty().name( "jboss.as.jpa.providerModule" ).value( "org.hibernate:5.2" ).up().up() - .getOrCreateProperties().createProperty().name( "hibernate.delay_cdi_access" ).value( "true" ).up().up() - .getOrCreateProperties().createProperty().name( "hibernate.hbm2ddl.auto" ).value( "create-drop" ).up().up().up(); - } - - @PersistenceUnit - EntityManagerFactory emf; - - @Resource - private UserTransaction utx; - private static int count; @Test @SuppressWarnings("unchecked") - public void testIt() throws Exception { + public void testIt() { + final SeContainerInitializer cdiInitializer = SeContainerInitializer.newInstance() + .disableDiscovery() + .addBeanClasses( Monitor.class, EventQueue.class, Event.class ); + count = 0; - utx.begin(); - EntityManager em = emf.createEntityManager(); - em.persist( new MyEntity( 1 ) ); - utx.commit(); + try ( final SeContainer cdiContainer = cdiInitializer.initialize() ) { + BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder().build(); - assertEquals( 1, count ); + final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder( bsr ) + .applySetting( AvailableSettings.CDI_BEAN_MANAGER, cdiContainer.getBeanManager() ) + .applySetting( AvailableSettings.DELAY_CDI_ACCESS, "true" ) + .applySetting( AvailableSettings.HBM2DDL_AUTO, Action.CREATE_DROP ) + .build(); - utx.begin(); - em = emf.createEntityManager(); - MyEntity it = em.find( MyEntity.class, 1 ); - assertNotNull( it ); - em.remove( it ); - utx.commit(); + final SessionFactoryImplementor sessionFactory; + + try { + sessionFactory = (SessionFactoryImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( MyEntity.class ) + .buildMetadata() + .getSessionFactoryBuilder() + .build(); + } + catch ( Exception e ) { + StandardServiceRegistryBuilder.destroy( ssr ); + throw e; + } + + try { + inTransaction( + sessionFactory, + session -> session.persist( new MyEntity( 1 ) ) + ); + + assertEquals( 1, count ); + + inTransaction( + sessionFactory, + session -> { + MyEntity it = session.find( MyEntity.class, 1 ); + assertNotNull( it ); + } + ); + } + finally { + inTransaction( + sessionFactory, + session -> { + session.createQuery( "delete MyEntity" ).executeUpdate(); + } + ); + + sessionFactory.close(); + } + } } - @Entity + @Entity( name = "MyEntity" ) @EntityListeners( Monitor.class ) @Table(name = "my_entity") public static class MyEntity { @@ -133,7 +138,7 @@ public static class EventQueue { public void addEvent(Event anEvent) { if ( events == null ) { - events = new ArrayList(); + events = new ArrayList<>(); } events.add( anEvent ); count++; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ConversationalPersistenceContextExtendedTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ConversationalPersistenceContextExtendedTest.java index ae6e503fba21..585108da8608 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ConversationalPersistenceContextExtendedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ConversationalPersistenceContextExtendedTest.java @@ -10,6 +10,7 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +30,7 @@ * @author Vlad Mihalcea */ @RunWith(Arquillian.class) +@Ignore( "WildFly has not released a version supporting JPA 2.2 and CDI 2.0" ) public class ConversationalPersistenceContextExtendedTest { @Deployment @@ -37,17 +39,16 @@ public static Archive buildDeployment() { .addClass( Event.class ) .addClass( ConversationalEventManager.class ) .addAsManifestResource( EmptyAsset.INSTANCE, "beans.xml" ) - .addAsManifestResource( "jboss-deployment-structure.xml" ) .addAsManifestResource( new StringAsset( persistenceXml().exportAsString() ), "persistence.xml" ); } private static PersistenceDescriptor persistenceXml() { return Descriptors.create( PersistenceDescriptor.class ) - .createPersistenceUnit().name( "pu-cdi-basic" ) + .createPersistenceUnit().name( "pu-beans-basic" ) .clazz( Event.class.getName() ) .excludeUnlistedClasses( true ) .nonJtaDataSource( "java:jboss/datasources/ExampleDS" ) - .getOrCreateProperties().createProperty().name( "jboss.as.jpa.providerModule" ).value( "org.hibernate:5.2" ).up().up() + .getOrCreateProperties().createProperty().name( "jboss.as.jpa.providerModule" ).value( "org.hibernate:5.3" ).up().up() .getOrCreateProperties().createProperty().name( "hibernate.hbm2ddl.auto" ).value( "create-drop" ).up().up().up(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ManualFlushConversationalPersistenceContextExtendedTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ManualFlushConversationalPersistenceContextExtendedTest.java index 3ee578a0c1b5..f32081827cdf 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ManualFlushConversationalPersistenceContextExtendedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/ManualFlushConversationalPersistenceContextExtendedTest.java @@ -11,6 +11,7 @@ import javax.persistence.PersistenceContext; import org.hibernate.testing.TestForIssue; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,6 +31,7 @@ * @author Vlad Mihalcea */ @RunWith(Arquillian.class) +@Ignore( "WildFly has not released a version supporting JPA 2.2 and CDI 2.0" ) public class ManualFlushConversationalPersistenceContextExtendedTest { @Deployment @@ -38,17 +40,16 @@ public static Archive buildDeployment() { .addClass( Event.class ) .addClass( ManualFlushConversationalEventManager.class ) .addAsManifestResource( EmptyAsset.INSTANCE, "beans.xml" ) - .addAsManifestResource( "jboss-deployment-structure.xml" ) .addAsManifestResource( new StringAsset( persistenceXml().exportAsString() ), "persistence.xml" ); } private static PersistenceDescriptor persistenceXml() { return Descriptors.create( PersistenceDescriptor.class ) - .createPersistenceUnit().name( "pu-cdi-basic" ) + .createPersistenceUnit().name( "pu-beans-basic" ) .clazz( Event.class.getName() ) .excludeUnlistedClasses( true ) .nonJtaDataSource( "java:jboss/datasources/ExampleDS" ) - .getOrCreateProperties().createProperty().name( "jboss.as.jpa.providerModule" ).value( "org.hibernate:5.2" ).up().up() + .getOrCreateProperties().createProperty().name( "jboss.as.jpa.providerModule" ).value( "org.hibernate:5.3" ).up().up() .getOrCreateProperties().createProperty().name( "hibernate.hbm2ddl.auto" ).value( "create-drop" ).up().up().up(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/NonConversationalPersistenceContextExtendedTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/NonConversationalPersistenceContextExtendedTest.java index aa2e68a0b585..462d4783dfd7 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/NonConversationalPersistenceContextExtendedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/cdi/extended/NonConversationalPersistenceContextExtendedTest.java @@ -10,6 +10,7 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +30,7 @@ * @author Vlad Mihalcea */ @RunWith(Arquillian.class) +@Ignore( "WildFly has not released a version supporting JPA 2.2 and CDI 2.0" ) public class NonConversationalPersistenceContextExtendedTest { @Deployment @@ -37,17 +39,16 @@ public static Archive buildDeployment() { .addClass( Event.class ) .addClass( NonConversationalEventManager.class ) .addAsManifestResource( EmptyAsset.INSTANCE, "beans.xml" ) - .addAsManifestResource( "jboss-deployment-structure.xml" ) .addAsManifestResource( new StringAsset( persistenceXml().exportAsString() ), "persistence.xml" ); } private static PersistenceDescriptor persistenceXml() { return Descriptors.create( PersistenceDescriptor.class ) - .createPersistenceUnit().name( "pu-cdi-basic" ) + .createPersistenceUnit().name( "pu-beans-basic" ) .clazz( Event.class.getName() ) .excludeUnlistedClasses( true ) .nonJtaDataSource( "java:jboss/datasources/ExampleDS" ) - .getOrCreateProperties().createProperty().name( "jboss.as.jpa.providerModule" ).value( "org.hibernate:5.2" ).up().up() + .getOrCreateProperties().createProperty().name( "jboss.as.jpa.providerModule" ).value( "org.hibernate:5.3" ).up().up() .getOrCreateProperties().createProperty().name( "hibernate.hbm2ddl.auto" ).value( "create-drop" ).up().up().up(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/BaseDataSource.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/BaseDataSource.java new file mode 100644 index 000000000000..c9312e733737 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/BaseDataSource.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 . + */ + +//$Id$ +package org.hibernate.jpa.test.connection; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; +import javax.sql.DataSource; + +/** + * @author Vlad Mihalcea + */ +public abstract class BaseDataSource implements DataSource { + + public PrintWriter getLogWriter() throws SQLException { + return new PrintWriter( System.out ); + } + + public void setLogWriter(PrintWriter out) throws SQLException { + } + + public void setLoginTimeout(int seconds) throws SQLException { + } + + public int getLoginTimeout() throws SQLException { + return 0; + } + + public T unwrap(Class tClass) throws SQLException { + return (T) this; + } + + public boolean isWrapperFor(Class aClass) throws SQLException { + return false; + } + + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java index 8a113e016f89..8c1125218c9d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java @@ -9,53 +9,106 @@ package org.hibernate.jpa.test.connection; -import java.io.File; -import javax.persistence.EntityManagerFactory; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import javax.persistence.PersistenceException; +import javax.sql.DataSource; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.jpa.test.Distributor; +import org.hibernate.jpa.test.Item; +import org.hibernate.jpa.test.xml.Light; +import org.hibernate.jpa.test.xml.Lighter; +import org.hibernate.testing.util.jpa.PersistenceUnitInfoAdapter; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; /** * @author Emmanuel Bernard */ public class DataSourceInjectionTest { - EntityManagerFactory emf; @Test public void testDatasourceInjection() throws Exception { - File current = new File("."); - File sub = new File(current, "puroot"); - sub.mkdir(); - PersistenceUnitInfoImpl info = new PersistenceUnitInfoImpl( sub.toURI().toURL(), new String[]{} ); - try { - emf = new HibernatePersistenceProvider().createContainerEntityManagerFactory( info, null ); - try { - emf.createEntityManager().createQuery( "select i from Item i" ).getResultList(); - } - finally { - try { - emf.close(); - } - catch (Exception ignore) { - int i = 0; + withPuRoot( + puRootUrl -> { + final PersistenceUnitInfoAdapter persistenceUnitInfo = createPuDescriptor( puRootUrl, new FakeDataSource() ); + + // otherwise the FakeDataSourceException will be eaten trying to resolve the Dialect + final Map intgOverrides = Collections.singletonMap( + AvailableSettings.DIALECT, + H2Dialect.class + ); + + final HibernatePersistenceProvider provider = new HibernatePersistenceProvider(); + try ( final SessionFactoryImplementor sf = provider.createContainerEntityManagerFactory( + persistenceUnitInfo, + intgOverrides + ).unwrap( SessionFactoryImplementor.class ) ) { + + try ( final SessionImplementor session = sf.openSession().unwrap( SessionImplementor.class ) ) { + session.createQuery( "select i from Item i" ).list(); + Assert.fail( "Expecting FakeDataSourceException" ); + } + catch (PersistenceException pe) { + try { + throw (RuntimeException) pe.getCause(); + } + catch (FakeDataSourceException fde) { + //success + } + } + catch (FakeDataSourceException fde) { + //success + } + } } + ); + } + + protected PersistenceUnitInfoAdapter createPuDescriptor(URL puRootUrl, DataSource dataSource) { + return new PersistenceUnitInfoAdapter() { + @Override + public DataSource getNonJtaDataSource() { + return dataSource; } - Assert.fail( "FakeDatasource should have been used" ); - } - catch (PersistenceException pe) { - if(emf != null){ - emf.close(); + + @Override + public URL getPersistenceUnitRootUrl() { + return puRootUrl; } - Assert.assertTrue( pe.getCause() instanceof FakeDataSourceException ); - } - catch (FakeDataSourceException fde) { - //success + + public List getManagedClassNames() { + List classes = new ArrayList<>(); + classes.add( Item.class.getName() ); + classes.add( Distributor.class.getName() ); + classes.add( Light.class.getName() ); + classes.add( Lighter.class.getName() ); + return classes; + } + }; + } + + private void withPuRoot(Consumer puRootUrlConsumer) throws Exception { + // create a temporary directory to serve as the "PU root URL" + final Path puroot = Files.createTempDirectory( "puroot" ); + final URL puRootUrl = puroot.toUri().toURL(); + + try { + puRootUrlConsumer.accept( puRootUrl ); } finally { - sub.delete(); + Files.deleteIfExists( puroot ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java index cec9460c9793..8e8ed9c0e264 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java @@ -37,7 +37,7 @@ public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { private URL puRoot; public PersistenceUnitInfoImpl(URL puRoot, String[] mappingFiles) { - this.mappingFiles = new ArrayList( mappingFiles.length ); + this.mappingFiles = new ArrayList<>( mappingFiles.length ); this.mappingFiles.addAll( Arrays.asList( mappingFiles ) ); this.puRoot = puRoot; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Animal.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Animal.java index 6a45364a46e2..2301bb86fd14 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Animal.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Animal.java @@ -6,10 +6,14 @@ */ package org.hibernate.jpa.test.criteria; +import java.util.Date; + import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; /** * Animal @@ -22,6 +26,7 @@ public class Animal { private Animal mother; private Animal father; private String name; + private Date born; public String getName() { return name; @@ -57,4 +62,13 @@ public Animal getFather() { public void setFather(Animal father) { this.father = father; } + + @Temporal(TemporalType.TIMESTAMP) + public Date getBorn() { + return born; + } + + public void setBorn(Date born) { + this.born = born; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Attribute.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Attribute.java new file mode 100644 index 000000000000..17d9a09d364c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Attribute.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 . + */ +package org.hibernate.jpa.test.criteria; + +/** + * @author Chris Cranford + */ +public interface Attribute { + String getType(); +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Book.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Book.java new file mode 100644 index 000000000000..3d9a67243a00 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Book.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.jpa.test.criteria; + +import java.util.List; +import java.util.Set; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Chris Cranford + */ +@Entity +public class Book { + @Id + @GeneratedValue + private Integer id; + + @OneToMany + private Set stores; + + public Book() { + + } + + public Book(Set stores) { + this.stores = stores; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Set getStores() { + return stores; + } + + public void setStores(Set stores) { + this.stores = stores; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Color.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Color.java new file mode 100644 index 000000000000..c10eb15e683d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Color.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.jpa.test.criteria; + +import javax.persistence.Embeddable; + +/** + * @author Chris Cranford + */ +@Embeddable +public class Color implements Attribute { + public static String TYPE = "Color"; + + @Override + public String getType() { + return TYPE; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/CriteriaQueryTypeQueryAdapterTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/CriteriaQueryTypeQueryAdapterTest.java new file mode 100644 index 000000000000..ce0be39508ba --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/CriteriaQueryTypeQueryAdapterTest.java @@ -0,0 +1,213 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Parameter; +import javax.persistence.TemporalType; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.testing.TestForIssue; +import org.hibernate.type.StringType; +import org.junit.Test; + +public class CriteriaQueryTypeQueryAdapterTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Bid.class, + Item.class + }; + } + + @Test + @TestForIssue(jiraKey = "HHH-12685") + public void testCriteriaQueryParameterIsBoundCheckNotFails() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Item.class ); + Root root = query.from( Item.class ); + ParameterExpression parameter = builder.parameter( String.class, "name" ); + Predicate predicate = builder.equal( root.get( "name" ), parameter ); + query.where( predicate ); + TypedQuery criteriaQuery = entityManager.createQuery( query ); + Parameter dynamicParameter = criteriaQuery.getParameter( "name" ); + boolean bound = criteriaQuery.isBound( dynamicParameter ); + assertFalse( bound ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12685") + public void testCriteriaQueryGetParameters() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Item.class ); + Root root = query.from( Item.class ); + ParameterExpression parameter = builder.parameter( String.class, "name" ); + Predicate predicate = builder.equal( root.get( "name" ), parameter ); + query.where( predicate ); + + TypedQuery criteriaQuery = entityManager.createQuery( query ); + + Set> parameters = criteriaQuery.getParameters(); + assertEquals( 1, parameters.size() ); + + Parameter dynamicParameter = parameters.iterator().next(); + assertEquals( "name", dynamicParameter.getName() ); + } ); + } + + @TestForIssue(jiraKey = "HHH-12685") + @Test(expected = IllegalArgumentException.class) + public void testCriteriaQueryGetParameterOfWrongType() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Item.class ); + Root root = query.from( Item.class ); + ParameterExpression parameter = builder.parameter( String.class, "name" ); + Predicate predicate = builder.equal( root.get( "name" ), parameter ); + query.where( predicate ); + TypedQuery criteriaQuery = entityManager.createQuery( query ); + criteriaQuery.getParameter( "name", Integer.class ); + } ); + } + + @TestForIssue(jiraKey = "HHH-12685") + @Test(expected = IllegalArgumentException.class) + public void testCriteriaQueryGetNonExistingParameter() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Item.class ); + Root root = query.from( Item.class ); + ParameterExpression parameter = builder.parameter( String.class, "name" ); + Predicate predicate = builder.equal( root.get( "name" ), parameter ); + query.where( predicate ); + TypedQuery criteriaQuery = entityManager.createQuery( query ); + criteriaQuery.getParameter( "placedAt" ); + } ); + } + + @Test + public void testSetParameterPassingTypeNotFails() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Item.class ); + + Predicate predicate = builder.equal( + query.from( Item.class ).get( "name" ), + builder.parameter( String.class, "name" ) + ); + query.where( predicate ); + + QueryImplementor criteriaQuery = (QueryImplementor) entityManager.createQuery( query ); + + criteriaQuery.setParameter( "name", "2", StringType.INSTANCE ).list(); + } ); + } + + @Test + public void testSetParameterTypeInstantNotFails() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Bid.class ); + + Predicate predicate = builder.equal( + query.from( Bid.class ).get( "placedAt" ), + builder.parameter( Instant.class, "placedAt" ) + ); + query.where( predicate ); + + QueryImplementor criteriaQuery = (QueryImplementor) entityManager.createQuery( query ); + + criteriaQuery.setParameter( "placedAt", Instant.now(), TemporalType.TIMESTAMP ).list(); + } ); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetParameterOfTypeInstantToAFloatParameterType() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Bid.class ); + + Predicate predicate = builder.equal( + query.from( Bid.class ).get( "amount" ), + builder.parameter( Instant.class, "placedAt" ) + ); + query.where( predicate ); + + QueryImplementor criteriaQuery = (QueryImplementor) entityManager.createQuery( query ); + + criteriaQuery.setParameter( "placedAt", Instant.now(), TemporalType.TIMESTAMP ).list(); + } ); + } + + + @Test(expected = IllegalArgumentException.class) + public void testSetParameterOfTypeDateToAFloatParameterType() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Bid.class ); + + Predicate predicate = builder.equal( + query.from( Bid.class ).get( "amount" ), + builder.parameter( Date.class, "placedAt" ) + ); + query.where( predicate ); + + QueryImplementor criteriaQuery = (QueryImplementor) entityManager.createQuery( query ); + + criteriaQuery.setParameter( "placedAt", Date.from(Instant.now()), TemporalType.DATE ).list(); + } ); + } + + @Entity(name = "Bid") + public static class Bid implements Serializable { + @Id + Long id; + + float amount; + + Instant placedAt; + + @Id + @ManyToOne + Item item; + } + + @Entity(name = "Item") + public static class Item implements Serializable { + @Id + Long id; + + String name; + + @OneToMany(mappedBy = "item") + Set bids = new HashSet<>(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ElementCollectionConverterTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ElementCollectionConverterTest.java new file mode 100644 index 000000000000..4f0ce306694f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ElementCollectionConverterTest.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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; + +import java.util.List; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Chris Cranford + */ +public class ElementCollectionConverterTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Item.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-12581") + public void testCriteriaQueryWithElementCollectionUsingConverter() { + doInJPA( this::entityManagerFactory, entityManager -> { + Item item1 = new Item( "P1" ); + item1.getRoles().add( new Color() ); + + Item item2 = new Item( "P2" ); + item2.getRoles().add( new Industry() ); + + Item item3 = new Item( "P3" ); + item3.getRoles().add( new Color() ); + item3.getRoles().add( new Industry() ); + + entityManager.persist( item1 ); + entityManager.persist( item2 ); + entityManager.persist( item3 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery( Item.class ); + Root root = query.from( Item.class ); + + // HHH-12338 effectively caused Item_.roles to be null. + // Therefore this caused a NPE with the commit originally applied for HHH-12338. + // Reverting that fix avoids the regression and this proceeds as expected. + root.fetch( Item_.roles ); + + // Just running the query here. + // the outcome is less important than the above for context of this test case. + query = query.select( root ).distinct( true ); + List items = entityManager.createQuery( query ).getResultList(); + assertEquals( 3, items.size() ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/HumanDTO.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/HumanDTO.java new file mode 100644 index 000000000000..e36b590c73d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/HumanDTO.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria; + +public class HumanDTO { + + private Long id; + + private String name; + + private String dateBorn; + + public HumanDTO(Long id, String name, String dateBorn) { + this.id = id; + this.name = name; + this.dateBorn = dateBorn; + } + + 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 getDateBorn() { + return dateBorn; + } + + public void setDateBorn(String dateBorn) { + this.dateBorn = dateBorn; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Industry.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Industry.java new file mode 100644 index 000000000000..220439f7365c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Industry.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.jpa.test.criteria; + +import javax.persistence.Embeddable; + +/** + * @author Chris Cranford + */ +@Embeddable +public class Industry implements Attribute { + public static String TYPE = "Industry"; + + @Override + public String getType() { + return TYPE; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Item.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Item.java new file mode 100644 index 000000000000..91837e9ca8ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Item.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria; + +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.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class Item { + @Id + @GeneratedValue + private Integer id; + + private String name; + + @ElementCollection + @CollectionTable(name = "item_roles") + @Convert(converter = ItemAttributeConverter.class) + public List roles = new ArrayList<>(); + + Item() { + + } + + Item(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; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ItemAttributeConverter.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ItemAttributeConverter.java new file mode 100644 index 000000000000..c029ff96e3a5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ItemAttributeConverter.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria; + +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.AttributeConverter; + +/** + * @author Chris Cranford + */ +public class ItemAttributeConverter implements AttributeConverter { + private static Map> attributeMap; + + static { + attributeMap = new HashMap<>(); + attributeMap.put( Color.TYPE, Color.class ); + attributeMap.put( Industry.TYPE, Industry.class ); + } + + @Override + public String convertToDatabaseColumn(Attribute attribute) { + return attribute == null ? null : attribute.getType(); + } + + @Override + public Attribute convertToEntityAttribute(String s) { + try { + Class attributeClass = attributeMap.get( s ); + return attributeClass == null ? null : attributeClass.newInstance(); + } + catch ( Exception e ) { + // ignore + return null; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java index 3267b8b0e229..a8ece15d23f3 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java @@ -26,8 +26,8 @@ public class MultiTypedBasicAttributesEntity { @GeneratedValue( generator = "increment" ) @GenericGenerator( name = "increment", strategy = "increment" ) private Long id; - private byte[] someBytes; - private Byte[] someWrappedBytes; + private int[] someInts; + private Integer[] someWrappedIntegers; public Long getId() { return id; @@ -37,19 +37,19 @@ public void setId(Long id) { this.id = id; } - public byte[] getSomeBytes() { - return someBytes; + public int[] getSomeInts() { + return someInts; } - public void setSomeBytes(byte[] someBytes) { - this.someBytes = someBytes; + public void setSomeInts(int[] someInts) { + this.someInts = someInts; } - public Byte[] getSomeWrappedBytes() { - return someWrappedBytes; + public Integer[] getSomeWrappedIntegers() { + return someWrappedIntegers; } - public void setSomeWrappedBytes(Byte[] someWrappedBytes) { - this.someWrappedBytes = someWrappedBytes; + public void setSomeWrappedIntegers(Integer[] someWrappedIntegers) { + this.someWrappedIntegers = someWrappedIntegers; } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java index 1b10c4516637..7a201372b94f 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java @@ -9,8 +9,8 @@ import javax.persistence.EntityManager; import javax.persistence.Parameter; import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Expression; import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Path; import javax.persistence.criteria.Root; @@ -22,6 +22,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil; import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; @@ -39,12 +40,12 @@ public void testPrimitiveArrayParameterBinding() { CriteriaQuery criteria = em.getCriteriaBuilder() .createQuery( MultiTypedBasicAttributesEntity.class ); Root rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class ); - Path someBytesPath = rootEntity.get( MultiTypedBasicAttributesEntity_.someBytes ); - ParameterExpression param = em.getCriteriaBuilder().parameter( byte[].class, "theBytes" ); - criteria.where( em.getCriteriaBuilder().equal( someBytesPath, param ) ); + Path someIntsPath = rootEntity.get( MultiTypedBasicAttributesEntity_.someInts ); + ParameterExpression param = em.getCriteriaBuilder().parameter( int[].class, "theInts" ); + criteria.where( em.getCriteriaBuilder().equal( someIntsPath, param ) ); TypedQuery query = em.createQuery( criteria ); - query.setParameter( param, new byte[] { 1,1,1 } ); - assertThat( query.getParameterValue( param.getName() ), instanceOf( byte[].class) ); + query.setParameter( param, new int[] { 1,1,1 } ); + assertThat( query.getParameterValue( param.getName() ), instanceOf( int[].class) ); query.getResultList(); em.getTransaction().commit(); em.close(); @@ -57,12 +58,12 @@ public void testNonPrimitiveArrayParameterBinding() { CriteriaQuery criteria = em.getCriteriaBuilder() .createQuery( MultiTypedBasicAttributesEntity.class ); Root rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class ); - Path thePath = rootEntity.get( MultiTypedBasicAttributesEntity_.someWrappedBytes ); - ParameterExpression param = em.getCriteriaBuilder().parameter( Byte[].class, "theBytes" ); + Path thePath = rootEntity.get( MultiTypedBasicAttributesEntity_.someWrappedIntegers ); + ParameterExpression param = em.getCriteriaBuilder().parameter( Integer[].class, "theIntegers" ); criteria.where( em.getCriteriaBuilder().equal( thePath, param ) ); TypedQuery query = em.createQuery( criteria ); - query.setParameter( param, new Byte[] { Byte.valueOf((byte)1), Byte.valueOf((byte)1), Byte.valueOf((byte)1) } ); - assertThat( query.getParameterValue( param.getName() ), instanceOf( Byte[].class ) ); + query.setParameter( param, new Integer[] { Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(1) } ); + assertThat( query.getParameterValue( param.getName() ), instanceOf( Integer[].class ) ); query.getResultList(); em.getTransaction().commit(); em.close(); @@ -122,32 +123,20 @@ public void testParameterInParameterList() { @Test @TestForIssue(jiraKey = "HHH-10870") public void testParameterInParameterList2() { - EntityManager em = getOrCreateEntityManager(); - try { - em.getTransaction().begin(); - final CriteriaQuery query = em.getCriteriaBuilder() + TransactionUtil.doInJPA( this::entityManagerFactory, em -> { + final CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); + final CriteriaQuery criteria = criteriaBuilder .createQuery( MultiTypedBasicAttributesEntity.class ); - final Root root = query.from( MultiTypedBasicAttributesEntity.class ); - root.get( "id" ); - final ParameterExpression parameter = em.getCriteriaBuilder().parameter( Iterable.class ); - root.in( new Expression[] {parameter} ); - query.select( root ); + final ParameterExpression parameter = criteriaBuilder.parameter( Iterable.class ); + + final Root root = criteria.from( MultiTypedBasicAttributesEntity.class ); + criteria.select( root ).where( root.get( "id" ).in( parameter ) ); - final TypedQuery query1 = em.createQuery( query ); + final TypedQuery query1 = em.createQuery( criteria ); query1.setParameter( parameter, Arrays.asList( 1L, 2L, 3L ) ); query1.getResultList(); - - em.getTransaction().commit(); - } - catch (Exception e) { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - } - finally { - em.close(); - } + } ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/QueryBuilderTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/QueryBuilderTest.java index fb9ea22f7e9b..0e75a54ba61e 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/QueryBuilderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/QueryBuilderTest.java @@ -7,11 +7,16 @@ package org.hibernate.jpa.test.criteria; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; +import javax.persistence.criteria.SetJoin; import javax.persistence.metamodel.EntityType; import org.hibernate.dialect.H2Dialect; @@ -33,10 +38,12 @@ import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.predicate.ComparisonPredicate; +import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; /** @@ -51,13 +58,16 @@ public Class[] getAnnotatedClasses() { Country.class, CreditCard.class, Customer.class, + Human.class, Info.class, LineItem.class, Order.class, Phone.class, Product.class, ShelfLife.class, - Spouse.class + Spouse.class, + Book.class, + Store.class }; } @@ -271,4 +281,70 @@ public void testFunctionDialectFunctions() { em.getTransaction().commit(); em.close(); } + + @Test + @TestForIssue(jiraKey = "HHH-10737") + @FailureExpected( jiraKey = "HHH-10737" ) + public void testMissingDialectFunction() { + doInJPA( this::entityManagerFactory, em -> { + Human human = new Human(); + human.setId(200L); + human.setName("2"); + human.setBorn(new Date()); + em.persist(human); + + em.getTransaction().commit(); + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery criteria = cb.createQuery( HumanDTO.class ); + Root root = criteria.from( Human.class ); + + criteria.select( + cb.construct( + HumanDTO.class, + root.get(Human_.id), + root.get(Human_.name), + cb.function( + "convert", + String.class, + root.get(Human_.born), + cb.literal(110) + ) + ) + ); + + em.createQuery( criteria ).getResultList(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12314") + public void testJoinUsingNegatedPredicate() { + // Write test data + doInJPA( this::entityManagerFactory, entityManager -> { + final Store store = new Store(); + store.setName( "Acme Books" ); + store.setAddress( "123 Main St" ); + entityManager.persist( store ); + + final Book book = new Book(); + book.setStores( new HashSet<>( Arrays.asList( store ) ) ); + entityManager.persist( book ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Book.class ); + final Root bookRoot = query.from( Book.class ); + + SetJoin storeJoin = bookRoot.join( Book_.stores ); + storeJoin.on( cb.isNotNull( storeJoin.get( Store_.address ) ) ); + + // Previously failed due to ClassCastException + // org.hibernate.query.criteria.internal.predicate.NegatedPredicateWrapper + // cannot be cast to + // org.hibernate.query.criteria.internal.predicate.AbstractPredicateImpl + entityManager.createQuery( query ).getResultList(); + } ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Store.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Store.java new file mode 100644 index 000000000000..6b8bb1dd8fe4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/Store.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 . + */ +package org.hibernate.jpa.test.criteria; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class Store { + @Id + @GeneratedValue + private Integer id; + private String name; + private String address; + + 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 getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java index bd21d68e8bdc..c88117627659 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java @@ -6,7 +6,11 @@ */ package org.hibernate.jpa.test.criteria.basic; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.util.List; + import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -14,20 +18,20 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import org.junit.Before; -import org.junit.Test; - +import org.hibernate.dialect.Oracle12cDialect; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.dialect.Oracle9Dialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; import org.hibernate.jpa.test.metamodel.CreditCard; import org.hibernate.jpa.test.metamodel.CreditCard_; import org.hibernate.jpa.test.metamodel.Customer_; import org.hibernate.jpa.test.metamodel.Order; import org.hibernate.jpa.test.metamodel.Order_; - +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; /** * Test the various predicates. @@ -211,7 +215,7 @@ public void testCharArray() { em.getTransaction().begin(); CriteriaQuery orderCriteria = builder.createQuery( Order.class ); Root orderRoot = orderCriteria.from( Order.class ); - + orderCriteria.select( orderRoot ); Predicate p = builder.equal( orderRoot.get( "domen" ), new char[]{'r','u'} ); orderCriteria.where( p ); @@ -223,15 +227,17 @@ public void testCharArray() { } /** - * Check predicate for field which has simple char array type (byte[]). + * Check predicate for field which has simple byte array type (byte[]). */ @Test + @SkipForDialect(value = Oracle12cDialect.class, jiraKey = "HHH-10603", + comment = "Oracle12cDialect uses blob to store byte arrays and it's not possible to compare blobs with simple equality operators.") public void testByteArray() { EntityManager em = getOrCreateEntityManager(); em.getTransaction().begin(); CriteriaQuery orderCriteria = builder.createQuery( Order.class ); Root orderRoot = orderCriteria.from( Order.class ); - + orderCriteria.select( orderRoot ); Predicate p = builder.equal( orderRoot.get( "number" ), new byte[]{'1','2'} ); orderCriteria.where( p ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/ComponentInWhereClauseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/ComponentInWhereClauseTest.java index 289d53f7a0eb..0750751544a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/ComponentInWhereClauseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/ComponentInWhereClauseTest.java @@ -7,6 +7,7 @@ package org.hibernate.jpa.test.criteria.components; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Embedded; @@ -304,6 +305,7 @@ public String getName() { @Embeddable public static class Phone { + @Column(name = "phone_number") private String number; public Phone() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/EntitySuperclassComponentWithCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/EntitySuperclassComponentWithCollectionTest.java index 0bfb27607af2..dbc4d582256a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/EntitySuperclassComponentWithCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/EntitySuperclassComponentWithCollectionTest.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Set; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Embedded; @@ -237,6 +238,7 @@ public String getName() { @Embeddable public static class Phone { + @Column(name = "phone_number") private String number; public Phone() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/MappedSuperclassComponentWithCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/MappedSuperclassComponentWithCollectionTest.java index 017d9dcd4bf1..d870d2d5ad3a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/MappedSuperclassComponentWithCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/MappedSuperclassComponentWithCollectionTest.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Set; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Embedded; @@ -236,6 +237,7 @@ public String getName() { @Embeddable public static class Phone { + @Column(name = "phone_number") private String number; public Phone() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/CriteriaToScrollableResultsFetchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/CriteriaToScrollableResultsFetchTest.java new file mode 100755 index 000000000000..7042fbe9ea32 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/CriteriaToScrollableResultsFetchTest.java @@ -0,0 +1,239 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.Query; +import org.hibernate.ScrollableResults; +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-10062") +public class CriteriaToScrollableResultsFetchTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ + Customer.class, + Order.class, + OrderLine.class, + Product.class, + PurchaseOrg.class, + Facility.class, + Site.class + }; + } + + @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA only supports forward-only cursors") + public void testWithScroll() { + // Creates data necessary for test + Long facilityId = populate(); + // Controller iterates the data + for ( OrderLine line : getOrderLinesScrolled( facilityId ) ) { + // This should ~NOT~ fail with a LazilyLoadException + assertNotNull( line.getProduct().getFacility().getSite().getName() ); + } + } + + @Test + public void testNoScroll() { + // Creates data necessary for test. + Long facilityId = populate(); + // Controller iterates the data + for ( OrderLine line : getOrderLinesJpaFetched( facilityId ) ) { + assertNotNull( line.getProduct().getFacility().getSite().getName() ); + } + } + + private List getOrderLinesScrolled(Long facilityId) { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + + Set purchaseOrgs = getPurchaseOrgsByFacilityId( facilityId, em ); + assertEquals( "Expected one purchase organization.", 1, purchaseOrgs.size() ); + System.out.println( purchaseOrgs ); + + TypedQuery query = getOrderLinesQuery( purchaseOrgs, em ); + + Query hibernateQuery = query.unwrap( Query.class ); + hibernateQuery.setReadOnly( true ); + hibernateQuery.setCacheable( false ); + + List lines = new ArrayList<>(); + ScrollableResults scrollableResults = hibernateQuery.scroll(); + scrollableResults.last(); + int rows = scrollableResults.getRowNumber() + 1; + scrollableResults.beforeFirst(); + while ( scrollableResults.next() ) { + lines.add( (OrderLine) scrollableResults.get( 0 ) ); + } + assertNotNull( lines ); + assertEquals( "Expected one order line", 1, lines.size() ); + + em.getTransaction().commit(); + return lines; + } + catch (Throwable t) { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + throw t; + } + finally { + em.close(); + } + } + + private List getOrderLinesJpaFetched(Long facilityId) { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + + Set purchaseOrgs = getPurchaseOrgsByFacilityId( facilityId, em ); + assertEquals( "Expected one purchase organization.", 1, purchaseOrgs.size() ); + System.out.println( purchaseOrgs ); + + TypedQuery query = getOrderLinesQuery( purchaseOrgs, em ); + List lines = query.getResultList(); + em.getTransaction().commit(); + return lines; + } + catch (Throwable t) { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + throw t; + } + finally { + em.close(); + } + } + + private Set getPurchaseOrgsByFacilityId(Long facilityId, EntityManager em) { + Set orgs = new HashSet<>(); + try { + for ( PurchaseOrg purchaseOrg : findAll( PurchaseOrg.class, em ) ) { + for ( Facility facility : purchaseOrg.getFacilities() ) { + if ( facility.getId().equals( facilityId ) ) { + orgs.add( purchaseOrg ); + break; + } + } + } + } + catch (Exception e) { + + } + finally { + return orgs; + } + } + + private List findAll(Class clazz, EntityManager em) { + return em.createQuery( "SELECT o FROM " + clazz.getSimpleName() + " o", clazz ).getResultList(); + } + + private TypedQuery getOrderLinesQuery(Collection purchaseOrgs, EntityManager em) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery( OrderLine.class ); + Root root = query.from( OrderLine.class ); + Path idPath = root.get( OrderLine_.id ); + + Join productJoin = (Join) root.fetch( OrderLine_.product ); + productJoin.fetch( Product_.facility ).fetch( Facility_.site ); + + Join orderJoin = (Join) root.fetch( OrderLine_.header ); + orderJoin.fetch( Order_.purchaseOrg ); + + Set ids = new HashSet<>(); + for ( PurchaseOrg org : purchaseOrgs ) + ids.add( org.getId() ); + + List predicates = new ArrayList<>(); + predicates.add( idPath.get( OrderLineId_.purchaseOrgId ).in( ids ) ); + + query.select( root ).where( predicates.toArray( new Predicate[predicates.size()] ) ); + + return em.createQuery( query ); + } + + private Long populate() { + final EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + + Customer customer = new Customer(); + customer.setName( "MGM" ); + em.persist( customer ); + + Site site = new Site(); + site.setName( "NEW YORK" ); + site.setCustomer( customer ); + em.persist( site ); + + Facility facility = new Facility(); + facility.setName( "ACME" ); + facility.setSite( site ); + facility.setCustomer( customer ); + em.persist( facility ); + + PurchaseOrg purchaseOrg = new PurchaseOrg(); + purchaseOrg.setName( "LOONEY TUNES" ); + purchaseOrg.setCustomer( customer ); + purchaseOrg.setFacilities( Arrays.asList( facility ) ); + em.persist( purchaseOrg ); + + Product product = new Product( facility, "0000 0001" ); + em.persist( product ); + + Order order = new Order( purchaseOrg, "12345" ); + OrderLine line1 = new OrderLine( order, 1L, product ); + + Set lines = new HashSet<>(); + lines.add( line1 ); + + order.setLines( lines ); + + em.persist( order ); + + em.getTransaction().commit(); + + return facility.getId(); + } + catch (Throwable t) { + if ( em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + throw t; + } + finally { + em.close(); + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Customer.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Customer.java new file mode 100755 index 000000000000..0dc695caa673 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Customer.java @@ -0,0 +1,62 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.io.Serializable; +import javax.persistence.*; + +@Entity +@Table(name = "customers") +public class Customer implements Comparable, Serializable { + + private Long id; + private String name; + + public Customer() { + } + + public Customer(String name) { + this.name = name; + } + + @Id + @GeneratedValue + @Column(name = "CUSTOMER_ID", nullable = false, updatable = false) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name = "CUSTOMER_NAME", length = 40, nullable = false, updatable = false) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + return 17 * 31 + id.hashCode(); + } + + @Override + public boolean equals(Object object) { + boolean result = false; + if(object instanceof Customer) { + Customer other = (Customer) object; + result = id.equals(other.id); + } + return result; + } + + @Override + public int compareTo(Customer other) { + return name.compareTo(other.getName()); + } + +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Facility.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Facility.java new file mode 100755 index 000000000000..4b2dd933fc7d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Facility.java @@ -0,0 +1,78 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.io.Serializable; +import javax.persistence.*; + +@Entity +@Table(name = "facilities") +public class Facility implements Comparable, Serializable { + + private static final long serialVersionUID = -2705232202888517103L; + + private Long id; + private String name; + private Site site; + private Customer customer; + + @Id + @GeneratedValue + @Column(name = "FACILITY_ID", nullable = false, updatable = false) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name = "FACILITY_NAME", length = 40, nullable = false, updatable = false) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "SITE_ID", referencedColumnName = "SITE_ID", nullable = false, updatable = false) + public Site getSite() { + return site; + } + + public void setSite(Site site) { + this.site = site; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID", nullable = false, updatable = false) + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + @Override + public int hashCode() { + return 17 * 31 + id.hashCode(); + } + + @Override + public boolean equals(Object object) { + boolean result = false; + if(object instanceof Facility) { + Facility other = (Facility)object; + result = other.getId().equals(id); + } + return result; + } + + @Override + public int compareTo(Facility other) { + return name.compareTo(other.name); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Order.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Order.java new file mode 100755 index 000000000000..889dbcff5602 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Order.java @@ -0,0 +1,55 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.util.*; +import javax.persistence.*; + +@Entity +@Table(name = "order_headers") +public class Order { + + private OrderId id; + private PurchaseOrg purchaseOrg; + private Set lines; + + public Order() { + + } + + public Order(PurchaseOrg purchaseOrg, String number) { + this.id = new OrderId(); + this.id.setPurchaseOrgId(purchaseOrg.getId()); + this.id.setNumber(number); + this.purchaseOrg = purchaseOrg; + } + + @EmbeddedId + public OrderId getId() { + return id; + } + + public void setId(OrderId id) { + this.id = id; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "PURCHASE_ORG_ID", referencedColumnName = "PURCHASE_ORG_ID", nullable = false, insertable = false, updatable = false) + public PurchaseOrg getPurchaseOrg() { + return purchaseOrg; + } + + public void setPurchaseOrg(PurchaseOrg purchaseOrg) { + this.purchaseOrg = purchaseOrg; + } + + @OneToMany(mappedBy = "header", orphanRemoval = true, cascade = CascadeType.ALL) + public Set getLines() { + return lines; + } + + public void setLines(Set lines) { + this.lines = lines; + } + +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderId.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderId.java new file mode 100755 index 000000000000..5b9716e6a590 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderId.java @@ -0,0 +1,33 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.io.Serializable; +import javax.persistence.*; + +@Embeddable +@MappedSuperclass +public class OrderId implements Serializable { + + private Long purchaseOrgId; + private String number; + + @Column(name = "PURCHASE_ORG_ID", nullable = false, updatable = false) + public Long getPurchaseOrgId() { + return purchaseOrgId; + } + + public void setPurchaseOrgId(Long purchaseOrgId) { + this.purchaseOrgId = purchaseOrgId; + } + + @Column(name = "ORDER_NUMBER", length = 40, nullable = false, updatable = false) + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderLine.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderLine.java new file mode 100755 index 000000000000..7defb4847cf1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderLine.java @@ -0,0 +1,62 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import javax.persistence.*; + +@Entity +@Table(name = "order_lines") +public class OrderLine { + + private OrderLineId id; + private Product product; + private Order header; + + public OrderLine() { + } + + public OrderLine(Order order, Long lineNumber, Product product) { + this.id = new OrderLineId(); + this.id.setPurchaseOrgId(order.getId().getPurchaseOrgId()); + this.id.setNumber(order.getId().getNumber()); + this.id.setLineNumber(lineNumber); + this.header = order; + this.product = product; + } + + @EmbeddedId + public OrderLineId getId() { + return id; + } + + public void setId(OrderLineId id) { + this.id = id; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "FACILITY_ID", referencedColumnName = "FACILITY_ID", nullable = false, updatable = false), + @JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID", nullable = false, updatable = false) + }) + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "PURCHASE_ORG_ID", referencedColumnName = "PURCHASE_ORG_ID", nullable = false, insertable = false, updatable = false), + @JoinColumn(name = "ORDER_NUMBER", referencedColumnName = "ORDER_NUMBER", nullable = false, insertable = false, updatable = false) + }) + public Order getHeader() { + return header; + } + + public void setHeader(Order header) { + this.header = header; + } + +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderLineId.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderLineId.java new file mode 100755 index 000000000000..9df05d911b7b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/OrderLineId.java @@ -0,0 +1,20 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import javax.persistence.*; + +@Embeddable +public class OrderLineId extends OrderId { + + private Long lineNumber; + + @Column(name = "LINE_NUMBER", nullable = false, updatable = false) + public Long getLineNumber() { + return lineNumber; + } + + public void setLineNumber(Long lineNumber) { + this.lineNumber = lineNumber; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Product.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Product.java new file mode 100755 index 000000000000..67db0b238638 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Product.java @@ -0,0 +1,53 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import javax.persistence.*; + +@Entity +@Table(name = "products") +public class Product { + + private ProductId id; + private String number; + private Facility facility; + + public Product() { + } + + public Product(Facility facility, String number) { + this.id = new ProductId(); + this.id.setFacilityId(facility.getId()); + this.id.setItemId(1L); + this.number = number; + this.facility = facility; + } + + @Id + private ProductId getId() { + return id; + } + + public void setId(ProductId id) { + this.id = id; + } + + @Column(name = "PRODUCT_NUMBER", length = 40, nullable = false, updatable = false) + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "FACILITY_ID", referencedColumnName = "FACILITY_ID", nullable = false, insertable = false, updatable = false) + public Facility getFacility() { + return facility; + } + + public void setFacility(Facility facility) { + this.facility = facility; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/ProductId.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/ProductId.java new file mode 100755 index 000000000000..3af7bf3a1ba1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/ProductId.java @@ -0,0 +1,32 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.io.Serializable; +import javax.persistence.*; + +@Embeddable +public class ProductId implements Serializable { + + private Long facilityId; + private Long itemId; + + @Column(name = "FACILITY_ID", nullable = false, updatable = false) + public Long getFacilityId() { + return facilityId; + } + + public void setFacilityId(Long facilityId) { + this.facilityId = facilityId; + } + + @Column(name = "PRODUCT_ID", nullable = false, updatable = false) + public Long getItemId() { + return itemId; + } + + public void setItemId(Long itemId) { + this.itemId = itemId; + } + +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/PurchaseOrg.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/PurchaseOrg.java new file mode 100755 index 000000000000..50eb000460e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/PurchaseOrg.java @@ -0,0 +1,76 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.util.List; +import java.io.Serializable; +import javax.persistence.*; + +@Entity +@Table(name = "purchase_organizations") +public class PurchaseOrg implements Serializable { + + private static final long serialVersionUID = -6659835148502079000L; + + private Long id; + private String name; + private Customer customer; + private List facilities; + + @Id + @GeneratedValue + @Column(name = "PURCHASE_ORG_ID", nullable = false, updatable = false) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name = "PURCHASE_ORG_NAME", length = 40, nullable = false, updatable = false) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID", nullable = false, updatable = false) + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "PURCHASE_FACILITY", + joinColumns = @JoinColumn(name = "PURCHASE_ORG_ID"), + inverseJoinColumns = @JoinColumn(name = "FACILITY_ID")) + public List getFacilities() { + return facilities; + } + + public void setFacilities(List facilities) { + this.facilities = facilities; + } + + @Override + public int hashCode() { + return 17 * 31 + id.hashCode(); + } + + @Override + public boolean equals(Object object) { + boolean result = false; + if(object instanceof PurchaseOrg) { + PurchaseOrg other = (PurchaseOrg) object; + result = id.equals(other.id); + } + return result; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Site.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Site.java new file mode 100755 index 000000000000..694b9fd109db --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/fetchscroll/Site.java @@ -0,0 +1,62 @@ + +package org.hibernate.jpa.test.criteria.fetchscroll; + +import java.io.Serializable; +import javax.persistence.*; + +@Entity +@Table(name = "sites") +public class Site implements Serializable { + + private static final long serialVersionUID = 9213996389556805371L; + + private Long id; + private String name; + private Customer customer; + + @Id + @GeneratedValue + @Column(name = "SITE_ID", nullable = false, updatable = false) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name = "SITE_NAME", length = 40, nullable = false, updatable = false) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID", nullable = false, updatable = false) + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + @Override + public int hashCode() { + return 17 * 31 + id.hashCode(); + } + + @Override + public boolean equals(Object object) { + boolean result = false; + if(object instanceof Site) { + Site other = (Site)object; + result = other.getId().equals(id); + } + return result; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java new file mode 100644 index 000000000000..1b5dab054677 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.hibernate.testing.transaction.TransactionUtil.setJdbcTimeout; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public abstract class AbstractCriteriaLiteralHandlingModeTest extends BaseEntityManagerFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.id = 1; + book.name = bookName(); + + entityManager.persist( book ); + } ); + } + + @Test + public void testLiteralHandlingMode() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + + final CriteriaQuery query = cb.createQuery( Tuple.class ); + + final Root entity = query.from( Book.class ); + query.where( + cb.and( + cb.equal( + entity.get( "id" ), + cb.literal( 1 ) + ), + cb.equal( + entity.get( "name" ), + cb.literal( bookName() ) + ) + ) + ); + + query.multiselect( + cb.literal( "abc" ), + entity.get( "name" ) + ); + + sqlStatementInterceptor.clear(); + + List tuples = entityManager.createQuery( query ).getResultList(); + assertEquals( 1, tuples.size() ); + + sqlStatementInterceptor.assertExecuted( expectedSQL() ); + } ); + } + + protected abstract String expectedSQL(); + + @Entity(name = "Book") + public static class Book { + + @Id + private Integer id; + + private String name; + } + + protected String bookName() { + return "Vlad's High-Performance Java Persistence"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeAutoTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeAutoTest.java new file mode 100644 index 000000000000..f9b8133277f6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeAutoTest.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.literal; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeAutoTest extends AbstractCriteriaLiteralHandlingModeTest { + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name=?"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeBindTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeBindTest.java new file mode 100644 index 000000000000..9bc5d34a95cc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeBindTest.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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeBindTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + LiteralHandlingMode.BIND + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=? and abstractcr0_.name=?"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineShortNameLowercaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineShortNameLowercaseTest.java new file mode 100644 index 000000000000..db2ff88ba6d5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineShortNameLowercaseTest.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeInlineShortNameLowercaseTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + "inline" + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name='Vlad''s High-Performance Java Persistence'"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineShortNameUppercaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineShortNameUppercaseTest.java new file mode 100644 index 000000000000..17def5a97d69 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineShortNameUppercaseTest.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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeInlineShortNameUppercaseTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + "INLINE" + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name='Vlad''s High-Performance Java Persistence'"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineTest.java new file mode 100644 index 000000000000..8d566cecda06 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineTest.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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeInlineTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + LiteralHandlingMode.INLINE + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name='Vlad''s High-Performance Java Persistence'"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java new file mode 100644 index 000000000000..b643babcaffd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java @@ -0,0 +1,138 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.literal; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; + +import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-14077") +public class CriteriaLiteralWithSingleQuoteTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void literalSingleQuoteTest() throws Exception { + + doInJPA( + this::entityManagerFactory, + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.select( cb.literal( '\'' ) ).from( Student.class ); + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( "'", object ); + } + ); + } + + @Test + public void literalProjectionTest() throws Exception { + + doInJPA( + this::entityManagerFactory, + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.multiselect( cb.literal( "' || aValue || '" ) ).from( Student.class ); + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( "' || aValue || '", object ); + } + ); + } + + @Test + @SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL does not support literals in group by statement") + public void testLiteralProjectionAndGroupBy() throws Exception { + doInJPA( + this::entityManagerFactory, + entityManager -> { + + final String literal = "' || aValue || '"; + + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.multiselect( cb.literal( literal ) ) + .from( Student.class ); + query.groupBy( cb.literal( literal ) ); + + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( literal, object ); + } + ); + } + + @Before + public void setupData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + Student student = new Student(); + student.setAValue( "A Value" ); + entityManager.persist( student ); + } + ); + } + + @After + public void cleanupData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + entityManager.createQuery( "delete from Student" ); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Student.class }; + } + + @Entity(name = "Student") + @Table(name = "Students") + public static class Student { + + @Id + @GeneratedValue + private Long id; + + @Column + private String aValue; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + this.id = id; + } + + public String getAValue() { + return aValue; + } + + public void setAValue(String value) { + this.aValue = value; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/MySQLCriteriaLiteralHandlingModeInlineTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/MySQLCriteriaLiteralHandlingModeInlineTest.java new file mode 100644 index 000000000000..9e008ccf78f1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/MySQLCriteriaLiteralHandlingModeInlineTest.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(MySQLDialect.class) +public class MySQLCriteriaLiteralHandlingModeInlineTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + LiteralHandlingMode.INLINE + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name='Vlad\\\\''s High-Performance Java Persistence'"; + } + + @Override + protected String bookName() { + return "Vlad\\'s High-Performance Java Persistence"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralInSelectExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralInSelectExpressionTest.java index f71d65dc6f95..9e63f6797251 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralInSelectExpressionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralInSelectExpressionTest.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.List; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.SQLServerDialect; @@ -203,7 +204,8 @@ public void testStringLiteral() throws Exception { Oracle8iDialect.class, DB2Dialect.class, SQLServerDialect.class, - SybaseDialect.class + SybaseDialect.class, + AbstractHANADialect.class }) public void testStringLiteral2() { final EntityManager entityManager = getOrCreateEntityManager(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java index 16f850c43f53..b38f9ab95efb 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java @@ -11,6 +11,7 @@ import java.util.Map; import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.EntityManager; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; @@ -28,7 +29,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; -import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.junit.Before; import org.junit.Test; @@ -43,23 +44,12 @@ */ @RequiresDialect(H2Dialect.class) public class CriteriaLiteralsTest extends BaseEntityManagerFunctionalTestCase { - - private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); - @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - return config; - } + private SQLStatementInterceptor sqlStatementInterceptor; @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); } @Override @@ -121,20 +111,63 @@ public void testLiteralsInWhereClause() throws Exception { entity.get( "name" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); List tuples = entityManager.createQuery( query ) .getResultList(); assertEquals( 1, - connectionProvider.getPreparedStatements().size() + sqlStatementInterceptor.getSqlQueries().size() ); - assertNotNull( connectionProvider.getPreparedStatement( - "select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.name=?" ) ); + sqlStatementInterceptor.assertExecuted("select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.name=?"); assertTrue( tuples.isEmpty() ); } ); } + @Test + public void testNumericLiteralsInWhereClause() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + testNumericLiterals( + entityManager, + "select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.id=1" + ); + } ); + } + + @Test + public void testNumericLiteralsInWhereClauseUsingBindParameters() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + testNumericLiterals( + entityManager, + "select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.id=1" + ); + } ); + } + + private void testNumericLiterals(EntityManager entityManager, String expectedSQL) { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + + final CriteriaQuery query = cb.createQuery( Tuple.class ); + + final Root entity = query.from( Book.class ); + query.where( cb.equal( + entity.get( "id" ), + cb.literal( 1 ) + ) ); + + query.multiselect( + cb.literal( "abc" ), + entity.get( "name" ) + ); + + sqlStatementInterceptor.clear(); + + List tuples = entityManager.createQuery( query ).getResultList(); + assertEquals( 1, tuples.size() ); + + sqlStatementInterceptor.assertExecuted( expectedSQL ); + } + @Test public void testCriteriaParameters() throws Exception { doInJPA( this::entityManagerFactory, entityManager -> { @@ -149,15 +182,13 @@ public void testCriteriaParameters() throws Exception { ), cb.equal( authors.index(), 0 ) ) .select( authors.get( "name" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); entityManager.createQuery( query ).getResultList(); assertEquals( 1, - connectionProvider.getPreparedStatements().size() - ); - assertNotNull( connectionProvider.getPreparedStatement( - "select authors1_.name as col_0_0_ from Book criteriali0_ inner join Author authors1_ on criteriali0_.id=authors1_.book_id where criteriali0_.name=? and authors1_.index_id=0" ) + sqlStatementInterceptor.getSqlQueries().size() ); + sqlStatementInterceptor.assertExecuted( "select authors1_.name as col_0_0_ from Book criteriali0_ inner join Author authors1_ on criteriali0_.id=authors1_.book_id where criteriali0_.name=? and authors1_.index_id=0" ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java index 069a1ca3b84c..4c9cae742c84 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java @@ -13,18 +13,19 @@ import javax.persistence.criteria.Join; import javax.persistence.criteria.Root; -import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; -import org.hibernate.jpa.test.metamodel.LineItem; -import org.hibernate.jpa.test.metamodel.LineItem_; -import org.hibernate.jpa.test.metamodel.Order; -import org.hibernate.jpa.test.metamodel.Order_; - +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.junit.Test; /** * @author Steve Ebersole */ -public class ImplicitJoinTest extends AbstractMetamodelSpecificTest { +public class ImplicitJoinTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Order.class, LineItem.class }; + } + @Test public void testImplicitJoinFromExplicitCollectionJoin() { EntityManager em = getOrCreateEntityManager(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java new file mode 100644 index 000000000000..03583422f1eb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.paths; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "LINEITEM_TABLE") +public class LineItem { + + private String id; + private int quantity; + private Order order; + + public LineItem() { + } + + public LineItem(String v1, int v2, Order v3) { + id = v1; + quantity = v2; + order = v3; + } + + public LineItem(String v1, int v2) { + id = v1; + quantity = v2; + } + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String v) { + id = v; + } + + @Column(name = "QUANTITY") + public int getQuantity() { + return quantity; + } + + public void setQuantity(int v) { + quantity = v; + } + + @ManyToOne + @JoinColumn(name = "FK1_FOR_ORDER_TABLE") + public Order getOrder() { + return order; + } + + public void setOrder(Order v) { + order = v; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java new file mode 100644 index 000000000000..ff82ed9b80a4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.paths; + +import java.util.Collection; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "ORDER_TABLE") +public class Order { + + private String id; + private double totalPrice; + private LineItem sampleLineItem; + private Collection lineItems = new java.util.ArrayList(); + + public Order() { + } + + public Order(String id, double totalPrice) { + this.id = id; + this.totalPrice = totalPrice; + } + + public Order(String id) { + this.id = id; + } + + // ==================================================================== + // getters and setters for State fields + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Column(name = "TOTALPRICE") + public double getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(double price) { + this.totalPrice = price; + } + + // ==================================================================== + // getters and setters for Association fields + + // 1x1 + + @OneToOne(cascade = CascadeType.REMOVE) + @JoinColumn(name = "FK0_FOR_LINEITEM_TABLE") + public LineItem getSampleLineItem() { + return sampleLineItem; + } + + public void setSampleLineItem(LineItem l) { + this.sampleLineItem = l; + } + + // 1xMANY + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "order") + public Collection getLineItems() { + return lineItems; + } + + public void setLineItems(Collection c) { + this.lineItems = c; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java index ba2a4efda79f..800f7dd4123b 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java @@ -1,85 +1,96 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.jpa.test.criteria.paths; - -import javax.persistence.EntityManager; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.From; -import javax.persistence.criteria.JoinType; -import javax.persistence.criteria.Path; -import javax.persistence.metamodel.Attribute; -import javax.persistence.metamodel.Bindable; -import javax.persistence.metamodel.SingularAttribute; -import javax.persistence.metamodel.Type; - -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; -import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; -import org.hibernate.query.criteria.internal.PathSource; -import org.hibernate.query.criteria.internal.path.SingularAttributeJoin; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; - -/** - * @author Brad Koehn - */ -public class SingularAttributeJoinTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected String[] getMappings() { - return new String[] { - getClass().getPackage().getName().replace( '.', '/' ) + "/PolicyAndDistribution.hbm.xml" - }; - } - - /** - * When building a join from a non-class based entity (EntityMode.MAP), make sure you get the Bindable from - * the SingularAttribute as the join model. If you don't, you'll get the first non-classed based entity - * you added to your configuration. Regression for HHH-9142. - */ - @Test - public void testEntityModeMapJoins() throws Exception { - CriteriaBuilderImpl criteriaBuilder = mock( CriteriaBuilderImpl.class); - PathSource pathSource = mock( PathSource.class); - SingularAttribute joinAttribute = mock( SingularAttribute.class); - when(joinAttribute.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.MANY_TO_ONE); - Type joinType = mock( Type.class, withSettings().extraInterfaces( Bindable.class)); - when(joinAttribute.getType()).thenReturn(joinType); - SingularAttributeJoin join = new SingularAttributeJoin(criteriaBuilder, null, pathSource, joinAttribute, JoinType.LEFT); - - assertEquals( joinType, join.getModel()); - } - - @Test - public void testEntityModeMapJoinCriteriaQuery() throws Exception { - final EntityManager entityManager = entityManagerFactory().createEntityManager(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); - javax.persistence.metamodel.EntityType distributionEntity = getEntityType("Distribution"); - From distributionFrom = criteriaQuery.from(distributionEntity); - From policyJoin = distributionFrom.join("policy"); - Path policyId = policyJoin.get("policyId"); - criteriaQuery.select(policyId); - TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); -// typedQuery.getResultList(); - } - - private javax.persistence.metamodel.EntityType getEntityType(String entityName) { - for(javax.persistence.metamodel.EntityType entityType : entityManagerFactory().getMetamodel().getEntities()) { - if (entityType.getName().equals("Distribution")) { - return entityType; - } - } - - throw new IllegalStateException("Unable to find entity " + entityName); - } -} +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.paths; + +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Path; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.Bindable; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.Type; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; +import org.hibernate.query.criteria.internal.PathSource; +import org.hibernate.query.criteria.internal.path.SingularAttributeJoin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +/** + * @author Brad Koehn + */ +public class SingularAttributeJoinTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected String[] getMappings() { + return new String[] { + getClass().getPackage().getName().replace( '.', '/' ) + "/PolicyAndDistribution.hbm.xml" + }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + + // make sure that dynamic-map mode entity types are returned in the metamodel. + options.put( AvailableSettings.JPA_METAMODEL_POPULATION, "enabled" ); + } + + /** + * When building a join from a non-class based entity (EntityMode.MAP), make sure you get the Bindable from + * the SingularAttribute as the join model. If you don't, you'll get the first non-classed based entity + * you added to your configuration. Regression for HHH-9142. + */ + @Test + public void testEntityModeMapJoins() throws Exception { + CriteriaBuilderImpl criteriaBuilder = mock( CriteriaBuilderImpl.class); + PathSource pathSource = mock( PathSource.class); + SingularAttribute joinAttribute = mock( SingularAttribute.class); + when(joinAttribute.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.MANY_TO_ONE); + Type joinType = mock( Type.class, withSettings().extraInterfaces( Bindable.class)); + when(joinAttribute.getType()).thenReturn(joinType); + SingularAttributeJoin join = new SingularAttributeJoin(criteriaBuilder, null, pathSource, joinAttribute, JoinType.LEFT); + + assertEquals( joinType, join.getModel()); + } + + @Test + public void testEntityModeMapJoinCriteriaQuery() throws Exception { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); + javax.persistence.metamodel.EntityType distributionEntity = getEntityType("Distribution"); + From distributionFrom = criteriaQuery.from(distributionEntity); + From policyJoin = distributionFrom.join("policy"); + Path policyId = policyJoin.get("policyId"); + criteriaQuery.select(policyId); + TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); +// typedQuery.getResultList(); + } + + private javax.persistence.metamodel.EntityType getEntityType(String entityName) { + for(javax.persistence.metamodel.EntityType entityType : entityManagerFactory().getMetamodel().getEntities()) { + if (entityType.getName().equals("Distribution")) { + return entityType; + } + } + + throw new IllegalStateException("Unable to find entity " + entityName); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java new file mode 100644 index 000000000000..f68e6b9d8676 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.selectcase; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.Id; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.PostgreSQL95Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.metadata.Person_; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-12230") +@SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") +public class GroupBySelectCaseTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-12230") + public void selectCaseInGroupByAndSelectExpression() { + + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( Person.class ); + + Predicate childPredicate = cb.between( from.get( Person_.AGE ), 0, 10 ); + Predicate teenagerPredicate = cb.between( from.get( Person_.AGE ), 11, 20 ); + CriteriaBuilder.Case selectCase = cb.selectCase(); + selectCase.when( childPredicate, "child" ) + .when( teenagerPredicate, "teenager" ) + .otherwise( "adult" ); + + query.multiselect( selectCase ); + query.groupBy( selectCase ); + + List resultList = entityManager.createQuery( query ).getResultList(); + assertNotNull( resultList ); + assertTrue( resultList.isEmpty() ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12230") + public void selectCaseInOrderByAndSelectExpression() { + + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( Person.class ); + + Predicate childPredicate = cb.between( from.get( Person_.AGE ), 0, 10 ); + Predicate teenagerPredicate = cb.between( from.get( Person_.AGE ), 11, 20 ); + CriteriaBuilder.Case selectCase = cb.selectCase(); + selectCase.when( childPredicate, "child" ) + .when( teenagerPredicate, "teenager" ) + .otherwise( "adult" ); + + query.multiselect( selectCase ); + query.orderBy( cb.asc( selectCase ) ); + + List resultList = entityManager.createQuery( query ).getResultList(); + assertNotNull( resultList ); + assertTrue( resultList.isEmpty() ); + } ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private Integer age; + } +} 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 19943b8503b3..28486f516251 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java @@ -34,14 +34,17 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @TestForIssue( jiraKey = "HHH-9731" ) +@SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") public class SelectCaseTest extends BaseEntityManagerFunctionalTestCase { @Override @@ -79,7 +82,7 @@ public void selectCaseWithCastedTypeValuesShouldWork() { CriteriaQuery query = cb.createQuery( Entity.class ); Root from = query.from( Entity.class ); - query.select( from ).where( cb.equal( from.get( "value" ), selectCase.as( String.class ) ) ); + query.select( from ).where( cb.equal( from.get( "value" ).as( String.class ), selectCase.as( String.class ) ) ); entityManager.createQuery( query ).getResultList(); } @@ -113,7 +116,7 @@ public void simpleSelectCaseWithCastedTypeValuesShouldWork() { CriteriaQuery query = cb.createQuery( Entity.class ); Root from = query.from( Entity.class ); - query.select( from ).where( cb.equal( from.get( "value" ), selectCase.as( String.class ) ) ); + query.select( from ).where( cb.equal( from.get( "value" ).as( String.class ), selectCase.as( String.class ) ) ); entityManager.createQuery( query ).getResultList(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java index ac3e4736519c..8ed46a1a832d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java @@ -14,6 +14,8 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Wallet; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -24,9 +26,10 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class DisableDiscardPersistenceContextOnCloseTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); @Override protected Map getConfig() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java index 31d26f90349c..71fc4fda1722 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java @@ -9,10 +9,14 @@ import java.util.Map; import javax.persistence.EntityManager; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Wallet; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -23,9 +27,10 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class EnableDiscardPersistenceContextOnCloseTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); @Override protected Map getConfig() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/InterceptorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/InterceptorTest.java index b2ff8513dc90..bcf77cf558bc 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/InterceptorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/InterceptorTest.java @@ -8,9 +8,18 @@ import java.util.Arrays; import java.util.Map; -import javax.persistence.EntityManager; +import java.util.function.Supplier; import javax.persistence.EntityManagerFactory; +import org.hibernate.Interceptor; +import org.hibernate.SessionFactory; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.jpa.AvailableSettings; @@ -20,8 +29,11 @@ import org.hibernate.jpa.test.PersistenceUnitDescriptorAdapter; import org.hibernate.jpa.test.SettingsGenerator; +import org.junit.After; import org.junit.Test; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -30,6 +42,26 @@ */ public class InterceptorTest { + public Class[] getAnnotatedClasses() { + return new Class[] { + Item.class, + Distributor.class + }; + } + + private EntityManagerFactory entityManagerFactory; + + @After + public void releaseResources() { + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + } + } + + public EntityManagerFactory entityManagerFactory() { + return entityManagerFactory; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // test deprecated Interceptor settings @@ -37,52 +69,42 @@ public class InterceptorTest { public void testDeprecatedConfiguredInterceptor() { Map settings = basicSettings(); settings.put( AvailableSettings.INTERCEPTOR, ExceptionInterceptor.class.getName() ); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); - EntityManager em = emf.createEntityManager(); - Item i = new Item(); - i.setName( "Laptop" ); - try { - em.getTransaction().begin(); - em.persist( i ); - em.getTransaction().commit(); - fail( "No interceptor" ); - } - catch ( IllegalStateException e ) { - assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); - } - finally { - if ( em.getTransaction() != null && em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - em.close(); - emf.close(); - } + buildEntityManagerFactory( settings ); + + Item i = new Item(); + i.setName( "Laptop" ); + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( i ); + fail( "No interceptor" ); + return null; + }); + } + catch ( IllegalStateException e ) { + assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); + } } @Test public void testDeprecatedConfiguredSessionInterceptor() { Map settings = basicSettings(); settings.put( AvailableSettings.SESSION_INTERCEPTOR, LocalExceptionInterceptor.class.getName() ); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); - EntityManager em = emf.createEntityManager(); + buildEntityManagerFactory( settings ); + Item i = new Item(); i.setName( "Laptop" ); + try { - em.getTransaction().begin(); - em.persist( i ); - em.getTransaction().commit(); - fail( "No interceptor" ); + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( i ); + fail( "No interceptor" ); + return null; + }); } catch ( IllegalStateException e ) { assertEquals( LocalExceptionInterceptor.LOCAL_EXCEPTION_MESSAGE, e.getMessage() ); } - finally { - if ( em.getTransaction() != null && em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - em.close(); - emf.close(); - } } @@ -93,106 +115,161 @@ public void testDeprecatedConfiguredSessionInterceptor() { public void testConfiguredInterceptor() { Map settings = basicSettings(); settings.put( org.hibernate.cfg.AvailableSettings.INTERCEPTOR, ExceptionInterceptor.class.getName() ); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); - EntityManager em = emf.createEntityManager(); + buildEntityManagerFactory( settings ); + Item i = new Item(); i.setName( "Laptop" ); - try { - em.getTransaction().begin(); - em.persist( i ); - em.getTransaction().commit(); - fail( "No interceptor" ); - } - catch ( IllegalStateException e ) { - assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); - } - finally { - if ( em.getTransaction() != null && em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - em.close(); - emf.close(); - } + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( i ); + fail( "No interceptor" ); + return null; + }); + } + catch ( IllegalStateException e ) { + assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); + } } @Test public void testConfiguredSessionInterceptor() { Map settings = basicSettings(); settings.put( org.hibernate.cfg.AvailableSettings.SESSION_SCOPED_INTERCEPTOR, LocalExceptionInterceptor.class.getName() ); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); - EntityManager em = emf.createEntityManager(); + buildEntityManagerFactory( settings ); + Item i = new Item(); i.setName( "Laptop" ); - try { - em.getTransaction().begin(); - em.persist( i ); - em.getTransaction().commit(); - fail( "No interceptor" ); - } - catch ( IllegalStateException e ) { - assertEquals( LocalExceptionInterceptor.LOCAL_EXCEPTION_MESSAGE, e.getMessage() ); - } - finally { - if ( em.getTransaction() != null && em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - em.close(); - emf.close(); - } + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( i ); + fail( "No interceptor" ); + return null; + }); + } + catch ( IllegalStateException e ) { + assertEquals( LocalExceptionInterceptor.LOCAL_EXCEPTION_MESSAGE, e.getMessage() ); + } + } + + @Test + public void testConfiguredSessionInterceptorWithSessionFactory() { + + StandardServiceRegistryImpl standardRegistry = (StandardServiceRegistryImpl) + new StandardServiceRegistryBuilder().build(); + + SessionFactory sessionFactory = null; + + try { + MetadataSources metadataSources = new MetadataSources( standardRegistry ); + for(Class annotatedClass : getAnnotatedClasses()) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + + Metadata metadata = metadataSources.getMetadataBuilder().build(); + + SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder(); + + sessionFactoryBuilder.applyStatelessInterceptor( LocalExceptionInterceptor.class ); + sessionFactory = sessionFactoryBuilder.build(); + + final SessionFactory sessionFactoryInstance = sessionFactory; + + Supplier sessionFactorySupplier = () -> sessionFactoryInstance; + + Item i = new Item(); + i.setName( "Laptop" ); + + try { + doInHibernate( sessionFactorySupplier, session -> { + session.persist( i ); + fail( "No interceptor" ); + return null; + }); + } + catch ( IllegalStateException e ) { + assertEquals( LocalExceptionInterceptor.LOCAL_EXCEPTION_MESSAGE, e.getMessage() ); + } + } + finally { + if(sessionFactory != null) { + sessionFactory.close(); + } + standardRegistry.destroy(); + } + } + + @Test + public void testConfiguredSessionInterceptorSupplier() { + Map settings = basicSettings(); + settings.put( org.hibernate.cfg.AvailableSettings.SESSION_SCOPED_INTERCEPTOR, (Supplier) LocalExceptionInterceptor::new); + buildEntityManagerFactory( settings ); + + Item i = new Item(); + i.setName( "Laptop" ); + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( i ); + fail( "No interceptor" ); + return null; + }); + } + catch ( IllegalStateException e ) { + assertEquals( LocalExceptionInterceptor.LOCAL_EXCEPTION_MESSAGE, e.getMessage() ); + } } @Test public void testEmptyCreateEntityManagerFactoryAndPropertyUse() { Map settings = basicSettings(); settings.put( AvailableSettings.INTERCEPTOR, ExceptionInterceptor.class.getName() ); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); - EntityManager em = emf.createEntityManager(); + buildEntityManagerFactory( settings ); + Item i = new Item(); i.setName( "Laptop" ); - try { - em.getTransaction().begin(); - em.persist( i ); - em.getTransaction().commit(); - fail( "No interceptor" ); - } - catch ( IllegalStateException e ) { - assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); - } - finally { - if ( em.getTransaction() != null && em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - em.close(); - emf.close(); - } + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( i ); + fail( "No interceptor" ); + return null; + }); + } + catch ( IllegalStateException e ) { + assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); + } } @Test public void testOnLoadCallInInterceptor() { Map settings = basicSettings(); settings.put( AvailableSettings.INTERCEPTOR, new ExceptionInterceptor( true ) ); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); - EntityManager em = emf.createEntityManager(); + buildEntityManagerFactory( settings ); + Item i = new Item(); i.setName( "Laptop" ); - em.getTransaction().begin(); - em.persist( i ); - em.flush(); - em.clear(); - try { - em.find( Item.class, i.getName() ); - fail( "No interceptor" ); - } - catch ( IllegalStateException e ) { - assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); - } - finally { - if ( em.getTransaction() != null && em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - em.close(); - emf.close(); - } + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( i ); + + entityManager.persist( i ); + entityManager.flush(); + entityManager.clear(); + try { + entityManager.find( Item.class, i.getName() ); + fail( "No interceptor" ); + } + catch ( IllegalStateException e ) { + assertEquals( ExceptionInterceptor.EXCEPTION_MESSAGE, e.getMessage() ); + } + }); + } + catch ( IllegalStateException e ) { + assertEquals( LocalExceptionInterceptor.LOCAL_EXCEPTION_MESSAGE, e.getMessage() ); + } } @@ -205,11 +282,10 @@ protected Map basicSettings() { ); } - public Class[] getAnnotatedClasses() { - return new Class[] { - Item.class, - Distributor.class - }; - } + private void buildEntityManagerFactory(Map settings) { + entityManagerFactory = Bootstrap + .getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ) + .build(); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java index 4536ceb455e8..98bd45c81f6e 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java @@ -21,8 +21,8 @@ import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.engine.internal.MutableEntityEntryFactory; @@ -40,6 +40,7 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.metadata.ClassMetadata; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.MultiLoadOptions; @@ -100,8 +101,8 @@ public static class GoofyProvider implements EntityPersister { @SuppressWarnings( {"UnusedParameters"}) public GoofyProvider( org.hibernate.mapping.PersistentClass persistentClass, - org.hibernate.cache.spi.access.EntityRegionAccessStrategy strategy, - NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy, + EntityDataAccess entityDataAccessstrategy, + NaturalIdDataAccess naturalIdRegionAccessStrategy, PersisterCreationContext creationContext) { throw new GoofyException(); } @@ -135,6 +136,11 @@ public SessionFactoryImplementor getFactory() { return null; } + @Override + public NavigableRole getNavigableRole() { + return null; + } + @Override public EntityEntryFactory getEntityEntryFactory() { return MutableEntityEntryFactory.INSTANCE; @@ -240,7 +246,6 @@ public boolean isVersioned() { return false; } - @Override public Comparator getVersionComparator() { return null; } @@ -282,7 +287,7 @@ public boolean hasNaturalIdCache() { } @Override - public NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy() { + public NaturalIdDataAccess getNaturalIdCacheAccessStrategy() { return null; } @@ -411,13 +416,23 @@ public boolean isLazyPropertiesCacheable() { return false; } + @Override + public boolean canReadFromCache() { + return false; + } + + @Override + public boolean canWriteToCache() { + return false; + } + @Override public boolean hasCache() { return false; } @Override - public EntityRegionAccessStrategy getCacheAccessStrategy() { + public EntityDataAccess getCacheAccessStrategy() { return null; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/emops/RemoveTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/emops/RemoveTest.java index 38e2f2680cec..dcbb2d05976a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/emops/RemoveTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/emops/RemoveTest.java @@ -20,7 +20,6 @@ * @author Emmanuel Bernard */ public class RemoveTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( RemoveTest.class ); @Test public void testRemove() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/InstrumentedClassLoader.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/InstrumentedClassLoader.java deleted file mode 100644 index ffb0ea558faa..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/InstrumentedClassLoader.java +++ /dev/null @@ -1,137 +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.jpa.test.enhancement; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.instrument.IllegalClassFormatException; -import java.util.List; - -import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; -import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.bytecode.enhance.spi.UnloadedClass; -import org.hibernate.jpa.internal.enhance.EnhancingClassTransformerImpl; - -/** - * @author Emmanuel Bernard - * @author Dustin Schultz - */ -public class InstrumentedClassLoader extends ClassLoader { - private List entities; - - public InstrumentedClassLoader(ClassLoader parent) { - super( parent ); - } - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - // Do not instrument the following packages - if ( name != null - && ( name.startsWith( "java.lang." ) || - name.startsWith( "java.util." ) ) ) { - return getParent().loadClass( name ); - } - Class c = findLoadedClass( name ); - if ( c != null ) { - return c; - } - - byte[] transformed = loadClassBytes( name ); - - return defineClass( name, transformed, 0, transformed.length ); - } - - /** - * Specialized {@link ClassLoader#loadClass(String)} that returns the class - * as a byte array. - * - * @param name - * - * @return - * - * @throws ClassNotFoundException - */ - public byte[] loadClassBytes(String name) throws ClassNotFoundException { - InputStream is = this.getResourceAsStream( name.replace( ".", "/" ) + ".class" ); - if ( is == null ) { - throw new ClassNotFoundException( name ); - } - byte[] buffer = new byte[409600]; - byte[] originalClass = new byte[0]; - int r = 0; - try { - r = is.read( buffer ); - } - catch (IOException e) { - throw new ClassNotFoundException( name + " not found", e ); - } - while ( r >= buffer.length ) { - byte[] temp = new byte[originalClass.length + buffer.length]; - System.arraycopy( originalClass, 0, temp, 0, originalClass.length ); - System.arraycopy( buffer, 0, temp, originalClass.length, buffer.length ); - originalClass = temp; - } - if ( r != -1 ) { - byte[] temp = new byte[originalClass.length + r]; - System.arraycopy( originalClass, 0, temp, 0, originalClass.length ); - System.arraycopy( buffer, 0, temp, originalClass.length, r ); - originalClass = temp; - } - try { - is.close(); - } - catch (IOException e) { - throw new ClassNotFoundException( name + " not found", e ); - } - - EnhancingClassTransformerImpl t = new EnhancingClassTransformerImpl( getEnhancementContext( getParent(), entities ) ); - try { - byte[] transformed = t.transform( - getParent(), - name, - null, - null, - originalClass - ); - - if ( transformed == null ) { - return originalClass; - } - else { - return transformed; - } - } - catch (IllegalClassFormatException e) { - throw new ClassNotFoundException( name + " not found", e ); - } - } - - public void setEntities(List entities) { - this.entities = entities; - } - - public EnhancementContext getEnhancementContext(final ClassLoader cl, final List entities) { - return new DefaultEnhancementContext() { - - @Override - public ClassLoader getLoadingClassLoader() { - return cl; - } - - @Override - public boolean isEntityClass(UnloadedClass classDescriptor) { - return entities.contains( classDescriptor.getName() ) && super.isEntityClass( classDescriptor ); - } - - @Override - public boolean isCompositeClass(UnloadedClass classDescriptor) { - return entities.contains( classDescriptor.getName() ) && super.isCompositeClass( classDescriptor ); - } - }; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/InterceptFieldClassFileTransformerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/InterceptFieldClassFileTransformerTest.java deleted file mode 100644 index 259a6a7148b8..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/InterceptFieldClassFileTransformerTest.java +++ /dev/null @@ -1,68 +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.jpa.test.enhancement; - -import java.util.ArrayList; -import java.util.List; - -import org.hibernate.engine.spi.Managed; -import org.hibernate.engine.spi.ManagedComposite; -import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.jpa.test.enhancement.cases.domain.Simple; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * @author Emmanuel Bernard - * @author Hardy Ferentschik - * @author Dustin Schultz - */ -public class InterceptFieldClassFileTransformerTest { - - private List entities = new ArrayList(); - private InstrumentedClassLoader loader = null; - - @Before - public void setup() { - entities.add( Simple.class.getName() ); - // use custom class loader which enhances the class - InstrumentedClassLoader cl = new InstrumentedClassLoader( Thread.currentThread().getContextClassLoader() ); - cl.setEntities( entities ); - this.loader = cl; - } - - /** - * Tests that class file enhancement works. - * - * @throws Exception in case the test fails. - */ - @Test - public void testEnhancement() throws Exception { - // sanity check that the class is unmodified and does not contain getFieldHandler() - assertFalse( implementsManaged( Simple.class ) ); - - Class clazz = loader.loadClass( entities.get( 0 ) ); - - // enhancement would have added the ManagedEntity interface... - assertTrue( implementsManaged( clazz ) ); - } - - private boolean implementsManaged(Class clazz) { - for ( Class intf : clazz.getInterfaces() ) { - if ( Managed.class.getName().equals( intf.getName() ) - || ManagedEntity.class.getName().equals( intf.getName() ) - || ManagedComposite.class.getName().equals( intf.getName() ) ) { - return true; - } - } - return false; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java new file mode 100644 index 000000000000..9719a0f67319 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java @@ -0,0 +1,194 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.enhancement; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.transaction.TransactionUtil.JPATransactionVoidFunction; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.PreUpdate; +import javax.persistence.Table; +import java.util.Arrays; +import java.util.Map; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@TestForIssue( jiraKey = "HHH-7573" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class TestLazyPropertyOnPreUpdate extends BaseEntityManagerFunctionalTestCase { + + private EntityWithLazyProperty entity; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{EntityWithLazyProperty.class}; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() ); + } + + @Before + public void prepare() throws Exception { + EntityPersister ep = entityManagerFactory().getMetamodel().entityPersister( EntityWithLazyProperty.class.getName() ); + assertTrue( ep.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ); + + byte[] testArray = new byte[]{0x2A}; + + doInJPA( this::entityManagerFactory, em -> { + //persist the test entity.d + entity = new EntityWithLazyProperty(); + entity.setSomeField( "TEST" ); + entity.setLazyData( testArray ); + em.persist( entity ); + } ); + + checkLazyField( entity, testArray ); + } + + /** + * Set a non lazy field, therefore the lazyData field will be LazyPropertyInitializer.UNFETCHED_PROPERTY + * for both state and newState so the field should not change. This should no longer cause a ClassCastException. + */ + @Test + public void testNoUpdate() { + byte[] testArray = new byte[]{0x2A}; + + doInJPA( this::entityManagerFactory, new JPATransactionVoidFunction() { + @Override + public void accept(EntityManager em) { + entity = em.find( EntityWithLazyProperty.class, entity.id ); + entity.setSomeField( "TEST1" ); + assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); + } + + @Override + public void afterTransactionCompletion() { + assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); + } + } ); + + checkLazyField( entity, testArray ); + } + + /** + * Set the updateLazyFieldInPreUpdate flag so that the lazy field is updated from within the + * PreUpdate annotated callback method. So state == LazyPropertyInitializer.UNFETCHED_PROPERTY and + * newState == EntityWithLazyProperty.PRE_UPDATE_VALUE. This should no longer cause a ClassCastException. + */ + @Test + public void testPreUpdate() { + doInJPA( this::entityManagerFactory, new JPATransactionVoidFunction() { + @Override + public void accept(EntityManager em) { + entity = em.find( EntityWithLazyProperty.class, entity.id ); + entity.setUpdateLazyFieldInPreUpdate( true ); + entity.setSomeField( "TEST2" ); + assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); + } + + @Override + public void afterTransactionCompletion() { + assertTrue( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); + } + } ); + + checkLazyField( entity, EntityWithLazyProperty.PRE_UPDATE_VALUE ); + } + + /** + * Set the updateLazyFieldInPreUpdate flag so that the lazy field is updated from within the + * PreUpdate annotated callback method and also set the lazyData field directly to testArray1. When we reload we + * should get EntityWithLazyProperty.PRE_UPDATE_VALUE. + */ + @Test + public void testPreUpdateOverride() { + byte[] testArray = new byte[]{0x2A}; + + doInJPA( this::entityManagerFactory, em -> { + entity = em.find( EntityWithLazyProperty.class, entity.id ); + entity.setUpdateLazyFieldInPreUpdate( true ); + assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); + entity.setLazyData( testArray ); + assertTrue( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); + entity.setSomeField( "TEST3" ); + } ); + + checkLazyField( entity, EntityWithLazyProperty.PRE_UPDATE_VALUE ); + } + + private void checkLazyField(EntityWithLazyProperty entity, byte[] expected) { + // reload the entity and check the lazy value matches what we expect. + doInJPA( this::entityManagerFactory, em -> { + EntityWithLazyProperty testEntity = em.find( EntityWithLazyProperty.class, entity.id ); + assertFalse( Hibernate.isPropertyInitialized( testEntity, "lazyData" ) ); + assertTrue( Arrays.equals( expected, testEntity.lazyData ) ); + assertTrue( Hibernate.isPropertyInitialized( testEntity, "lazyData" ) ); + } ); + } + + // --- // + + /** + * Test entity with a lazy property which requires build time instrumentation. + * + * @author Martin Ball + */ + @Entity + @Table( name = "ENTITY_WITH_LAZY_PROPERTY" ) + private static class EntityWithLazyProperty { + + public static final byte[] PRE_UPDATE_VALUE = new byte[]{0x2A, 0x2A, 0x2A, 0x2A}; + + @Id + @GeneratedValue + private Long id; + + @Basic( fetch = FetchType.LAZY ) + private byte[] lazyData; + + private String someField; + + private boolean updateLazyFieldInPreUpdate; + + public void setLazyData(byte[] lazyData) { + this.lazyData = lazyData; + } + + public void setSomeField(String someField) { + this.someField = someField; + } + + public void setUpdateLazyFieldInPreUpdate(boolean updateLazyFieldInPreUpdate) { + this.updateLazyFieldInPreUpdate = updateLazyFieldInPreUpdate; + } + + @PreUpdate + public void onPreUpdate() { + //Allow the update of the lazy field from within the pre update to check that this does not break things. + if ( updateLazyFieldInPreUpdate ) { + this.lazyData = PRE_UPDATE_VALUE; + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/AbstractExecutable.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/AbstractExecutable.java deleted file mode 100644 index 06aab92293fc..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/AbstractExecutable.java +++ /dev/null @@ -1,192 +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.jpa.test.enhancement.cases; - -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import javax.persistence.EntityManager; -import javax.persistence.SharedCacheMode; -import javax.persistence.ValidationMode; -import javax.persistence.spi.PersistenceUnitTransactionType; - -import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.Dialect; -import org.hibernate.jpa.AvailableSettings; -import org.hibernate.jpa.HibernateEntityManagerFactory; -import org.hibernate.jpa.HibernatePersistenceProvider; -import org.hibernate.jpa.boot.spi.Bootstrap; -import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; - -import org.hibernate.testing.bytecode.enhancement.EnhancerTestTask; - -/** - * @author Steve Ebersole - * @author Gail Badner - */ -public abstract class AbstractExecutable implements EnhancerTestTask { - private static final Dialect dialect = Dialect.getDialect(); - private HibernateEntityManagerFactory entityManagerFactory; - private EntityManager em; - - @Override - public final void prepare() { - // make sure we pick up the TCCL, and make sure its the isolated CL... - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if ( classLoader == null ) { - throw new RuntimeException( "Isolated ClassLoader not yet set as TCCL" ); - } -// if ( !InstrumentedClassLoader.class.isInstance( classLoader ) ) { -// throw new RuntimeException( "Isolated ClassLoader not yet set as TCCL" ); -// } - - entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( - buildPersistenceUnitDescriptor( getClass().getSimpleName() ), - buildSettings(), - classLoader - ).build().unwrap( HibernateEntityManagerFactory.class ); - - prepared(); - } - - protected void prepared() { - - } - - @Override - public final void complete() { - try { - cleanup(); - } - finally { - if ( em != null && em.isOpen() ) { - em.close(); - } - em = null; - entityManagerFactory.close(); - entityManagerFactory = null; - } - } - - protected HibernateEntityManagerFactory getEntityManagerFactory() { - return entityManagerFactory; - } - - protected EntityManager getOrCreateEntityManager() { - if ( em == null || !em.isOpen() ) { - em = entityManagerFactory.createEntityManager(); - } - return em; - } - - protected void cleanup() { - } - - private Map buildSettings() { - Map settings = Environment.getProperties(); - ArrayList classes = new ArrayList(); - classes.addAll( Arrays.asList( getAnnotatedClasses() ) ); - settings.put( AvailableSettings.LOADED_CLASSES, classes ); - 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, dialect.getClass().getName() ); - return settings; - } - - private PersistenceUnitDescriptor buildPersistenceUnitDescriptor(final String puName) { - return new PersistenceUnitDescriptor() { - private final String name = puName; - - @Override public URL getPersistenceUnitRootUrl() { - return null; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getProviderClassName() { - return HibernatePersistenceProvider.class.getName(); - } - - @Override - public boolean isUseQuotedIdentifiers() { - return false; - } - - @Override - public boolean isExcludeUnlistedClasses() { - return false; - } - - @Override - public PersistenceUnitTransactionType getTransactionType() { - return null; - } - - @Override - public ValidationMode getValidationMode() { - return null; - } - - @Override - public SharedCacheMode getSharedCacheMode() { - return null; - } - - @Override - public List getManagedClassNames() { - return null; - } - - @Override - public List getMappingFileNames() { - return null; - } - - @Override - public List getJarFileUrls() { - return null; - } - - @Override - public Object getNonJtaDataSource() { - return null; - } - - @Override - public Object getJtaDataSource() { - return null; - } - - @Override - public Properties getProperties() { - return null; - } - - @Override - public ClassLoader getClassLoader() { - return null; - } - - @Override - public ClassLoader getTempClassLoader() { - return null; - } - - @Override - public void pushClassTransformer(EnhancementContext enhancementContext) { - } - }; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/TestLazyPropertyOnPreUpdateExecutable.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/TestLazyPropertyOnPreUpdateExecutable.java deleted file mode 100644 index ce864be74dab..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/TestLazyPropertyOnPreUpdateExecutable.java +++ /dev/null @@ -1,116 +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.jpa.test.enhancement.cases; - -import java.util.Arrays; -import javax.persistence.EntityManager; - -import org.hibernate.Hibernate; -import org.hibernate.jpa.test.enhancement.cases.domain.EntityWithLazyProperty; -import org.hibernate.persister.entity.EntityPersister; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * - */ -public class TestLazyPropertyOnPreUpdateExecutable extends AbstractExecutable { - @Override - protected void prepared() { - final EntityPersister ep = getEntityManagerFactory().getSessionFactory().getEntityPersister( EntityWithLazyProperty.class.getName() ); - assertTrue( ep.getInstrumentationMetadata().isEnhancedForLazyLoading() ); - } - - @Override - public void execute() { - EntityWithLazyProperty entity; - EntityManager em = getOrCreateEntityManager(); - - byte[] testArray = new byte[]{0x2A}; - - //persist the test entity. - em.getTransaction().begin(); - entity = new EntityWithLazyProperty(); - entity.setSomeField("TEST"); - entity.setLazyData(testArray); - em.persist(entity); - em.getTransaction().commit(); - em.close(); - - checkLazyField(entity, em, testArray); - - /** - * Set a non lazy field, therefore the lazyData field will be LazyPropertyInitializer.UNFETCHED_PROPERTY - * for both state and newState so the field should not change. This should no longer cause a ClassCastException. - */ - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - entity = em.find(EntityWithLazyProperty.class, entity.getId()); - entity.setSomeField("TEST1"); - assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); - em.getTransaction().commit(); - assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); - em.close(); - - checkLazyField(entity, em, testArray); - - /** - * Set the updateLazyFieldInPreUpdate flag so that the lazy field is updated from within the - * PreUpdate annotated callback method. So state == LazyPropertyInitializer.UNFETCHED_PROPERTY and - * newState == EntityWithLazyProperty.PRE_UPDATE_VALUE. This should no longer cause a ClassCastException. - */ - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - entity = em.find(EntityWithLazyProperty.class, entity.getId()); - entity.setUpdateLazyFieldInPreUpdate(true); - entity.setSomeField("TEST2"); - assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); - em.getTransaction().commit(); - assertTrue( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); - em.close(); - - checkLazyField(entity, em, EntityWithLazyProperty.PRE_UPDATE_VALUE); - - /** - * Set the updateLazyFieldInPreUpdate flag so that the lazy field is updated from within the - * PreUpdate annotated callback method and also set the lazyData field directly to testArray1. When we reload we - * should get EntityWithLazyProperty.PRE_UPDATE_VALUE. - */ - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - entity = em.find(EntityWithLazyProperty.class, entity.getId()); - entity.setUpdateLazyFieldInPreUpdate(true); - assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); - entity.setLazyData(testArray); - assertTrue( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); - entity.setSomeField("TEST3"); - em.getTransaction().commit(); - em.close(); - - checkLazyField( entity, em, EntityWithLazyProperty.PRE_UPDATE_VALUE); - } - - private void checkLazyField(EntityWithLazyProperty entity, EntityManager em, byte[] expected) { - // reload the entity and check the lazy value matches what we expect. - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - entity = em.find(EntityWithLazyProperty.class, entity.getId()); - assertFalse( Hibernate.isPropertyInitialized( entity, "lazyData") ); - assertTrue( Arrays.equals( expected, entity.getLazyData() ) ); - assertTrue( Hibernate.isPropertyInitialized( entity, "lazyData" ) ); - em.getTransaction().commit(); - em.close(); - } - - - @Override - public Class[] getAnnotatedClasses() { - return new Class[] { EntityWithLazyProperty.class }; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/EntityWithLazyProperty.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/EntityWithLazyProperty.java deleted file mode 100644 index 44125be50c4f..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/EntityWithLazyProperty.java +++ /dev/null @@ -1,76 +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.jpa.test.enhancement.cases.domain; - -import javax.persistence.Basic; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.PreUpdate; - -/** - * Test entity with a lazy property which requires build time instrumentation. - * - * @author Martin Ball - */ -@Entity -public class EntityWithLazyProperty { - - public static final byte[] PRE_UPDATE_VALUE = new byte[]{0x2A, 0x2A, 0x2A, 0x2A}; - - @Id - @GeneratedValue - private Long id; - - @Basic(fetch = FetchType.LAZY) - private byte[] lazyData; - - private String someField; - - private boolean updateLazyFieldInPreUpdate; - - public Long getId() { - return id; - } - - public void setId(final Long id) { - this.id = id; - } - - public byte[] getLazyData() { - return lazyData; - } - - public void setLazyData(final byte[] lazyData) { - this.lazyData = lazyData; - } - - public String getSomeField() { - return someField; - } - - public void setSomeField(String someField) { - this.someField = someField; - } - - public boolean isUpdateLazyFieldInPreUpdate() { - return updateLazyFieldInPreUpdate; - } - - public void setUpdateLazyFieldInPreUpdate(boolean updateLazyFieldInPreUpdate) { - this.updateLazyFieldInPreUpdate = updateLazyFieldInPreUpdate; - } - - @PreUpdate - public void onPreUpdate() { - //Allow the update of the lazy field from within the pre update to check that this does not break things. - if(isUpdateLazyFieldInPreUpdate()) { - this.setLazyData(PRE_UPDATE_VALUE); - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/Simple.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/Simple.java deleted file mode 100644 index c434b7bea99a..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/Simple.java +++ /dev/null @@ -1,43 +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 . - */ - -//$Id$ -package org.hibernate.jpa.test.enhancement.cases.domain; - -import java.util.Collection; - -import javax.persistence.Entity; - - -/** - * @author Emmanuel Bernard - * @author Dustin Schultz - */ -@Entity -public class Simple { - private String name; - - // Have an additional attribute that will ensure that the enhanced classes - // will see all class attributes of an entity without CNFEs - private Collection relations; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Collection getRelations() { - return relations; - } - - public void setRelations(Collection relations) { - this.relations = relations; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/SimpleRelation.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/SimpleRelation.java deleted file mode 100644 index 8920cefb8d37..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/cases/domain/SimpleRelation.java +++ /dev/null @@ -1,27 +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.jpa.test.enhancement.cases.domain; - -/** - * A simple entity relation used by {@link Simple} to ensure that enhanced - * classes load all classes. - * - * @author Dustin Schultz - */ -public class SimpleRelation { - - private String blah; - - public String getBlah() { - return blah; - } - - public void setBlah(String blah) { - this.blah = blah; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/runtime/JpaRuntimeEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/runtime/JpaRuntimeEnhancementTest.java deleted file mode 100644 index 20e4386c3886..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/runtime/JpaRuntimeEnhancementTest.java +++ /dev/null @@ -1,120 +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.jpa.test.enhancement.runtime; - -import org.hibernate.jpa.test.enhancement.cases.TestLazyPropertyOnPreUpdateExecutable; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseUnitTestCase; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.junit.Test; - -/** - * @author Steve Ebersole - */ -public class JpaRuntimeEnhancementTest extends BaseUnitTestCase { -// -// @Rule -// public ClassLoadingIsolater isolater = new ClassLoadingIsolater( -// new ClassLoadingIsolater.IsolatedClassLoaderProvider() { -// @Override -// public ClassLoader buildIsolatedClassLoader() { -// final EnhancementContext enhancementContext = new DefaultEnhancementContext() { -// @Override -// public boolean doExtendedEnhancement(CtClass classDescriptor) { -// return classDescriptor.getPackageName().startsWith( "org.hibernate.jpa.test.enhancement.domain" ); -// } -// }; -// -// final Enhancer enhancer = new Enhancer( enhancementContext ); -// -// return new InstrumentedClassLoader( -// Thread.currentThread().getContextClassLoader(), -// new ClassTransformer() { -// @Override -// public byte[] transform( -// ClassLoader loader, -// String className, -// Class classBeingRedefined, -// ProtectionDomain protectionDomain, -// byte[] classfileBuffer) throws IllegalClassFormatException { -// -// try { -// return enhancer.enhance( className, classfileBuffer ); -// } -// catch (final Exception e) { -// throw new IllegalClassFormatException( "Error performing enhancement" ) { -// @Override -// public synchronized Throwable getCause() { -// return e; -// } -// }; -// } -// } -// } -// ); -// } -// -// @Override -// public void releaseIsolatedClassLoader(ClassLoader isolatedClassLoader) { -// // nothing to do -// } -// } -// ); - - - // the tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - /** - * Test for HHH-7573. - * Load some test data into an entity which has a lazy property and a @PreUpdate callback, then reload and update a - * non lazy field which will trigger the PreUpdate lifecycle callback. - * @throws Exception - */ - @Test - @TestForIssue( jiraKey = "HHH-7573" ) - public void LazyPropertyOnPreUpdate() throws Exception { - EnhancerTestUtils.runEnhancerTestTask( TestLazyPropertyOnPreUpdateExecutable.class ); - } - -// // reflection code to ensure isolation into the created classloader ~~~~~~~ -// -// private static final Class[] SIG = new Class[] {}; -// private static final Object[] ARGS = new Object[] {}; -// -// public void executeExecutable(String name) { -// Class execClass = null; -// Object executable = null; -// try { -// execClass = Thread.currentThread().getContextClassLoader().loadClass( name ); -// executable = execClass.newInstance(); -// } -// catch( Throwable t ) { -// throw new HibernateException( "could not load executable", t ); -// } -// try { -// execClass.getMethod( "prepare", SIG ).invoke( executable, ARGS ); -// execClass.getMethod( "execute", SIG ).invoke( executable, ARGS ); -// } -// catch ( NoSuchMethodException e ) { -// throw new HibernateException( "could not exeucte executable", e ); -// } -// catch ( IllegalAccessException e ) { -// throw new HibernateException( "could not exeucte executable", e ); -// } -// catch ( InvocationTargetException e ) { -// throw new HibernateException( "could not exeucte executable", e.getTargetException() ); -// } -// finally { -// try { -// execClass.getMethod( "complete", SIG ).invoke( executable, ARGS ); -// } -// catch ( Throwable ignore ) { -// } -// } -// } -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/exception/ExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/exception/ExceptionTest.java index bb5c4b2470f7..54ce978edcb7 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/exception/ExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/exception/ExceptionTest.java @@ -30,7 +30,6 @@ */ @SuppressWarnings("unchecked") public class ExceptionTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( ExceptionTest.class ); @Test public void testOptimisticLockingException() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/factory/puUtil/GetIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/factory/puUtil/GetIdentifierTest.java index b2a06b8003f9..afda0a25c2a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/factory/puUtil/GetIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/factory/puUtil/GetIdentifierTest.java @@ -15,6 +15,8 @@ import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.fail; + /** * @author Steve Ebersole */ @@ -51,6 +53,28 @@ public void getIdentifierTest() throws Exception { entityManager.close(); } + @Test + public void getIdentifierOfNonEntityTest() { + try { + entityManagerFactory().getPersistenceUnitUtil().getIdentifier( this ); + fail( "should have thrown IllegalArgumentException" ); + } + catch (IllegalArgumentException ex) { + // expected + } + } + + @Test + public void getIdentifierOfNullTest() { + try { + entityManagerFactory().getPersistenceUnitUtil().getIdentifier( null ); + fail( "should have thrown IllegalArgumentException" ); + } + catch (IllegalArgumentException ex) { + // expected + } + } + private NestedLegacyEntity createExisitingNestedLegacyEntity() { ModernEntity modernEntity = new ModernEntity(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Course.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Course.java new file mode 100644 index 000000000000..de964b154b1e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Course.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.graphs; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +@Entity +@Cache(usage=CacheConcurrencyStrategy.READ_WRITE) +public class Course { + @Id + @GeneratedValue + private int id; + + private String name; + + @ManyToMany(mappedBy="courses", cascade=CascadeType.ALL) + private List students = new ArrayList<>(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getStudents() { + return students; + } + + public void setStudents(List students) { + this.students = students; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getId(); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + int id = getId(); + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Course other = (Course) obj; + if (id != other.id) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + @Override + public String toString() { + return "Course [name=" + name + "]"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphNativeQueryTest.java new file mode 100644 index 000000000000..996e48a7a80f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphNativeQueryTest.java @@ -0,0 +1,157 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.graphs; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.EntityGraph; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.jpa.QueryHints; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12476" ) +public class EntityGraphNativeQueryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Foo.class, Bar.class, Baz.class }; + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, em -> { + Bar bar = new Bar(); + em.persist( bar ); + + Baz baz = new Baz(); + em.persist( baz ); + + Foo foo = new Foo(); + foo.bar = bar; + foo.baz = baz; + em.persist( foo ); + } ); + } + + @Test + public void testQuery() { + Foo foo = doInJPA( this::entityManagerFactory, em -> { + EntityGraph fooGraph = em.createEntityGraph( Foo.class ); + fooGraph.addAttributeNodes( "bar", "baz" ); + + return em.createQuery( "select f from Foo f", Foo.class ) + .setHint( "javax.persistence.loadgraph", fooGraph ) + .getSingleResult(); + } ); + + assertNotNull( foo.bar ); + assertNotNull( foo.baz ); + } + + @Test + public void testNativeQueryLoadGraph() { + try { + doInJPA( this::entityManagerFactory, em -> { + EntityGraph fooGraph = em.createEntityGraph( Foo.class ); + fooGraph.addAttributeNodes( "bar", "baz" ); + + em.createNativeQuery( + "select " + + " f.id as id, " + + " f.bar_id as bar_id, " + + " f.baz_id as baz_id " + + "from Foo f", Foo.class ) + .setHint( QueryHints.HINT_LOADGRAPH, fooGraph ) + .getSingleResult(); + + fail("Should throw exception"); + } ); + } + catch (Exception e) { + assertEquals( "A native SQL query cannot use EntityGraphs", e.getMessage() ); + } + } + + @Test + public void testNativeQueryFetchGraph() { + try { + doInJPA( this::entityManagerFactory, em -> { + EntityGraph fooGraph = em.createEntityGraph( Foo.class ); + fooGraph.addAttributeNodes( "bar", "baz" ); + + em.createNativeQuery( + "select " + + " f.id as id, " + + " f.bar_id as bar_id, " + + " f.baz_id as baz_id " + + "from Foo f", Foo.class ) + .setHint( QueryHints.HINT_FETCHGRAPH, fooGraph ) + .getSingleResult(); + + fail("Should throw exception"); + } ); + } + catch (Exception e) { + assertEquals( "A native SQL query cannot use EntityGraphs", e.getMessage() ); + } + } + + @Entity(name = "Foo") + public static class Foo { + + @Id + @GeneratedValue + public Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + public Bar bar; + + @ManyToOne(fetch = FetchType.LAZY) + public Baz baz; + } + + @Entity(name = "Bar") + public static class Bar { + + @Id + @GeneratedValue + public Integer id; + + @OneToMany(mappedBy = "bar") + public Set foos = new HashSet<>(); + } + + @Entity(name = "Baz") + public static class Baz { + + @Id + @GeneratedValue + public Integer id; + + @OneToMany(mappedBy = "baz") + public Set foos = new HashSet<>(); + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphUsingFetchGraphForLazyTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphUsingFetchGraphForLazyTest.java new file mode 100644 index 000000000000..d667ac9d8e76 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphUsingFetchGraphForLazyTest.java @@ -0,0 +1,176 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.graphs; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Subgraph; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.TypedQuery; + +import org.hibernate.Hibernate; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; +import static org.wildfly.common.Assert.assertFalse; + +/** + * @author Vlad Mihalcea + */ +public class EntityGraphUsingFetchGraphForLazyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {CustomerOrder.class, OrderPosition.class, Product.class, Address.class}; + } + + @Test + @TestForIssue( jiraKey = "HHH-10179" ) + @FailureExpected( jiraKey = "HHH-10179" ) + public void testFetchLazyWithGraphsSubsequently() { + Address address = new Address(); + address.city = "C9"; + Product product = new Product(); + product.productName = "P1"; + + OrderPosition orderPosition = new OrderPosition(); + orderPosition.product = product; + orderPosition.amount = 100; + + CustomerOrder customerOrder = new CustomerOrder(); + customerOrder.orderPosition = orderPosition; + customerOrder.shippingAddress = address; + + doInJPA( this::entityManagerFactory, em-> { + em.persist(address); + em.persist( product ); + + em.persist( orderPosition ); + em.persist( customerOrder ); + } ); + + doInJPA( this::entityManagerFactory, em-> { + // First, load with graph on shippingAddress + EntityGraph addressGraph = em.createEntityGraph( CustomerOrder.class ); + addressGraph.addAttributeNodes( "shippingAddress" ); + + Map properties = new HashMap<>(); + properties.put( "javax.persistence.fetchgraph", addressGraph ); + + CustomerOrder _customerOrder = em.find( CustomerOrder.class, customerOrder.id, properties ); + + assertTrue( Hibernate.isInitialized( _customerOrder ) ); + assertTrue( Hibernate.isInitialized( _customerOrder.shippingAddress ) ); + assertFalse( Hibernate.isInitialized( _customerOrder.orderPosition ) ); + assertFalse( _customerOrder.orderPosition.product != null && Hibernate.isInitialized( _customerOrder.orderPosition.product ) ); + + // Second, load with graph on shippingAddress and orderPosition + EntityGraph addressAndPositionGraph = em.createEntityGraph( CustomerOrder.class ); + addressAndPositionGraph.addAttributeNodes( "shippingAddress" ); + addressAndPositionGraph.addAttributeNodes( "orderPosition" ); + + properties = new HashMap<>(); + properties.put( "javax.persistence.fetchgraph", addressAndPositionGraph ); + + _customerOrder = em.find( CustomerOrder.class, customerOrder.id, properties ); + + assertTrue( Hibernate.isInitialized( _customerOrder ) ); + assertTrue( Hibernate.isInitialized( _customerOrder.shippingAddress ) ); + assertTrue( Hibernate.isInitialized( _customerOrder.orderPosition ) ); + assertFalse( _customerOrder.orderPosition.product != null && Hibernate.isInitialized( _customerOrder.orderPosition.product ) ); + + // Third, load with graph on address, orderPosition, and orderPosition.product + EntityGraph addressAndPositionAndProductGraph = em.createEntityGraph( CustomerOrder.class ); + addressAndPositionAndProductGraph.addAttributeNodes( "shippingAddress" ); + addressAndPositionAndProductGraph.addAttributeNodes( "orderPosition" ); + addressAndPositionAndProductGraph + .addSubgraph( "orderPosition", OrderPosition.class ) + .addAttributeNodes( "product" ); + + properties = new HashMap<>(); + properties.put( "javax.persistence.fetchgraph", addressAndPositionAndProductGraph ); + + _customerOrder = em.find( CustomerOrder.class, customerOrder.id, properties ); + + assertTrue( Hibernate.isInitialized( _customerOrder ) ); + assertTrue( Hibernate.isInitialized( _customerOrder.shippingAddress ) ); + assertTrue( Hibernate.isInitialized( _customerOrder.orderPosition ) ); + assertTrue( _customerOrder.orderPosition.product != null && Hibernate.isInitialized( _customerOrder.orderPosition.product ) ); + } ); + } + + @Entity + @Table(name = "customerOrder") + public static class CustomerOrder { + @Id + @GeneratedValue + public Long id; + + @OneToOne(fetch = FetchType.LAZY) + public OrderPosition orderPosition; + + @Temporal(TemporalType.TIMESTAMP) + public Date orderDate; + + @OneToOne(fetch = FetchType.LAZY) + public Address shippingAddress; + } + + @Entity + @Table(name = "address") + public static class Address { + @Id + @GeneratedValue + public Long id; + + public String city; + } + + @Entity + @Table(name = "orderPosition") + public static class OrderPosition { + @Id + @GeneratedValue + public Long id; + + public Integer amount; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product") + public Product product; + } + + @Entity + @Table(name = "product") + public static class Product { + @Id + @GeneratedValue + public Long id; + + public String productName; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java new file mode 100644 index 000000000000..cfcc2176b4b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java @@ -0,0 +1,201 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.graphs; + +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.EntityGraph; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.jpa.QueryHints; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class EntityGraphWithFetchAnnotationTest + extends BaseEntityManagerFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Order.class, + Product.class, + Tag.class, + }; + } + + @Test + @TestForIssue(jiraKey = "HHH-10485") + public void testWithoutEntityGraph() { + + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder + .createQuery( Order.class ); + criteriaQuery.from( Order.class ); + + sqlStatementInterceptor.clear(); + + entityManager + .createQuery( criteriaQuery ) + .setFirstResult( 10 ) + .setMaxResults( 20 ) + .getResultList(); + + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().contains( "left outer join" ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-10485") + public void testWithEntityGraph() { + + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder + .createQuery( Order.class ); + criteriaQuery.from( Order.class ); + + EntityGraph entityGraph = entityManager.createEntityGraph( Order.class ); + entityGraph.addAttributeNodes( "products" ); + + sqlStatementInterceptor.clear(); + + entityManager + .createQuery( criteriaQuery ) + .setFirstResult( 10 ) + .setMaxResults( 20 ) + .setHint( QueryHints.HINT_FETCHGRAPH, entityGraph ) + .getResultList(); + + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().contains( "left outer join" ) ); + } ); + } + + @Entity(name = "Order") + @Table(name = "orders") + public static class Order { + + @Id + @GeneratedValue + private long id; + + @OneToMany + @Fetch(FetchMode.SELECT) + private List products; + + @OneToMany + @Fetch(FetchMode.SELECT) + private List tags; + + public long getId() { + return this.id; + } + + public void setId(long id) { + this.id = id; + } + + public List getProducts() { + return this.products; + } + + public void setProducts(List products) { + this.products = products; + } + + public List getTags() { + return this.tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + } + + @Entity(name = "Product") + @Table(name = "products") + public static class Product { + + @Id + @GeneratedValue + private long id; + + private String name; + + public long getId() { + return this.id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + } + + @Entity(name = "Tag") + @Table(name = "tags") + public static class Tag { + + @Id + @GeneratedValue + private long id; + + private String name; + + public long getId() { + return this.id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Student.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Student.java new file mode 100644 index 000000000000..cddf7621fa0d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Student.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.graphs; + +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.UniqueConstraint; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +@Entity +@Cache(usage=CacheConcurrencyStrategy.READ_WRITE) +@NamedEntityGraphs({ + @NamedEntityGraph( + name = "Student.Full", + attributeNodes = { + @NamedAttributeNode(value = Student_.COURSES) + } + ) +}) +@NamedQueries({ + @NamedQuery(name="LIST_OF_STD", query="select std from Student std") +}) +public class Student { + @Id + private int id; + + private String name; + + @ManyToMany(cascade=CascadeType.PERSIST) + @JoinTable( + name="STUDENT_COURSES", + joinColumns=@JoinColumn(referencedColumnName="ID", name="STUDENT_ID"), + inverseJoinColumns=@JoinColumn(referencedColumnName="ID", name="COURSE_ID"), + uniqueConstraints={@UniqueConstraint(columnNames={"STUDENT_ID", "COURSE_ID"})} + ) + private Set courses; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Set getCourses() { + return courses; + } + + public void setCourses(Set courses) { + this.courses = courses; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Student [name=" + name + "]"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mapped_by_id/LoadGraphFindByIdTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mapped_by_id/LoadGraphFindByIdTest.java new file mode 100644 index 000000000000..c6a50321dd11 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mapped_by_id/LoadGraphFindByIdTest.java @@ -0,0 +1,130 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.graphs.mapped_by_id; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Oliver Breidenbach + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class LoadGraphFindByIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {User.class, UserStatistic.class}; + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, em -> { + UserStatistic statistic = new UserStatistic(); + statistic.id = 1L; + statistic.commentCount = 7; + User user = new User(); + user.id = 1L; + user.userStatistic = statistic; + + em.persist( statistic ); + em.persist( user ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-10842") + @FailureExpected( jiraKey = "HHH-10842" ) + public void findByPrimaryKeyWithId() { + doInJPA( this::entityManagerFactory, em -> { + User result = em.find( User.class, 1L, createProperties( em ) ); + Assert.assertNotNull( result.userStatistic.commentCount ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-10842") + public void findByPrimaryKeyWithQuery() { + doInJPA( this::entityManagerFactory, em -> { + User result = createTypedQuery( em ).getSingleResult(); + Assert.assertNotNull( result.userStatistic.commentCount ); + } ); + } + + private TypedQuery createTypedQuery(EntityManager em) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery( User.class ); + Root root = cq.from( User.class ); + + cq.where( cb.equal( root.get( "id" ), 1L ) ); + TypedQuery tq = em.createQuery( cq ); + tq.setHint( "javax.persistence.loadgraph", createEntityGraph( em ) ); + return tq; + } + + private Map createProperties(EntityManager em) { + Map properties = new HashMap(); + properties.put( + "javax.persistence.loadgraph", + createEntityGraph( em ) + ); + return properties; + } + + private EntityGraph createEntityGraph(EntityManager em) { + EntityGraph entityGraph = em.createEntityGraph( User.class ); + entityGraph.addAttributeNodes( "userStatistic" ); + return entityGraph; + } + + @Entity(name = "UserStatistic") + public static class UserStatistic { + + @Id + private Long id; + + private Integer commentCount; + } + + @Entity(name = "User") + @Table(name = "USERS") + public static class User { + + @Id + private Long id; + + private String name; + + @OneToOne(fetch = FetchType.LAZY, optional = false) + @MapsId + private UserStatistic userStatistic; + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java index 00504adbd6f3..60a7cebeb57c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java @@ -6,8 +6,11 @@ */ package org.hibernate.jpa.test.graphs.queryhint; +import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import javax.persistence.EntityGraph; import javax.persistence.EntityManager; import javax.persistence.Query; @@ -17,15 +20,18 @@ import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.graphs.Company; +import org.hibernate.jpa.test.graphs.Course; import org.hibernate.jpa.test.graphs.Employee; import org.hibernate.jpa.test.graphs.Location; import org.hibernate.jpa.test.graphs.Manager; import org.hibernate.jpa.test.graphs.Market; +import org.hibernate.jpa.test.graphs.Student; import org.hibernate.testing.TestForIssue; import org.junit.Before; import org.junit.Test; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -133,17 +139,15 @@ public void testLoadGraphOrderByWithImplicitJoin() { query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph ); List results = query.getResultList(); - // we expect 3 results: // - 1st will be the Company with location.zip == 11234 with an empty markets collection - // - 2nd and 3rd should be the Company with location.zip == 12345 - // (2nd and 3rd are duplicated because that entity has 2 elements in markets collection - assertEquals( 3, results.size() ); + // - 2nd should be the Company with location.zip == 12345 + assertEquals( 2, results.size() ); Company companyResult = (Company) results.get( 0 ); assertFalse( Hibernate.isInitialized( companyResult.employees ) ); assertFalse( Hibernate.isInitialized( companyResult.location ) ); // initialize and check zip - // TODO: must have getters to access lazy entity afterQuery being initialized (why?) + // TODO: must have getters to access lazy entity after being initialized (why?) //assertEquals( 11234, companyResult.location.zip ); assertEquals( 11234, companyResult.getLocation().getZip() ); assertTrue( Hibernate.isInitialized( companyResult.markets ) ); @@ -157,7 +161,7 @@ public void testLoadGraphOrderByWithImplicitJoin() { assertFalse( Hibernate.isInitialized( companyResult.employees ) ); assertFalse( Hibernate.isInitialized( companyResult.location ) ); // initialize and check zip - // TODO: must have getters to access lazy entity afterQuery being initialized (why?) + // TODO: must have getters to access lazy entity after being initialized (why?) //assertEquals( 12345, companyResult.location.zip ); assertEquals( 12345, companyResult.getLocation().getZip() ); assertTrue( Hibernate.isInitialized( companyResult.markets ) ); @@ -167,8 +171,6 @@ public void testLoadGraphOrderByWithImplicitJoin() { assertTrue( Hibernate.isInitialized( companyResult.phoneNumbers ) ); assertEquals( 2, companyResult.phoneNumbers.size() ); - assertSame( companyResult, results.get( 2 ) ); - entityManager.getTransaction().commit(); entityManager.close(); } @@ -310,6 +312,48 @@ public void testEntityGraphWithCollectionSubquery(){ entityManager.close(); } + @Test + @TestForIssue(jiraKey = "HHH-11569") + public void testCollectionSizeLoadedWithGraph() { + doInJPA( this::entityManagerFactory, entityManager -> { + + Student student1 = new Student(); + student1.setId( 1 ); + student1.setName( "Student 1" ); + Student student2 = new Student(); + student2.setId( 2 ); + student2.setName( "Student 2" ); + + Course course1 = new Course(); + course1.setName( "Full Time" ); + Course course2 = new Course(); + course2.setName( "Part Time" ); + + Set std1Courses = new HashSet(); + std1Courses.add( course1 ); + std1Courses.add( course2 ); + student1.setCourses( std1Courses ); + + Set std2Courses = new HashSet(); + std2Courses.add( course2 ); + student2.setCourses( std2Courses ); + + entityManager.persist( student1 ); + entityManager.persist( student2 ); + + }); + + doInJPA( this::entityManagerFactory, entityManager -> { + EntityGraph graph = entityManager.getEntityGraph( "Student.Full" ); + + List students = entityManager.createNamedQuery( "LIST_OF_STD", Student.class ) + .setHint( QueryHints.HINT_FETCHGRAPH, graph ) + .getResultList(); + + assertEquals( 2, students.size() ); + }); + } + @Before public void createData() { EntityManager entityManager = getOrCreateEntityManager(); @@ -348,6 +392,6 @@ public void createData() { @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Company.class, Employee.class, Manager.class, Location.class }; + return new Class[] { Company.class, Employee.class, Manager.class, Location.class, Course.class, Student.class }; } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/hibernateFilters/ProxyPreservingFiltersOutsideInitialSessionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/hibernateFilters/ProxyPreservingFiltersOutsideInitialSessionTest.java index 76b27eacbce7..3e4dd11572e3 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/hibernateFilters/ProxyPreservingFiltersOutsideInitialSessionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/hibernateFilters/ProxyPreservingFiltersOutsideInitialSessionTest.java @@ -27,7 +27,6 @@ public class ProxyPreservingFiltersOutsideInitialSessionTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( ProxyPreservingFiltersOutsideInitialSessionTest.class ); @Override public Class[] getAnnotatedClasses() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/indetifier/AssignedInitialValueTableGeneratorConfiguredTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/indetifier/AssignedInitialValueTableGeneratorConfiguredTest.java new file mode 100644 index 000000000000..0184f90d5943 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/indetifier/AssignedInitialValueTableGeneratorConfiguredTest.java @@ -0,0 +1,124 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.indetifier; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.TableGenerator; + +import org.hibernate.Session; +import org.hibernate.jdbc.Work; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Andrea Boriero + */ +public class AssignedInitialValueTableGeneratorConfiguredTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void testTheFirstGeneratedIdIsEqualToTableGeneratorInitialValuePlusOne() { + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = new Product(); + product.setName( "Hibernate" ); + entityManager.persist( product ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = entityManager.find( Product.class, 3L ); + assertThat( product, notNullValue() ); + } ); + } + + @Test + public void testTheGeneratedIdValuesAreCorrect() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( long i = 0; i < 3; i++ ) { + Product product = new Product(); + product.setName( "Hibernate " + i ); + entityManager.persist( product ); + } + } ); + + Session session = getOrCreateEntityManager().unwrap( Session.class ); + session.doWork( new Work() { + @Override + public void execute(Connection connection) throws SQLException { + ResultSet resultSet = connection.createStatement().executeQuery( + "select product_id from table_identifier" ); + resultSet.next(); + int productIdValue = resultSet.getInt( 1 ); + assertThat( productIdValue, is(12) ); + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + List products = entityManager.createQuery( "from Product p order by id " ).getResultList(); + assertThat( products.size(), is( 3 ) ); + assertThat( products.get( 0 ).getId(), is( 3L ) ); + assertThat( products.get( 1 ).getId(), is( 4L ) ); + assertThat( products.get( 2 ).getId(), is( 5L ) ); + } ); + } + + @Entity(name = "Product") + public static class Product { + + @Id + @GeneratedValue( + strategy = GenerationType.TABLE, + generator = "table-generator" + ) + @TableGenerator( + name = "table-generator", + table = "table_identifier", + pkColumnName = "table_name", + valueColumnName = "product_id", + allocationSize = 5, + initialValue = 2 + ) + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/indetifier/DefaultInitialValueTableGeneratorConfiguredTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/indetifier/DefaultInitialValueTableGeneratorConfiguredTest.java new file mode 100644 index 000000000000..f47f912d79c1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/indetifier/DefaultInitialValueTableGeneratorConfiguredTest.java @@ -0,0 +1,124 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.indetifier; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.TableGenerator; + +import org.hibernate.Session; +import org.hibernate.jdbc.Work; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +public class DefaultInitialValueTableGeneratorConfiguredTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void testTheFirstGeneratedIdIsEqualToTableGeneratorInitialValuePlusOne() { + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = new Product(); + product.setName( "Hibernate" ); + entityManager.persist( product ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Product product = entityManager.find( + Product.class, + 1L + ); + assertThat( product, notNullValue() ); + } ); + } + + @Test + public void testTheGeneratedIdValuesAreCorrect() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( long i = 0; i < 3; i++ ) { + Product product = new Product(); + product.setName( "Hibernate " + i ); + entityManager.persist( product ); + } + } ); + Session session = getOrCreateEntityManager().unwrap( Session.class ); + session.doWork( new Work() { + @Override + public void execute(Connection connection) throws SQLException { + ResultSet resultSet = connection.createStatement().executeQuery( + "select product_id from table_identifier" ); + resultSet.next(); + int productIdValue = resultSet.getInt( 1 ); + assertThat( productIdValue, is(10) ); + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + List products = entityManager.createQuery( + "from Product p order by id " ).getResultList(); + assertThat( products.size(), is( 3 ) ); + assertThat( products.get( 0 ).getId(), is( 1L ) ); + assertThat( products.get( 1 ).getId(), is( 2L ) ); + assertThat( products.get( 2 ).getId(), is( 3L ) ); + } ); + } + + @Entity(name = "Product") + public static class Product { + + @Id + @GeneratedValue( + strategy = GenerationType.TABLE, + generator = "table-generator" + ) + @TableGenerator( + name = "table-generator", + table = "table_identifier", + pkColumnName = "table_name", + valueColumnName = "product_id", + allocationSize = 5 + ) + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/jee/OrmVersionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/jee/OrmVersionTest.java deleted file mode 100644 index de42aae49059..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/jee/OrmVersionTest.java +++ /dev/null @@ -1,185 +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.jpa.test.jee; - -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Properties; -import javax.persistence.EntityManagerFactory; -import javax.persistence.PersistenceException; -import javax.persistence.SharedCacheMode; -import javax.persistence.ValidationMode; -import javax.persistence.spi.ClassTransformer; -import javax.persistence.spi.PersistenceUnitInfo; -import javax.persistence.spi.PersistenceUnitTransactionType; -import javax.sql.DataSource; - -import org.hibernate.AnnotationException; -import org.hibernate.InvalidMappingException; -import org.hibernate.jpa.HibernatePersistenceProvider; -import org.hibernate.jpa.test.pack.defaultpar.Lighter; -import org.hibernate.jpa.test.pack.defaultpar_1_0.Lighter1; - -import org.junit.Assert; -import org.junit.Test; - -/** - * "smoke" tests for JEE bootstrapping of HEM via a {@link PersistenceUnitInfo} - * - * @author Steve Ebersole - */ -public class OrmVersionTest { - @Test - public void testOrm1() { - PersistenceUnitInfoImpl pui = new PersistenceUnitInfoImpl( "orm1-test", "1.0" ) - .addMappingFileName( "org/hibernate/jpa/test/jee/valid-orm-1.xml" ); - HibernatePersistenceProvider hp = new HibernatePersistenceProvider(); - EntityManagerFactory emf = hp.createContainerEntityManagerFactory( pui, Collections.EMPTY_MAP ); - try { - emf.getMetamodel().entity( Lighter1.class ); // exception if not entity - } - finally { - emf.close(); - } - } - - @Test - public void testOrm2() { - PersistenceUnitInfoImpl pui = new PersistenceUnitInfoImpl( "orm2-test", "2.0" ) - .addMappingFileName( "org/hibernate/jpa/test/jee/valid-orm-2.xml" ); - HibernatePersistenceProvider hp = new HibernatePersistenceProvider(); - EntityManagerFactory emf = hp.createContainerEntityManagerFactory( pui, Collections.EMPTY_MAP ); - try { - emf.getMetamodel().entity( Lighter.class ); // exception if not entity - } - finally { - emf.close(); - } - } - - @Test - public void testInvalidOrm1() { - PersistenceUnitInfoImpl pui = new PersistenceUnitInfoImpl( "invalid-orm1-test", "1.0" ) - .addMappingFileName( "org/hibernate/jpa/test/jee/invalid-orm-1.xml" ); - HibernatePersistenceProvider hp = new HibernatePersistenceProvider(); - EntityManagerFactory emf = null; - try { - emf = hp.createContainerEntityManagerFactory( - pui, - Collections.EMPTY_MAP - ); - Assert.fail( "expecting 'invalid content' error" ); - } - catch (InvalidMappingException | AnnotationException expected) { - // expected condition - } - catch (PersistenceException expected) { - // expected condition - } - finally { - if ( emf != null ) { - emf.close(); - } - } - } - - public static class PersistenceUnitInfoImpl implements PersistenceUnitInfo { - private final String name; - private final String persistenceSchemaVersion; - - public PersistenceUnitInfoImpl(String name) { - this( name, "2.0" ); - } - - public PersistenceUnitInfoImpl(String name, String persistenceSchemaVersion) { - this.name = name; - this.persistenceSchemaVersion = persistenceSchemaVersion; - } - - public String getPersistenceUnitName() { - return name; - } - - public String getPersistenceXMLSchemaVersion() { - return persistenceSchemaVersion; - } - - private final List mappingFileNames = new ArrayList(); - - public List getMappingFileNames() { - return mappingFileNames; - } - - private final List managedClassNames = new ArrayList(); - - private PersistenceUnitInfoImpl addMappingFileName(String mappingFileName) { - mappingFileNames.add( mappingFileName ); - return this; - } - - public List getManagedClassNames() { - return managedClassNames; - } - - public String getPersistenceProviderClassName() { - return null; - } - - public PersistenceUnitTransactionType getTransactionType() { - return PersistenceUnitTransactionType.RESOURCE_LOCAL; - } - - public DataSource getJtaDataSource() { - return null; - } - - public DataSource getNonJtaDataSource() { - return null; - } - - private final List jarFileUrls = new ArrayList(); - - public List getJarFileUrls() { - return jarFileUrls; - } - - public URL getPersistenceUnitRootUrl() { - return null; - } - - public boolean excludeUnlistedClasses() { - return false; - } - - public SharedCacheMode getSharedCacheMode() { - return null; - } - - public ValidationMode getValidationMode() { - return null; - } - - private final Properties properties = new Properties(); - - public Properties getProperties() { - return properties; - } - - public ClassLoader getClassLoader() { - return Thread.currentThread().getContextClassLoader(); - } - - public void addTransformer(ClassTransformer transformer) { - } - - public ClassLoader getNewTempClassLoader() { - return getClassLoader(); - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java index 93f41f3c2837..ee50e511f558 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java @@ -10,12 +10,21 @@ import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; import javax.persistence.Version; /** * @author Emmanuel Bernard */ @Entity(name="Lock_") +@NamedQuery( + name="AllLocks", + query="from Lock_", + lockMode = LockModeType.PESSIMISTIC_WRITE, + hints = { @QueryHint( name = "javax.persistence.lock.timeout", value = "0")} +) public class Lock { private Integer id; private Integer version; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java index 17bd3ab74ebd..09c149d46cec 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java @@ -9,11 +9,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; -import javax.persistence.EntityManager; +import java.util.concurrent.atomic.AtomicBoolean; import javax.persistence.LockModeType; import javax.persistence.LockTimeoutException; import javax.persistence.OptimisticLockException; @@ -22,9 +22,15 @@ import javax.persistence.Query; import javax.persistence.QueryTimeoutException; +import org.hibernate.LockOptions; +import org.hibernate.Session; +import org.hibernate.TransactionException; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.Oracle10gDialect; +import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.QueryHints; @@ -35,10 +41,13 @@ import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil; +import org.hibernate.testing.util.ExceptionUtil; import org.junit.Test; import org.jboss.logging.Logger; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -48,37 +57,32 @@ * @author Emmanuel Bernard */ public class LockTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( LockTest.class ); - + private static final Logger log = Logger.getLogger( LockTest.class ); @Test public void testFindWithTimeoutHint() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "name" ); - em.persist( lock ); - em.getTransaction().commit(); - em.close(); - - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - Map properties = new HashMap(); - properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); - em.find( Lock.class, 1, LockModeType.PESSIMISTIC_WRITE, properties ); - em.getTransaction().commit(); - em.close(); - - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - lock = em.find( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + return lock.getId(); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Map properties = new HashMap(); + properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); + em.find( Lock.class, 1, LockModeType.PESSIMISTIC_WRITE, properties ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } - @Test(timeout = 5 * 60 * 1000) //5 minutes + @Test(timeout = 5 * 1000) //5 seconds @TestForIssue( jiraKey = "HHH-7252" ) @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, comment = "Test verifies proper exception throwing when a lock timeout is specified.", @@ -86,455 +90,658 @@ public void testFindWithTimeoutHint() { public void testFindWithPessimisticWriteLockTimeoutException() { Lock lock = new Lock(); lock.setName( "name" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.close(); - - EntityManager em2 = createIsolatedEntityManager(); - em2.getTransaction().begin(); - Map properties = new HashMap(); - properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); - Lock lock2 = em2.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties ); - assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, em2.getLockMode( lock2 ) ); - - EntityManager em3 = createIsolatedEntityManager(); - em3.getTransaction().begin(); - try { - em3.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties ); - fail( "Exception should be thrown" ); - } - catch (LockTimeoutException lte) { - // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. - } - catch (PessimisticLockException pe) { - fail( "Find with immediate timeout should have thrown LockTimeoutException." ); - } - catch (PersistenceException pe) { - log.info("EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + - "This is likely a consequence of " + getDialect().getClass().getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + - "See HHH-7251 for an example of one such situation.", pe); - fail( "EntityManager should be throwing LockTimeoutException." ); - } - finally { - if (em3.getTransaction().getRollbackOnly()) { - em3.getTransaction().rollback(); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + Map properties = new HashMap(); + properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); + + entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties ); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getSingleResult.", + jiraKey = "HHH-13364" ) + public void testQuerySingleResultPessimisticWriteLockTimeoutException() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createQuery( "from Lock_ where id = " + lock.getId(), Lock.class ) + .setLockMode( LockModeType.PESSIMISTIC_WRITE ) + .setHint( "javax.persistence.lock.timeout", 0 ) + .getSingleResult(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info("EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass().getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getResultList.", + jiraKey = "HHH-13364" ) + public void testQueryResultListPessimisticWriteLockTimeoutException() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createQuery( "from Lock_ where id = " + lock.getId(), Lock.class ) + .setLockMode( LockModeType.PESSIMISTIC_WRITE ) + .setHint( "javax.persistence.lock.timeout", 0 ) + .getResultList(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for NamedQuery#getResultList.", + jiraKey = "HHH-13364" ) + public void testNamedQueryResultListPessimisticWriteLockTimeoutException() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createNamedQuery( "AllLocks", Lock.class ).getResultList(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test + @RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class ) + public void testUpdateWithPessimisticReadLockSkipLocked() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( + this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } + ); + + doInJPA( this::entityManagerFactory, _entityManagaer -> { + Map properties = new HashMap<>(); + properties.put( org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT, LockOptions.SKIP_LOCKED ); + _entityManagaer.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ, properties ); + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + try { + entityManager.createNativeQuery( updateStatement() ) + .setParameter( "name", "changed" ) + .setParameter( "id", lock.getId() ) + .executeUpdate(); + fail("Should throw Exception"); + } + catch (Exception e) { + if ( !ExceptionUtil.isSqlLockTimeout( e) ) { + fail( "Unknown exception thrown: " + e.getMessage() ); + } + } + } ); } - else { - em3.getTransaction().commit(); + catch (Exception e) { + log.error( "Failure", e ); } - em3.close(); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Lock _lock = entityManager.merge( lock ); + entityManager.remove( _lock ); + } ); + } + + @Test + @RequiresDialectFeature(value = DialectChecks.SupportsLockTimeouts.class) + public void testUpdateWithPessimisticReadLockWithoutNoWait() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ ); + + AtomicBoolean failureExpected = new AtomicBoolean(); + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createNativeQuery( updateStatement() ) + .setParameter( "name", "changed" ) + .setParameter( "id", lock.getId() ) + .executeUpdate(); + } + catch (Exception e) { + if ( ExceptionUtil.isSqlLockTimeout( e ) ) { + failureExpected.set( true ); + } + } + } ); + } + catch (Exception e) { + if ( !failureExpected.get() ) { + fail( "Should throw LockTimeoutException or PessimisticLockException" ); + } + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Lock _lock = entityManager.merge( lock ); + entityManager.remove( _lock ); + } ); + } + + protected String updateStatement() { + if( SQLServerDialect.class.isAssignableFrom( Dialect.getDialect().getClass() ) ) { + return "UPDATE Lock_ WITH(NOWAIT) SET name = :name where id = :id"; } - - em2.getTransaction().commit(); - em2.getTransaction().begin(); - em2.remove( lock2 ); - em2.getTransaction().commit(); - em2.close(); + return "UPDATE Lock_ SET name = :name where id = :id"; } @Test public void testLockRead() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "name" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.READ ); - lock.setName( "surname" ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.find( Lock.class, lock.getId() ); - assertEquals( "surname", lock.getName() ); - em.remove( lock ); - em.getTransaction().commit(); - - em.close(); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.READ ); + _lock.setName( "surname" ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + assertEquals( "surname", _lock.getName() ); + em.remove( _lock ); + } ); } @Test public void testLockOptimistic() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "name" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.OPTIMISTIC ); - lock.setName( "surname" ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.find( Lock.class, lock.getId() ); - assertEquals( "surname", lock.getName() ); - em.remove( lock ); - em.getTransaction().commit(); - - em.close(); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.OPTIMISTIC ); + _lock.setName( "surname" ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + assertEquals( "surname", _lock.getName() ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockWrite() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "second" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - Integer version = lock.getVersion(); - em.lock( lock, LockModeType.WRITE ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + Integer version = doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + Integer _version = _lock.getVersion(); + em.lock( _lock, LockModeType.WRITE ); + return _version; + } ); + + try { - assertEquals( "should increase the version number EJB-106", 1, lock.getVersion() - version ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + assertEquals( "should increase the version number EJB-106", 1, _lock.getVersion() - version ); + } ); } finally { - em.remove( lock ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } - em.close(); } @Test public void testLockWriteOnUnversioned() throws Exception { - UnversionedLock lock = new UnversionedLock(); + final UnversionedLock lock = new UnversionedLock(); lock.setName( "second" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.getTransaction().begin(); - lock = em.getReference( UnversionedLock.class, lock.getId() ); - try { - // getting a READ (optimistic) lock on unversioned entity is not expected to work. - // To get the same functionality as prior release, change the LockModeType.READ lock to: - // em.lock(lock,LockModeType.PESSIMISTIC_READ); - em.lock( lock, LockModeType.READ ); - fail( "expected OptimisticLockException exception" ); - } - catch ( OptimisticLockException expected ) { - } - em.getTransaction().rollback(); - - // the previous code block can be rewritten as follows (to get the previous behavior) - em.getTransaction().begin(); - lock = em.getReference( UnversionedLock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_READ ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( UnversionedLock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + UnversionedLock _lock = em.getReference( UnversionedLock.class, lock.getId() ); + try { + // getting a READ (optimistic) lock on unversioned entity is not expected to work. + // To get the same functionality as prior release, change the LockModeType.READ lock to: + // em.lock(lock,LockModeType.PESSIMISTIC_READ); + em.lock( _lock, LockModeType.READ ); + fail( "expected OptimisticLockException exception" ); + } + catch ( OptimisticLockException expected ) { + } + } ); + + doInJPA( this::entityManagerFactory, em -> { + // the previous code block can be rewritten as follows (to get the previous behavior) + UnversionedLock _lock = em.getReference( UnversionedLock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_READ ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + UnversionedLock _lock = em.getReference( UnversionedLock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockPessimisticForceIncrement() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "force" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - Integer version = lock.getVersion(); - em.lock( lock, LockModeType.PESSIMISTIC_FORCE_INCREMENT ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - try { - assertEquals( "should increase the version number ", 1, lock.getVersion() - version ); - } - finally { - em.remove( lock ); - em.getTransaction().commit(); - } - em.close(); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + Integer version = doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + Integer _version = _lock.getVersion(); + em.lock( _lock, LockModeType.PESSIMISTIC_FORCE_INCREMENT ); + + return _version; + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + assertEquals( "should increase the version number ", 1, _lock.getVersion() - version ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockOptimisticForceIncrement() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "force" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - Integer version = lock.getVersion(); - em.lock( lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); - em.getTransaction().commit(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - try { - assertEquals( "should increase the version number ", 1, lock.getVersion() - version ); - } - finally { - em.remove( lock ); - em.getTransaction().commit(); - } - em.close(); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + Integer version = doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + Integer _version = _lock.getVersion(); + em.lock( _lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); + + return _version; + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + assertEquals( "should increase the version number ", 1, _lock.getVersion() - version ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockOptimisticForceIncrementDifferentEm() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "force" ); - EntityManager em1 = createIsolatedEntityManager(); - em1.getTransaction().begin(); - em1.persist( lock ); - em1.getTransaction().commit(); - em1.close(); - - EntityManager em2 = createIsolatedEntityManager(); - em2.getTransaction().begin(); - lock = em2.find( Lock.class, lock.getId(), LockModeType.OPTIMISTIC ); - assertEquals( "lock mode should be OPTIMISTIC ", LockModeType.OPTIMISTIC, em2.getLockMode( lock ) ); - em2.lock( lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); - assertEquals( - "lock mode should be OPTIMISTIC_FORCE_INCREMENT ", - LockModeType.OPTIMISTIC_FORCE_INCREMENT, - em2.getLockMode( lock ) - ); - em2.getTransaction().commit(); - em2.getTransaction().begin(); - em2.remove( lock ); - em2.getTransaction().commit(); - em2.close(); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId(), LockModeType.OPTIMISTIC ); + assertEquals( "lock mode should be OPTIMISTIC ", LockModeType.OPTIMISTIC, em.getLockMode( _lock ) ); + em.lock( _lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); + assertEquals( + "lock mode should be OPTIMISTIC_FORCE_INCREMENT ", + LockModeType.OPTIMISTIC_FORCE_INCREMENT, + em.getLockMode( _lock ) + ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test @SkipForDialect(HSQLDialect.class) // ASE15.5 will generate select...holdlock and fail at this test, but ASE15.7 passes it. Skip it for ASE15.5 // only. - @SkipForDialect(value = { SybaseASE15Dialect.class }, strictMatching = true, jiraKey = "HHH-6820") + @SkipForDialect(value = { SQLServerDialect.class }) public void testContendedPessimisticLock() throws Exception { - final EntityManager em = getOrCreateEntityManager(); - final EntityManager isolatedEntityManager = createIsolatedEntityManager(); - - Lock lock = createAndPersistLockInstance( em ); + final CountDownLatch latch = new CountDownLatch( 1 ); + final Lock lock = new Lock(); - try { - inFirstTransactionReloadAndModifyLockInstance( em, lock ); + final AtomicBoolean backgroundThreadHasReadNewValue = new AtomicBoolean(); - final CountDownLatch latch = new CountDownLatch( 1 ); - FutureTask future = inBackgroundThreadStartSecondTransactionAndReadLockInstance( - latch, - isolatedEntityManager - ); - - // wait with timeout on the background thread - log.debug( "testContendedPessimisticLock: wait on BG thread" ); - boolean backGroundThreadCompleted = latch.await( 3, TimeUnit.SECONDS ); - - if ( backGroundThreadCompleted ) { - // the background thread read a value. At the very least we need to assert that he did not see the - // changed value - boolean backgroundThreadHasReadNewValue = future.get(); - assertFalse( - "The background thread is not allowed to see the updated value while the first transaction has not committed yet", - backgroundThreadHasReadNewValue - ); - em.getTransaction().commit(); - } - else { - log.debug( "The background thread was blocked" ); - // commit first transaction so that background thread can continue - em.getTransaction().commit(); - boolean backgroundThreadHasReadNewValue = future.get(); - assertTrue( - "Background thread should read the new value afterQuery being unblocked", - backgroundThreadHasReadNewValue - ); - } - } - finally { - cleanup( em, isolatedEntityManager, lock ); - } - } + FutureTask bgTask = new FutureTask<>( + () -> { + try { - private void cleanup(EntityManager em, EntityManager isolatedEntityManager, Lock lock) throws InterruptedException { - // only commit the second transaction afterQuery the first one completed - isolatedEntityManager.getTransaction().commit(); - isolatedEntityManager.close(); - - // cleanup test data - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - } + doInJPA( this::entityManagerFactory, _entityManager -> { + TransactionUtil.setJdbcTimeout( _entityManager.unwrap( Session.class ) ); + log.info( "testContendedPessimisticLock: (BG) about to issue (PESSIMISTIC_READ) query against write-locked entity" ); - private FutureTask inBackgroundThreadStartSecondTransactionAndReadLockInstance(final CountDownLatch latch, final EntityManager isolatedEntityManager) { - FutureTask bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - isolatedEntityManager.getTransaction().begin(); - log.debug( - "testContendedPessimisticLock: (BG) about to issue (PESSIMISTIC_READ) query against write-locked entity" - ); - // we should block on the following read - Query query = isolatedEntityManager.createQuery( - "select L from Lock_ L where L.id < 10000 " - ); - query.setLockMode( LockModeType.PESSIMISTIC_READ ); - List resultList = query.getResultList(); - Lock lock = resultList.get( 0 ); - return lock.getName().equals( "foo" ); - } - catch ( RuntimeException e ) { - fail( "An error occurred waiting while attempting to read the entity: " + e.getMessage() ); - throw e; - } - finally { - latch.countDown(); // signal that we got the read lock + try { + // we should block on the following read + Query query = _entityManager.createQuery( + "select L from Lock_ L where L.id < 10000 " + ); + query.setLockMode( LockModeType.PESSIMISTIC_READ ); + List resultList = query.getResultList(); + Lock _lock = resultList.get( 0 ); + backgroundThreadHasReadNewValue.set( _lock.getName().equals( "foo" ) ); + } + catch ( RuntimeException e ) { + if ( !ExceptionUtil.isSqlLockTimeout( e ) ) { + fail( "An error occurred waiting while attempting to read the entity: " + e.getMessage() ); + } + backgroundThreadHasReadNewValue.set( false ); + } + } ); + } + catch (TransactionException e) { + if( !ExceptionUtil.isConnectionClose( e ) ) { + fail("Unexpected exception: " + e.getMessage()); } } + finally { + latch.countDown(); // signal that we finished + } + return backgroundThreadHasReadNewValue.get(); } ); - Thread thread = new Thread( bgTask ); - thread.setDaemon( true ); - thread.setName( "LockTest read lock" ); - thread.start(); + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); - return bgTask; - } - - private void inFirstTransactionReloadAndModifyLockInstance(EntityManager em, Lock lock) { - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - // modify and flush, but don't commit the transaction - lock.setName( "foo" ); - em.flush(); - log.debug( "testContendedPessimisticLock: got write lock" ); - } + try { + lock.setName( "testContendedPessimisticLock" ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + // modify and flush, but don't commit the transaction + _lock.setName( "foo" ); + em.flush(); + log.info( "testContendedPessimisticLock: got write lock" ); + + try { + t.start(); + boolean backGroundThreadCompleted = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + + if ( backGroundThreadCompleted ) { + // the background thread read a value. At the very least we need to assert that he did not see the + // changed value + assertFalse( + "The background thread is not allowed to see the updated value while the first transaction has not committed yet", + backgroundThreadHasReadNewValue.get() + ); + } + else { + log.debug( "The background thread was blocked" ); + assertTrue( + "Background thread should read the new value after being unblocked", + backgroundThreadHasReadNewValue.get() + ); + } + } + catch (InterruptedException e) { + Thread.interrupted(); + } + } ); + } + finally { + t.join(); // wait for background thread to finish before deleting entity - private Lock createAndPersistLockInstance(EntityManager em) { - Lock lock = new Lock(); - lock.setName( "testContendedPessimisticLock" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - return lock; + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); + } } @Test @RequiresDialect( Oracle10gDialect.class ) @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testContendedPessimisticReadLockTimeout() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask = null; final CountDownLatch latch = new CountDownLatch( 1 ); - try { - lock.setName( "testContendedPessimisticReadLockTimeout" ); + final Lock lock = new Lock(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testContendedPessimisticReadLockTimeout: got write lock" ); - - bgTask = new FutureTask( - new Callable() { - public Boolean call() { + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testContendedPessimisticReadLockTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testContendedPessimisticReadLockTimeout: (BG) read write-locked entity" ); + Map props = new HashMap(); + // timeout is in milliseconds + props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( - "testContendedPessimisticReadLockTimeout: (BG) about to read write-locked entity" - ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testContendedPessimisticReadLockTimeout: (BG) read write-locked entity" ); - Map props = new HashMap(); - // timeout is in milliseconds - props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_READ, props ); - } - catch ( LockTimeoutException e ) { - // success - log.info( - "testContendedPessimisticReadLockTimeout: (BG) got expected timeout exception" - ); - timedOut = true; - em2.getTransaction().rollback(); - return timedOut; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - throw new RuntimeException( - "Expected LockTimeoutException but got unexpected exception", e - ); - } - em2.getTransaction().commit(); - return timedOut; + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_READ, props ); } - finally { - latch.countDown(); // signal that we finished + catch ( LockTimeoutException e ) { + // success + log.info( "testContendedPessimisticReadLockTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); } - } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + throw new RuntimeException( + "Expected LockTimeoutException but got unexpected exception", e + ); + } + } ); + + return timedOut.get(); } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + + try { + lock.setName( "testContendedPessimisticReadLockTimeout" ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testContendedPessimisticReadLockTimeout: got write lock" ); + + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish before deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -543,87 +750,83 @@ public Boolean call() { @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testContendedPessimisticWriteLockTimeout() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); - try { - lock.setName( "testContendedPessimisticWriteLockTimeout" ); + final Lock lock = new Lock(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockTimeout: got write lock" ); - - bgTask = new FutureTask( - new Callable() { - public Boolean call() { + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testContendedPessimisticWriteLockTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockTimeout: (BG) read write-locked entity" ); + Map props = new HashMap(); + // timeout is in milliseconds + props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( - "testContendedPessimisticWriteLockTimeout: (BG) about to read write-locked entity" - ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockTimeout: (BG) read write-locked entity" ); - Map props = new HashMap(); - // timeout is in milliseconds - props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); - } - catch ( LockTimeoutException e ) { - // success - log.info( - "testContendedPessimisticWriteLockTimeout: (BG) got expected timeout exception" - ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - } - em2.getTransaction().commit(); - return timedOut; + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); } - finally { - latch.countDown(); // signal that we finished + catch ( LockTimeoutException e ) { + // success + log.info( "testContendedPessimisticWriteLockTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); } - } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + } ); + + return timedOut.get(); } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + + try { + lock.setName( "testContendedPessimisticWriteLockTimeout" ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockTimeout: got write lock" ); + + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish before deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -632,87 +835,83 @@ public Boolean call() { @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testContendedPessimisticWriteLockNoWait() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); - try { - lock.setName( "testContendedPessimisticWriteLockNoWait" ); + final Lock lock = new Lock(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockNoWait: got write lock" ); - - bgTask = new FutureTask( - new Callable() { - public Boolean call() { + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testContendedPessimisticWriteLockNoWait: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockNoWait: (BG) read write-locked entity" ); + Map props = new HashMap(); + // timeout of zero means no wait (for lock) + props.put( AvailableSettings.LOCK_TIMEOUT, 0 ); try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( - "testContendedPessimisticWriteLockNoWait: (BG) about to read write-locked entity" - ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockNoWait: (BG) read write-locked entity" ); - Map props = new HashMap(); - // timeout of zero means no wait (for lock) - props.put( AvailableSettings.LOCK_TIMEOUT, 0 ); - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); - } - catch ( LockTimeoutException e ) { - // success - log.info( - "testContendedPessimisticWriteLockNoWait: (BG) got expected timeout exception" - ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - } - em2.getTransaction().commit(); - return timedOut; + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); } - finally { - latch.countDown(); // signal that we finished + catch ( LockTimeoutException e ) { + // success + log.info( "testContendedPessimisticWriteLockNoWait: (BG) got expected timeout exception" ); + timedOut.set( true ); } - } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + } ); + + return timedOut.get(); } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + + try { + lock.setName( "testContendedPessimisticWriteLockNoWait" ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockNoWait: got write lock" ); + + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish before deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -721,91 +920,88 @@ public Boolean call() { @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testQueryTimeout() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); - try { - lock.setName( "testQueryTimeout" ); + final Lock lock = new Lock(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testQueryTimeout: got write lock" ); - - bgTask = new FutureTask( - new Callable() { - public Boolean call() { + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testQueryTimeout: (BG) read write-locked entity" ); try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testQueryTimeout: (BG) read write-locked entity" ); - try { - // we should block on the following read - Query query = em2.createQuery( - "select L from Lock_ L where L.id < 10000 " - ); - query.setLockMode( LockModeType.PESSIMISTIC_READ ); - query.setHint( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout - List resultList = query.getResultList(); - String name = resultList.get( 0 ).getName(); // force entity to be read - log.info( "testQueryTimeout: name read =" + name ); - } - catch ( QueryTimeoutException e ) { - // success - log.info( "testQueryTimeout: (BG) got expected timeout exception" ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( - "testQueryTimeout: Expected LockTimeoutException but got unexpected exception", - e - ); - } - em2.getTransaction().commit(); - return timedOut; + Query query = _entityManager.createQuery( + "select L from Lock_ L where L.id < 10000 " + ); + query.setLockMode( LockModeType.PESSIMISTIC_READ ); + query.setHint( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout + List resultList = query.getResultList(); + String name = resultList.get( 0 ).getName(); // force entity to be read + log.info( "testQueryTimeout: name read =" + name ); } - finally { - latch.countDown(); // signal that we finished + catch ( QueryTimeoutException e ) { + // success + log.info( "testQueryTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); } - } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + } ); + + return timedOut.get(); } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "testQueryTimeout (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + + try { + lock.setName( "testQueryTimeout" ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testQueryTimeout: got write lock" ); + + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish before deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -813,92 +1009,90 @@ public Boolean call() { @RequiresDialect( Oracle10gDialect.class ) @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testQueryTimeoutEMProps() throws Exception { - EntityManager em = getOrCreateEntityManager(); - Map queryTimeoutProps = new HashMap(); - queryTimeoutProps.put( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout (should round up) - final EntityManager em2 = createIsolatedEntityManager( queryTimeoutProps ); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); - try { - lock.setName( "testQueryTimeout" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testQueryTimeout: got write lock" ); - - bgTask = new FutureTask( - new Callable() { - public Boolean call() { + final Map timeoutProps = new HashMap(); + timeoutProps.put( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout (should round up) + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testQueryTimeout: (BG) read write-locked entity" ); try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testQueryTimeout: (BG) read write-locked entity" ); - try { - // we should block on the following read - Query query = em2.createQuery( - "select L from Lock_ L where L.id < 10000 " - ); - query.setLockMode( LockModeType.PESSIMISTIC_READ ); - List resultList = query.getResultList(); - String name = resultList.get( 0 ).getName(); // force entity to be read - log.info( "testQueryTimeout: name read =" + name ); - } - catch ( QueryTimeoutException e ) { - // success - log.info( "testQueryTimeout: (BG) got expected timeout exception" ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( - "testQueryTimeout: Expected LockTimeoutException but got unexpected exception", - e - ); - } - em2.getTransaction().commit(); - return timedOut; + Query query = _entityManager.createQuery( + "select L from Lock_ L where L.id < 10000 " + ); + query.setLockMode( LockModeType.PESSIMISTIC_READ ); + List resultList = query.getResultList(); + String name = resultList.get( 0 ).getName(); // force entity to be read + log.info( "testQueryTimeout: name read =" + name ); } - finally { - latch.countDown(); // signal that we finished + catch ( QueryTimeoutException e ) { + // success + log.info( "testQueryTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); } - } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + }, timeoutProps ); + + return timedOut.get(); } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "testQueryTimeout (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + + try { + lock.setName( "testQueryTimeout" ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testQueryTimeout: got write lock" ); + + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish before deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -907,83 +1101,84 @@ public Boolean call() { @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testLockTimeoutEMProps() throws Exception { - EntityManager em = getOrCreateEntityManager(); - Map TimeoutProps = new HashMap(); - TimeoutProps.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); // 1 second timeout - final EntityManager em2 = createIsolatedEntityManager( TimeoutProps ); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); - try { - lock.setName( "testLockTimeoutEMProps" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testLockTimeoutEMProps: got write lock" ); - - bgTask = new FutureTask( - new Callable() { - public Boolean call() { + final Map timeoutProps = new HashMap(); + timeoutProps.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); // 1 second timeout + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testLockTimeoutEMProps: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testLockTimeoutEMProps: (BG) read write-locked entity" ); + // em2 already has AvailableSettings.LOCK_TIMEOUT of 1 second applied try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( "testLockTimeoutEMProps: (BG) about to read write-locked entity" ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testLockTimeoutEMProps: (BG) read write-locked entity" ); - // em2 already has AvailableSettings.LOCK_TIMEOUT of 1 second applied - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE ); - } - catch ( LockTimeoutException e ) { - // success - log.info( "testLockTimeoutEMProps: (BG) got expected timeout exception" ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - } - em2.getTransaction().commit(); - return timedOut; + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_WRITE ); } - finally { - latch.countDown(); // signal that we finished + catch ( LockTimeoutException e ) { + // success + log.info( "testLockTimeoutEMProps: (BG) got expected timeout exception" ); + timedOut.set( true ); } - } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + }, timeoutProps ); + + return timedOut.get(); } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + + try { + lock.setName( "testLockTimeoutEMProps" ); + + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testLockTimeoutEMProps: got write lock" ); + + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish before deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } 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 new file mode 100644 index 000000000000..304839017aea --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java @@ -0,0 +1,120 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.lock; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.LockModeType; + +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.transaction.TransactionUtil; +import org.hibernate.testing.util.ExceptionUtil; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +public class StatementIsClosedAfterALockExceptionTest extends BaseEntityManagerFunctionalTestCase { + + private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( false, false ); + + private Integer lockId; + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, + CONNECTION_PROVIDER + ); + return config; + } + + @Before + public void setUp() { + lockId = TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + Lock lock = new Lock(); + lock.setName( "name" ); + entityManager.persist( lock ); + return lock.getId(); + } ); + } + + @Override + public void releaseResources() { + super.releaseResources(); + CONNECTION_PROVIDER.stop(); + } + + @Test(timeout = 1000 * 30) //30 seconds + @TestForIssue(jiraKey = "HHH-11617") + public void testStatementIsClosed() { + + TransactionUtil.doInJPA( this::entityManagerFactory, em1 -> { + + Map properties = new HashMap<>(); + properties.put( org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT, 0L ); + Lock lock2 = em1.find( Lock.class, lockId, LockModeType.PESSIMISTIC_WRITE, properties ); + assertEquals( + "lock mode should be PESSIMISTIC_WRITE ", + LockModeType.PESSIMISTIC_WRITE, + em1.getLockMode( lock2 ) + ); + + TransactionUtil.doInJPA( this::entityManagerFactory, em2 -> { + TransactionUtil.setJdbcTimeout( em2.unwrap( Session.class ) ); + try { + em2.find( Lock.class, lockId, LockModeType.PESSIMISTIC_WRITE, properties ); + fail( "Exception should be thrown" ); + } + catch (Exception lte) { + if( !ExceptionUtil.isSqlLockTimeout( lte )) { + fail("Should have thrown a Lock timeout exception"); + } + } + finally { + try { + for ( PreparedStatement statement : CONNECTION_PROVIDER.getPreparedStatements() ) { + assertThat( + "A SQL Statement was not closed : " + statement.toString(), + statement.isClosed(), + is( true ) + ); + } + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } + } ); + + } ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Lock.class, + UnversionedLock.class + }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/UpgradeLockTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/UpgradeLockTest.java index de819c7d177c..eb069b9cfb4c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/UpgradeLockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/UpgradeLockTest.java @@ -25,7 +25,6 @@ * @author Scott Marlow */ public class UpgradeLockTest extends BaseEntityManagerFunctionalTestCase { - private static final Logger log = Logger.getLogger( UpgradeLockTest.class ); /** * Initially in tx1, get a LockModeType.READ and upgrade to LockModeType.OPTIMISTIC_FORCE_INCREMENT. diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/DefaultCascadeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/DefaultCascadeTest.java new file mode 100644 index 000000000000..1b6064f26764 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/DefaultCascadeTest.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.mapping; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class DefaultCascadeTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void testCascadePersist() { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent parent = new Parent(); + Child child = new Child(); + child.parent = parent; + + entityManager.persist( child ); + } ); + } + + @Override + public String[] getEjb3DD() { + return new String[] { + "org/hibernate/jpa/test/mapping/orm.xml" + }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Entity + @Table(name = "Parent") + public static class Parent { + + @Id + @GeneratedValue + private Integer id; + } + + @Entity + @Table(name = "Child") + public static class Child { + + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private Parent parent; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java new file mode 100644 index 000000000000..dfba5e9e5c9f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java @@ -0,0 +1,318 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.mapping; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.persistence.AssociationOverride; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Type; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.criteria.components.Alias; +import org.hibernate.jpa.test.criteria.components.Client; +import org.hibernate.jpa.test.criteria.components.Client_; +import org.hibernate.jpa.test.criteria.components.Name_; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class NestedEmbeddableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Categorization.class, + Category.class, + CcmObject.class, + Domain.class + }; + } + + @Test + public void test() { + + } + + @Entity + @Table(name = "CATEGORIZATIONS") + public static class Categorization implements Serializable { + + @Id + @Column(name = "CATEGORIZATION_ID") + @GeneratedValue(strategy = GenerationType.AUTO) + private long categorizationId; + + @ManyToOne + @JoinColumn(name = "CATEGORY_ID") + private Category category; + + @ManyToOne + @JoinColumn(name = "OBJECT_ID") + private CcmObject categorizedObject; + + public long getCategorizationId() { + return categorizationId; + } + + public void setCategorizationId(long categorizationId) { + this.categorizationId = categorizationId; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public CcmObject getCategorizedObject() { + return categorizedObject; + } + + public void setCategorizedObject(CcmObject categorizedObject) { + this.categorizedObject = categorizedObject; + } + } + + @Entity + @Table(name = "CATEGORIES") + public static class Category extends CcmObject implements Serializable { + + private static final long serialVersionUID = 1L; + + @Column(name = "NAME", nullable = false) + private String name; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "CATEGORY_TITLES", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")} + )) + private LocalizedString title; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "CATEGORY_DESCRIPTIONS", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")} + )) + private LocalizedString description; + + @OneToMany(mappedBy = "category", fetch = FetchType.LAZY) + @OrderBy("objectOrder ASC") + private List objects; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + + @Entity + @Table(name = "CCM_OBJECTS") + @Inheritance(strategy = InheritanceType.JOINED) + public static class CcmObject implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "OBJECT_ID") + @GeneratedValue(strategy = GenerationType.AUTO) + private long objectId; + + @Column(name = "DISPLAY_NAME") + private String displayName; + + @OneToMany(mappedBy = "categorizedObject", fetch = FetchType.LAZY) + @OrderBy("categoryOrder ASC") + private List categories; + + public long getObjectId() { + return objectId; + } + + public void setObjectId(long objectId) { + this.objectId = objectId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + } + + @Entity + @Table(name = "CATEGORY_DOMAINS") + public static class Domain extends CcmObject { + + private static final long serialVersionUID = 1L; + + @Column(name = "DOMAIN_KEY", nullable = false, unique = true, length = 255) + private String domainKey; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "DOMAIN_TITLES", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")})) + private LocalizedString title; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "DOMAIN_DESCRIPTIONS", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")})) + private LocalizedString description; + + public String getDomainKey() { + return domainKey; + } + + public void setDomainKey(String domainKey) { + this.domainKey = domainKey; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + + @Embeddable + public static class LocalizedString implements Serializable { + + private static final long serialVersionUID = 1L; + + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "LOCALE") + @Column(name = "LOCALIZED_VALUE") + @Lob + @Type(type = "org.hibernate.type.TextType") + private Map values; + + public LocalizedString() { + values = new HashMap<>(); + } + + public Map getValues() { + if (values == null) { + return null; + } else { + return Collections.unmodifiableMap( values); + } + } + + protected void setValues(final Map values) { + if (values == null) { + this.values = new HashMap<>(); + } else { + this.values = new HashMap<>(values); + } + } + + public String getValue() { + return getValue(Locale.getDefault()); + } + + public String getValue(final Locale locale) { + return values.get(locale); + } + + public void addValue(final Locale locale, final String value) { + values.put(locale, value); + } + + public void removeValue(final Locale locale) { + values.remove(locale); + } + + public boolean hasValue(final Locale locale) { + return values.containsKey(locale); + } + + public Set getAvailableLocales() { + return values.keySet(); + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyIndexColumnTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyIndexColumnTest.java new file mode 100644 index 000000000000..e24e6559dd69 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyIndexColumnTest.java @@ -0,0 +1,110 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.mapping; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.IndexColumn; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-1268") +public class UnidirectionalOneToManyIndexColumnTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + @Test + public void testRemovingAChild() { + int parentId = TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Parent parent = new Parent(); + parent.getChildren().add( new Child() ); + parent.getChildren().add( new Child() ); + parent.getChildren().add( new Child() ); + session.persist( parent ); + return parent.getId(); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Parent parent = session.find( Parent.class, parentId ); + List children = parent.getChildren(); + assertThat( children.size(), is( 3 ) ); + children.remove( 0 ); + session.persist( parent ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Parent parent = session.find( Parent.class, parentId ); + List children = parent.getChildren(); + assertThat( children.size(), is( 2 ) ); + } ); + } + + + @Entity + @Table(name = "PARENT") + public static class Parent { + + @Id + @GeneratedValue + private int id; + + @OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL) + @IndexColumn(name = "position") + private List children = new ArrayList<>(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + } + + @Entity + @Table(name = "CHILD") + public static class Child { + @Id + @GeneratedValue + private int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyOrderColumnTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyOrderColumnTest.java new file mode 100644 index 000000000000..d75e623b33b0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyOrderColumnTest.java @@ -0,0 +1,183 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.mapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; +import javax.persistence.Table; + +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; + +@TestForIssue(jiraKey = "HHH-11587") +public class UnidirectionalOneToManyOrderColumnTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void testRemovingAnElement() { + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = new ParentData(); + entityManager.persist( parent ); + + String[] childrenStr = new String[] {"One", "Two", "Three"}; + for ( String str : childrenStr ) { + ChildData child = new ChildData( str ); + entityManager.persist( child ); + parent.getChildren().add( child ); + } + + entityManager.flush(); + + List children = parent.getChildren(); + children.remove( 0 ); + } ); + } + + @Test + public void testAddingAnElement() { + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = new ParentData(); + entityManager.persist( parent ); + + String[] childrenStr = new String[] {"One", "Two", "Three"}; + for ( String str : childrenStr ) { + ChildData child = new ChildData( str ); + entityManager.persist( child ); + parent.getChildren().add( child ); + } + + entityManager.flush(); + + List children = parent.getChildren(); + children.add( 1, new ChildData( "Another" ) ); + } ); + } + + @Test + public void testRemovingAndAddingAnElement() { + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = new ParentData(); + entityManager.persist( parent ); + + String[] childrenStr = new String[] {"One", "Two", "Three"}; + for ( String str : childrenStr ) { + ChildData child = new ChildData( str ); + entityManager.persist( child ); + parent.getChildren().add( child ); + } + + entityManager.flush(); + + List children = parent.getChildren(); + children.remove( 0 ); + children.add( 1, new ChildData( "Another" ) ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = entityManager.find( ParentData.class, 1L ); + List childIds = parent.getChildren().stream().map( ChildData::toString ).collect( Collectors.toList() ); + int i = 0; + assertEquals( "Two", childIds.get( i++ )); + assertEquals( "Another", childIds.get( i++ )); + assertEquals( "Three", childIds.get( i++ )); + } ); + } + + @Test + public void testRemovingOneAndAddingTwoElements() { + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = new ParentData(); + entityManager.persist( parent ); + + String[] childrenStr = new String[] {"One", "Two", "Three"}; + for ( String str : childrenStr ) { + ChildData child = new ChildData( str ); + entityManager.persist( child ); + parent.getChildren().add( child ); + } + + entityManager.flush(); + + List children = parent.getChildren(); + children.remove( 0 ); + children.add( 1, new ChildData( "Another" ) ); + children.add( new ChildData( "Another Another" ) ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = entityManager.find( ParentData.class, 1L ); + List childIds = parent.getChildren().stream().map( ChildData::toString ).collect( Collectors.toList() ); + int i = 0; + assertEquals( "Two", childIds.get( i++ )); + assertEquals( "Another", childIds.get( i++ )); + assertEquals( "Three", childIds.get( i++ )); + assertEquals( "Another Another", childIds.get( i++ )); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + ParentData.class, + ChildData.class + }; + } + + @Entity(name = "ParentData") + @Table(name = "PARENT") + public static class ParentData { + @Id + @GeneratedValue + long id; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @OrderColumn(name = "listOrder") + private List children = new ArrayList<>(); + + public List getChildren() { + return children; + } + } + + @Entity(name = "ChildData") + @Table(name = "CHILD") + public static class ChildData { + @Id + @GeneratedValue + long id; + + String childId; + + public ChildData() { + } + + public ChildData(String id) { + childId = id; + } + + @Override + public String toString() { + return childId; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyUniqueConstraintOrderColumnTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyUniqueConstraintOrderColumnTest.java new file mode 100644 index 000000000000..adfcb1b73ba7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/UnidirectionalOneToManyUniqueConstraintOrderColumnTest.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.mapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@TestForIssue(jiraKey = "HHH-1268") +public class UnidirectionalOneToManyUniqueConstraintOrderColumnTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = new ParentData(); + parent.id = 1L; + entityManager.persist( parent ); + + String[] childrenStr = new String[] {"One", "Two", "Three"}; + for ( String str : childrenStr ) { + ChildData child = new ChildData( str ); + parent.getChildren().add( child ); + } + } ); + } + + @Test + @FailureExpected( jiraKey = "HHH-1268" ) + public void testRemovingAnElement() { + doInJPA( this::entityManagerFactory, entityManager -> { + ParentData parent = entityManager.find( ParentData.class, 1L ); + + List children = parent.getChildren(); + children.remove( 0 ); + } ); + } + + @Test + @FailureExpected( jiraKey = "HHH-1268" ) + public void testAddingAnElement() { + doInJPA( this::entityManagerFactory, entityManager -> { + ParentData parent = entityManager.find( ParentData.class, 1L ); + + List children = parent.getChildren(); + children.add( 1, new ChildData( "Another" ) ); + } ); + } + + @Test + @FailureExpected( jiraKey = "HHH-1268" ) + public void testRemovingAndAddingAnElement() { + doInJPA( this::entityManagerFactory, entityManager -> { + ParentData parent = entityManager.find( ParentData.class, 1L ); + + List children = parent.getChildren(); + children.remove( 0 ); + children.add( 1, new ChildData( "Another" ) ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + ParentData parent = entityManager.find( ParentData.class, 1L ); + + List childIds = parent.getChildren() + .stream() + .map( ChildData::toString ) + .collect( Collectors.toList() ); + + int i = 0; + + assertEquals( "Two", childIds.get( i++ )); + assertEquals( "Another", childIds.get( i++ )); + assertEquals( "Three", childIds.get( i )); + } ); + } + + @Test + @FailureExpected( jiraKey = "HHH-1268" ) + public void testRemovingOneAndAddingTwoElements() { + doInJPA( this::entityManagerFactory, entityManager -> { + ParentData parent = entityManager.find( ParentData.class, 1L ); + + List children = parent.getChildren(); + children.remove( 0 ); + children.add( 1, new ChildData( "Another" ) ); + children.add( new ChildData( "Another Another" ) ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + ParentData parent = entityManager.find( ParentData.class, 1L ); + List childIds = parent.getChildren() + .stream() + .map( ChildData::toString ) + .collect( Collectors.toList() ); + + int i = 0; + + assertEquals( "Two", childIds.get( i++ ) ); + assertEquals( "Another", childIds.get( i++ ) ); + assertEquals( "Three", childIds.get( i++ ) ); + assertEquals( "Another Another", childIds.get( i ) ); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + ParentData.class, + ChildData.class + }; + } + + @Entity(name = "ParentData") + public static class ParentData { + @Id + long id; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "parentId", nullable = false) + @OrderColumn(name = "listOrder") + private List children = new ArrayList<>(); + + public List getChildren() { + return children; + } + } + + @Entity(name = "ChildData") + @Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "parentId", "listOrder" }) }) + public static class ChildData { + @Id + @GeneratedValue + long id; + + String childId; + + public ChildData() { + } + + public ChildData(String id) { + childId = id; + } + + @Override + public String toString() { + return childId; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metadata/MetadataTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metadata/MetadataTest.java index 908c5aead7ef..a2831032c1a0 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/metadata/MetadataTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metadata/MetadataTest.java @@ -94,7 +94,7 @@ public void testBuildingMetamodelWithParameterizedCollection() { .addAnnotatedClass( WithGenericCollection.class ) .buildMetadata(); SessionFactoryImplementor sfi = (SessionFactoryImplementor) metadata.buildSessionFactory(); - MetamodelImpl metamodel = new MetamodelImpl( sfi ); + MetamodelImpl metamodel = new MetamodelImpl( sfi, ( (MetadataImplementor) metadata ).getTypeConfiguration() ); metamodel.initialize( (MetadataImplementor) metadata, JpaMetaModelPopulationSetting.IGNORE_UNSUPPORTED ); sfi.close(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/attribute/MappedSuperclassWithAttributesTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/attribute/MappedSuperclassWithAttributesTest.java index a32c6576f7d8..ba056b2522eb 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/attribute/MappedSuperclassWithAttributesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/attribute/MappedSuperclassWithAttributesTest.java @@ -6,18 +6,15 @@ */ package org.hibernate.jpa.test.metagen.mappedsuperclass.attribute; -import javax.persistence.EntityManagerFactory; import java.util.Arrays; +import javax.persistence.EntityManagerFactory; -import org.hibernate.jpa.test.TestingEntityManagerFactoryGenerator; -import org.hibernate.jpa.test.metagen.mappedsuperclass.attribute.AbstractNameable_; -import org.hibernate.jpa.test.metagen.mappedsuperclass.attribute.Product_; import org.hibernate.jpa.AvailableSettings; - -import org.junit.Test; +import org.hibernate.jpa.test.TestingEntityManagerFactoryGenerator; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; import static org.junit.Assert.assertNotNull; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/AbstractProduct.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/AbstractProduct.java new file mode 100644 index 000000000000..a2d95c315220 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/AbstractProduct.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 . + */ +package org.hibernate.jpa.test.metagen.mappedsuperclass.overridden; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Oliver Breidenbach + */ +@MappedSuperclass +@Access(AccessType.PROPERTY) +public abstract class AbstractProduct { + private Long id; + private String name; + + protected AbstractProduct() { + } + + protected AbstractProduct(String name) { + this.name = name; + } + @Id + public Long getId() { + return id; + } + + private void setId(Long id) { + this.id = id; + } + @Column(name = "name") + 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/metagen/mappedsuperclass/overridden/MappedSuperclassWithOverriddenAttributeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/MappedSuperclassWithOverriddenAttributeTest.java new file mode 100644 index 000000000000..d3d4fed79dc0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/MappedSuperclassWithOverriddenAttributeTest.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 . + */ +package org.hibernate.jpa.test.metagen.mappedsuperclass.overridden; + +import java.util.Arrays; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.test.TestingEntityManagerFactoryGenerator; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * @author Oliver Breidenbach + */ +@TestForIssue(jiraKey = "HHH-11078") +public class MappedSuperclassWithOverriddenAttributeTest + extends BaseUnitTestCase { + + @Test + @FailureExpected(jiraKey = "HHH-11078") + public void testStaticMetamodelOverridden() { + EntityManagerFactory emf = TestingEntityManagerFactoryGenerator.generateEntityManagerFactory( + AvailableSettings.LOADED_CLASSES, + Arrays.asList( Product2.class ) + ); + try { + assertNotNull( + "'Product1_.overridenName' should not be null)", + Product1_.overridenName + ); + + assertNotNull( + "'Product2_.overridenName' should not be null)", + Product2_.overridenName + ); // is null + } + finally { + emf.close(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/Product1.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/Product1.java new file mode 100644 index 000000000000..80038c24d0ed --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/Product1.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 . + */ +package org.hibernate.jpa.test.metagen.mappedsuperclass.overridden; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Oliver Breidenbach + */ +@Entity +@Table(name = "product") +@Access(AccessType.PROPERTY) +public class Product1 extends AbstractProduct { + + private String overridenName; + + public Product1() { + } + + public Product1(String name) { + super( name ); + } + + + @Column(name = "overridenName") + public String getOverridenName() { + return overridenName; + } + + public void setOverridenName(String overridenName) { + this.overridenName = overridenName; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/Product2.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/Product2.java new file mode 100644 index 000000000000..571eb87f9073 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metagen/mappedsuperclass/overridden/Product2.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 . + */ +package org.hibernate.jpa.test.metagen.mappedsuperclass.overridden; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; + +/** + * @author Oliver Breidenbach + */ +@Entity +@Access(AccessType.PROPERTY) +public class Product2 extends Product1 { + + @Column(name = "overridenName"/*, insertable = false, updatable = false*/) + public String getOverridenName() { + return super.getOverridenName(); + } + + public void setOverridenName(String overridenName) { + super.setOverridenName(overridenName); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java new file mode 100644 index 000000000000..6f487f2b8219 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java @@ -0,0 +1,183 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import java.io.Serializable; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.metamodel.EmbeddableType; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.Metamodel; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public abstract class AbstractJpaMetamodelPopulationTest extends BaseEntityManagerFunctionalTestCase { + @Entity(name = "SimpleAnnotatedEntity") + public static class SimpleAnnotatedEntity { + @Id + @GeneratedValue + private Integer id; + private String data; + } + + @Entity(name = "CompositeIdAnnotatedEntity") + public static class CompositeIdAnnotatedEntity { + @EmbeddedId + private CompositeIdId id; + private String data; + } + + @Embeddable + public static class CompositeIdId implements Serializable { + private Integer id1; + private Integer id2; + } + + @Override + protected String[] getMappings() { + return new String[] { + "org/hibernate/jpa/test/metamodel/SimpleEntity.hbm.xml", + "org/hibernate/jpa/test/metamodel/CompositeIdEntity.hbm.xml", + "org/hibernate/jpa/test/metamodel/CompositeId2Entity.hbm.xml" + }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleAnnotatedEntity.class, CompositeIdAnnotatedEntity.class }; + } + + protected abstract String getJpaMetamodelPopulationValue(); + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( AvailableSettings.JPA_METAMODEL_POPULATION, getJpaMetamodelPopulationValue() ); + } + + @Test + public void testMetamodel() { + EntityManager entityManager = getOrCreateEntityManager(); + try { + final Metamodel metamodel = entityManager.getMetamodel(); + + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "disabled" ) ) { + // In 5.1, metamodel returned null. + // In 5.2+, metamodel erturned as a non-null instance. + assertNotNull( metamodel ); + assertEquals( 0, metamodel.getManagedTypes().size() ); + assertEquals( 0, metamodel.getEntities().size() ); + assertEquals( 0, metamodel.getEmbeddables().size() ); + return; + } + + assertNotNull( metamodel ); + + assertManagedTypes( metamodel ); + assertEntityTypes( metamodel ); + assertEmbeddableTypes( metamodel ); + } + finally { + entityManager.close(); + } + } + + private void assertManagedTypes(Metamodel metamodel) { + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // All managed types should be included, dynamic-map and annotation based. + // EntityType(SimpleAnnotatedEntity) + // EntityType(CompositeIdAnnotatedEntity) + // EntityType(null) - SimpleEntity (dynamic-map entity) + // EntityType(null) - CompositeIdEntity (dynamic-map entity) + // EntityType(null) - CompositeId2Entity (dynamic-map entity) + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // EmbeddableType(Map) - CompositeIdEntity's (dynamic-map entity) identifier + // EmbeddableType(Map) - CompositeId2Entity's (dynamic-map entity) identifier + assertEquals( 8, metamodel.getManagedTypes().size() ); + } + else { + // When ignoreUnsupported is used, any managed-type that refers to a dynamic-map entity type + // or a managed type that is owned by said dynamic-map entity type should be excluded. + // Therefore this collection should only include 3 elements + // EntityType(SimpleAnnotated) + // EntityType(CompositeIdAnnotatedEntity) + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + assertEquals( 3, metamodel.getManagedTypes().size() ); + } + } + + private void assertEntityTypes(Metamodel metamodel) { + final Set entityNames = metamodel.getEntities().stream() + .map( EntityType::getName ) + .collect( Collectors.toSet() ); + + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // Should include all entity types + // EntityType(SimpleAnnotatedEntity) + // EntityType(CompositeIdAnnotatedEntity) + // EntityType(null) - SimpleEntity (dynamic-map entity) + // EntityType(null) - CompositeIdEntity (dynamic-map entity) + // EntityType(null) - CompositeId2Entity (dynamic-map entity) + assertEquals( 5, entityNames.size() ); + assertTrue( entityNames.contains( "SimpleAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "SimpleEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdEntity" ) ); + assertTrue( entityNames.contains( "CompositeId2Entity" ) ); + } + else { + // In 5.1, this returns 5 elements + // CompositeIdAnnotatedEntity + // SimpleAnnotatedEntity + // SimpleEntity <-- this should not exist since its entity-type is filtered + // CompositeIdEntity <-- this should not exist since its entity-type is filtered + // CompsoiteId2Entity <-- this should not exist since its entity-type is filtered + // + // In 5.2, this returns 5 elements too. + // In 5.3, this returns 5 elements too. + assertEquals( 2, entityNames.size() ); + assertTrue( entityNames.contains( "SimpleAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdAnnotatedEntity" ) ); + } + } + + private void assertEmbeddableTypes(Metamodel metamodel) { + final Set> embeddableTypes = metamodel.getEmbeddables(); + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // EmbeddableType(Map) - CompositeIdEntity (dynamic-map entity) identifier + // EmbeddableType(Map) - CompositeId2Entity (dynamic-map entity) identifier + assertEquals( 3, embeddableTypes.size() ); + } + else { + // This should return only 1 element + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // The dynamic-map entity type's composite-id embeddable types should be excluded. + assertEquals( 1, embeddableTypes.size() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/BaseEmbeddedEntity.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/BaseEmbeddedEntity.java new file mode 100644 index 000000000000..0180a2cde150 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/BaseEmbeddedEntity.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 . + */ +package org.hibernate.jpa.test.metamodel; + +import java.io.Serializable; +import javax.persistence.EmbeddedId; +import javax.persistence.MappedSuperclass; + +/** + * @author Christian Beikov + */ +@MappedSuperclass +public abstract class BaseEmbeddedEntity implements Serializable { + + private I id; + + public BaseEmbeddedEntity() { + } + + public BaseEmbeddedEntity(I id) { + this.id = id; + } + + @EmbeddedId + public I getId() { + return id; + } + + public void setId(I id) { + this.id = id; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 47 * hash + (this.id != null ? this.id.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BaseEmbeddedEntity other = (BaseEmbeddedEntity) obj; + if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) { + return false; + } + return true; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/GenericsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/GenericsTest.java new file mode 100644 index 000000000000..a47df0bef2b0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/GenericsTest.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import javax.persistence.EntityManager; +import javax.persistence.metamodel.EmbeddableType; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.SingularAttribute; + +import static org.junit.Assert.*; + +/** + * @author Christian Beikov + */ +@TestForIssue( jiraKey = "HHH-11540" ) +public class GenericsTest extends BaseEntityManagerFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + PersonId.class + }; + } + + @Test + public void testEmbeddableTypeExists() { + EntityManager em = getOrCreateEntityManager(); + EmbeddableType idType = em.getMetamodel().embeddable( PersonId.class) ; + assertNotNull( idType ); + em.close(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java new file mode 100644 index 000000000000..f8e9ee2c378e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java @@ -0,0 +1,20 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelDisabledPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "disabled"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java new file mode 100644 index 000000000000..fa1eed102447 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java @@ -0,0 +1,20 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelEnabledPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "enabled"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java new file mode 100644 index 000000000000..b54d40d7bb72 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java @@ -0,0 +1,20 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelIgnoreUnsupportedPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "ignoreUnsupported"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Person.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Person.java new file mode 100644 index 000000000000..6940fd34ffbd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Person.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import javax.persistence.Entity; + +/** + * @author Christian Beikov + */ +@Entity +public class Person extends BaseEmbeddedEntity { + + private String firstName; + private String lastName; + + public Person() { + } + + public Person(PersonId id, String firstName, String lastName) { + super(id); + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/PersonId.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/PersonId.java new file mode 100644 index 000000000000..7a92d74767a4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/PersonId.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.jpa.test.metamodel; + +import java.io.Serializable; +import javax.persistence.Embeddable; + +/** + * @author Christian Beikov + */ +@Embeddable +public class PersonId implements Serializable { + + private String ssn; + private String name; + + public PersonId() { + } + + public PersonId(String ssn, String name) { + this.ssn = ssn; + this.name = name; + } + + public String getSsn() { + return ssn; + } + + public void setSsn(String ssn) { + this.ssn = ssn; + } + + public String getName() { + return name; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 67 * hash + (this.ssn != null ? this.ssn.hashCode() : 0); + hash = 67 * hash + (this.name != null ? this.name.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PersonId other = (PersonId) obj; + if ((this.ssn == null) ? (other.ssn != null) : !this.ssn.equals(other.ssn)) { + return false; + } + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + return false; + } + return true; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/ContainsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/ContainsTest.java new file mode 100644 index 000000000000..bfb82e230bd9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/ContainsTest.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 . + */ +package org.hibernate.jpa.test.ops; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class ContainsTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + }; + } + + @Test + public void testLifecycle() { + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.id = 1L; + person.name = "John Doe"; + entityManager.persist( person ); + + assertTrue(entityManager.contains( person )); + + return person; + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + assertFalse(entityManager.contains( _person )); + + Person person = entityManager.find( Person.class, 1L ); + + assertTrue(entityManager.contains( person )); + } ); + } + + @Entity(name = "PersonEntity") + public static class Person { + + @Id + private Long id; + + private String name; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadJpaComplianceDifferentSessionsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadJpaComplianceDifferentSessionsTest.java new file mode 100644 index 000000000000..5b512f69d752 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadJpaComplianceDifferentSessionsTest.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.jpa.test.ops; + +import java.util.Map; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.boot.spi.Bootstrap; +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 Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12273" ) +public class GetLoadJpaComplianceDifferentSessionsTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Workload.class, + }; + } + + @Override + @SuppressWarnings( "unchecked" ) + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.JPA_PROXY_COMPLIANCE, Boolean.FALSE.toString() ); + } + + @Test + @TestForIssue( jiraKey = "HHH-9856" ) + public void testReattachEntityToSessionWithJpaComplianceProxy() { + final Integer _workloadId = doInJPA( this::entityManagerFactory, entityManager -> { + Workload workload = new Workload(); + workload.load = 123; + workload.name = "Package"; + entityManager.persist( workload ); + + return workload.getId(); + } ); + + Workload _workload = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.getReference( Workload.class, _workloadId ); + } ); + + Map settings = buildSettings(); + settings.put( AvailableSettings.JPA_PROXY_COMPLIANCE, Boolean.TRUE.toString() ); + settings.put( AvailableSettings.HBM2DDL_AUTO, "none" ); + + EntityManagerFactory newEntityManagerFactory = Bootstrap + .getEntityManagerFactoryBuilder( + new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + settings ) + .build(); + + try { + doInJPA( () -> newEntityManagerFactory, entityManager -> { + entityManager.unwrap( Session.class ).update( _workload ); + + _workload.getId(); + }); + } + finally { + newEntityManagerFactory.close(); + } + + assertEquals( "Package", _workload.getName() ); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadJpaComplianceTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadJpaComplianceTest.java new file mode 100644 index 000000000000..0dc1934ec21d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadJpaComplianceTest.java @@ -0,0 +1,153 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.ops; + +import java.util.Map; +import javax.persistence.EntityManager; +import javax.persistence.EntityNotFoundException; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Gavin King + * @author Hardy Ferentschik + */ +public class GetLoadJpaComplianceTest extends BaseEntityManagerFunctionalTestCase { + + @Override + @SuppressWarnings( {"unchecked"}) + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.JPA_PROXY_COMPLIANCE, true ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testLoadIdNotFound_FieldBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + Session s = (Session) em.getDelegate(); + + assertNull( s.get( Workload.class, 999 ) ); + + Workload proxy = s.load( Workload.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + fail( "Should have failed because there is no Employee Entity with id == 999" ); + } + catch (EntityNotFoundException ex) { + // expected + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testReferenceIdNotFound_FieldBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + + assertNull( em.find( Workload.class, 999 ) ); + + Workload proxy = em.getReference( Workload.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + fail( "Should have failed because there is no Workload Entity with id == 999" ); + } + catch (EntityNotFoundException ex) { + // expected + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testLoadIdNotFound_PropertyBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + Session s = (Session) em.getDelegate(); + + assertNull( s.get( Employee.class, 999 ) ); + + Employee proxy = s.load( Employee.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + fail( "Should have failed because there is no Employee Entity with id == 999" ); + } + catch (EntityNotFoundException ex) { + // expected + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testReferenceIdNotFound_PropertyBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + + assertNull( em.find( Employee.class, 999 ) ); + + Employee proxy = em.getReference( Employee.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + fail( "Should have failed because there is no Employee Entity with id == 999" ); + } + catch (EntityNotFoundException ex) { + // expected + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + + @Override + protected String[] getMappings() { + return new String[] { + "org/hibernate/jpa/test/ops/Node.hbm.xml", + "org/hibernate/jpa/test/ops/Employer.hbm.xml" + }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Workload.class }; + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadTest.java index 4a39a2e831cc..70478e5ea79c 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/GetLoadTest.java @@ -8,12 +8,16 @@ import java.util.Map; import javax.persistence.EntityManager; +import javax.persistence.EntityNotFoundException; import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -29,7 +33,8 @@ */ public class GetLoadTest extends BaseEntityManagerFunctionalTestCase { @Test - public void testGetLoad() { + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") + public void testGet() { clearCounts(); EntityManager em = getOrCreateEntityManager(); @@ -62,22 +67,43 @@ public void testGetLoad() { em = getOrCreateEntityManager(); em.getTransaction().begin(); s = ( Session ) em.getDelegate(); - emp = ( Employer ) s.load( Employer.class, emp.getId() ); - emp.getId(); - assertFalse( Hibernate.isInitialized( emp ) ); - node = ( Node ) s.load( Node.class, node.getName() ); - assertEquals( node.getName(), "foo" ); - assertFalse( Hibernate.isInitialized( node ) ); + emp = ( Employer ) s.get( Employer.class.getName(), emp.getId() ); + assertTrue( Hibernate.isInitialized( emp ) ); + node = ( Node ) s.get( Node.class.getName(), node.getName() ); + assertTrue( Hibernate.isInitialized( node ) ); + em.getTransaction().commit(); + em.close(); + + assertFetchCount( 0 ); + } + + @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") + public void testLoad() { + clearCounts(); + + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Session s = ( Session ) em.getDelegate(); + + Employer emp = new Employer(); + s.persist( emp ); + Node node = new Node( "foo" ); + Node parent = new Node( "bar" ); + parent.addChild( node ); + s.persist( parent ); em.getTransaction().commit(); em.close(); em = getOrCreateEntityManager(); em.getTransaction().begin(); s = ( Session ) em.getDelegate(); - emp = ( Employer ) s.get( Employer.class.getName(), emp.getId() ); - assertTrue( Hibernate.isInitialized( emp ) ); - node = ( Node ) s.get( Node.class.getName(), node.getName() ); - assertTrue( Hibernate.isInitialized( node ) ); + emp = ( Employer ) s.load( Employer.class, emp.getId() ); + emp.getId(); + assertFalse( Hibernate.isInitialized( emp ) ); + node = ( Node ) s.load( Node.class, node.getName() ); + assertEquals( node.getName(), "foo" ); + assertFalse( Hibernate.isInitialized( node ) ); em.getTransaction().commit(); em.close(); @@ -123,6 +149,116 @@ public void testNonEntity() { } } + @Test + @TestForIssue( jiraKey = "HHH-11838") + public void testLoadGetId() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Session s = ( Session ) em.getDelegate(); + Workload workload = new Workload(); + s.persist(workload); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + s = ( Session ) em.getDelegate(); + + Workload proxy = s.load(Workload.class, workload.id); + proxy.getId(); + + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getName(); + + assertTrue( Hibernate.isInitialized( proxy ) ); + + em.getTransaction().commit(); + em.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testLoadIdNotFound_FieldBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + Session s = (Session) em.getDelegate(); + + assertNull( s.get( Workload.class, 999 ) ); + + Workload proxy = s.load( Workload.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testReferenceIdNotFound_FieldBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + + assertNull( em.find( Workload.class, 999 ) ); + + Workload proxy = em.getReference( Workload.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testLoadIdNotFound_PropertyBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + Session s = (Session) em.getDelegate(); + + assertNull( s.get( Employee.class, 999 ) ); + + Employee proxy = s.load( Employee.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-12034") + public void testReferenceIdNotFound_PropertyBasedAccess() { + EntityManager em = getOrCreateEntityManager(); + try { + em.getTransaction().begin(); + + assertNull( em.find( Employee.class, 999 ) ); + + Employee proxy = em.getReference( Employee.class, 999 ); + assertFalse( Hibernate.isInitialized( proxy ) ); + + proxy.getId(); + } + finally { + em.getTransaction().rollback(); + em.close(); + } + } + @Override @SuppressWarnings( {"unchecked"}) protected void addConfigOptions(Map options) { @@ -137,5 +273,10 @@ protected String[] getMappings() { "org/hibernate/jpa/test/ops/Employer.hbm.xml" }; } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Workload.class }; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/PersistTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/PersistTest.java index 46e9256628a9..fbc9ce05748c 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/PersistTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/PersistTest.java @@ -17,8 +17,9 @@ import javax.persistence.RollbackException; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -164,6 +165,7 @@ public void testCreateExceptionWithGeneratedId() { } @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") public void testBasic() throws Exception { EntityManager em = getOrCreateEntityManager(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/Workload.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/Workload.java index 6b0c449357ae..ddaed2ca3b55 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/Workload.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/Workload.java @@ -23,4 +23,12 @@ public class Workload { public String name; @Column(name="load_") public Integer load; + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/orphan/onetomany/DeleteSharedOneToManyOrphansTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/orphan/onetomany/DeleteSharedOneToManyOrphansTest.java new file mode 100644 index 000000000000..93381a0d8d71 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/orphan/onetomany/DeleteSharedOneToManyOrphansTest.java @@ -0,0 +1,187 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.orphan.onetomany; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.Environment; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +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 Andrea Boriero + */ +public class DeleteSharedOneToManyOrphansTest extends BaseEntityManagerFunctionalTestCase { + + /* + A value of BATCH_FETCH_SIZE > 1 along with the initialization of the Item#higherItemRelations + collection causes the issue + */ + private static final String BATCH_FETCH_SIZE = "2"; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Item.class, ItemRelation.class}; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( Environment.DEFAULT_BATCH_FETCH_SIZE, BATCH_FETCH_SIZE ); + } + + @Before + public void prepareTest() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + + final Item item1 = new Item( "first" ); + entityManager.persist( item1 ); + + final Item item2 = new Item( "second" ); + entityManager.persist( item2 ); + + final ItemRelation rel = new ItemRelation(); + item1.addLowerItemRelations( rel ); + item2.addHigherItemRelations( rel ); + + entityManager.persist( rel ); + } ); + } + + @After + public void cleanupTest() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete from ItemRelation" ).executeUpdate(); + entityManager.createQuery( "delete from Item" ).executeUpdate(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11144") + @FailureExpected( jiraKey = "HHH-11144" ) + public void testInitializingSecondCollection() { + doInJPA( this::entityManagerFactory, entityManager -> { + + Item item = entityManager.createQuery( "select x from Item x where x.code = 'first'", Item.class ) + .getSingleResult(); + + Set lowerItemRelations = item.getLowerItemRelations(); + Hibernate.initialize( lowerItemRelations ); + + Set higherItemRelations = item.getHigherItemRelations(); + Hibernate.initialize( higherItemRelations ); + + Assert.assertEquals( 1, lowerItemRelations.size() ); + + lowerItemRelations.clear(); + } ); + checkLowerItemRelationsAreDeleted(); + } + + private void checkLowerItemRelationsAreDeleted() { + doInJPA( this::entityManagerFactory, entityManager -> { + + Item item = entityManager.createQuery( "select x from Item x where x.code = 'first'", Item.class ) + .getSingleResult(); + + Set lowerItemRelations = item.getLowerItemRelations(); + Hibernate.initialize( lowerItemRelations ); + + Assert.assertEquals( "The collection should be empty", 0, lowerItemRelations.size() ); + } ); + } + + @Entity(name = "Item") + public static class Item { + @Id + @GeneratedValue + protected Long id; + + @Column + protected String code; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) + protected Set lowerItemRelations = new LinkedHashSet<>(); + + @OneToMany(mappedBy = "child", cascade = CascadeType.ALL, orphanRemoval = true) + protected Set higherItemRelations = new LinkedHashSet<>(); + + public Item() { + } + + public Item(String code) { + this.code = code; + } + + public Set getLowerItemRelations() { + return lowerItemRelations; + } + + public Set getHigherItemRelations() { + return higherItemRelations; + } + + public void addHigherItemRelations(ItemRelation itemRelation) { + higherItemRelations.add( itemRelation ); + itemRelation.setChild( this ); + } + + public void addLowerItemRelations(ItemRelation itemRelation) { + lowerItemRelations.add( itemRelation ); + itemRelation.setParent( this ); + } + } + + @Entity(name = "ItemRelation") + public static class ItemRelation { + @Id + @GeneratedValue + protected Long id; + + @ManyToOne(optional = false) + @JoinColumn(name = "PARENT_ID") + private Item parent; + + @ManyToOne(optional = false) + @JoinColumn(name = "CHILD_ID") + private Item child; + + public Item getParent() { + return parent; + } + + public void setParent(Item parent) { + this.parent = parent; + } + + public Item getChild() { + return child; + } + + public void setChild(Item child) { + this.child = child; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java index f83b1b6cd362..a4ed9ab2dacf 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java @@ -381,7 +381,7 @@ public void testGetBytesFromInputStream() throws Exception { assertTrue( oldTime > newTime ); } - // This is the old getBytesFromInputStream from JarVisitorFactory beforeQuery + // This is the old getBytesFromInputStream from JarVisitorFactory before // it was changed by HHH-7835. Use it as a regression test. private byte[] getBytesFromInputStream(InputStream inputStream) throws IOException { int size; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagedEntityManagerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagedEntityManagerTest.java index d253ce8a5d7e..aed7664b0684 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagedEntityManagerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagedEntityManagerTest.java @@ -57,7 +57,7 @@ * In this test we verify that it is possible to bootstrap Hibernate/JPA from * various bundles (war, par, ...) using {@code Persistence.createEntityManagerFactory()} *

    - * Each test will beforeQuery its run build the required bundle and place them into the classpath. + * Each test will before its run build the required bundle and place them into the classpath. * * @author Gavin King * @author Hardy Ferentschik diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java index 689fbbbb847c..02afe9c02766 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java @@ -199,7 +199,7 @@ protected File buildDefaultPar_1_0() { protected File buildExplicitPar() { // explicitpar/persistence.xml references externaljar.jar so build that from here. - // this is the reason for tests failing afterQuery clean at least on my (Steve) local system + // this is the reason for tests failing after clean at least on my (Steve) local system buildExternalJar(); String fileName = "explicitpar.par"; @@ -226,7 +226,7 @@ protected File buildExplicitPar() { protected File buildExplicitPar2() { // explicitpar/persistence.xml references externaljar.jar so build that from here. - // this is the reason for tests failing afterQuery clean at least on my (Steve) local system + // this is the reason for tests failing after clean at least on my (Steve) local system File jar = buildExternalJar2(); String fileName = "explicitpar2.par"; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/persistenceunit/DuplicatePersistenceUnitNameTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/persistenceunit/DuplicatePersistenceUnitNameTest.java new file mode 100644 index 000000000000..f75c0b04669f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/persistenceunit/DuplicatePersistenceUnitNameTest.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 . + */ +package org.hibernate.jpa.test.persistenceunit; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.boot.internal.PersistenceXmlParser; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.jboss.byteman.contrib.bmunit.BMUnitRunner; +import org.jboss.logging.Logger; + +import static org.hibernate.internal.util.ConfigHelper.findAsResource; +import static org.junit.Assert.assertTrue; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11845") +@RunWith(BMUnitRunner.class) +public class DuplicatePersistenceUnitNameTest extends BaseUnitTestCase { + private Triggerable triggerable; + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, PersistenceXmlParser.class.getName() ) + ); + + @Before + public void setUp() { + final Set messagesPrefixes = new HashSet<>(); + messagesPrefixes.add( "HHH015018" ); + triggerable = logInspection.watchForLogMessages( messagesPrefixes ); + } + + @After + public void tearDown() { + triggerable.reset(); + } + + @Test + public void testDuplicatePersistenceUnitNameLogAWarnMessage() { + final Map properties = new HashMap(); + properties.put( AvailableSettings.CLASSLOADERS, Arrays.asList( new TestClassLoader() ) ); + PersistenceXmlParser.locatePersistenceUnits( properties ); + assertTrue( "The warn HHH015018 has not been logged ", triggerable.wasTriggered() ); + } + + private static class TestClassLoader extends ClassLoader { + final List urls; + + public TestClassLoader() { + urls = Arrays.asList( + findAsResource( + "org/hibernate/jpa/test/persistenceunit/META-INF/persistence.xml" + ) + , + findAsResource( + "org/hibernate/jpa/test/persistenceunit/META-INF/persistenceUnitForNameDuplicationTest.xml" + ) + ); + } + + @Override + protected Enumeration findResources(String name) throws IOException { + return name.equals( "META-INF/persistence.xml" ) ? + Collections.enumeration( urls ) : + Collections.emptyEnumeration(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/persistenceunit/TwoPersistenceUnits2LCDisabledEnabled.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/persistenceunit/TwoPersistenceUnits2LCDisabledEnabled.java new file mode 100644 index 000000000000..af22ffdd77fb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/persistenceunit/TwoPersistenceUnits2LCDisabledEnabled.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.persistenceunit; + +import java.util.Collections; +import java.util.Map; +import javax.persistence.Cacheable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.SharedCacheMode; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Gail Badner + */ +public class TwoPersistenceUnits2LCDisabledEnabled { + + @Test + @TestForIssue( jiraKey = "HHH-11516" ) + public void testDisabledEnabled() { + final Map config = Environment.getProperties(); + config.put( org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, Collections.singletonList( AnEntity.class ) ); + config.put( "javax.persistence.sharedCache.mode", SharedCacheMode.ENABLE_SELECTIVE ); + config.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + + testIt( config ); + + config.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" ); + + testIt( config ); + } + + private void testIt(Map config) { + EntityManagerFactoryBuilder entityManagerFactoryBuilder = Bootstrap.getEntityManagerFactoryBuilder( + new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + config + ); + SessionFactoryImplementor sf = entityManagerFactoryBuilder.build().unwrap( SessionFactoryImplementor.class ); + final EntityPersister persister = sf.getMetamodel().entityPersister( AnEntity.class.getName() ); + + try { + if ( config.get( AvailableSettings.USE_SECOND_LEVEL_CACHE ).equals( "true" ) ) { + assertNotNull( persister.getCacheAccessStrategy() ); + } + else { + assertNull( persister.getCacheAccessStrategy() ); + } + } + finally { + sf.close(); + } + } + + + @Cacheable + @Entity( name = "AnEntity" ) + public static class AnEntity { + @Id + private Long id; + } +} 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 8c3e0a92f356..335ddbef4305 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 @@ -197,7 +197,7 @@ private void createProcedures(HibernateEntityManagerFactory emf) { conn.commit(); } catch (SQLException e) { - System.out.println( "Unable to commit transaction afterQuery creating creating procedures"); + System.out.println( "Unable to commit transaction after creating creating procedures"); } try { @@ -286,7 +286,7 @@ private void dropProcedures(HibernateEntityManagerFactory emf) { conn.commit(); } catch (SQLException e) { - System.out.println( "Unable to commit transaction afterQuery creating dropping procedures"); + System.out.println( "Unable to commit transaction after creating dropping procedures"); } 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 4d21f9a1c116..4aee30970c53 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 @@ -328,7 +328,7 @@ private void createProcedures(HibernateEntityManagerFactory emf) { conn.commit(); } catch (SQLException e) { - System.out.println( "Unable to commit transaction afterQuery creating creating procedures"); + System.out.println( "Unable to commit transaction after creating creating procedures"); } try { @@ -434,7 +434,7 @@ private void dropProcedures(HibernateEntityManagerFactory emf) { conn.commit(); } catch (SQLException e) { - System.out.println( "Unable to commit transaction afterQuery creating dropping procedures"); + System.out.println( "Unable to commit transaction after creating dropping procedures"); } try { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByNameTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByNameTest.java index 360be4c36923..046b8ca5c187 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByNameTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByNameTest.java @@ -102,7 +102,7 @@ private void createProcedures(EntityManagerFactory emf) { conn.commit(); } catch (SQLException e) { - System.out.println( "Unable to commit transaction afterQuery creating creating procedures" ); + System.out.println( "Unable to commit transaction after creating creating procedures" ); fail(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByPositionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByPositionTest.java index 0d01f9648767..c190790216af 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByPositionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/StoreProcedureRefCursorOutParameterByPositionTest.java @@ -102,7 +102,7 @@ private void createProcedures(EntityManagerFactory emf) { conn.commit(); } catch (SQLException e) { - System.out.println( "Unable to commit transaction afterQuery creating creating procedures" ); + System.out.println( "Unable to commit transaction after creating creating procedures" ); fail(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java index f46e7b9eb0ab..5921fa508cd7 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java @@ -121,7 +121,8 @@ public void testConfigValueHandling() { // NOTE: here we check "query options" via the Hibernate contract (allowing nullness checking); see below for access via the JPA contract assertNull( hibernateQuery.getQueryOptions().getFirstRow() ); assertNull( hibernateQuery.getQueryOptions().getMaxRows() ); - assertEquals( FlushMode.AUTO, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); // jpa timeout is in milliseconds, whereas Hibernate's is in seconds @@ -137,7 +138,8 @@ public void testConfigValueHandling() { // NOTE: here we check "query options" via the JPA contract assertEquals( 0, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); @@ -150,7 +152,8 @@ public void testConfigValueHandling() { // assert the state of the query config settings based on the initial named query assertEquals( 0, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); @@ -163,7 +166,8 @@ public void testConfigValueHandling() { // assert the state of the query config settings based on the initial named query assertEquals( 51, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CachedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CachedQueryTest.java index 2f55003037a5..083efefe93e6 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CachedQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CachedQueryTest.java @@ -114,7 +114,7 @@ public void testCacheableQuery() { assertEquals( 1, stats.getQueryCacheHitCount() ); assertEquals( 0, stats.getQueryCacheMissCount() ); assertEquals( 0, stats.getQueryCachePutCount() ); - // since entity regions were evicted, the 10 entities are not found, and are re-put afterQuery loading + // since entity regions were evicted, the 10 entities are not found, and are re-put after loading // as each entity ID is read from the query cache, Hibernate will look the entity up in the // cache and will not find it; that's why the "miss" and "put" counts are both 10. assertEquals( 0, stats.getSecondLevelCacheHitCount() ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java new file mode 100644 index 000000000000..c0e07bcea0a6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java @@ -0,0 +1,276 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.Query; +import javax.persistence.Table; +import javax.persistence.TypedQuery; + +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.NamedQuery; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.junit.After; +import org.junit.Before; +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 + */ +@TestForIssue(jiraKey = "HHH-11640") +public class NamedQueryCommentTest extends BaseEntityManagerFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); + } + + private static final String[] GAME_TITLES = { "Halo", "Grand Theft Auto", "NetHack" }; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Game.class }; + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( String title : GAME_TITLES ) { + Game game = new Game( title ); + entityManager.persist( game ); + } + } ); + } + + @After + public void tearDown() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete from Game" ).executeUpdate(); + } ); + } + + @Test + public void testSelectNamedQueryWithSqlComment() { + doInJPA( this::entityManagerFactory, entityManager -> { + sqlStatementInterceptor.clear(); + + TypedQuery query = entityManager.createNamedQuery( "SelectNamedQuery", Game.class ); + query.setParameter( "title", GAME_TITLES[0] ); + List list = query.getResultList(); + assertEquals( 1, list.size() ); + + sqlStatementInterceptor.assertExecutedCount(1); + + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ where namedquery0_.title=?" + ); + } ); + } + + @Test + public void testSelectNamedNativeQueryWithSqlComment() { + doInJPA( this::entityManagerFactory, entityManager -> { + sqlStatementInterceptor.clear(); + + TypedQuery query = entityManager.createNamedQuery( "SelectNamedNativeQuery", Game.class ); + query.setParameter( "title", GAME_TITLES[0] ); + List list = query.getResultList(); + assertEquals( 1, list.size() ); + + sqlStatementInterceptor.assertExecutedCount(1); + + sqlStatementInterceptor.assertExecuted( + "/* + INDEX (game idx_game_title) */ select * from game g where title = ?" + + ); + } ); + } + + @Test + public void testUpdateNamedQueryWithSqlComment() { + doInJPA( this::entityManagerFactory, entityManager -> { + sqlStatementInterceptor.clear(); + + Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); + query.setParameter( "title", GAME_TITLES[0] ); + query.setParameter( "id", 1L ); + int updateCount = query.executeUpdate(); + assertEquals( 1, updateCount ); + + sqlStatementInterceptor.assertExecutedCount(1); + + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" + ); + } ); + } + + @Test + public void testUpdateNamedNativeQueryWithSqlComment() { + doInJPA( this::entityManagerFactory, entityManager -> { + sqlStatementInterceptor.clear(); + + Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); + query.setParameter( "title", GAME_TITLES[0] ); + query.setParameter( "id", 1L ); + int updateCount = query.executeUpdate(); + assertEquals( 1, updateCount ); + + sqlStatementInterceptor.assertExecutedCount(1); + + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" + ); + } ); + } + + @Test + @RequiresDialect(Oracle8iDialect.class) + public void testUpdateNamedNativeQueryWithQueryHintUsingOracle() { + doInJPA( this::entityManagerFactory, entityManager -> { + sqlStatementInterceptor.clear(); + + Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); + query.setParameter( "title", GAME_TITLES[0] ); + query.setParameter( "id", 1L ); + query.unwrap( org.hibernate.query.Query.class ).addQueryHint( "INDEX (game idx_game_id)" ); + int updateCount = query.executeUpdate(); + assertEquals( 1, updateCount ); + + sqlStatementInterceptor.assertExecutedCount(1); + + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_INDEX_game_title */ update /*+ INDEX (game idx_game_id) */ game set title = ? where id = ?" + ); + } ); + } + + @Test + @RequiresDialect(H2Dialect.class) + public void testUpdateNamedNativeQueryWithQueryHintUsingIndex() { + doInJPA( this::entityManagerFactory, entityManager -> { + sqlStatementInterceptor.clear(); + + Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); + query.setParameter( "title", GAME_TITLES[0] ); + query.setParameter( "id", 1L ); + query.unwrap( org.hibernate.query.Query.class ).addQueryHint( "INDEX (game idx_game_id)" ); + int updateCount = query.executeUpdate(); + assertEquals( 1, updateCount ); + + sqlStatementInterceptor.assertExecutedCount(1); + + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" + ); + } ); + } + + @Test + @RequiresDialect(MySQLDialect.class) + @RequiresDialect(H2Dialect.class) + public void testSelectNamedNativeQueryWithQueryHintUsingIndex() { + doInJPA( this::entityManagerFactory, entityManager -> { + sqlStatementInterceptor.clear(); + + Query query = entityManager.createNamedQuery( "SelectNamedQuery" ); + query.setParameter( "title", GAME_TITLES[0] ); + query.unwrap( org.hibernate.query.Query.class ).addQueryHint( "idx_game_id" ); + List list = query.getResultList(); + assertEquals( 1, list.size() ); + + sqlStatementInterceptor.assertExecutedCount(1); + + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ USE INDEX (idx_game_id) where namedquery0_.title=?" ) + ; + } ); + } + + @Entity(name = "Game") + @Table( + name = "game", + indexes = { + @Index(name = "idx_game_title", columnList = "title"), + @Index(name = "idx_game_id", columnList = "id") + } + ) + @NamedQuery( + name = "SelectNamedQuery", + query = "select g from Game g where title = :title", + comment = "COMMENT_SELECT_INDEX_game_title" + ) + @NamedQuery( + name = "UpdateNamedQuery", + query = "update Game set title = :title where id = :id", + comment = "INDEX (game idx_game_title) " + ) + @NamedNativeQuery( + name = "SelectNamedNativeQuery", + query = "select * from game g where title = :title", + comment = "+ INDEX (game idx_game_title) ", + resultClass = Game.class + ) + @NamedNativeQuery( + name = "UpdateNamedNativeQuery", + query = "update game set title = :title where id = :id", + comment = "COMMENT_INDEX_game_title", + resultClass = Game.class + ) + public static class Game { + + @Id + @GeneratedValue + private Long id; + + private String title; + + public Game() { + } + + public Game(String title) { + this.title = title; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java new file mode 100644 index 000000000000..30d52f4f3811 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java @@ -0,0 +1,257 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.query; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.FlushMode; +import org.hibernate.annotations.FlushModeType; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.NamedQuery; +import org.hibernate.query.NativeQuery; +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Yoann Rodiere + */ +@TestForIssue(jiraKey = "HHH-12795") +public class NamedQueryFlushModeTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Test + public void testNamedQueryWithFlushModeManual() { + String queryName = "NamedQueryFlushModeManual"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.MANUAL, query.getHibernateFlushMode() ); + // JPA flush mode is an approximation + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeCommit() { + String queryName = "NamedQueryFlushModeCommit"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.COMMIT, query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeAuto() { + String queryName = "NamedQueryFlushModeAuto"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.AUTO, query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeAlways() { + String queryName = "NamedQueryFlushModeAlways"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.ALWAYS, query.getHibernateFlushMode() ); + // JPA flush mode is an approximation + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModePersistenceContext() { + String queryName = "NamedQueryFlushModePersistenceContext"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query; + + // A null Hibernate flush mode means we will use whatever mode is set on the session + // JPA doesn't allow null flush modes, so we expect some approximation of the flush mode to be returned + + s.setHibernateFlushMode( FlushMode.MANUAL ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.COMMIT ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.AUTO ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.ALWAYS ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeManual() { + String queryName = "NamedNativeQueryFlushModeManual"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.MANUAL, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeCommit() { + String queryName = "NamedNativeQueryFlushModeCommit"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.COMMIT, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeAuto() { + String queryName = "NamedNativeQueryFlushModeAuto"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.AUTO, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeAlways() { + String queryName = "NamedNativeQueryFlushModeAlways"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.ALWAYS, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModePersistenceContext() { + String queryName = "NamedNativeQueryFlushModePersistenceContext"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query; + + // A null Hibernate flush mode means we will use whatever mode is set on the session + // JPA doesn't allow null flush modes, so we expect some approximation of the flush mode to be returned + + s.setHibernateFlushMode( FlushMode.MANUAL ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.COMMIT ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.AUTO ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.ALWAYS ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Entity(name = "TestEntity") + @NamedQuery( + name = "NamedQueryFlushModeManual", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.MANUAL + ) + @NamedQuery( + name = "NamedQueryFlushModeCommit", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.COMMIT + ) + @NamedQuery( + name = "NamedQueryFlushModeAuto", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.AUTO + ) + @NamedQuery( + name = "NamedQueryFlushModeAlways", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.ALWAYS + ) + @NamedQuery( + name = "NamedQueryFlushModePersistenceContext", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.PERSISTENCE_CONTEXT + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeManual", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.MANUAL + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeCommit", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.COMMIT + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeAuto", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.AUTO + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeAlways", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.ALWAYS + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModePersistenceContext", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.PERSISTENCE_CONTEXT + ) + public static class TestEntity { + + @Id + @GeneratedValue + private Long id; + + private String text; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryTest.java index 32f3c5513d28..682996be9183 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryTest.java @@ -18,7 +18,7 @@ import org.hibernate.Session; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - +import org.hibernate.query.NativeQuery; import org.hibernate.testing.TestForIssue; import org.junit.After; import org.junit.Before; @@ -79,8 +79,8 @@ public void testNamedQueryOrdinalParametersConflict() { assertEquals( 1, list.size() ); final Session session = entityManager.unwrap( Session.class ); - final org.hibernate.query.Query sessionQuery = session.createQuery( "select g from Game g where title = ?" ); - sessionQuery.setParameter( 0, GAME_TITLES[0] ); + final org.hibernate.query.Query sessionQuery = session.createQuery( "select g from Game g where title = ?1" ); + sessionQuery.setParameter( 1, GAME_TITLES[0] ); list = sessionQuery.getResultList(); query.setParameter( 1, GAME_TITLES[0] ); @@ -99,7 +99,7 @@ public void testNamedQueryOrdinalParametersConflict2() { final Session session = entityManager.unwrap( Session.class ); final org.hibernate.query.Query sessionQuery = session.getNamedQuery( "NamedQuery" ); - sessionQuery.setParameter( 0, GAME_TITLES[0] ); + sessionQuery.setParameter( 1, GAME_TITLES[0] ); list = sessionQuery.getResultList(); query.setParameter( 1, GAME_TITLES[0] ); @@ -130,7 +130,7 @@ public void testNativeNamedQueriesOrdinalParametersConflict() { final Session session = entityManager.unwrap( Session.class ); final org.hibernate.query.Query sessionQuery = session.createSQLQuery( "select * from Game g where title = ?" ); - sessionQuery.setParameter( 0, GAME_TITLES[0] ); + sessionQuery.setParameter( 1, GAME_TITLES[0] ); list = sessionQuery.getResultList(); query.setParameter( 1, GAME_TITLES[0] ); @@ -150,7 +150,7 @@ public void testNativeNamedQueriesOrdinalParametersConflict2() { final Session session = entityManager.unwrap( Session.class ); final org.hibernate.query.Query sessionQuery = session.getNamedNativeQuery( "NamedNativeQuery" ); - sessionQuery.setParameter( 0, GAME_TITLES[0] ); + sessionQuery.setParameter( 1, GAME_TITLES[0] ); list = sessionQuery.getResultList(); query.setParameter( 1, GAME_TITLES[0] ); @@ -159,8 +159,27 @@ public void testNativeNamedQueriesOrdinalParametersConflict2() { ); } + @Test + @TestForIssue(jiraKey = "HHH-12621") + public void testNativeQueriesFromNamedQueriesDoNotShareQuerySpaces() { + doInJPA( this::entityManagerFactory, entityManager -> { + Query originalQuery = entityManager.createNativeQuery( "select g from Game g where title = ?1" ); + entityManager.getEntityManagerFactory().addNamedQuery( "myQuery", originalQuery ); + + NativeQuery query1 = entityManager.createNamedQuery( "myQuery" ).unwrap( NativeQuery.class ); + query1.addSynchronizedQuerySpace( "newQuerySpace" ); + + assertEquals( 1, query1.getSynchronizedQuerySpaces().size() ); + assertEquals( "newQuerySpace", query1.getSynchronizedQuerySpaces().iterator().next() ); + + NativeQuery query2 = entityManager.createNamedQuery( "myQuery" ).unwrap( NativeQuery.class ); + + assertEquals( 0, query2.getSynchronizedQuerySpaces().size() ); + } ); + } + @Entity(name = "Game") - @NamedQueries(@NamedQuery(name = "NamedQuery", query = "select g from Game g where title = ?")) + @NamedQueries(@NamedQuery(name = "NamedQuery", query = "select g from Game g where title = ?1")) @NamedNativeQueries(@NamedNativeQuery(name = "NamedNativeQuery", query = "select * from Game g where title = ?")) public static class Game { private Long id; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NativeQueryOrdinalParametersTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NativeQueryOrdinalParametersTest.java index d26bc2d721bb..9fd79d9a8a6d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NativeQueryOrdinalParametersTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NativeQueryOrdinalParametersTest.java @@ -115,7 +115,7 @@ public void testConflictWithSessionNativeQuery(){ final String sqlString = "SELECT * FROM GAME g WHERE title = ?"; try { NativeQuery sqlQuery = em.unwrap( Session.class ).createSQLQuery( sqlString ); - sqlQuery.setString( 0, "Super Mario Brothers").setCacheable( true ); + sqlQuery.setString( 1, "Super Mario Brothers").setCacheable( true ); List results = sqlQuery.list(); assertEquals( 1, results.size() ); @@ -126,7 +126,7 @@ public void testConflictWithSessionNativeQuery(){ assertEquals( 1, list.size() ); sqlQuery = em.unwrap( Session.class ).createSQLQuery( sqlString ); - sqlQuery.setString( 0, "Super Mario Brothers").setCacheable( true ); + sqlQuery.setString( 1, "Super Mario Brothers").setCacheable( true ); results = sqlQuery.list(); assertEquals( 1, results.size() ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryParametersValidationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryParametersValidationTest.java new file mode 100644 index 000000000000..8b4badc60c10 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryParametersValidationTest.java @@ -0,0 +1,182 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.annotations.Type; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.usertype.UserType; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +public class QueryParametersValidationTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {TestEntity.class}; + } + + @TestForIssue(jiraKey = "HHH-11397") + @Test(expected = IllegalArgumentException.class) + public void setParameterWithWrongTypeShouldThrowIllegalArgumentException() { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + try { + entityManager.createQuery( "select e from TestEntity e where e.id = :id" ).setParameter( "id", 1 ); + } + finally { + entityManager.close(); + } + } + + @Test + @TestForIssue(jiraKey = "HHH-11579") + public void setParameterWithWrongTypeShouldNotThrowIllegalArgumentExceptionWhenValidationIsDisabled() { + final SessionFactory sessionFactory = entityManagerFactory().unwrap( SessionFactory.class ); + final Session session = sessionFactory.withOptions().setQueryParameterValidation( false ).openSession(); + try { + session.createQuery( "select e from TestEntity e where e.id = :id" ).setParameter( "id", 1 ); + } + finally { + session.close(); + sessionFactory.close(); + } + } + + @Test + public void setParameterWithCorrectTypeShouldNotThrowIllegalArgumentException() { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + try { + entityManager.createQuery( "select e from TestEntity e where e.id = :id" ).setParameter( "id", 1L ); + } + finally { + entityManager.close(); + } + } + + @Test + @TestForIssue(jiraKey = "HHH-11971") + public void setPrimitiveParameterShouldNotThrowExceptions() { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + try { + entityManager.createQuery( "select e from TestEntity e where e.active = :active" ).setParameter( + "active", + true + ); + entityManager.createQuery( "select e from TestEntity e where e.active = :active" ).setParameter( + "active", + Boolean.TRUE + ); + } + finally { + entityManager.close(); + } + } + + @Test(expected = IllegalArgumentException.class) + @TestForIssue( jiraKey = "HHH-11971") + public void setWrongPrimitiveParameterShouldThrowIllegalArgumentException() { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + try { + entityManager.createQuery( "select e from TestEntity e where e.active = :active" ).setParameter( "active", 'c' ); + } + finally { + entityManager.close(); + } + } + + @Entity(name = "TestEntity") + public class TestEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Type(type = "org.hibernate.jpa.test.query.QueryParametersValidationTest$BooleanUserType") + private boolean active; + } + + public static class BooleanUserType implements UserType { + + @Override + public int[] sqlTypes() { + return new int[] { Types.CHAR }; + } + + @Override + public Class returnedClass() { + return boolean.class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return Objects.equals( x, y); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return Objects.hashCode(x); + } + + @Override + public Object nullSafeGet( + ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) + throws HibernateException, SQLException { + return "Y".equals(rs.getString(names[0])); + } + + @Override + public void nullSafeSet( + PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) + throws HibernateException, SQLException { + st.setString(index, ((Boolean) value).booleanValue() ? "Y" : "N"); + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(Object value) throws HibernateException { + return null; + } + + @Override + public Object assemble(Serializable cached, Object owner) throws HibernateException { + return null; + } + + @Override + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return null; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryParametersWithDisabledValidationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryParametersWithDisabledValidationTest.java new file mode 100644 index 000000000000..c7446c29ceb8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryParametersWithDisabledValidationTest.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.jpa.test.query; + +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11579") +public class QueryParametersWithDisabledValidationTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {QueryParametersValidationTest.TestEntity.class}; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.VALIDATE_QUERY_PARAMETERS, false ); + } + + @Test + public void setParameterWithWrongTypeShouldNotThrowIllegalArgumentException() { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + try { + entityManager.createQuery( "select e from TestEntity e where e.id = :id" ).setParameter( "id", 1 ); + } + finally { + entityManager.close(); + } + } + + @Test + public void setParameterWithCorrectTypeShouldNotThrowIllegalArgumentException() { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + try { + entityManager.createQuery( "select e from TestEntity e where e.id = :id" ).setParameter( "id", 1L ); + } + finally { + entityManager.close(); + } + } + + @Entity(name = "TestEntity") + public class TestEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + } +} \ No newline at end of file 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 28557027096b..90ffa7c2d2a8 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 @@ -23,6 +23,7 @@ import javax.persistence.Tuple; import org.hibernate.Hibernate; +import org.hibernate.QueryException; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL9Dialect; @@ -42,6 +43,7 @@ import static junit.framework.Assert.assertNull; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -139,17 +141,17 @@ public void testNullPositionalParameter() throws Exception { try { Item item = new Item( "Mouse", "Micro$oft mouse" ); em.persist( item ); - Query q = em.createQuery( "from Item i where i.intVal=?" ); - q.setParameter( 0, null ); + Query q = em.createQuery( "from Item i where i.intVal=?1" ); + q.setParameter( 1, null ); List results = q.getResultList(); // null != null assertEquals( 0, results.size() ); - q = em.createQuery( "from Item i where i.intVal is null and ? is null" ); - q.setParameter( 0, null ); + q = em.createQuery( "from Item i where i.intVal is null and ?1 is null" ); + q.setParameter( 1, null ); results = q.getResultList(); assertEquals( 1, results.size() ); - q = em.createQuery( "from Item i where i.intVal is null or i.intVal = ?" ); - q.setParameter( 0, null ); + q = em.createQuery( "from Item i where i.intVal is null or i.intVal = ?1" ); + q.setParameter( 1, null ); results = q.getResultList(); assertEquals( 1, results.size() ); } @@ -169,7 +171,7 @@ public void testNullPositionalParameterParameter() throws Exception { try { Item item = new Item( "Mouse", "Micro$oft mouse" ); em.persist( item ); - Query q = em.createQuery( "from Item i where i.intVal=?" ); + Query q = em.createQuery( "from Item i where i.intVal=?1" ); Parameter p = new Parameter() { @Override public String getName() { @@ -178,7 +180,7 @@ public String getName() { @Override public Integer getPosition() { - return 0; + return 1; } @Override @@ -191,11 +193,11 @@ public Class getParameterType() { List results = q.getResultList(); // null != null assertEquals( 0, results.size() ); - q = em.createQuery( "from Item i where i.intVal is null and ? is null" ); + q = em.createQuery( "from Item i where i.intVal is null and ?1 is null" ); q.setParameter( p, null ); results = q.getResultList(); assertEquals( 1, results.size() ); - q = em.createQuery( "from Item i where i.intVal is null or i.intVal = ?" ); + q = em.createQuery( "from Item i where i.intVal is null or i.intVal = ?1" ); q.setParameter( p, null ); results = q.getResultList(); assertEquals( 1, results.size() ); @@ -216,7 +218,7 @@ public void testNullPositionalParameterParameterIncompatible() throws Exception try { Item item = new Item( "Mouse", "Micro$oft mouse" ); em.persist( item ); - Query q = em.createQuery( "from Item i where i.intVal=?" ); + Query q = em.createQuery( "from Item i where i.intVal=?1" ); Parameter p = new Parameter() { @Override public String getName() { @@ -225,7 +227,7 @@ public String getName() { @Override public Integer getPosition() { - return 0; + return 1; } @Override @@ -238,11 +240,11 @@ public Class getParameterType() { List results = q.getResultList(); // null != null assertEquals( 0, results.size() ); - q = em.createQuery( "from Item i where i.intVal is null and ? is null" ); + q = em.createQuery( "from Item i where i.intVal is null and ?1 is null" ); q.setParameter( p, null ); results = q.getResultList(); assertEquals( 1, results.size() ); - q = em.createQuery( "from Item i where i.intVal is null or i.intVal = ?" ); + q = em.createQuery( "from Item i where i.intVal is null or i.intVal = ?1" ); q.setParameter( p, null ); results = q.getResultList(); assertEquals( 1, results.size() ); @@ -688,9 +690,8 @@ public void testJpaPositionalParameters() { Query query = em.createQuery( "from Item item where item.name =?1 or item.descr = ?1" ); Parameter p1 = query.getParameter( 1 ); Assert.assertNotNull( p1 ); - // in 5.2, '? { + entityManager.persist( item ); + entityManager.persist( item2 ); + assertTrue( entityManager.contains( item ) ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Query q = entityManager.createQuery( "select item from Item item where item.name in ?1 and item.descr = ?2" ); + List params = new ArrayList(); + params.add( item.getName() ); + params.add( item2.getName() ); + q.setParameter( 1, params ); + q.setParameter( 2, item2.getDescr() ); + List result = q.getResultList(); + assertNotNull( result ); + assertEquals( 1, result.size() ); + } ); + + } + + @Test + @TestForIssue(jiraKey = "HHH-12290") + public void testParameterCollectionParenthesesAndPositional() { + final Item item = new Item( "Mouse", "Microsoft mouse" ); + final Item item2 = new Item( "Computer", "Dell computer" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( item ); + entityManager.persist( item2 ); + assertTrue( entityManager.contains( item ) ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Query q = entityManager.createQuery( + "select item from Item item where item.name in (?1) and item.descr = ?2" ); + List params = new ArrayList(); + params.add( item.getName() ); + params.add( item2.getName() ); + q.setParameter( 1, params ); + q.setParameter( 2, item2.getDescr() ); + List result = q.getResultList(); + assertNotNull( result ); + assertEquals( 1, result.size() ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12290") + public void testParameterCollectionSingletonParenthesesAndPositional() { + final Item item = new Item( "Mouse", "Microsoft mouse" ); + final Item item2 = new Item( "Computer", "Dell computer" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( item ); + entityManager.persist( item2 ); + assertTrue( entityManager.contains( item ) ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Query q = entityManager.createQuery( + "select item from Item item where item.name in (?1) and item.descr = ?2" ); + List params = new ArrayList(); + params.add( item2.getName() ); + q.setParameter( 1, params ); + q.setParameter( 2, item2.getDescr() ); + List result = q.getResultList(); + assertNotNull( result ); + assertEquals( 1, result.size() ); + } ); + } + @Test public void testParameterList() throws Exception { final Item item = new Item( "Mouse", "Micro$oft mouse" ); @@ -745,7 +823,7 @@ public void testParameterList() throws Exception { params.add( item.getName() ); params.add( item2.getName() ); // deprecated usage of positional parameter by String - q.setParameter( "1", params ); + q.setParameter( 1, params ); result = q.getResultList(); assertNotNull( result ); assertEquals( 2, result.size() ); @@ -807,7 +885,7 @@ public void testParameterListInExistingParens() throws Exception { params.add( item.getName() ); params.add( item2.getName() ); // deprecated usage of positional parameter by String - q.setParameter( "1", params ); + q.setParameter( 1, params ); result = q.getResultList(); assertNotNull( result ); assertEquals( 2, result.size() ); @@ -1011,13 +1089,7 @@ public void testPositionalParameterForms() throws Exception { // next using jpa-style positional parameter, but as a name (which is how Hibernate core treats these query = em.createQuery( "select w from Wallet w where w.brand = ?1" ); // deprecated usage of positional parameter by String - query.setParameter( "1", "Lacoste" ); - w = (Wallet) query.getSingleResult(); - assertNotNull( w ); - - // finally using hql-style positional parameter - query = em.createQuery( "select w from Wallet w where w.brand = ?" ); - query.setParameter( 0, "Lacoste" ); + query.setParameter( 1, "Lacoste" ); w = (Wallet) query.getSingleResult(); assertNotNull( w ); @@ -1047,8 +1119,19 @@ public void testPositionalParameterWithUserError() throws Exception { em.persist( w ); em.flush(); - // using jpa-style, position index should match syntax '?'. + Query jpaQuery = em.createQuery( "select w from Wallet w where w.brand = ?1" ); jpaQuery.setParameter( 1, "Lacoste" ); try { jpaQuery.setParameter( 2, "Expensive" ); @@ -1077,17 +1160,6 @@ public void testPositionalParameterWithUserError() throws Exception { catch (Exception e) { assertTyping( IllegalArgumentException.class, e ); } - - // using hql-style, should be 0-based - Query hqlQuery = em.createQuery( "select w from Wallet w where w.brand = ? and w.model = ?" ); - try { - hqlQuery.setParameter( 1, "Lacoste" ); - hqlQuery.setParameter( 2, "Expensive" ); - fail( "Should fail due to a user error in parameters" ); - } - catch (Exception e) { - assertTyping( IllegalArgumentException.class, e ); - } } finally { if ( em.getTransaction() != null && em.getTransaction().isActive() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java index a019da291957..6e7e18432a2a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java @@ -19,7 +19,9 @@ import javax.persistence.SqlResultSetMapping; import javax.persistence.Table; +import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.RequiresDialect; import org.junit.Test; /** @@ -27,6 +29,7 @@ * * @author Gunnar Morling */ +@RequiresDialect(H2Dialect.class) public class ScalarResultNativeQueryTest extends BaseEntityManagerFunctionalTestCase { @Entity(name="Person") diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TupleNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TupleNativeQueryTest.java new file mode 100644 index 000000000000..25eb4f514f95 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TupleNativeQueryTest.java @@ -0,0 +1,778 @@ +package org.hibernate.jpa.test.query; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.spi.NativeQueryImplementor; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.persistence.*; +import javax.persistence.criteria.CriteriaDelete; +import java.math.BigInteger; +import java.util.List; +import java.util.stream.Collectors; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RequiresDialect(H2Dialect.class) +public class TupleNativeQueryTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{User.class}; + } + + @Before + public void setUp() { + doInJPA(this::entityManagerFactory, entityManager -> { + User user = new User("Arnold"); + entityManager.persist(user); + }); + } + + @After + public void tearDown() { + doInJPA(this::entityManagerFactory, entityManager -> { + CriteriaDelete delete = entityManager.getCriteriaBuilder().createCriteriaDelete(User.class); + delete.from(User.class); + entityManager.createQuery(delete).executeUpdate(); + }); + } + + @Test + public void testPositionalGetterShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0)); + assertEquals("Arnold", tuple.get(1)); + }); + } + + @Test + public void testPositionalGetterWithClassShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0, BigInteger.class)); + assertEquals("Arnold", tuple.get(1, String.class)); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithClassShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithClassShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithClassShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test + public void testAliasGetterWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID")); + assertEquals("Arnold", tuple.get("FIRSTNAME")); + }); + } + + @Test + public void testAliasGetterShouldWorkWithoutExplicitAliasWhenLowerCaseAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get("id"); + }); + } + + @Test(expected = IllegalArgumentException.class) + public void testAliasGetterShouldThrowExceptionWithoutExplicitAliasWhenWrongAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get("e"); + }); + } + + @Test + public void testAliasGetterWithClassWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID", BigInteger.class)); + assertEquals("Arnold", tuple.get("FIRSTNAME", String.class)); + }); + } + + + @Test + public void testAliasGetterWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleAliasedResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1")); + assertEquals("Arnold", tuple.get("ALIAS2")); + }); + } + + @Test + public void testAliasGetterWithClassWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleAliasedResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1", BigInteger.class)); + assertEquals("Arnold", tuple.get("ALIAS2", String.class)); + }); + } + + @Test + public void testToArrayShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getTupleResult(entityManager); + Object[] result = tuples.get(0).toArray(); + assertArrayEquals(new Object[]{BigInteger.ONE, "Arnold"}, result); + }); + } + + @Test + public void testGetElementsShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getTupleResult(entityManager); + List> result = tuples.get(0).getElements(); + assertEquals(2, result.size()); + assertEquals(BigInteger.class, result.get(0).getJavaType()); + assertEquals("ID", result.get(0).getAlias()); + assertEquals(String.class, result.get(1).getJavaType()); + assertEquals("FIRSTNAME", result.get(1).getAlias()); + }); + } + + @Test + public void testPositionalGetterWithNamedNativeQueryShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0)); + assertEquals("Arnold", tuple.get(1)); + }); + } + + @Test + public void testPositionalGetterWithNamedNativeQueryWithClassShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0, BigInteger.class)); + assertEquals("Arnold", tuple.get(1, String.class)); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test + public void testAliasGetterWithNamedNativeQueryWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID")); + assertEquals("Arnold", tuple.get("FIRSTNAME")); + }); + } + + @Test + public void testAliasGetterWithNamedNativeQueryShouldWorkWithoutExplicitAliasWhenLowerCaseAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get("id"); + }); + } + + @Test(expected = IllegalArgumentException.class) + public void testAliasGetterWithNamedNativeQueryShouldThrowExceptionWithoutExplicitAliasWhenWrongAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get("e"); + }); + } + + @Test + public void testAliasGetterWithNamedNativeQueryWithClassWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID", BigInteger.class)); + assertEquals("Arnold", tuple.get("FIRSTNAME", String.class)); + }); + } + + + @Test + public void testAliasGetterWithNamedNativeQueryWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard_with_alias"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1")); + assertEquals("Arnold", tuple.get("ALIAS2")); + }); + } + + @Test + public void testAliasGetterWithNamedNativeQueryWithClassWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleNamedResult(entityManager, "standard_with_alias"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1", BigInteger.class)); + assertEquals("Arnold", tuple.get("ALIAS2", String.class)); + }); + } + + @Test + public void testToArrayShouldWithNamedNativeQueryWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getTupleNamedResult(entityManager, "standard"); + Object[] result = tuples.get(0).toArray(); + assertArrayEquals(new Object[]{BigInteger.ONE, "Arnold"}, result); + }); + } + + @Test + public void testGetElementsWithNamedNativeQueryShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getTupleNamedResult(entityManager, "standard"); + List> result = tuples.get(0).getElements(); + assertEquals(2, result.size()); + assertEquals(BigInteger.class, result.get(0).getJavaType()); + assertEquals("ID", result.get(0).getAlias()); + assertEquals(String.class, result.get(1).getJavaType()); + assertEquals("FIRSTNAME", result.get(1).getAlias()); + }); + } + + @Test + public void testStreamedPositionalGetterShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0)); + assertEquals("Arnold", tuple.get(1)); + }); + } + + @Test + public void testStreamedPositionalGetterWithClassShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0, BigInteger.class)); + assertEquals("Arnold", tuple.get(1, String.class)); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithClassShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithClassShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithClassShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test + public void testStreamedAliasGetterWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID")); + assertEquals("Arnold", tuple.get("FIRSTNAME")); + }); + } + + @Test + public void testStreamedAliasGetterShouldWorkWithoutExplicitAliasWhenLowerCaseAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get("id"); + }); + } + + @Test(expected = IllegalArgumentException.class) + public void testStreamedAliasGetterShouldThrowExceptionWithoutExplicitAliasWhenWrongAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + tuple.get("e"); + }); + } + + @Test + public void testStreamedAliasGetterWithClassWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedTupleResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID", BigInteger.class)); + assertEquals("Arnold", tuple.get("FIRSTNAME", String.class)); + }); + } + + + @Test + public void testStreamedAliasGetterWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleAliasedResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1")); + assertEquals("Arnold", tuple.get("ALIAS2")); + }); + } + + @Test + public void testStreamedAliasGetterWithClassWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getTupleAliasedResult(entityManager); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1", BigInteger.class)); + assertEquals("Arnold", tuple.get("ALIAS2", String.class)); + }); + } + + @Test + public void testStreamedToArrayShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getStreamedTupleResult(entityManager); + Object[] result = tuples.get(0).toArray(); + assertArrayEquals(new Object[]{BigInteger.ONE, "Arnold"}, result); + }); + } + + @Test + public void testStreamedGetElementsShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getStreamedTupleResult(entityManager); + List> result = tuples.get(0).getElements(); + assertEquals(2, result.size()); + assertEquals(BigInteger.class, result.get(0).getJavaType()); + assertEquals("ID", result.get(0).getAlias()); + assertEquals(String.class, result.get(1).getJavaType()); + assertEquals("FIRSTNAME", result.get(1).getAlias()); + }); + } + + @Test + public void testStreamedPositionalGetterWithNamedNativeQueryShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0)); + assertEquals("Arnold", tuple.get(1)); + }); + } + + @Test + public void testStreamedPositionalGetterWithNamedNativeQueryWithClassShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get(0, BigInteger.class)); + assertEquals("Arnold", tuple.get(1, String.class)); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenLessThanZeroGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(-1); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenTupleSizePositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(2); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test(expected = IllegalArgumentException.class) + public void testStreamedPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenExceedingPositionGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get(3); + }); + } + + + @Test + public void testStreamedAliasGetterWithNamedNativeQueryWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID")); + assertEquals("Arnold", tuple.get("FIRSTNAME")); + }); + } + + @Test + public void testStreamedAliasGetterWithNamedNativeQueryShouldWorkWithoutExplicitAliasWhenLowerCaseAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get("id"); + }); + } + + @Test(expected = IllegalArgumentException.class) + public void testStreamedAliasGetterWithNamedNativeQueryShouldThrowExceptionWithoutExplicitAliasWhenWrongAliasGiven() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + tuple.get("e"); + }); + } + + @Test + public void testStreamedAliasGetterWithNamedNativeQueryWithClassWithoutExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ID", BigInteger.class)); + assertEquals("Arnold", tuple.get("FIRSTNAME", String.class)); + }); + } + + + @Test + public void testStreamedAliasGetterWithNamedNativeQueryWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard_with_alias"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1")); + assertEquals("Arnold", tuple.get("ALIAS2")); + }); + } + + @Test + public void testStreamedAliasGetterWithNamedNativeQueryWithClassWithExplicitAliasShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List result = getStreamedNamedTupleResult(entityManager, "standard_with_alias"); + Tuple tuple = result.get(0); + assertEquals(BigInteger.ONE, tuple.get("ALIAS1", BigInteger.class)); + assertEquals("Arnold", tuple.get("ALIAS2", String.class)); + }); + } + + @Test + public void testStreamedToArrayShouldWithNamedNativeQueryWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getStreamedNamedTupleResult(entityManager, "standard"); + Object[] result = tuples.get(0).toArray(); + assertArrayEquals(new Object[]{BigInteger.ONE, "Arnold"}, result); + }); + } + + @Test + public void testStreamedGetElementsWithNamedNativeQueryShouldWorkProperly() { + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getStreamedNamedTupleResult(entityManager, "standard"); + List> result = tuples.get(0).getElements(); + assertEquals(2, result.size()); + assertEquals(BigInteger.class, result.get(0).getJavaType()); + assertEquals("ID", result.get(0).getAlias()); + assertEquals(String.class, result.get(1).getJavaType()); + assertEquals("FIRSTNAME", result.get(1).getAlias()); + }); + } + + @Test + @TestForIssue(jiraKey = "HHH-11897") + public void testGetElementsShouldNotThrowExceptionWhenResultContainsNullValue() { + doInJPA(this::entityManagerFactory, entityManager -> { + User user = entityManager.find(User.class, 1L); + user.firstName = null; + }); + doInJPA(this::entityManagerFactory, entityManager -> { + List tuples = getTupleResult(entityManager); + final Tuple tuple = tuples.get(0); + List> result = tuple.getElements(); + assertEquals(2, result.size()); + final TupleElement firstTupleElement = result.get(0); + assertEquals(BigInteger.class, firstTupleElement.getJavaType()); + assertEquals("ID", firstTupleElement.getAlias()); + assertEquals(BigInteger.valueOf(1L), tuple.get(firstTupleElement.getAlias())); + final TupleElement secondTupleElement = result.get(1); + assertEquals(Object.class, secondTupleElement.getJavaType()); + assertEquals("FIRSTNAME", secondTupleElement.getAlias()); + assertNull(tuple.get(secondTupleElement.getAlias())); + }); + } + + @SuppressWarnings("unchecked") + private List getTupleAliasedResult(EntityManager entityManager) { + Query query = entityManager.createNativeQuery("SELECT id AS alias1, firstname AS alias2 FROM users", Tuple.class); + return (List) query.getResultList(); + } + + @SuppressWarnings("unchecked") + private List getStreamedTupleAliasedResult(EntityManager entityManager) { + NativeQueryImplementor query = (NativeQueryImplementor) entityManager.createNativeQuery("SELECT id AS alias1, firstname AS alias2 FROM users", Tuple.class); + return (List) query.stream().collect(Collectors.toList()); + } + + @SuppressWarnings("unchecked") + private List getTupleResult(EntityManager entityManager) { + Query query = entityManager.createNativeQuery("SELECT id, firstname FROM users", Tuple.class); + return (List) query.getResultList(); + } + + @SuppressWarnings("unchecked") + private List getTupleNamedResult(EntityManager entityManager, String name) { + return entityManager.createNamedQuery(name, Tuple.class).getResultList(); + } + + @SuppressWarnings("unchecked") + private List getStreamedTupleResult(EntityManager entityManager) { + NativeQueryImplementor query = (NativeQueryImplementor) entityManager.createNativeQuery("SELECT id, firstname FROM users", Tuple.class); + return (List) query.stream().collect(Collectors.toList()); + } + + @SuppressWarnings("unchecked") + private List getStreamedNamedTupleResult(EntityManager entityManager, String name) { + return (List)((NativeQueryImplementor) entityManager.createNamedQuery(name, Tuple.class)).stream().collect(Collectors.toList()); + } + + @Entity + @Table(name = "users") + @NamedNativeQueries({ + @NamedNativeQuery( + name = "standard", + query = "SELECT id, firstname FROM users" + ), + @NamedNativeQuery( + name = "standard_with_alias", + query = "SELECT id AS alias1, firstname AS alias2 FROM users" + ) + }) + public static class User { + @Id + private long id; + + private String firstName; + + public User() { + } + + public User(String firstName) { + this.id = 1L; + this.firstName = firstName; + } + } +} 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 9a152a2d7ff3..b41e75228471 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 @@ -132,7 +132,7 @@ protected String getDropSqlScript() { @SuppressWarnings("unchecked") private void doTest(Map settings) { - // We want a fresh db afterQuery emf close + // 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" ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaDatabaseFileGenerationFailureTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaDatabaseFileGenerationFailureTest.java new file mode 100644 index 000000000000..037d87a9135c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaDatabaseFileGenerationFailureTest.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.schemagen; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.regex.Pattern; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.PersistenceException; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.tool.schema.spi.CommandAcceptanceException; +import org.hibernate.tool.schema.spi.SchemaManagementException; + +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * @author Vlad Mihalcea + */ +public class SchemaDatabaseFileGenerationFailureTest { + private Connection connection; + private EntityManagerFactoryBuilder entityManagerFactoryBuilder; + + @Before + public void setUp() throws IOException, SQLException { + connection = Mockito.mock( Connection.class ); + Statement statement = Mockito.mock( Statement.class ); + when( connection.createStatement() ).thenReturn( statement ); + when( statement.execute( anyString() ) ).thenThrow( new SQLException( "Expected" ) ); + + entityManagerFactoryBuilder = Bootstrap.getEntityManagerFactoryBuilder( + buildPersistenceUnitDescriptor(), + getConfig() + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12192") + public void testErrorMessageContainsTheFailingDDLCommand() { + try { + entityManagerFactoryBuilder.generateSchema(); + fail( "Should haave thrown IOException" ); + } + catch (Exception e) { + assertTrue( e instanceof PersistenceException ); + assertTrue( e.getCause() instanceof SchemaManagementException ); + assertTrue( e.getCause().getCause() instanceof CommandAcceptanceException ); + + CommandAcceptanceException commandAcceptanceException = (CommandAcceptanceException) e.getCause() + .getCause(); + + boolean ddlCommandFound = Pattern.compile( "drop table( if exists)? test_entity( if exists)?" ) + .matcher( commandAcceptanceException.getMessage().toLowerCase() ).find(); + assertTrue( "The Exception Message does not contain the DDL command causing the failure", ddlCommandFound ); + + assertTrue( commandAcceptanceException.getCause() instanceof SQLException ); + + SQLException root = (SQLException) e.getCause().getCause().getCause(); + assertEquals( "Expected", root.getMessage() ); + + } + } + + @Entity + @Table(name = "test_entity") + public static class TestEntity { + @Id + private String field; + + private String table; + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + } + + private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { + return new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); + } + + private Map getConfig() { + final Map config = Environment.getProperties(); + config.put( AvailableSettings.HBM2DDL_CONNECTION, connection ); + config.put( AvailableSettings.HBM2DDL_DATABASE_ACTION, "drop" ); + config.put( AvailableSettings.HBM2DDL_HALT_ON_ERROR, true ); + ArrayList classes = new ArrayList<>(); + + classes.addAll( Arrays.asList( new Class[] { TestEntity.class } ) ); + config.put( org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, classes ); + return config; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaScriptFileGenerationFailureTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaScriptFileGenerationFailureTest.java new file mode 100644 index 000000000000..e7f4e6a10375 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaScriptFileGenerationFailureTest.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.jpa.test.schemagen; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.regex.Pattern; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.PersistenceException; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.tool.schema.spi.CommandAcceptanceException; +import org.hibernate.tool.schema.spi.SchemaManagementException; + +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +public class SchemaScriptFileGenerationFailureTest { + private Writer writer; + private EntityManagerFactoryBuilder entityManagerFactoryBuilder; + + + @Before + public void setUp() { + writer = new FailingWriter(); + + entityManagerFactoryBuilder = Bootstrap.getEntityManagerFactoryBuilder( + buildPersistenceUnitDescriptor(), + getConfig() + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12192") + public void testErrorMessageContainsTheFailingDDLCommand() { + try { + entityManagerFactoryBuilder.generateSchema(); + fail( "Should haave thrown IOException" ); + } + catch (Exception e) { + assertTrue( e instanceof PersistenceException ); + assertTrue( e.getCause() instanceof SchemaManagementException ); + assertTrue( e.getCause().getCause() instanceof CommandAcceptanceException ); + + CommandAcceptanceException commandAcceptanceException = (CommandAcceptanceException) e.getCause() + .getCause(); + + + boolean ddlCommandFound = Pattern.compile( "drop table( if exists)? test_entity( if exists)?" ) + .matcher( commandAcceptanceException.getMessage().toLowerCase() ).find(); + assertTrue( "The Exception Message does not contain the DDL command causing the failure", ddlCommandFound ); + + assertTrue( commandAcceptanceException.getCause() instanceof IOException ); + + IOException root = (IOException) e.getCause().getCause().getCause(); + assertEquals( "Expected", root.getMessage() ); + } + } + + @Entity + @Table(name = "test_entity") + public static class TestEntity { + @Id + private String field; + + private String table; + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + } + + private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { + return new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); + } + + private Map getConfig() { + final Map config = Environment.getProperties(); + config.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_SCRIPTS_DROP_TARGET, writer ); + config.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_SCRIPTS_ACTION, "drop-and-create" ); + config.put( AvailableSettings.HBM2DDL_HALT_ON_ERROR, true ); + ArrayList classes = new ArrayList<>(); + + classes.addAll( Arrays.asList( new Class[] { TestEntity.class } ) ); + config.put( org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, classes ); + return config; + } + + public class FailingWriter extends Writer { + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + + } + + @Override + public void flush() throws IOException { + throw new IOException( "Expected" ); + } + + @Override + public void close() throws IOException { + + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaScriptFileGenerationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaScriptFileGenerationTest.java index e79167135e49..c70aca3fbe5b 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaScriptFileGenerationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaScriptFileGenerationTest.java @@ -15,6 +15,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.hibernate.cfg.Environment; import org.hibernate.jpa.boot.spi.Bootstrap; @@ -59,10 +61,12 @@ public void testGenerateSchemaDoesNotProduceTheSameStatementTwice() throws Excep final String fileContent = new String( Files.readAllBytes( createSchema.toPath() ) ).toLowerCase(); - assertThat( fileContent.contains( "create table test_entity" ), is( true ) ); + Pattern createStatementPattern = Pattern.compile( "create( (column|row))? table test_entity" ); + Matcher createStatementMatcher = createStatementPattern.matcher( fileContent ); + assertThat( createStatementMatcher.find(), is( true ) ); assertThat( "The statement 'create table test_entity' is generated twice", - fileContent.replaceFirst( "create table test_entity", "" ).contains( "create table test_entity" ), + createStatementMatcher.find(), is( false ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/AbstractNonOptionalSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/AbstractNonOptionalSecondaryTableTest.java new file mode 100644 index 000000000000..31b8f549d6a7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/AbstractNonOptionalSecondaryTableTest.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.secondarytable; + +import java.util.Arrays; +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.runners.Parameterized; + +public abstract class AbstractNonOptionalSecondaryTableTest extends BaseEntityManagerFunctionalTestCase { + public enum JpaComplianceCachingSetting{ DEFAULT, TRUE, FALSE }; + + private final JpaComplianceCachingSetting jpaComplianceCachingSetting; + + @Parameterized.Parameters(name = "JpaComplianceCachingSetting={0}") + public static Iterable parameters() { + return Arrays.asList( + new Object[][] { + { JpaComplianceCachingSetting.DEFAULT }, + { JpaComplianceCachingSetting.FALSE }, + { JpaComplianceCachingSetting.TRUE } + } + ); + } + + AbstractNonOptionalSecondaryTableTest(JpaComplianceCachingSetting jpaComplianceCachingSetting) { + this.jpaComplianceCachingSetting = jpaComplianceCachingSetting; + } + + @Override + protected void addConfigOptions(Map options) { + switch ( jpaComplianceCachingSetting ) { + case DEFAULT: + // Keep the default (false) + break; + case TRUE: + options.put( + AvailableSettings.JPA_CACHING_COMPLIANCE, + Boolean.TRUE + ); + break; + case FALSE: + options.put( + AvailableSettings.JPA_CACHING_COMPLIANCE, + Boolean.FALSE + ); + break; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/JoinedTableNullNonOptionalSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/JoinedTableNullNonOptionalSecondaryTableTest.java new file mode 100644 index 000000000000..5dbb0e75a513 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/JoinedTableNullNonOptionalSecondaryTableTest.java @@ -0,0 +1,141 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.secondarytable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.SecondaryTable; + +import org.hibernate.annotations.Table; + +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Gail Badner + */ +@RunWith(CustomParameterized.class) +public class JoinedTableNullNonOptionalSecondaryTableTest extends AbstractNonOptionalSecondaryTableTest { + + public JoinedTableNullNonOptionalSecondaryTableTest(JpaComplianceCachingSetting jpaComplianceCachingSetting) { + super( jpaComplianceCachingSetting ); + } + + @Test + public void testRowAddedForNullValue() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = new AnEntity( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + final Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + assertEquals( + 1, + id.intValue() + ); + } + ); + } + + @Test + public void testRowAddedForNullValueInSubclassTable() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + + assertEquals( 1, id.intValue() ); + // assert that a row was inserted into MoreDetails when its property is null + id = (Number) entityManager.createNativeQuery( + "select id from MoreDetails where anotherDetail is null" + ).getSingleResult(); + assertEquals( 1,id.intValue() ); + } + ); + } + + @After + public void cleanupData() { + doInJPA( + this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "delete from Details" ).executeUpdate(); + entityManager.createNativeQuery( "delete from MoreDetails" ).executeUpdate(); + entityManager.createNativeQuery( "delete from AnEntitySubclass" ).executeUpdate(); + entityManager.createNativeQuery( "delete from AnEntity" ).executeUpdate(); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { AnEntity.class, AnEntitySubclass.class }; + } + + @Entity(name = "AnEntity") + @SecondaryTable(name = "Details") + @Table(appliesTo = "Details", optional = false) + @Inheritance(strategy = InheritanceType.JOINED) + public static class AnEntity { + @Id + private int id; + + @Column(name = "aDetail", table="Details") + private String aDetail; + + public AnEntity() { + } + + public AnEntity(int id) { + this.id = id; + } + } + + @Entity(name = "AnEntitySubclass") + @SecondaryTable( name = "MoreDetails" ) + @Table(appliesTo = "MoreDetails", optional = false) + public static class AnEntitySubclass extends AnEntity { + @Column(name = "anotherDetail", table="MoreDetails") + private String anotherDetail; + + public AnEntitySubclass() { + } + + public AnEntitySubclass(int id) { + super( id ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/SingleTableNullNonOptionalSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/SingleTableNullNonOptionalSecondaryTableTest.java new file mode 100644 index 000000000000..13bceb453503 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/SingleTableNullNonOptionalSecondaryTableTest.java @@ -0,0 +1,175 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.secondarytable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.SecondaryTable; + +import org.hibernate.annotations.Table; + +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Gail Badner + */ +@RunWith(CustomParameterized.class) +public class SingleTableNullNonOptionalSecondaryTableTest extends AbstractNonOptionalSecondaryTableTest { + + public SingleTableNullNonOptionalSecondaryTableTest(JpaComplianceCachingSetting jpaComplianceCachingSetting) { + super( jpaComplianceCachingSetting ); + } + + @Test + public void testRowAddedForNullValue() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = new AnEntity( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + final Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + assertEquals( 1, id.intValue() ); + } + ); + } + + @Test + public void testRowAddedForNullValueInSubclassTable() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + assertEquals( 1, id.intValue() ); + // assert that a row was inserted into MoreDetails when its property is null + id = (Number) entityManager.createNativeQuery( + "select id from MoreDetails where anotherDetail is null" + ).getSingleResult(); + assertEquals( 1, id.intValue() ); + } + ); + } + + @Test + public void testEntityWithBadDataInBaseSecondaryTableIgnored() { + // Not sure we really want to support this; + // It only works with single-table inheritance. + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + + doInJPA( + this::entityManagerFactory, entityManager -> { + // Delete the data in a secondary table + entityManager.createNativeQuery( "delete from Details where id = 1" ).executeUpdate(); + // The entity with bad data should be ignored. + AnEntitySubclass anEntity = entityManager.find( AnEntitySubclass.class, 1 ); + assertNull( anEntity ); + } + ); + } + + @Test + public void testEntityWithBadDataInSubclassSecondaryTableIgnored() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + + doInJPA( + this::entityManagerFactory, entityManager -> { + // Delete the data in a secondary table + entityManager.createNativeQuery( "delete from MoreDetails where id = 1" ).executeUpdate(); + // The entity with bad data should be ignored. + AnEntitySubclass anEntity = entityManager.find( AnEntitySubclass.class, 1 ); + assertNull( anEntity ); + } + ); + } + + @After + public void cleanupData() { + doInJPA( + this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "delete from Details" ).executeUpdate(); + entityManager.createNativeQuery( "delete from MoreDetails" ).executeUpdate(); + entityManager.createNativeQuery( "delete from AnEntity" ).executeUpdate(); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { AnEntity.class, AnEntitySubclass.class }; + } + + @Entity(name = "AnEntity") + @SecondaryTable(name = "Details") + @Table(appliesTo = "Details", optional = false) + public static class AnEntity { + @Id + private int id; + + @Column(name = "aDetail", table="Details") + private String aDetail; + + public AnEntity() { + } + + public AnEntity(int id) { + this.id = id; + } + } + + @Entity(name = "AnEntitySubclass") + @SecondaryTable( name = "MoreDetails" ) + @Table(appliesTo = "MoreDetails", optional = false) + public static class AnEntitySubclass extends AnEntity { + @Column(name = "anotherDetail", table="MoreDetails") + private String anotherDetail; + + public AnEntitySubclass() { + } + + public AnEntitySubclass(int id) { + super( id ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/serialization/EntityManagerDeserializationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/serialization/EntityManagerDeserializationTest.java new file mode 100644 index 000000000000..5fe9a51e9316 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/serialization/EntityManagerDeserializationTest.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 . + */ +package org.hibernate.jpa.test.serialization; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.PersistenceException; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +public class EntityManagerDeserializationTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {TestEntity.class}; + } + + @Test(expected = PersistenceException.class) + @TestForIssue(jiraKey = "HHH-11555") + public void deserializedEntityManagerPersistenceExceptionManagementTest() throws IOException { + EntityManager entityManager = getOrCreateEntityManager(); + final EntityManager deserializedSession = spoofSerialization( entityManager ); + try { + deserializedSession.getTransaction().begin(); + TestEntity entity = new TestEntity(); + entity.setName( "Andrea" ); + deserializedSession.persist( entity ); + entity.setName( null ); + deserializedSession.flush(); + } + finally { + deserializedSession.getTransaction().rollback(); + deserializedSession.close(); + entityManager.close(); + } + } + + private EntityManager spoofSerialization(EntityManager entityManager) throws IOException { + try { + // Serialize the incoming out to memory + ByteArrayOutputStream serBaOut = new ByteArrayOutputStream(); + ObjectOutputStream serOut = new ObjectOutputStream( serBaOut ); + + serOut.writeObject( entityManager ); + + // Now, re-constitute the model from memory + ByteArrayInputStream serBaIn = + new ByteArrayInputStream( serBaOut.toByteArray() ); + ObjectInputStream serIn = new ObjectInputStream( serBaIn ); + + EntityManager outgoing = (EntityManager) serIn.readObject(); + + return outgoing; + } + catch (ClassNotFoundException cnfe) { + throw new IOException( "Unable to locate class on reconstruction" ); + } + } + + @Entity + @Table(name = "TEST_ENTITY") + public static class TestEntity { + @Id + @GeneratedValue + private Long id; + + @Column(nullable = false) + String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/statistics/SessionCloseCountTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/statistics/SessionCloseCountTest.java new file mode 100644 index 000000000000..52d7f094c367 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/statistics/SessionCloseCountTest.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.statistics; + +import java.util.Map; +import javax.persistence.EntityManager; + +import org.hibernate.SessionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11602") +public class SessionCloseCountTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Test + public void sessionCountClosetShouldBeIncrementedWhenTheEntityManagerIsClosed() { + final SessionFactoryImplementor entityManagerFactory = entityManagerFactory(); + final Statistics statistics = entityManagerFactory.unwrap( SessionFactory.class ).getStatistics(); + EntityManager em = createEntityManager(); + assertThat( "The session close count should be zero", statistics.getSessionCloseCount(), is( 0L ) ); + + em.close(); + + assertThat( "The session close count was not incremented", statistics.getSessionCloseCount(), is( 1L ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/CloseEntityManagerWithActiveTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/CloseEntityManagerWithActiveTransactionTest.java index b06398fbf743..bd81641e846c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/CloseEntityManagerWithActiveTransactionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/CloseEntityManagerWithActiveTransactionTest.java @@ -50,6 +50,7 @@ protected void addConfigOptions(Map options) { super.addConfigOptions( options ); TestingJtaBootstrap.prepare( options ); options.put( AvailableSettings.TRANSACTION_TYPE, "JTA" ); + options.put( org.hibernate.cfg.AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/ClosedEntityManagerWithJpaTransactionComplianceTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/ClosedEntityManagerWithJpaTransactionComplianceTest.java new file mode 100644 index 000000000000..46b037f4ee7b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/ClosedEntityManagerWithJpaTransactionComplianceTest.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 . + */ +package org.hibernate.jpa.test.transaction; + +import java.util.Map; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.hamcrest.CoreMatchers; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +public class ClosedEntityManagerWithJpaTransactionComplianceTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + } + + @Test + public void shouldReturnNotNullValueTest() { + EntityManager em = createEntityManager(); + em.close(); + assertThat( em.getTransaction(), CoreMatchers.notNullValue() ); + } + + @Test + public void testCallTransactionIsActive() { + EntityManager em = createEntityManager(); + em.close(); + assertFalse( em.getTransaction().isActive() ); + } + + @Test + public void testCallTransactionIsActive2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + assertFalse( transaction.isActive() ); + } + + @Test(expected = IllegalStateException.class) + public void testCallCommit() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().commit(); + } + + @Test(expected = IllegalStateException.class) + public void testCallCommit2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.commit(); + } + + @Test(expected = IllegalStateException.class) + public void testCallBegin() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().begin(); + } + + @Test(expected = IllegalStateException.class) + public void testCallBegin2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.begin(); + } + + @Test(expected = IllegalStateException.class) + public void testCallSetRollBackOnly() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().setRollbackOnly(); + } + + @Test(expected = IllegalStateException.class) + public void testCallSetRollBackOnly2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.setRollbackOnly(); + } + + @Test(expected = IllegalStateException.class) + public void testCallRollBack() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().rollback(); + } + + @Test(expected = IllegalStateException.class) + public void testCallRollBack2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.rollback(); + } + + @Test(expected = IllegalStateException.class) + public void testCallGetRollBackOnly() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().getRollbackOnly(); + } + + @Test(expected = IllegalStateException.class) + public void testCallGetRollBackOnly2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.getRollbackOnly(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/ClosedEntityManagerWithoutJpaTransactionComplianceTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/ClosedEntityManagerWithoutJpaTransactionComplianceTest.java new file mode 100644 index 000000000000..1bbd01801106 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/ClosedEntityManagerWithoutJpaTransactionComplianceTest.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.transaction; + +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.hamcrest.CoreMatchers; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +public class ClosedEntityManagerWithoutJpaTransactionComplianceTest extends BaseEntityManagerFunctionalTestCase { + @Test + public void shouldReturnNotNullValueTest() { + EntityManager em = createEntityManager(); + em.close(); + assertThat( em.getTransaction(), CoreMatchers.notNullValue() ); + } + + @Test + public void testCallTransactionIsActive() { + EntityManager em = createEntityManager(); + em.close(); + assertFalse( em.getTransaction().isActive() ); + } + + @Test + public void testCallTransactionIsActive2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + assertFalse( transaction.isActive() ); + } + + @Test(expected = IllegalStateException.class) + public void testCallCommit() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().commit(); + } + + @Test(expected = IllegalStateException.class) + public void testCallCommit2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.commit(); + } + + @Test(expected = IllegalStateException.class) + public void testCallBegin() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().begin(); + } + + @Test(expected = IllegalStateException.class) + public void testCallBegin2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.begin(); + } + + @Test + public void testCallSetRollBackOnly() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().setRollbackOnly(); + } + + @Test + public void testCallSetRollBackOnly2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.setRollbackOnly(); + } + + @Test + public void testCallRollBack() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction().rollback(); + } + + @Test + public void testCallRollBack2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + transaction.rollback(); + } + + @Test + public void testCallGetRollBackOnly() { + EntityManager em = createEntityManager(); + em.close(); + assertFalse( em.getTransaction().getRollbackOnly() ); + } + + @Test + public void testCallGetRollBackOnly2() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + em.close(); + assertFalse( transaction.getRollbackOnly() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/FlushAndTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/FlushAndTransactionTest.java index bb96e02e4655..1037e9940b81 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/FlushAndTransactionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/FlushAndTransactionTest.java @@ -7,6 +7,7 @@ package org.hibernate.jpa.test.transaction; import java.util.List; +import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.persistence.OptimisticLockException; @@ -16,6 +17,7 @@ import javax.persistence.TransactionRequiredException; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.stat.Statistics; @@ -226,6 +228,7 @@ public void testRollbackOnlyOnPersistenceException() throws Exception { catch ( PersistenceException e ) { //success } + try { em.getTransaction().commit(); fail( "Commit should be rollbacked" ); @@ -288,13 +291,6 @@ public void testRollbackClearPC() throws Exception { em.close(); } - @Override - public Class[] getAnnotatedClasses() { - return new Class[] { - Book.class - }; - } - @Test public void testSetRollbackOnlyAndFlush() throws Exception { Book book = new Book(); @@ -313,4 +309,17 @@ public void testSetRollbackOnlyAndFlush() throws Exception { em.close(); } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/GetTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/GetTransactionTest.java new file mode 100644 index 000000000000..ef3fdb60fecb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/GetTransactionTest.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.transaction; + +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Andrea Boriero + */ +public class GetTransactionTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void testMultipleCallsReturnTheSameTransaction() { + EntityManager em = createEntityManager(); + EntityTransaction t = em.getTransaction(); + assertSame( t, em.getTransaction() ); + assertFalse( t.isActive() ); + t.begin(); + assertSame( t, em.getTransaction() ); + assertTrue( t.isActive() ); + t.commit(); + assertSame( t, em.getTransaction() ); + assertFalse( t.isActive() ); + em.close(); + assertSame( t, em.getTransaction() ); + assertFalse( t.isActive() ); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaGetTransactionThrowsExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaGetTransactionThrowsExceptionTest.java new file mode 100644 index 000000000000..003a69beaca3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaGetTransactionThrowsExceptionTest.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.jpa.test.transaction; + +import java.util.Map; + +import javax.persistence.EntityManager; + +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.junit.Test; + +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +public class JtaGetTransactionThrowsExceptionTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + TestingJtaBootstrap.prepare( options ); + options.put( AvailableSettings.TRANSACTION_TYPE, "JTA" ); + options.put( org.hibernate.cfg.AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + } + + @Test(expected = IllegalStateException.class) + @TestForIssue( jiraKey = "HHH-12487") + public void onCloseEntityManagerTest() { + EntityManager em = createEntityManager(); + em.close(); + em.getTransaction(); + fail( "Calling getTransaction on a JTA entity manager should throw an IllegalStateException" ); + } + + @Test(expected = IllegalStateException.class) + @TestForIssue(jiraKey = "HHH-12487") + public void onOpenEntityManagerTest() { + EntityManager em = createEntityManager(); + try { + em.getTransaction(); + fail( "Calling getTransaction on a JTA entity manager should throw an IllegalStateException" ); + } + finally { + em.close(); + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaReusingEntityTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaReusingEntityTransactionTest.java new file mode 100644 index 000000000000..a46e9e305d12 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaReusingEntityTransactionTest.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.jpa.test.transaction; + +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +public class JtaReusingEntityTransactionTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + TestingJtaBootstrap.prepare( options ); + options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); + } + + @Test + public void entityTransactionShouldBeReusableTest() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = null; + try { + transaction = em.getTransaction(); + em.persist( new TestEntity() ); + transaction.begin(); + transaction.commit(); + transaction.begin(); + em.persist( new TestEntity() ); + transaction.commit(); + } + finally { + if ( transaction != null && transaction.isActive() ) { + transaction.rollback(); + } + em.close(); + } + em = createEntityManager(); + try { + transaction = em.getTransaction(); + transaction.begin(); + List results = em.createQuery( "from TestEntity" ).getResultList(); + assertThat( results.size(), is( 2 ) ); + transaction.commit(); + } + finally { + if ( transaction != null && transaction.isActive() ) { + transaction.rollback(); + } + em.close(); + } + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + @GeneratedValue + private Long id; + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/SynchronizationTypeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/SynchronizationTypeTest.java index 83a36683284a..ed6a6cde378d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/SynchronizationTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/SynchronizationTypeTest.java @@ -63,7 +63,7 @@ public void testUnSynchronizedExplicitJoinHandling() throws Exception { @Test public void testImplicitJoining() throws Exception { - // here the transaction is started beforeQuery the EM is opened. Because the SynchronizationType is UNSYNCHRONIZED + // here the transaction is started before the EM is opened. Because the SynchronizationType is UNSYNCHRONIZED // though, it should not auto join the transaction assertFalse( "setup problem", JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionCommitFailureTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionCommitFailureTest.java index 69ae41a1b273..1d679c8815f5 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionCommitFailureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionCommitFailureTest.java @@ -15,7 +15,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; -import javax.persistence.PersistenceException; import javax.persistence.RollbackException; import org.hibernate.cfg.Environment; @@ -25,86 +24,137 @@ import org.hibernate.jpa.test.PersistenceUnitDescriptorAdapter; import org.hibernate.jpa.test.SettingsGenerator; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; /** * @author Vlad Mihalcea */ -public class TransactionCommitFailureTest { +public class TransactionCommitFailureTest extends BaseUnitTestCase { - public static final String COMMIT_FAILURE = "Commit failed!"; + public static final String COMMIT_FAILURE = "Error while committing the transaction"; - private static final AtomicBoolean transactionFailureTrigger = new AtomicBoolean( false ); + private static AtomicBoolean transactionFailureTrigger; + private static AtomicBoolean connectionIsOpen; - @Test - public void testConfiguredInterceptor() { - Map settings = basicSettings(); - EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); - EntityManager em = emf.createEntityManager(); + private EntityManagerFactory emf; - try { - em.getTransaction().begin(); - transactionFailureTrigger.set( true ); - em.getTransaction().commit(); - } + @Before + public void setUp() { + // static variables need to be initialized before the EMF is set up, because they can be referenced during EMF setup via the connection provider. + transactionFailureTrigger = new AtomicBoolean(); + connectionIsOpen = new AtomicBoolean(); + + final Map settings = basicSettings(); + emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build(); + } + + @After + public void tearDown() { + emf.close(); + } + + @Test + public void assertConnectionIsReleasedIfCommitFails() { + EntityManager em = emf.createEntityManager(); + + try { + em.getTransaction().begin(); + transactionFailureTrigger.set( true ); + em.getTransaction().commit(); + } catch (RollbackException e) { - assertEquals( COMMIT_FAILURE, e.getCause().getMessage() ); + assertEquals( COMMIT_FAILURE, e.getLocalizedMessage()); + } + finally { + if ( em.getTransaction() != null && em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + em.close(); + } + + assertEquals( "The connection was not released", false, connectionIsOpen.get() ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12285") + public void assertConnectionIsReleasedIfRollbackFails() { + EntityManager em = emf.createEntityManager(); + try { + em.getTransaction().begin(); + assertEquals( true, connectionIsOpen.get() ); + transactionFailureTrigger.set( true ); + em.getTransaction().rollback(); + fail( "Rollback failure, Exception expected" ); + } + catch (Exception pe) { + //expected } finally { - if ( em.getTransaction() != null && em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - em.close(); - emf.close(); - } - } - - protected Map basicSettings() { + em.close(); + } + + assertEquals( "The connection was not released", false, connectionIsOpen.get() ); + } + + private Map basicSettings() { return SettingsGenerator.generateSettings( Environment.HBM2DDL_AUTO, "create-drop", Environment.USE_NEW_ID_GENERATOR_MAPPINGS, "true", Environment.DIALECT, Dialect.getDialect().getClass().getName(), - Environment.CONNECTION_PROVIDER, ProxyConnectionProvider.class.getName() + Environment.CONNECTION_PROVIDER, ProxyConnectionProvider.class.getName() ); - } + } public static class ProxyConnectionProvider extends DriverManagerConnectionProviderImpl { @Override public Connection getConnection() throws SQLException { Connection delegate = super.getConnection(); + connectionIsOpen.set( true ); return (Connection) Proxy.newProxyInstance( this.getClass().getClassLoader(), - new Class[]{Connection.class}, - new ConnectionInvocationHandler(delegate)); + new Class[] { Connection.class }, + new ConnectionInvocationHandler( delegate ) + ); + } + + @Override + public void closeConnection(Connection conn) throws SQLException { + super.closeConnection( conn ); + connectionIsOpen.set( false ); } } - private static class ConnectionInvocationHandler implements InvocationHandler { - - private final Connection delegate; - - public ConnectionInvocationHandler(Connection delegate) { - this.delegate = delegate; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if("commit".equals( method.getName() )) { - if ( transactionFailureTrigger.get() ) { - throw new PersistenceException( COMMIT_FAILURE ); - } - } - else if("rollback".equals( method.getName() )) { - if ( transactionFailureTrigger.get() ) { - transactionFailureTrigger.set( false ); - throw new PersistenceException( "Rollback failed!" ); - } - } - return method.invoke(delegate, args); - } - } + private static class ConnectionInvocationHandler implements InvocationHandler { + + private final Connection delegate; + + public ConnectionInvocationHandler(Connection delegate) { + this.delegate = delegate; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ( "commit".equals( method.getName() ) ) { + if ( transactionFailureTrigger.get() ) { + throw new SQLException( COMMIT_FAILURE ); + } + } + else if ( "rollback".equals( method.getName() ) ) { + if ( transactionFailureTrigger.get() ) { + transactionFailureTrigger.set( false ); + throw new SQLException( "Rollback failed!" ); + } + } + return method.invoke( delegate, args ); + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java index 4bcf15f86538..c444e653b65a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java @@ -72,7 +72,7 @@ public void testExplicitJoiningTransactionRequiredException() throws Exception { @Test public void testImplicitJoining() throws Exception { - // here the transaction is started beforeQuery the EM is opened... + // here the transaction is started before the EM is opened... assertFalse( JtaStatusHelper.isActive( TestingJtaPlatformImpl.INSTANCE.getTransactionManager() ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java new file mode 100644 index 000000000000..4342954524f2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java @@ -0,0 +1,142 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Before; +import org.junit.Rule; + +import org.jboss.logging.Logger; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +public abstract class AbstractJtaBatchTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AbstractBatchImpl.class.getName() ) + ); + + protected Triggerable triggerable; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + TestingJtaBootstrap.prepare( options ); + options.put( BatchBuilderInitiator.BUILDER, getBatchBuilderClassName() ); + options.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); + options.put( AvailableSettings.STATEMENT_BATCH_SIZE, "50" ); + } + + @Before + public void setUp() { + triggerable = logInspection.watchForLogMessages( + "HHH000352: Unable to release batch statement..." ); + triggerable.reset(); + } + + protected void assertAllStatementsAreClosed(List statements) { + statements.forEach( statement -> { + try { + assertThat( "A PreparedStatement has not been closed", statement.isClosed(), is( true ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); + } + + protected abstract String getBatchBuilderClassName(); + + @Entity(name = "Comment") + @Table(name = "COMMENTS") + public static class Comment { + private Long id; + private String message; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + + @Entity(name = "EventLog") + public static class EventLog { + private Long id; + private String message; + + @Id + @GeneratedValue(generator = "eventLogIdGenerator") + @GenericGenerator(name = "eventLogIdGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { + @Parameter(name = "table_name", value = "primaryKeyPools"), + @Parameter(name = "segment_value", value = "eventLog"), + @Parameter(name = "optimizer", value = "pooled"), + @Parameter(name = "increment_size", value = "500"), + @Parameter(name = "initial_value", value = "1") + }) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java new file mode 100644 index 000000000000..ee38b55626ad --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java @@ -0,0 +1,170 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Gail Badner + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithFailingBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Test + public void testAllStatementsAreClosedInCaseOfBatchExecutionFailure() throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + + try { + em.persist( comment ); + transactionManager.commit(); + } + catch (Exception expected) { + //expected + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + } + + assertThat( + "AbstractBatchImpl#releaseStatements() has not been callled", + testBatch.calledReleaseStatements, + is( true ) + ); + assertAllStatementsAreClosed( testBatch.createdStatements ); + assertStatementsListIsCleared(); + } + finally { + + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createdStatements = new ArrayList<>(); + private boolean calledReleaseStatements; + + private String currentStatementSql; + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + @Override + public PreparedStatement getBatchStatement(String sql, boolean callable) { + currentStatementSql = sql; + PreparedStatement batchStatement = super.getBatchStatement( sql, callable ); + createdStatements.add( batchStatement ); + return batchStatement; + } + + @Override + public void addToBatch() { + // Implementations really should call abortBatch() before throwing an exception. + // Purposely skipping the call to abortBatch() to ensure that Hibernate works properly when + // a legacy implementation does not call abortBatch(). + throw sqlExceptionHelper().convert( + new SQLException( "fake SQLException" ), + "could not perform addBatch", + currentStatementSql + ); + } + + @Override + protected void releaseStatements() { + super.releaseStatements(); + calledReleaseStatements = true; + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java new file mode 100644 index 000000000000..da0459e800a6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java @@ -0,0 +1,147 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithStatementsBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Test + public void testUnableToReleaseStatementMessageIsNotLogged() + throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + em.persist( comment ); + + transactionManager.commit(); + assertStatementsListIsCleared(); + assertAllStatementsAreClosed( testBatch.createtdStatements ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + + em = createEntityManager(); + + try { + transactionManager.begin(); + Integer savedComments + = em.createQuery( "from Comment" ).getResultList().size(); + assertThat( savedComments, is( 1 ) ); + + Integer savedEventLogs + = em.createQuery( "from EventLog" ).getResultList().size(); + assertThat( savedEventLogs, is( 2 ) ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createtdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createtdStatements = new ArrayList<>(); + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + protected void releaseStatements() { + createtdStatements.addAll( getStatements().values() ); + super.releaseStatements(); + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/SchemaToolingAutoActionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/SchemaToolingAutoActionTests.java new file mode 100644 index 000000000000..cc581d334645 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/SchemaToolingAutoActionTests.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap; + +import java.util.Properties; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.tool.schema.Action; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class SchemaToolingAutoActionTests { + @Test + public void testLegacySettingAsAction() { + final Properties props = new Properties(); + props.put( AvailableSettings.HBM2DDL_AUTO, Action.CREATE_DROP ); + + final SchemaManagementToolCoordinator.ActionGrouping actionGrouping = SchemaManagementToolCoordinator.ActionGrouping.interpret( props ); + + assertThat( actionGrouping.getDatabaseAction(), is( Action.CREATE_DROP ) ); + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitInfoTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitInfoTests.java new file mode 100644 index 000000000000..1c0ca4544421 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitInfoTests.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap.jpa; + +import java.util.Collections; +import java.util.Map; +import javax.persistence.EntityManagerFactory; +import javax.persistence.spi.PersistenceProvider; +import javax.sql.DataSource; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.testing.jdbc.DataSourceStub; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.util.jpa.PersistenceUnitInfoAdapter; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ +public class PersistenceUnitInfoTests extends BaseUnitTestCase { + @Test + @TestForIssue( jiraKey = "HHH-13432" ) + public void testNonJtaDataExposedAsProperty() { + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getNonJtaDataSource() { + return puDataSource; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + Collections.emptyMap() + ); + + // first let's check the DataSource used in the EMF... + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + final DatasourceConnectionProviderImpl dsCp = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsCp.getDataSource(), is( puDataSource ) ); + + // now let's check that it is exposed via the EMF properties + // - note : the spec does not indicate that this should work, but + // it worked this way in previous versions + final Object o = emf.getProperties().get( AvailableSettings.JPA_NON_JTA_DATASOURCE ); + assertThat( o, is( puDataSource ) ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13432" ) + public void testJtaDataExposedAsProperty() { + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getJtaDataSource() { + return puDataSource; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + Collections.emptyMap() + ); + + // first let's check the DataSource used in the EMF... + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + final DatasourceConnectionProviderImpl dsCp = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsCp.getDataSource(), is( puDataSource ) ); + + // now let's check that it is exposed via the EMF properties + // - again, the spec does not indicate that this should work, but + // it worked this way in previous versions + final Map properties = emf.getProperties(); + final Object o = properties.get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertEquals( puDataSource, o ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java new file mode 100644 index 000000000000..422ee62c7e15 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java @@ -0,0 +1,544 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap.jpa; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import javax.persistence.metamodel.EntityType; +import javax.persistence.spi.PersistenceProvider; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.sql.DataSource; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.env.ConnectionProviderBuilder; +import org.hibernate.testing.jdbc.DataSourceStub; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.util.jpa.DelegatingPersistenceUnitInfo; +import org.hibernate.testing.util.jpa.PersistenceUnitInfoAdapter; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class PersistenceUnitOverridesTests extends BaseUnitTestCase { + + @Test + public void testPassingIntegrationJpaJdbcOverrides() { + + // the integration overrides say to use the "db2" JPA connection settings (which should override the persistence unit values) + final Map integrationOverrides = ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ); + + final EntityManagerFactory emf = new HibernatePersistenceProvider().createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter() { + @Override + public Properties getProperties() { + // effectively, the `persistence.xml` defines `db1` as the connection settings + return ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db1" ); + } + }, + integrationOverrides + ); + + try { + final Map properties = emf.getProperties(); + + final Object hibernateJdbcDriver = properties.get( AvailableSettings.URL ); + assertThat( hibernateJdbcDriver, notNullValue() ); + + final Object jpaJdbcDriver = properties.get( AvailableSettings.JPA_JDBC_URL ); + assertThat( (String) jpaJdbcDriver, containsString( "db2" ) ); + } + finally { + emf.close(); + } + } + + @Test + public void testPassingIntegrationJtaDataSourceOverrideForJpaJdbcSettings() { + final PersistenceUnitInfoAdapter puInfo = new PersistenceUnitInfoAdapter( + ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ) + ); + + final DataSource integrationDataSource = new DataSourceStub( "integrationDataSource" ); + + final HibernatePersistenceProvider provider = new HibernatePersistenceProvider(); + puInfo.getProperties().setProperty( AvailableSettings.HQL_BULK_ID_STRATEGY, MultiTableBulkIdStrategyStub.class.getName() ); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + puInfo, + Collections.singletonMap( AvailableSettings.JPA_JTA_DATASOURCE, integrationDataSource ) + ); + + try { + // first let's check the DataSource used in the EMF... + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + final DatasourceConnectionProviderImpl dsCp = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsCp.getDataSource(), is( integrationDataSource ) ); + + // now let's check that it is exposed via the EMF properties + // - note : the spec does not indicate that this should work, but + // it worked this way in previous versions + final Object jtaDs = emf.getProperties().get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertThat( jtaDs, is( integrationDataSource ) ); + + // Additionally, we should have set Hibernate's DATASOURCE setting + final Object hibDs = emf.getProperties().get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertThat( hibDs, is( integrationDataSource ) ); + + // Make sure the non-jta-data-source setting was cleared or otherwise null + final Object nonJtaDs = emf.getProperties().get( AvailableSettings.JPA_NON_JTA_DATASOURCE ); + assertThat( nonJtaDs, nullValue() ); + } + finally { + emf.close(); + } + } + + @Test + public void testPassingIntegrationJpaJdbcOverrideForJtaDataSourceProperty() { + PersistenceProvider provider = new HibernatePersistenceProvider() { + @Override + public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map integrationOverrides) { + return super.createContainerEntityManagerFactory( + new DelegatingPersistenceUnitInfo( info ) { + + // inject a JPA JTA DataSource setting into the PU + final DataSource puDataSource; + final Properties puProperties; + + { + puDataSource = new DataSourceStub( "puDataSource" ); + + puProperties = new Properties(); + puProperties.putAll( info.getProperties() ); + puProperties.put( AvailableSettings.JPA_JTA_DATASOURCE, puDataSource ); + } + + @Override + public DataSource getJtaDataSource() { + return null; + } + + @Override + public DataSource getNonJtaDataSource() { + return null; + } + + @Override + public Properties getProperties() { + return puProperties; + } + }, + integrationOverrides + ); + } + }; + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter(), + // however, provide JPA connection settings as "integration settings", which according to JPA spec should override the persistence unit values. + // - note that it is unclear in the spec whether JDBC value in the integration settings should override + // a JTA DataSource (nor the reverse). However, that is a useful thing to support + ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ) + ); + + try { + final Map properties = emf.getProperties(); + + final Object hibernateJdbcDriver = properties.get( AvailableSettings.URL ); + assertThat( hibernateJdbcDriver, notNullValue() ); + + final Object jpaJdbcDriver = properties.get( AvailableSettings.JPA_JDBC_URL ); + assertThat( (String) jpaJdbcDriver, containsString( "db2" ) ); + + // see if the values had the affect to adjust the `ConnectionProvider` used + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DriverManagerConnectionProviderImpl.class ) ); + } + finally { + emf.close(); + } + } + + @Test +// @FailureExpected( +// jiraKey = "HHH-12858", +// message = "Even though the JDBC settings override a DataSource *property*, it" + +// " does not override a DataSource defined using the dedicated persistence.xml element" +// ) + public void testPassingIntegrationJpaJdbcOverridesForJtaDataSourceElement() { + PersistenceProvider provider = new HibernatePersistenceProvider() { + @Override + public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map integrationOverrides) { + return super.createContainerEntityManagerFactory( + new DelegatingPersistenceUnitInfo( info ) { + // inject a JPA JTA DataSource setting into the PU + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + + @Override + public DataSource getJtaDataSource() { + return puDataSource; + } + }, + integrationOverrides + ); + } + }; + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter(), + // however, provide JPA connection settings as "integration settings", which according to JPA spec should override the persistence unit values. + // - note that it is unclear in the spec whether JDBC value in the integration settings should override + // a JTA DataSource (nor the reverse). However, that is a useful thing to support + ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ) + ); + + try { + final Map properties = emf.getProperties(); + + final Object hibernateJdbcDriver = properties.get( AvailableSettings.URL ); + assertThat( hibernateJdbcDriver, notNullValue() ); + + final Object jpaJdbcDriver = properties.get( AvailableSettings.JPA_JDBC_URL ); + assertThat( (String) jpaJdbcDriver, containsString( "db2" ) ); + + // see if the values had the affect to adjust the `ConnectionProvider` used + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DriverManagerConnectionProviderImpl.class ) ); + } + finally { + emf.close(); + } + } + + @Test +// @FailureExpected( +// jiraKey = "HHH-12858", +// message = "So it appears any use of the persistence.xml `jta-data-source` or `non-jta-data-source` " + +// "have precedence over integration settings, which is also incorrect" +// ) + public void testPassingIntegrationJpaDataSourceOverrideForJtaDataSourceElement() { + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + final DataSource integrationDataSource = new DataSourceStub( "integrationDataSource" ); + + PersistenceProvider provider = new HibernatePersistenceProvider() { + @Override + public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map integrationOverrides) { + return super.createContainerEntityManagerFactory( + new DelegatingPersistenceUnitInfo( info ) { + @Override + public DataSource getJtaDataSource() { + // pretend the DataSource was defined using the `jta-data-source` element in persistence.xml + // - as opposed using `javax.persistence.jtaDataSource` under the `properties` element + return puDataSource; + } + }, + integrationOverrides + ); + } + }; + + final Map integrationOverrides = new HashMap(); + //noinspection unchecked + integrationOverrides.put( AvailableSettings.JPA_JTA_DATASOURCE, integrationDataSource ); + integrationOverrides.put( AvailableSettings.HQL_BULK_ID_STRATEGY, new MultiTableBulkIdStrategyStub() ); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter(), + integrationOverrides + ); + + try { + final Map properties = emf.getProperties(); + + final Object datasource = properties.get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertThat( datasource, is( integrationDataSource ) ); + + // see if the values had the affect to adjust the `ConnectionProvider` used + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + + final DatasourceConnectionProviderImpl datasourceConnectionProvider = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( datasourceConnectionProvider.getDataSource(), is( integrationDataSource ) ); + } + finally { + emf.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-13640" ) + public void testIntegrationOverridesOfPersistenceXmlDataSource() { + + // mimics a DataSource defined in the persistence.xml + final DataSourceStub dataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getNonJtaDataSource() { + return dataSource; + } + }; + + + // Now create "integration Map" that overrides the DataSource to use + final DataSource override = new DataSourceStub( "integrationDataSource" ); + final Map integrationSettings = new HashMap<>(); + integrationSettings.put( AvailableSettings.JPA_NON_JTA_DATASOURCE, override ); + integrationSettings.put( AvailableSettings.HQL_BULK_ID_STRATEGY, new MultiTableBulkIdStrategyStub() ); + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + final Map properties = emf.getProperties(); + + assertThat( properties.get( AvailableSettings.JPA_NON_JTA_DATASOURCE ), notNullValue() ); + assertThat( properties.get( AvailableSettings.JPA_NON_JTA_DATASOURCE ), is( override ) ); + + final SessionFactoryImplementor sessionFactory = emf.unwrap( SessionFactoryImplementor.class ); + final ConnectionProvider connectionProvider = sessionFactory.getServiceRegistry().getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + + final DatasourceConnectionProviderImpl dsProvider = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsProvider.getDataSource(), is( override ) ); + } + finally { + emf.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-13640" ) + public void testIntegrationOverridesOfPersistenceXmlDataSourceWithDriverManagerInfo() { + + // mimics a DataSource defined in the persistence.xml + final DataSourceStub dataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getNonJtaDataSource() { + return dataSource; + } + }; + + final Map integrationSettings = new HashMap<>(); + integrationSettings.put( AvailableSettings.JPA_JDBC_DRIVER, ConnectionProviderBuilder.DRIVER ); + integrationSettings.put( AvailableSettings.JPA_JDBC_URL, ConnectionProviderBuilder.URL ); + integrationSettings.put( AvailableSettings.JPA_JDBC_USER, ConnectionProviderBuilder.USER ); + integrationSettings.put( AvailableSettings.JPA_JDBC_PASSWORD, ConnectionProviderBuilder.PASS ); + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + final SessionFactoryImplementor sessionFactory = emf.unwrap( SessionFactoryImplementor.class ); + final ConnectionProvider connectionProvider = sessionFactory.getServiceRegistry().getService( + ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DriverManagerConnectionProviderImpl.class ) ); + } + finally { + emf.close(); + } + } + + @Test + public void testCfgXmlBaseline() { + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + private final Properties props = new Properties(); + { + props.put( org.hibernate.jpa.AvailableSettings.CFG_FILE, "org/hibernate/orm/test/bootstrap/jpa/hibernate.cfg.xml" ); + } + + @Override + public Properties getProperties() { + return props; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final Map integrationSettings = Collections.emptyMap(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + assertThat( + emf.getProperties().get( AvailableSettings.DIALECT ), + is( PersistenceUnitDialect.class.getName() ) + ); + assertThat( + emf.unwrap( SessionFactoryImplementor.class ).getJdbcServices().getDialect(), + instanceOf( PersistenceUnitDialect.class ) + ); + + final EntityType entityMapping = emf.getMetamodel().entity( MappedEntity.class ); + assertThat( entityMapping, notNullValue() ); + } + finally { + emf.close(); + } + } + + @Test + public void testIntegrationOverridesOfCfgXml() { + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + private final Properties props = new Properties(); + { + props.put( org.hibernate.jpa.AvailableSettings.CFG_FILE, "org/hibernate/orm/test/bootstrap/jpa/hibernate.cfg.xml" ); + } + + @Override + public Properties getProperties() { + return props; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final Map integrationSettings = Collections.singletonMap( + AvailableSettings.DIALECT, + IntegrationDialect.class.getName() + ); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + assertThat( + emf.getProperties().get( AvailableSettings.DIALECT ), + is( IntegrationDialect.class.getName() ) + ); + assertThat( + emf.unwrap( SessionFactoryImplementor.class ).getJdbcServices().getDialect(), + instanceOf( IntegrationDialect.class ) + ); + + final EntityPersister entityMapping = emf.unwrap( SessionFactoryImplementor.class ) + .getMetamodel() + .entityPersister( MappedEntity.class ); + assertThat( entityMapping, notNullValue() ); + assertThat( + entityMapping.getCacheAccessStrategy().getAccessType(), + is( AccessType.READ_ONLY ) + ); + } + finally { + emf.close(); + } + } + + public static class PersistenceUnitDialect extends Dialect { + } + + @SuppressWarnings("WeakerAccess") + public static class IntegrationDialect extends Dialect { + } + + @Entity + public static class MappedEntity { + private Integer id; + private String name; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class MultiTableBulkIdStrategyStub implements MultiTableBulkIdStrategy { + + @Override + public void prepare( + JdbcServices jdbcServices, + JdbcConnectionAccess connectionAccess, + MetadataImplementor metadata, + SessionFactoryOptions sessionFactoryOptions) { + + } + + @Override + public void release( + JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) { + + } + + @Override + public UpdateHandler buildUpdateHandler( + SessionFactoryImplementor factory, HqlSqlWalker walker) { + return null; + } + + @Override + public DeleteHandler buildDeleteHandler( + SessionFactoryImplementor factory, HqlSqlWalker walker) { + return null; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/package-info.java new file mode 100644 index 000000000000..17cbbb7c3cfb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/package-info.java @@ -0,0 +1,11 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +/** + * Tests for Hibernate bootstrapping Hibernate + */ +package org.hibernate.orm.test.bootstrap; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/SynchronizedSpaceTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/SynchronizedSpaceTests.java new file mode 100644 index 000000000000..800220092b4a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/SynchronizedSpaceTests.java @@ -0,0 +1,402 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.orm.test.query.sql; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.persistence.Cacheable; +import javax.persistence.Entity; +import javax.persistence.EntityResult; +import javax.persistence.Id; +import javax.persistence.Query; +import javax.persistence.QueryHint; +import javax.persistence.SqlResultSetMapping; +import javax.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.jpa.QueryHints; +import org.hibernate.query.spi.NativeQueryImplementor; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class SynchronizedSpaceTests extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void testNonSyncedCachedScenario() { + // CachedEntity updated by native-query without adding query spaces + // - the outcome should be all cached data being invalidated + + checkUseCase( + "cached_entity", + query -> {}, + // the 2 CachedEntity entries should not be there + false + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from CachedEntity", CachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + private void checkUseCase( + String table, + Consumer queryConsumer, + boolean shouldExistAfter) { + + checkUseCase( + (session) -> { + final Query nativeQuery = session.createNativeQuery( "update " + table + " set name = 'updated'" ); + queryConsumer.accept( nativeQuery ); + return nativeQuery; + }, + Query::executeUpdate, + shouldExistAfter + ); + } + + private void checkUseCase( + Function queryProducer, + Consumer executor, + boolean shouldExistAfter) { + + // first, load both `CachedEntity` instances into the L2 cache + loadAll(); + + final CacheImplementor cacheSystem = sessionFactory().getCache(); + + // make sure they are there + assertThat( cacheSystem.containsEntity( CachedEntity.class, 1 ), is( true ) ); + assertThat( cacheSystem.containsEntity( CachedEntity.class, 2 ), is( true ) ); + + // create a query to update the specified table - allowing the passed consumer to register a space if needed + inTransaction( + session -> { + // notice the type is the JPA Query interface + final Query nativeQuery = queryProducer.apply( session ); + executor.accept( nativeQuery ); + } + ); + + // see if the entries exist based on the expectation + assertThat( cacheSystem.containsEntity( CachedEntity.class, 1 ), is( shouldExistAfter ) ); + assertThat( cacheSystem.containsEntity( CachedEntity.class, 2 ), is( shouldExistAfter ) ); + } + + @Test + public void testSyncedCachedScenario() { + final String tableName = "cached_entity"; + + checkUseCase( + tableName, + query -> ( (NativeQueryImplementor) query ).addSynchronizedQuerySpace( tableName ), + // the 2 CachedEntity entries should not be there + false + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from CachedEntity", CachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testNonSyncedNonCachedScenario() { + // NonCachedEntity updated by native-query without adding query spaces + // - the outcome should be all cached data being invalidated + + checkUseCase( + "non_cached_entity", + query -> {}, + // the 2 CachedEntity entries should not be there + false + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenario() { + // NonCachedEntity updated by native-query with query spaces + // - the caches for CachedEntity are not invalidated - they are not affected by the specified query-space + + final String tableName = "non_cached_entity"; + + checkUseCase( + tableName, + query -> ( (NativeQueryImplementor) query ).addSynchronizedQuerySpace( tableName ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingHint() { + // same as `#testSyncedNonCachedScenario`, but here using the hint + + final String tableName = "non_cached_entity"; + + checkUseCase( + tableName, + query -> query.setHint( QueryHints.HINT_NATIVE_SPACES, tableName ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingHintWithCollection() { + // same as `#testSyncedNonCachedScenario`, but here using the hint + + final String tableName = "non_cached_entity"; + final Set spaces = new HashSet<>(); + spaces.add( tableName ); + + checkUseCase( + tableName, + query -> query.setHint( QueryHints.HINT_NATIVE_SPACES, spaces ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingHintWithArray() { + // same as `#testSyncedNonCachedScenario`, but here using the hint + + final String tableName = "non_cached_entity"; + final String[] spaces = { tableName }; + + checkUseCase( + tableName, + query -> query.setHint( QueryHints.HINT_NATIVE_SPACES, spaces ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingAnnotationWithReturnClass() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_return_class" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingAnnotationWithResultSetMapping() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_resultset_mapping" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingAnnotationWithSpaces() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_spaces" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingJpaAnnotationWithNoResultMapping() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_raw_jpa" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingJpaAnnotationWithHint() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_hint_jpa" ), + Query::getResultList, + true + ); + } + + private void loadAll() { + inTransaction( + session -> { + session.createQuery( "from CachedEntity" ).list(); + + // this one is not strictly needed since this entity is not cached. + // but it helps my OCD feel better to have it ;) + session.createQuery( "from NonCachedEntity" ).list(); + } + ); + } + + public void prepareTest() { + inTransaction( + session -> { + session.persist( new CachedEntity( 1, "first cached" ) ); + session.persist( new CachedEntity( 2, "second cached" ) ); + + session.persist( new NonCachedEntity( 1, "first non-cached" ) ); + session.persist( new NonCachedEntity( 2, "second non-cached" ) ); + } + ); + + cleanupCache(); + } + + public void cleanupTest() { + cleanupCache(); + + inTransaction( + session -> { + session.createQuery( "delete CachedEntity" ).executeUpdate(); + session.createQuery( "delete NonCachedEntity" ).executeUpdate(); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { CachedEntity.class, NonCachedEntity.class }; + } + + @Override + protected boolean overrideCacheStrategy() { + return false; + } + + @Entity( name = "CachedEntity" ) + @Table( name = "cached_entity" ) + @Cacheable( true ) + @Cache( usage = CacheConcurrencyStrategy.READ_WRITE ) + public static class CachedEntity { + @Id + private Integer id; + private String name; + + public CachedEntity() { + } + + public CachedEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity( name = "NonCachedEntity" ) + @Table( name = "non_cached_entity" ) + @Cacheable( false ) + @NamedNativeQuery( + name = "NonCachedEntity_return_class", + query = "select * from non_cached_entity", + resultClass = NonCachedEntity.class + ) + @NamedNativeQuery( + name = "NonCachedEntity_resultset_mapping", + query = "select * from non_cached_entity", + resultSetMapping = "NonCachedEntity_resultset_mapping" + ) + @SqlResultSetMapping( + name = "NonCachedEntity_resultset_mapping", + entities = @EntityResult( entityClass = NonCachedEntity.class ) + ) + @NamedNativeQuery( + name = "NonCachedEntity_spaces", + query = "select * from non_cached_entity", + querySpaces = "non_cached_entity" + ) + @javax.persistence.NamedNativeQuery( + name = "NonCachedEntity_raw_jpa", + query = "select * from non_cached_entity" + ) + @javax.persistence.NamedNativeQuery( + name = "NonCachedEntity_hint_jpa", + query = "select * from non_cached_entity", + hints = { + @QueryHint( name = QueryHints.HINT_NATIVE_SPACES, value = "non_cached_entity" ) + } + ) + public static class NonCachedEntity { + @Id + private Integer id; + private String name; + + public NonCachedEntity() { + } + + public NonCachedEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/persister/entity/AbstractSchemaSubstitutionFormulaTest.java b/hibernate-core/src/test/java/org/hibernate/persister/entity/AbstractSchemaSubstitutionFormulaTest.java new file mode 100644 index 000000000000..6ffb4cf4b8e4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/persister/entity/AbstractSchemaSubstitutionFormulaTest.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.persister.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.Formula; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Mykhaylo Gnylorybov + */ +public abstract class AbstractSchemaSubstitutionFormulaTest extends BaseCoreFunctionalTestCase { + + protected static final String SCHEMA_PLACEHOLDER = "h-schema"; + + @Test + public void test() { + final String className = FooBar.class.getName(); + final AbstractEntityPersister persister = (AbstractEntityPersister) sessionFactory() + .getMetamodel().entityPersister( className ); + final String formula = persister.getSubclassFormulaTemplateClosure()[0]; + + doInHibernate( this::sessionFactory, session -> { + Foo foo = new Foo(); + foo.id = 1; + foo.name = "fooName"; + session.persist( foo ); + + Bar bar = new Bar(); + bar.id = 2; + bar.name = "barName"; + session.persist( bar ); + + FooBar fooBar = new FooBar(); + fooBar.id = 3; + fooBar.bar = bar; + fooBar.foo = foo; + + session.persist( fooBar ); + } ); + + doInHibernate( this::sessionFactory, session -> { + FooBar entity = session.find( FooBar.class, 3 ); + assertTrue( "Invalid result of formula expression: ", entity.isValid ); + } ); + + + } + + abstract void validate(String formula); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + FooBar.class, + Bar.class, + Foo.class + }; + } + + @Entity(name = "FOOBAR") + @Table(name = "FOOBAR") + public static class FooBar { + + @Id + @Column(name = "ID") + public Integer id; + + @ManyToOne + @JoinColumn(name = "FOO_ID") + public Foo foo; + + @ManyToOne + @JoinColumn(name = "BAR_ID") + public Bar bar; + + @Formula("CASE WHEN (\n" + + " EXISTS (\n" + + " SELECT *\n" + + " FROM {h-schema}Foo foo\n" + + " JOIN {h-schema}FooBar fooBar\n" + + " ON foo.ID = fooBar.FOO_ID" + + " WHERE foo.name IS NOT NULL\n" + + " )\n" + + " AND\n" + + " EXISTS (\n" + + " SELECT *\n" + + " FROM {h-schema}Bar bar\n" + + " JOIN {h-schema}FooBar fooBar\n" + + " ON bar.ID = fooBar.BAR_ID\n" + + " WHERE bar.name IS NOT NULL\n" + + " ))\n" + + " THEN 1\n" + + " ELSE 0\n" + + "END") + public Boolean isValid; + } + + @Entity(name = "FOO") + @Table(name = "FOO") + public static class Foo { + + @Id + @Column(name = "ID") + public Integer id; + + @Column(name = "name") + public String name; + } + + @Entity(name = "BAR") + @Table(name = "BAR") + public static class Bar { + + @Id + @Column(name = "ID") + public Integer id; + + @Column(name = "name") + public String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/persister/entity/CustomSqlSchemaResolvingIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/persister/entity/CustomSqlSchemaResolvingIdentityTest.java new file mode 100644 index 000000000000..bb54388a2f9a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/persister/entity/CustomSqlSchemaResolvingIdentityTest.java @@ -0,0 +1,118 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.persister.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Loader; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.Persister; +import org.hibernate.annotations.ResultCheckStyle; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLInsert; +import org.hibernate.annotations.SQLUpdate; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +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; +import static org.junit.Assert.assertNull; + +/** + * @author Laabidi RAISSI + */ +@RequiresDialect(H2Dialect.class) +public class CustomSqlSchemaResolvingIdentityTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + CustomEntity.class, Dummy.class + }; + } + + @Test + public void testSchemaNotReplacedInCustomSQL() throws Exception { + + String className = CustomEntity.class.getName(); + + final AbstractEntityPersister persister = (AbstractEntityPersister) sessionFactory().getEntityPersister( className ); + String insertQuery = persister.getSQLInsertStrings()[0]; + String updateQuery = persister.getSQLUpdateStrings()[0]; + String deleteQuery = persister.getSQLDeleteStrings()[0]; + + assertEquals( "Incorrect custom SQL for insert in Entity: " + className, + "INSERT INTO FOO (name) VALUES (?)", insertQuery ); + + assertEquals( "Incorrect custom SQL for delete in Entity: " + className, + "DELETE FROM FOO WHERE id = ?", deleteQuery ); + + assertEquals( "Incorrect custom SQL for update in Entity: " + className, + "UPDATE FOO SET name = ? WHERE id = ? ", updateQuery ); + + CustomEntity _entitty = doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = new CustomEntity(); + session.persist( entity ); + + return entity; + } ); + + doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = session.find( CustomEntity.class, 1 ); + assertNotNull(entity); + + entity.name = "Vlad"; + } ); + + doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = session.find( CustomEntity.class, _entitty.id ); + session.delete( entity ); + } ); + + doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = session.find( CustomEntity.class, _entitty.id ); + assertNull(entity); + } ); + } + + @Entity(name = "CardWithCustomSQL") + @Persister( impl = SingleTableEntityPersister.class ) + @Loader(namedQuery = "find_foo_by_id") + @NamedNativeQuery( + name = "find_foo_by_id", + query = "SELECT id, name FROM {h-schema}FOO WHERE id = ?", + resultClass = CustomEntity.class + ) + @SQLInsert(sql = "INSERT INTO {h-schema}FOO (name) VALUES (?)") + @SQLDelete(sql = "DELETE FROM {h-schema}FOO WHERE id = ?", check = ResultCheckStyle.COUNT) + @SQLUpdate(sql = "UPDATE {h-schema}FOO SET name = ? WHERE id = ? ") + public static class CustomEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Integer id; + + private String name; + } + + @Entity(name = "Dummy") + @Table(name = "FOO") + public static class Dummy { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Integer id; + + private String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/persister/entity/CustomSqlSchemaResolvingTest.java b/hibernate-core/src/test/java/org/hibernate/persister/entity/CustomSqlSchemaResolvingTest.java new file mode 100644 index 000000000000..2abb3bca27cc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/persister/entity/CustomSqlSchemaResolvingTest.java @@ -0,0 +1,110 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.persister.entity; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Loader; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.Persister; +import org.hibernate.annotations.ResultCheckStyle; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLInsert; +import org.hibernate.annotations.SQLUpdate; + +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; +import static org.junit.Assert.assertNull; + +/** + * @author Laabidi RAISSI + */ +public class CustomSqlSchemaResolvingTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + CustomEntity.class, Dummy.class + }; + } + + @Test + public void testSchemaNotReplacedInCustomSQL() throws Exception { + + String className = CustomEntity.class.getName(); + + final AbstractEntityPersister persister = (AbstractEntityPersister) sessionFactory().getEntityPersister( className ); + String insertQuery = persister.getSQLInsertStrings()[0]; + String updateQuery = persister.getSQLUpdateStrings()[0]; + String deleteQuery = persister.getSQLDeleteStrings()[0]; + + assertEquals( "Incorrect custom SQL for insert in Entity: " + className, + "INSERT INTO FOO (name, id) VALUES (?, ?)", insertQuery ); + + assertEquals( "Incorrect custom SQL for delete in Entity: " + className, + "DELETE FROM FOO WHERE id = ?", deleteQuery ); + + assertEquals( "Incorrect custom SQL for update in Entity: " + className, + "UPDATE FOO SET name = ? WHERE id = ? ", updateQuery ); + + doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = new CustomEntity(); + entity.id = 1; + session.persist( entity ); + } ); + + doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = session.find( CustomEntity.class, 1 ); + assertNotNull(entity); + + entity.name = "Vlad"; + } ); + + doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = session.find( CustomEntity.class, 1 ); + session.delete( entity ); + } ); + + doInHibernate( this::sessionFactory, session -> { + CustomEntity entity = session.find( CustomEntity.class, 1 ); + assertNull(entity); + } ); + } + + @Entity(name = "CardWithCustomSQL") + @Persister( impl = SingleTableEntityPersister.class ) + @Loader(namedQuery = "find_foo_by_id") + @NamedNativeQuery( + name = "find_foo_by_id", + query = "SELECT id, name FROM {h-schema}FOO WHERE id = ?", + resultClass = CustomEntity.class + ) + @SQLInsert(sql = "INSERT INTO {h-schema}FOO (name, id) VALUES (?, ?)") + @SQLDelete(sql = "DELETE FROM {h-schema}FOO WHERE id = ?", check = ResultCheckStyle.COUNT) + @SQLUpdate(sql = "UPDATE {h-schema}FOO SET name = ? WHERE id = ? ") + public static class CustomEntity { + @Id + public Integer id; + + private String name; + } + + @Entity(name = "Dummy") + @Table(name = "FOO") + public static class Dummy { + @Id + public Integer id; + + private String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/persister/entity/FormulaTemplateEmptySchemaSubstitutionTest.java b/hibernate-core/src/test/java/org/hibernate/persister/entity/FormulaTemplateEmptySchemaSubstitutionTest.java new file mode 100644 index 000000000000..843d9bcf49a7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/persister/entity/FormulaTemplateEmptySchemaSubstitutionTest.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 . + */ +package org.hibernate.persister.entity; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Mykhaylo Gnylorybov + */ +@RequiresDialect(H2Dialect.class) +public class FormulaTemplateEmptySchemaSubstitutionTest extends AbstractSchemaSubstitutionFormulaTest { + + @Override + void validate(String formula) { + assertTrue( "Formula should not contain {} characters", formula.matches( "^[^{}]+$" ) ); + assertFalse( "Formula should not contain hibernate placeholder", formula.contains( SCHEMA_PLACEHOLDER ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/persister/entity/FormulaTemplateSchemaSubstitutionTest.java b/hibernate-core/src/test/java/org/hibernate/persister/entity/FormulaTemplateSchemaSubstitutionTest.java new file mode 100644 index 000000000000..967f0de7f564 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/persister/entity/FormulaTemplateSchemaSubstitutionTest.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.persister.entity; + +import java.util.Properties; + +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +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.assertTrue; + +@RequiresDialect(H2Dialect.class) +public class FormulaTemplateSchemaSubstitutionTest extends AbstractSchemaSubstitutionFormulaTest { + + private static final String CUSTOM_SCHEMA = "CUSTOM_SCHEMA"; + + @Override + protected void configure(Configuration configuration) { + final Properties properties = new Properties(); + properties.put( "hibernate.default_schema", CUSTOM_SCHEMA ); + configuration.addProperties( properties ); + } + + @Override + protected String createSecondSchema() { + return CUSTOM_SCHEMA; + } + + @Override + void validate(String formula) { + assertEquals( "Formula should not contain {} characters", + 4, formula.split( CUSTOM_SCHEMA + ".", -1 ).length - 1 + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/property/DirectPropertyAccessorTest.java b/hibernate-core/src/test/java/org/hibernate/property/DirectPropertyAccessorTest.java index b52cacd6e452..8fb4c74b739f 100644 --- a/hibernate-core/src/test/java/org/hibernate/property/DirectPropertyAccessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/property/DirectPropertyAccessorTest.java @@ -38,7 +38,9 @@ public void testDirectIdPropertyAccess() throws Exception { o = ( Order ) s.load( Order.class, 1 ); assertFalse( Hibernate.isInitialized( o ) ); o.getOrderNumber(); - // If you mapped with field access, any method call initializes the proxy + // If you mapped with field access, any method, except id, call initializes the proxy + assertFalse( Hibernate.isInitialized( o ) ); + o.getName(); assertTrue( Hibernate.isInitialized( o ) ); s.close(); } diff --git a/hibernate-core/src/test/java/org/hibernate/property/GetAndIsVariantGetterTest.java b/hibernate-core/src/test/java/org/hibernate/property/GetAndIsVariantGetterTest.java index d0762c1d8096..86ffd4a5858b 100644 --- a/hibernate-core/src/test/java/org/hibernate/property/GetAndIsVariantGetterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/property/GetAndIsVariantGetterTest.java @@ -17,6 +17,7 @@ import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.testing.TestForIssue; import org.junit.AfterClass; @@ -26,6 +27,7 @@ import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -97,6 +99,18 @@ public void testAnnotationsFieldAccess() { assertNotNull( metadata.getEntityBinding( AnotherEntity.class.getName() ).getIdentifierProperty() ); } + @Test + @TestForIssue( jiraKey = "HHH-12046" ) + public void testInstanceStaticConflict() { + Metadata metadata = new MetadataSources( ssr ) + .addAnnotatedClass( InstanceStaticEntity.class ) + .buildMetadata(); + assertNotNull( metadata.getEntityBinding( InstanceStaticEntity.class.getName() ).getIdentifier() ); + assertNotNull( metadata.getEntityBinding( InstanceStaticEntity.class.getName() ).getIdentifierProperty() ); + assertTrue( metadata.getEntityBinding( InstanceStaticEntity.class.getName() ).hasProperty("foo") ); + ReflectHelper.findGetterMethod( InstanceStaticEntity.class, "foo" ); + } + @Entity public static class TheEntity { private Integer id; @@ -153,4 +167,34 @@ public void setId(Integer id) { this.id = id; } } + + @Entity + public static class InstanceStaticEntity { + + private Integer id; + private boolean foo; + + @Id + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + + public boolean isFoo() { + return this.foo; + } + public void setFoo(boolean foo) { + this.foo = foo; + } + + public static Object getFoo() { + return null; + } + + public static boolean isId() { + return false; + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/property/GetAndIsVariantGetterWithTransientAnnotationTest.java b/hibernate-core/src/test/java/org/hibernate/property/GetAndIsVariantGetterWithTransientAnnotationTest.java new file mode 100644 index 000000000000..4416c4777457 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/property/GetAndIsVariantGetterWithTransientAnnotationTest.java @@ -0,0 +1,147 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.property; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11716") +public class GetAndIsVariantGetterWithTransientAnnotationTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {TestEntity.class, SecondTestEntity.class}; + } + + @Test + public void testGetAndIsVariantCanHaveDifferentReturnValueWhenOneHasATransientAnnotation() { + TransactionUtil.doInHibernate( this::sessionFactory, session1 -> { + TestEntity entity = new TestEntity(); + entity.setId( 1L ); + entity.setChecked( true ); + session1.save( entity ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session1 -> { + final TestEntity entity = session1.find( TestEntity.class, 1L ); + assertThat( entity.isChecked(), is( true ) ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session1 -> { + final TestEntity entity = session1.find( TestEntity.class, 1L ); + entity.setChecked( null ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session1 -> { + final TestEntity entity = session1.find( TestEntity.class, 1L ); + assertThat( entity.isChecked(), is( nullValue() ) ); + } ); + } + + @Test + public void testBothGetterAndIsVariantAreIgnoredWhenMarkedTransient() { + TransactionUtil.doInHibernate( this::sessionFactory, session1 -> { + SecondTestEntity entity = new SecondTestEntity(); + entity.setId( 1L ); + entity.setChecked( true ); + session1.save( entity ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session1 -> { + final SecondTestEntity entity = session1.find( SecondTestEntity.class, 1L ); + assertThat( entity.getChecked(), is( nullValue() ) ); + } ); + } + + @Entity(name = "TestEntity") + @Table(name = "TEST_ENTITY") + public static class TestEntity { + private Long id; + private Boolean checked; + private String name; + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public void setChecked(Boolean checked) { + this.checked = checked; + } + + @Transient + public boolean getChecked() { + return false; + } + + public Boolean isChecked() { + return this.checked; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Transient + public boolean isName() { + return name.length() > 0; + } + } + + @Entity(name = "SecondTestEntity") + @Table(name = "TEST_ENTITY_2") + public static class SecondTestEntity { + private Long id; + private Boolean checked; + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public void setChecked(Boolean checked) { + this.checked = checked; + } + + @Transient + public Boolean getChecked() { + return this.checked; + } + + @Transient + public boolean isChecked() { + return false; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/property/Order.java b/hibernate-core/src/test/java/org/hibernate/property/Order.java index 3640cba7fa3b..b9de55781ae3 100644 --- a/hibernate-core/src/test/java/org/hibernate/property/Order.java +++ b/hibernate-core/src/test/java/org/hibernate/property/Order.java @@ -27,6 +27,8 @@ public class Order implements Serializable { @Id private int orderNumber; + private String name; + @OneToMany( fetch = FetchType.LAZY ) private Set items = new HashSet(); @@ -41,4 +43,12 @@ public void setOrderNumber(int orderNumber) { public Set getItems() { return items; } + + 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 new file mode 100644 index 000000000000..1571ba547163 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.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 . + */ +package org.hibernate.property; + +import java.util.Date; +import java.util.HashMap; + +import org.hibernate.mapping.Map; +import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl; +import org.hibernate.property.access.spi.PropertyAccess; + +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class PropertyAccessStrategyMapTest extends BaseUnitTestCase { + + @Test + public void testBasicMapClass() { + testBasic( Map.class ); + } + + @Test + public void testBasicNullClass() { + testBasic( null ); + } + + @Test + public void testNonMap() { + final PropertyAccessStrategyMapImpl accessStrategy = PropertyAccessStrategyMapImpl.INSTANCE; + + try { + accessStrategy.buildPropertyAccess( Date.class, "time" ); + + fail("Should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + assertEquals( + "Expecting class: [org.hibernate.mapping.Map], but containerJavaType is of type: [java.util.Date] for propertyName: [time]", + e.getMessage() + ); + } + } + + private void testBasic(final Class clazz) { + + final String key = "testKey"; + final String value = "testValue"; + + final PropertyAccessStrategyMapImpl accessStrategy = PropertyAccessStrategyMapImpl.INSTANCE; + final PropertyAccess access = accessStrategy.buildPropertyAccess( clazz, key ); + + final HashMap map = new HashMap<>(); + + access.getSetter().set( map, value, null ); + assertEquals( value, map.get( key ) ); + assertEquals( value, access.getGetter().get( map ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java b/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java new file mode 100644 index 000000000000..77860ab63fdc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.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.query; + +import java.util.Arrays; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12469" ) +public class InClauseParameterPaddingTest extends BaseEntityManagerFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); + options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( int i = 1; i < 10; i++ ) { + Person person = new Person(); + person.setId( i ); + person.setName( String.format( "Person nr %d", i ) ); + + entityManager.persist( person ); + } + } ); + } + + @Test + public void testInClauseParameterPadding() { + validateInClauseParameterPadding( "in (?)", 1 ); + validateInClauseParameterPadding( "in (? , ?)", 1, 2 ); + validateInClauseParameterPadding( "in (? , ? , ? , ?)", 1, 2, 3 ); + validateInClauseParameterPadding( "in (? , ? , ? , ?)", 1, 2, 3, 4 ); + validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5 ); + validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6 ); + validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7 ); + validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7, 8 ); + validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7, 8, 9 ); + validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ); + } + + private void validateInClauseParameterPadding(String expectedInClause, Integer... ids) { + sqlStatementInterceptor.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.createQuery( + "select p " + + "from Person p " + + "where p.id in :ids" ) + .setParameter( "ids", Arrays.asList(ids) ) + .getResultList(); + } ); + + assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause )); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Integer id; + + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java b/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java new file mode 100644 index 000000000000..92fddb131521 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java @@ -0,0 +1,127 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12469" ) +@RequiresDialect(H2Dialect.class) +public class MaxInExpressionParameterPaddingTest extends BaseEntityManagerFunctionalTestCase { + + public static final int MAX_COUNT = 15; + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); + options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Override + protected Dialect getDialect() { + return new MaxCountInExpressionH2Dialect(); + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( int i = 0; i < MAX_COUNT; i++ ) { + Person person = new Person(); + person.setId( i ); + person.setName( String.format( "Person nr %d", i ) ); + + entityManager.persist( person ); + } + } ); + } + + @Test + public void testInClauseParameterPadding() { + sqlStatementInterceptor.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.createQuery( + "select p " + + "from Person p " + + "where p.id in :ids" ) + .setParameter( "ids", IntStream.range( 0, MAX_COUNT ).boxed().collect(Collectors.toList()) ) + .getResultList(); + } ); + + StringBuilder expectedInClause = new StringBuilder(); + expectedInClause.append( "in (?" ); + for ( int i = 1; i < MAX_COUNT; i++ ) { + expectedInClause.append( " , ?" ); + } + expectedInClause.append( ")" ); + + assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() )); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Integer id; + + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class MaxCountInExpressionH2Dialect extends H2Dialect { + @Override + public int getInExpressionCountLimit() { + return MAX_COUNT; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/NativeQueryWithParenthesesTest.java b/hibernate-core/src/test/java/org/hibernate/query/NativeQueryWithParenthesesTest.java new file mode 100644 index 000000000000..d7514fc569aa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/NativeQueryWithParenthesesTest.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class NativeQueryWithParenthesesTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Test + public void testParseParentheses() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( + "(SELECT p.id, p.name FROM Person p WHERE p.name LIKE 'A%') " + + "UNION " + + "(SELECT p.id, p.name FROM Person p WHERE p.name LIKE 'B%')", Person.class) + .getResultList(); + } ); + } + + @Entity + @Table(name = "Person") + public static class Person { + + @Id + private Integer id; + + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/AliasWithCriterionTest.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/AliasWithCriterionTest.java new file mode 100644 index 000000000000..9d0074b9a91f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/AliasWithCriterionTest.java @@ -0,0 +1,242 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.criteria.internal; + +import java.util.Date; +import java.util.List; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.Criteria; +import org.hibernate.criterion.CriteriaSpecification; +import org.hibernate.criterion.Criterion; +import org.hibernate.criterion.ProjectionList; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Restrictions; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.sql.JoinType; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class AliasWithCriterionTest extends BaseCoreFunctionalTestCase { + + @Test + @RequiresDialect( H2Dialect.class ) + public void testCaseClause() { + doInHibernate( this::sessionFactory, session -> { + Criteria criteria = session.createCriteria( TableA.class ); + + final String TABLE_B_ALIAS = "tableBAlias"; + final String TABLE_C_ALIAS = "tableCAlias"; + + Criterion tableCRestriction = Restrictions.eq( TABLE_C_ALIAS + ".tableCBoolean", false ); + criteria.createAlias( + TABLE_B_ALIAS + ".tableCs", + TABLE_C_ALIAS, + JoinType.LEFT_OUTER_JOIN, + tableCRestriction + ); + + Criterion tableBRestriction = Restrictions.eq( TABLE_B_ALIAS + ".tableBDate", new Date() ); + criteria.createAlias( "tableBs", TABLE_B_ALIAS, JoinType.LEFT_OUTER_JOIN, tableBRestriction ); + + criteria.add( Restrictions.eq( "tableACharacter", "c" ) ); + + ProjectionList projectionList = Projections.projectionList(); + projectionList.add( Projections.property( "tableACharacter" ) ); + criteria.setProjection( projectionList ); + + criteria.list(); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + TableA.class, + TableB.class, + TableC.class, + }; + } + + @Entity(name = "TableA") + public static class TableA { + + @Id + @Column(name = "table_a_id") + private Long tableAId; + + @Column(name = "table_a_character") + private String tableACharacter; + + @OneToMany(mappedBy = "tableA") + private Set tableBs; + + public TableA() { + } + + public Long getTableAId() { + return this.tableAId; + } + + public void setTableAId_(Long _tableAId_) { + this.tableAId = _tableAId_; + } + + public String getTableACharacter() { + return this.tableACharacter; + } + + public void setTableACharacter(String _tableACharacter_) { + this.tableACharacter = _tableACharacter_; + } + + public Set getTableBs() { + return this.tableBs; + } + + public void setTableBs(Set tableBs) { + this.tableBs = tableBs; + } + + } + + @Entity(name = "TableB") + public static class TableB { + + @Id + @Column(name = "table_b_id") + private Long tableBId; + + @Column(name = "table_a_id", insertable = false, updatable = false) + private Long tableAId; + + @Temporal(TemporalType.DATE) + @Column(name = "table_b_date") + private Date tableBDate; + + @ManyToOne + @JoinColumn(name = "table_a_id") + private TableA tableA; + + @OneToMany(mappedBy = "tableB") + private Set tableCs; + + + public TableB() { + } + + public Long getTableBId() { + return this.tableBId; + } + + public void setTableBId(Long _tableBId_) { + this.tableBId = _tableBId_; + } + + public Long getTableAId() { + return this.tableAId; + } + + public void setTableAId(Long _tableAId_) { + this.tableAId = _tableAId_; + } + + public Date getTableBDate() { + return this.tableBDate; + } + + public void setTableBDate(Date _tableBDate_) { + this.tableBDate = _tableBDate_; + } + + public TableA getTableA() { + return tableA; + } + + public void setTableA(TableA tableA) { + this.tableA = tableA; + } + + public Set getTableCs() { + return tableCs; + } + + public void setTableCs(Set tableCs) { + this.tableCs = tableCs; + } + + } + + @Entity(name = "TableC") + public static class TableC { + + @Id + @Column(name = "table_c_id") + private Long tableCId; + + @Column(name = "table_b_id", insertable = false, updatable = false) + private Long tableBId; + + @Column(name = "table_c_boolean") + private Boolean tableCBoolean; + + @ManyToOne + @JoinColumn(name = "table_b_id") + private TableB tableB; + + public TableC() { + } + + public Long getTableCId() { + return this.tableCId; + } + + public void setTableCId(Long tableCId) { + this.tableCId = tableCId; + } + + public Long getTableBId() { + return this.tableBId; + } + + public void setTableBId(Long tableBId) { + this.tableBId = tableBId; + } + + public Boolean getTableCBoolean() { + return this.tableCBoolean; + } + + public void setTableCBoolean(Boolean tableCBoolean) { + this.tableCBoolean = tableCBoolean; + } + + public TableB getTableB() { + return tableB; + } + + public void setTableB(TableB tableB) { + this.tableB = tableB; + } + } +} \ No newline at end of file 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 new file mode 100644 index 000000000000..228df1c5170a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.criteria.internal.expression; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Root; + +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * + * @author Vasyl Danyliuk + */ +public class SearchedCaseExpressionTest extends BaseCoreFunctionalTestCase { + + @Test + @RequiresDialect(H2Dialect.class) + public void testCaseClause() { + doInHibernate( this::sessionFactory, session -> { + CriteriaBuilder cb = session.getCriteriaBuilder(); + + CriteriaQuery criteria = cb.createQuery(Event.class); + + Root event = criteria.from(Event.class); + Path type = event.get("type"); + + Expression caseWhen = cb.selectCase(type) + .when(EventType.TYPE1, "Admin Event") + .when(EventType.TYPE2, "User Event") + .when(EventType.TYPE3, "Reporter Event") + .otherwise(""); + + criteria.select(event); + criteria.where(cb.equal(caseWhen, "Admin Event")); // OK when use cb.like() method and others + List resultList = session.createQuery(criteria).getResultList(); + + Assert.assertNotNull(resultList); + } ); + } + + @Test + @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") + public void testEqualClause() { + doInHibernate( this::sessionFactory, session -> { + CriteriaBuilder cb = session.getCriteriaBuilder(); + + CriteriaQuery criteria = cb.createQuery(Event.class); + + Root event = criteria.from(Event.class); + Path type = event.get("type"); + + Expression caseWhen = cb.selectCase() + .when(cb.equal(type, EventType.TYPE1), "Type1") + .otherwise(""); + + criteria.select(event); + criteria.where(cb.equal(caseWhen, "Admin Event")); // OK when use cb.like() method and others + List resultList = session.createQuery(criteria).getResultList(); + + + Assert.assertNotNull(resultList); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{Event.class, EventType.class}; + } + + @Entity(name = "Event") + public static class Event { + + @Id + private Long id; + + @Column + @Enumerated(EnumType.STRING) + private EventType type; + + protected Event() { + } + + public EventType getType() { + return type; + } + + public Event type(EventType type) { + this.type = type; + return this; + } + } + + public enum EventType { + + TYPE1, TYPE2, TYPE3 + + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/AnnotationMappingJoinClassTest.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/AnnotationMappingJoinClassTest.java new file mode 100644 index 000000000000..90d082d046cd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/AnnotationMappingJoinClassTest.java @@ -0,0 +1,1322 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +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.OrderColumn; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Version; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +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.assertNotNull; + +@TestForIssue(jiraKey = "HHH-12076") +public class AnnotationMappingJoinClassTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Claim.class, + Settlement.class, + Task.class, + SettlementTask.class, + TaskStatus.class, + Extension.class, + SettlementExtension.class, + GapAssessmentExtension.class, + EwtAssessmentExtension.class + }; + } + + @Override + protected void prepareTest() { + doInHibernate( this::sessionFactory, session -> { + TaskStatus taskStatus = new TaskStatus(); + taskStatus.setName("Enabled"); + taskStatus.setDisplayName("Enabled"); + session.save(taskStatus); + + for (long i = 0; i < 10; i++) { + SettlementTask settlementTask = new SettlementTask(); + settlementTask.setId(i); + Settlement settlement = new Settlement(); + settlementTask.setLinked(settlement); + settlementTask.setStatus(taskStatus); + + Claim claim = new Claim(); + claim.setId(i); + settlement.setClaim(claim); + + for (int j = 0; j < 2; j++) { + GapAssessmentExtension gapAssessmentExtension = new GapAssessmentExtension(); + gapAssessmentExtension.setSettlement(settlement); + EwtAssessmentExtension ewtAssessmentExtension = new EwtAssessmentExtension(); + ewtAssessmentExtension.setSettlement(settlement); + + settlement.getExtensions().add(gapAssessmentExtension); + settlement.getExtensions().add(ewtAssessmentExtension); + } + session.save(claim); + session.save(settlement); + session.save(settlementTask); + } + } ); + } + + @Test + public void testClassExpressionInOnClause() { + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "select " + + " rootAlias.id, " + + " linked.id, " + + " extensions.id " + + "from SettlementTask as rootAlias " + + "join rootAlias.linked as linked " + + "left join linked.extensions as extensions on extensions.class = EwtAssessmentExtension " + + "where linked.id = :claimId " + ) + .setParameter("claimId", 1L) + .getResultList(); + + assertNotNull(results); + } ); + } + + @Test + public void testClassExpressionInWhereClause() { + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "select " + + " rootAlias.id, " + + " linked.id, " + + " extensions.id " + + "from SettlementTask as rootAlias " + + "join rootAlias.linked as linked " + + "left join linked.extensions as extensions " + + "where linked.id = :claimId and (extensions is null or extensions.class = EwtAssessmentExtension)" + ) + .setParameter("claimId", 1L) + .getResultList(); + + assertNotNull(results); + } ); + } + + @Entity(name = "Claim") + @Table(name = "claim") + public static class Claim { + public static final long serialVersionUID = 1L; + + @Id + @GeneratedValue + private Long id; + + @Version + private Integer version; + + @CreationTimestamp + private Date creationDate; + + @UpdateTimestamp + private Date modifiedDate; + + private Long trackingId; + private Integer term; + private Double initialReserve = 0.0; + + @Temporal( TemporalType.DATE ) + private Date effectiveDate; + + @Temporal( TemporalType.DATE ) + private Date expiryDate; + + @Temporal( TemporalType.DATE ) + private Date notificationDate; + + @Temporal( TemporalType.DATE ) + private Date pendingDate; + + @Temporal( TemporalType.DATE ) + private Date openDate; + + @Temporal( TemporalType.DATE ) + private Date suspendDate; + + @Temporal( TemporalType.DATE ) + private Date closeDate; + + private String externalId; + private String importRef; + private String location; + + @OneToMany(mappedBy = "claim", cascade = CascadeType.ALL, orphanRemoval = true) + private Set extensions = new HashSet<>(); + + @OneToMany(mappedBy = "claim", cascade = CascadeType.ALL, orphanRemoval = true) + private Set settlements = new HashSet<>(); + + public Claim getClaim() { + return this; + } + + public void addExtension(Extension extension) { + extensions.add( extension); + extension.setClaim(this); + } + + public void addSettlement(Settlement settlement) { + settlements.add( settlement); + settlement.setClaim(this); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Long getTrackingId() { + return trackingId; + } + + public void setTrackingId(Long trackingId) { + this.trackingId = trackingId; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public Date getEffectiveDate() { + return effectiveDate; + } + + public void setEffectiveDate(Date effectiveDate) { + this.effectiveDate = effectiveDate; + } + + public Date getExpiryDate() { + return expiryDate; + } + + public void setExpiryDate(Date expiryDate) { + this.expiryDate = expiryDate; + } + + public Set getExtensions() { + return extensions; + } + + public void setExtensions(Set extensions) { + this.extensions = extensions; + } + + public Set getSettlements() { + return settlements; + } + + public void setSettlements(Set settlements) { + this.settlements = settlements; + } + + public Date getNotificationDate() { + return notificationDate; + } + + public void setNotificationDate(Date notificationDate) { + this.notificationDate = notificationDate; + } + + public Date getOpenDate() { + return openDate; + } + + public void setOpenDate(Date openDate) { + this.openDate = openDate; + } + + public Date getCloseDate() { + return closeDate; + } + + public void setCloseDate(Date closeDate) { + this.closeDate = closeDate; + } + + public Double getInitialReserve() { + return initialReserve; + } + + public void setInitialReserve(Double initialReserve) { + this.initialReserve = initialReserve; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getImportRef() { + return importRef; + } + + public void setImportRef(String importRef) { + this.importRef = importRef; + } + + public Date getPendingDate() { + return pendingDate; + } + + public void setPendingDate(Date startDate) { + pendingDate = startDate; + } + + public Date getSuspendDate() { + return suspendDate; + } + + public void setSuspendDate(Date suspendDate) { + this.suspendDate = suspendDate; + } + } + + @Entity(name = "EwtAssessmentExtension") + @Table(name = "claimsettlement_ext_i3_ewt") + public static class EwtAssessmentExtension extends SettlementExtension { + public static final long serialVersionUID = 1L; + + private Double requestedUnits = -1.0; //2 + private Double requestedUnitAmount = -1.0; //$150 + private Double requestedSubtotal = 0.0; //$300 + private Double requestedTaxAmount = 0.0; //$30 + private Double requestedTotal = 0.0; //$330 + + private Double coveredRatio = 0.0; + private Double coveredUnits = 0.0; + private Double coveredUnitAmount = 0.0; + private Double coveredUnitAmountOverride = 0.0; + private Double coveredSubtotal = 0.0; + private Double coveredTaxAmount = 0.0; + private Double coveredTotal = 0.0; + + private Double underinsuredAmount = 0.0; + private Double shortfallUnitAmount = 0.0; + private Double shortfallTotal = 0.0; + + private Double taxRate = 0.0; + + private String details; + private String damageType; + private String exclusion; + private Boolean validInspection; + private Boolean taxExempt = false; + + public EwtAssessmentExtension() { + } + + public Double getRequestedUnits() { + return requestedUnits; + } + + public void setRequestedUnits(Double requestedUnits) { + this.requestedUnits = requestedUnits; + } + + public Double getRequestedUnitAmount() { + return requestedUnitAmount; + } + + public void setRequestedUnitAmount(Double requestedBenefitPerUnit) { + requestedUnitAmount = requestedBenefitPerUnit; + } + + public Double getRequestedSubtotal() { + return requestedSubtotal; + } + + public void setRequestedSubtotal(Double requestedBenefitSubtotal) { + requestedSubtotal = requestedBenefitSubtotal; + } + + public Double getRequestedTaxAmount() { + return requestedTaxAmount; + } + + public void setRequestedTaxAmount(Double requestedBenefitTax) { + requestedTaxAmount = requestedBenefitTax; + } + + public Double getRequestedTotal() { + return requestedTotal; + } + + public void setRequestedTotal(Double requestedBenefitTotal) { + requestedTotal = requestedBenefitTotal; + } + + public Double getCoveredUnitAmount() { + return coveredUnitAmount; + } + + public void setCoveredUnitAmount(Double coveredBenefitPerUnit) { + coveredUnitAmount = coveredBenefitPerUnit; + } + + public Double getCoveredSubtotal() { + return coveredSubtotal; + } + + public void setCoveredSubtotal(Double coveredBenefitSubtotal) { + coveredSubtotal = coveredBenefitSubtotal; + } + + public Double getCoveredTaxAmount() { + return coveredTaxAmount; + } + + public void setCoveredTaxAmount(Double coveredTaxAmount) { + this.coveredTaxAmount = coveredTaxAmount; + } + + public Double getCoveredTotal() { + return coveredTotal; + } + + public void setCoveredTotal(Double coveredBenefitTotal) { + coveredTotal = coveredBenefitTotal; + } + + public Double getTaxRate() { + return taxRate; + } + + public void setTaxRate(Double taxRate) { + this.taxRate = taxRate; + } + + public Double getShortfallUnitAmount() { + return shortfallUnitAmount; + } + + public void setShortfallUnitAmount(Double shortfallUnitAmount) { + this.shortfallUnitAmount = shortfallUnitAmount; + } + + public Double getShortfallTotal() { + return shortfallTotal; + } + + public void setShortfallTotal(Double shortfallTotal) { + this.shortfallTotal = shortfallTotal; + } + + public String getDetails() { + return details; + } + + public void setDetails(String description) { + details = description; + } + + public Double getUnderinsuredAmount() { + return underinsuredAmount; + } + + public void setUnderinsuredAmount(Double truncatedAmount) { + underinsuredAmount = truncatedAmount; + } + + public Double getCoveredUnits() { + return coveredUnits; + } + + public void setCoveredUnits(Double coveredUnits) { + this.coveredUnits = coveredUnits; + } + + public String getDamageType() { + return damageType; + } + + public void setDamageType(String damageType) { + this.damageType = damageType; + } + + public Double getCoveredRatio() { + return coveredRatio; + } + + public void setCoveredRatio(Double coveredRatio) { + this.coveredRatio = coveredRatio; + } + + public String getExclusion() { + return exclusion; + } + + public void setExclusion(String exclusion) { + this.exclusion = exclusion; + } + + public Double getCoveredUnitAmountOverride() { + return coveredUnitAmountOverride; + } + + public void setCoveredUnitAmountOverride(Double coveredUnitOverride) { + coveredUnitAmountOverride = coveredUnitOverride; + } + + public Boolean isValidInspection() { + return validInspection; + } + + public void setValidInspection(Boolean validInspection) { + this.validInspection = validInspection; + } + + public Boolean isTaxExempt() { + return taxExempt; + } + + public void setTaxExempt(Boolean taxExempt) { + this.taxExempt = taxExempt; + } + + } + + @Entity(name = "Extension") + @Table(name = "claimext") + @Inheritance(strategy = InheritanceType.JOINED) + public abstract static class Extension { + + @Id + @GeneratedValue + private Long id; + + @Version + private Integer version; + + @CreationTimestamp + private Date creationDate; + + @UpdateTimestamp + private Date modifiedDate; + + private String type; + + @ManyToOne(fetch = FetchType.LAZY) + private Claim claim; + + public Extension() { + String[] name = this.getClass().getName().split("\\."); + type = name[name.length-1]; + } + + public Long getId() { + return id; + } + + protected void setId(Long id) { + this.id = id; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Claim getClaim() { + return claim; + } + + public void setClaim(Claim claim) { + this.claim = claim; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + } + + @Entity(name = "GapAssessmentExtension") + @Table(name = "claim_settlement_ext_gap") + public static class GapAssessmentExtension extends SettlementExtension { + + private Double insuredsObligation = 0.0; + private Double eligibleAmount = 0.0; + private Double assessedAmount = 0.0; + private Double underinsuredAmount = 0.0; + + public Double getAssessedAmount() { + return assessedAmount; + } + + public void setAssessedAmount(Double assessedAmount) { + this.assessedAmount = assessedAmount; + } + + public Double getEligibleAmount() { + return eligibleAmount; + } + + public void setEligibleAmount(Double eligible) { + eligibleAmount = eligible; + } + + public Double getUnderinsuredAmount() { + return underinsuredAmount; + } + + public void setUnderinsuredAmount(Double underinsuredAmount) { + this.underinsuredAmount = underinsuredAmount; + } + + public Double getInsuredsObligation() { + return insuredsObligation; + } + + public void setInsuredsObligation(Double insuredsObligation) { + this.insuredsObligation = insuredsObligation; + } + } + + @Entity(name = "Settlement") + @Table(name = "claim_settlement") + public static class Settlement { + + @Id + @GeneratedValue + private Long id; + + @Version + private Integer version; + + @CreationTimestamp + private Date creationDate; + + @UpdateTimestamp + private Date modifiedDate; + + private Boolean override = false; + private Boolean started = false; + private Boolean taxable = false; + + private Double units = 0.0; + private Double amount = 0.0; + private Double subtotal = 0.0; + + private Double taxRate = 0.0; + private Double taxAmount = 0.0; + + private Double goodwill = 0.0; + private Double totalAmount = 0.0; + private Double underinsuredAmount = 0.0; + + @Temporal( TemporalType.TIMESTAMP ) + private Date openDate; + + @Temporal( TemporalType.TIMESTAMP ) + private Date allocateDate; + + @Temporal( TemporalType.TIMESTAMP ) + private Date closeDate; + + private String trackingId; + + @ManyToOne(fetch = FetchType.LAZY) + private Claim claim; + + @Enumerated(EnumType.STRING) + private SettlementStatus status = SettlementStatus.RESERVED; + + @OneToMany(mappedBy = "settlement", cascade = CascadeType.ALL, orphanRemoval = true) + @OrderColumn(name = "orderindex") + private Set extensions = new HashSet<>(); + + private transient Map, SettlementExtension> extensionMap; + + public Long getId() { + return id; + } + + protected void setId(Long id) { + this.id = id; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public Claim getClaim() { + return claim; + } + + public void setClaim(Claim claim) { + this.claim = claim; + } + + public SettlementStatus getStatus() { + return status; + } + + public void setStatus(SettlementStatus status) { + this.status = status; + } + + public String getTrackingId() { + return trackingId; + } + + public void setTrackingId(String trackingId) { + this.trackingId = trackingId; + } + + public Double getUnits() { + return units; + } + + public void setUnits(Double units) { + this.units = units; + } + + public Double getAmount() { + return amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public Double getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(Double totalAmount) { + this.totalAmount = totalAmount; + } + + public Date getCloseDate() { + return closeDate; + } + + public void setCloseDate(Date settlementDate) { + closeDate = settlementDate; + } + + public Set getExtensions() { + return extensions; + } + + public void setExtensions(Set extensions) { + this.extensions = extensions; + } + + public void addExtension(SettlementExtension extension) { + if (!hasExtension(extension.getClass())) { + if (extension.getOrderIndex() == null) { + extension.setOrderIndex( extensions.size()); + } + extension.setSettlement(this); + extensions.add( extension); + } + } + + @SuppressWarnings("unchecked") + public X getExtension(Class extensionType) { + if (extensionMap == null || extensionMap.size() != extensions.size()) { + Map, SettlementExtension> map = new HashMap, SettlementExtension>( extensions.size()); + for (SettlementExtension extension : extensions ) { + map.put(extension.getClass(), extension); + } + extensionMap = map; + } + return (X)extensionMap.get(extensionType); + } + + public boolean hasExtension(Class extensionType) { + return getExtension(extensionType) != null; + } + + public Boolean isOverride() { + return override; + } + + public void setOverride(Boolean override) { + this.override = override; + } + + public Double getGoodwill() { + return goodwill; + } + + public void setGoodwill(Double goodwill) { + this.goodwill = goodwill; + } + + public Date getOpenDate() { + return openDate; + } + + public void setOpenDate(Date startDate) { + openDate = startDate; + } + + public Date getAllocateDate() { + return allocateDate; + } + + public void setAllocateDate(Date allocateDate) { + this.allocateDate = allocateDate; + } + + public Double getSubtotal() { + return subtotal; + } + + public void setSubtotal(Double subtotal) { + this.subtotal = subtotal; + } + + public Double getTaxRate() { + return taxRate; + } + + public void setTaxRate(Double taxRate) { + this.taxRate = taxRate; + } + + public Double getTaxAmount() { + return taxAmount; + } + + public void setTaxAmount(Double taxAmount) { + this.taxAmount = taxAmount; + } + + public Double getUnderinsuredAmount() { + return underinsuredAmount; + } + + public void setUnderinsuredAmount(Double underinsuredAmount) { + this.underinsuredAmount = underinsuredAmount; + } + + public Boolean isStarted() { + return started; + } + + public void setStarted(Boolean started) { + this.started = started; + } + + public Boolean isTaxable() { + return taxable; + } + + public void setTaxable(Boolean taxable) { + this.taxable = taxable; + } + + } + + @Entity(name = "SettlementExtension") + @Table(name = "claimsettlement_ext") + public abstract static class SettlementExtension { + + @Id + @GeneratedValue + private Long id; + + @Version + private Integer version; + + @CreationTimestamp + private Date creationDate; + + @UpdateTimestamp + private Date modifiedDate; + + @Column(name = "order_index") + private Integer orderIndex; + + @ManyToOne(fetch = FetchType.LAZY) + private Settlement settlement; + + public SettlementExtension() { + } + + public Claim getClaim() { + return settlement.getClaim(); + } + + public Long getId() { + return id; + } + + protected void setId(Long id) { + this.id = id; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public Settlement getSettlement() { + return settlement; + } + + public void setSettlement(Settlement settlement) { + this.settlement = settlement; + } + + public Integer getOrderIndex() { + return orderIndex; + } + + public void setOrderIndex(Integer orderIndex) { + this.orderIndex = orderIndex; + } + + } + + public enum SettlementStatus { + RESERVED, ALLOCATED, PAID, VOID, DENIED + } + + @Entity(name = "SettlementTask") + public static class SettlementTask extends Task { + + @ManyToOne(fetch = FetchType.LAZY) + private Settlement linked; + + public Settlement getLinked() { + return linked; + } + + public void setLinked(Settlement settlement) { + linked = settlement; + } + + } + + @Entity(name = "Task") + @Table(name = "wf_task") + @Inheritance + public abstract static class Task { + + @Id + @GeneratedValue + private Long id; + + @Version + private Integer version; + + @CreationTimestamp + private Date creationDate; + + @UpdateTimestamp + private Date modifiedDate; + + @Temporal( TemporalType.DATE ) + private Date startDate; + + @Temporal( TemporalType.DATE ) + private Date closeDate; + + @Temporal( TemporalType.DATE ) + private Date dueDate; + + @Temporal( TemporalType.DATE ) + private Date stateDueDate; + + @Temporal( TemporalType.DATE ) + private Date statusDueDate; + + @Temporal( TemporalType.DATE ) + private Date stateTransitionDate; + + @Temporal( TemporalType.DATE ) + private Date statusTransitionDate; + + @ManyToOne(fetch = FetchType.LAZY) + private Task parent; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "task_status") + private TaskStatus status; + + @OneToMany(mappedBy = "parent") + private Set> children = new HashSet<>(); + + @OneToMany(mappedBy = "status") + private Set> linkedTasks = new HashSet<>(); + + public abstract T getLinked(); + public abstract void setLinked(T linked); + + public void addChild(Task task) { + task.setParent(this); + children.add( task); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public TaskStatus getStatus() { + return status; + } + + public void setStatus(TaskStatus status) { + this.status = status; + } + + @SuppressWarnings("unchecked") + public > Set getChildren(Class ofType) { + Set children = null; + + children = new LinkedHashSet(); + for (Task child : this.children ) { + if (ofType.isInstance(child)) { + children.add((X) child); + } + } + return children; + } + + public Set> getChildren() { + return children; + } + + public void setChildren(Set> links) { + children = links; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date openDate) { + startDate = openDate; + } + + public Date getCloseDate() { + return closeDate; + } + + public void setCloseDate(Date closeDate) { + this.closeDate = closeDate; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date expiryDate) { + dueDate = expiryDate; + } + + public Task getParent() { + return parent; + } + + public void setParent(Task parentTask) { + parent = parentTask; + } + + public Set> getLinkedTasks() { + return linkedTasks; + } + + public void setLinkedTasks(Set> linkedTasks) { + this.linkedTasks = linkedTasks; + } + + public Date getStateTransitionDate() { + return stateTransitionDate; + } + + public void setStateTransitionDate(Date stateTransitionDate) { + this.stateTransitionDate = stateTransitionDate; + } + + public Date getStatusTransitionDate() { + return statusTransitionDate; + } + + public void setStatusTransitionDate(Date taskTransitionDate) { + statusTransitionDate = taskTransitionDate; + } + + public Date getStateDueDate() { + return stateDueDate; + } + + public void setStateDueDate(Date stateDueDate) { + this.stateDueDate = stateDueDate; + } + + public Date getStatusDueDate() { + return statusDueDate; + } + + public void setStatusDueDate(Date statusDueDate) { + this.statusDueDate = statusDueDate; + } + + } + + @Entity(name = "TaskStatus") + @Table(name = "wf_task_status") + public static class TaskStatus { + + @Id + @GeneratedValue + private Long id; + + @Version + private Integer version; + + @CreationTimestamp + private Date creationDate; + + @UpdateTimestamp + private Date modifiedDate; + + private boolean active; + + @Column(name = "order_index") + private Integer orderIndex; + + private String name; + private String displayName; + + public TaskStatus() { + } + + public String getEntityName() { + return displayName; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Override + public String toString() { + return name; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Claim.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Claim.java new file mode 100644 index 000000000000..9e6515afb708 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Claim.java @@ -0,0 +1,223 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class Claim { + public static final long serialVersionUID = 1L; + + private Long _id; + private Date _creationDate; + private Date _modifiedDate; + private Integer _version; + + private Long _trackingId; + private Integer _term; + private Double _initialReserve = 0.0; + + private Date _effectiveDate; + private Date _expiryDate; + + private Date _notificationDate; + private Date _pendingDate; + private Date _openDate; + private Date _suspendDate; + private Date _closeDate; + + private String _externalId; + private String _importRef; + private String _location; + + private Set _extensions; + private Set _settlements; + + private transient volatile Map, Extension> _extensionMap; + + /** + * default constructor + */ + public Claim() { + _extensions = new HashSet(); + _settlements = new HashSet(); + } + + public Claim getClaim() { + return this; + } + + public void addExtension(Extension extension) { + _extensions.add( extension ); + extension.setClaim( this ); + } + + public void addSettlement(Settlement settlement) { + _settlements.add( settlement ); + settlement.setClaim( this ); + } + + public Long getId() { + return _id; + } + + public void setId(Long id) { + _id = id; + } + + public Date getCreationDate() { + return _creationDate; + } + + public void setCreationDate(Date creationDate) { + _creationDate = creationDate; + } + + public Date getModifiedDate() { + return _modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + _modifiedDate = modifiedDate; + } + + public Integer getVersion() { + return _version; + } + + public void setVersion(Integer version) { + _version = version; + } + + public Long getTrackingId() { + return _trackingId; + } + + public void setTrackingId(Long trackingId) { + _trackingId = trackingId; + } + + public String getExternalId() { + return _externalId; + } + + public void setExternalId(String externalId) { + _externalId = externalId; + } + + public Date getEffectiveDate() { + return _effectiveDate; + } + + public void setEffectiveDate(Date effectiveDate) { + _effectiveDate = effectiveDate; + } + + public Date getExpiryDate() { + return _expiryDate; + } + + public void setExpiryDate(Date expiryDate) { + _expiryDate = expiryDate; + } + + public Set getExtensions() { + return _extensions; + } + + public void setExtensions(Set extensions) { + _extensions = extensions; + } + + public Set getSettlements() { + return _settlements; + } + + public void setSettlements(Set settlements) { + _settlements = settlements; + } + + public Date getNotificationDate() { + return _notificationDate; + } + + public void setNotificationDate(Date notificationDate) { + _notificationDate = notificationDate; + } + + public Date getOpenDate() { + return _openDate; + } + + public void setOpenDate(Date openDate) { + _openDate = openDate; + } + + public Date getCloseDate() { + return _closeDate; + } + + public void setCloseDate(Date closeDate) { + _closeDate = closeDate; + } + + public Double getInitialReserve() { + return _initialReserve; + } + + public void setInitialReserve(Double initialReserve) { + _initialReserve = initialReserve; + } + + public String getLocation() { + return _location; + } + + public void setLocation(String location) { + _location = location; + } + + public String getImportRef() { + return _importRef; + } + + public void setImportRef(String importRef) { + _importRef = importRef; + } + + public Date getPendingDate() { + return _pendingDate; + } + + public void setPendingDate(Date startDate) { + _pendingDate = startDate; + } + + public Date getSuspendDate() { + return _suspendDate; + } + + public void setSuspendDate(Date suspendDate) { + _suspendDate = suspendDate; + } + + @SuppressWarnings("unchecked") + public X getExtension(Class extensionType) { + if ( _extensionMap == null || _extensionMap.size() != _extensions.size() ) { + Map, Extension> map = new HashMap, Extension>( _extensions.size() ); + map = new HashMap, Extension>( _extensions.size() ); + for ( Extension extension : _extensions ) { + map.put( extension.getClass(), extension ); + } + _extensionMap = map; + } + return (X) _extensionMap.get( extensionType ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/EwtAssessmentExtension.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/EwtAssessmentExtension.java new file mode 100644 index 000000000000..2326dfbb562b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/EwtAssessmentExtension.java @@ -0,0 +1,213 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +public class EwtAssessmentExtension extends SettlementExtension { + public static final long serialVersionUID = 1L; + public static final String PROPERTY_DETAIL_OPTIONS = "detail_options"; + public static final String PROPERTY_DAMAGE_TYPE_OPTIONS = "damage_type_options"; + public static final String PROPERTY_EXCLUSION_OPTIONS = "exclusion_options"; + public static final String PROPERTY_OVERRIDE_ENABLED = "override_enabled"; + + private Double _requestedUnits = -1.0; //2 + private Double _requestedUnitAmount = -1.0; //$150 + private Double _requestedSubtotal = 0.0; //$300 + private Double _requestedTaxAmount = 0.0; //$30 + private Double _requestedTotal = 0.0; //$330 + + private Double _coveredRatio = 0.0; + private Double _coveredUnits = 0.0; + private Double _coveredUnitAmount = 0.0; + private Double _coveredUnitAmountOverride = 0.0; + private Double _coveredSubtotal = 0.0; + private Double _coveredTaxAmount = 0.0; + private Double _coveredTotal = 0.0; + + private Double _underinsuredAmount = 0.0; + private Double _shortfallUnitAmount = 0.0; + private Double _shortfallTotal = 0.0; + + private Double _taxRate = 0.0; + + private String _details; + private String _damageType; + private String _exclusion; + private Boolean _validInspection; + private Boolean _taxExempt = false; + + public EwtAssessmentExtension() { + } + + public Double getRequestedUnits() { + return _requestedUnits; + } + + public void setRequestedUnits(Double requestedUnits) { + _requestedUnits = requestedUnits; + } + + public Double getRequestedUnitAmount() { + return _requestedUnitAmount; + } + + public void setRequestedUnitAmount(Double requestedBenefitPerUnit) { + _requestedUnitAmount = requestedBenefitPerUnit; + } + + public Double getRequestedSubtotal() { + return _requestedSubtotal; + } + + public void setRequestedSubtotal(Double requestedBenefitSubtotal) { + _requestedSubtotal = requestedBenefitSubtotal; + } + + public Double getRequestedTaxAmount() { + return _requestedTaxAmount; + } + + public void setRequestedTaxAmount(Double requestedBenefitTax) { + _requestedTaxAmount = requestedBenefitTax; + } + + public Double getRequestedTotal() { + return _requestedTotal; + } + + public void setRequestedTotal(Double requestedBenefitTotal) { + _requestedTotal = requestedBenefitTotal; + } + + public Double getCoveredUnitAmount() { + return _coveredUnitAmount; + } + + public void setCoveredUnitAmount(Double coveredBenefitPerUnit) { + _coveredUnitAmount = coveredBenefitPerUnit; + } + + public Double getCoveredSubtotal() { + return _coveredSubtotal; + } + + public void setCoveredSubtotal(Double coveredBenefitSubtotal) { + _coveredSubtotal = coveredBenefitSubtotal; + } + + public Double getCoveredTaxAmount() { + return _coveredTaxAmount; + } + + public void setCoveredTaxAmount(Double coveredTaxAmount) { + _coveredTaxAmount = coveredTaxAmount; + } + + public Double getCoveredTotal() { + return _coveredTotal; + } + + public void setCoveredTotal(Double coveredBenefitTotal) { + _coveredTotal = coveredBenefitTotal; + } + + public Double getTaxRate() { + return _taxRate; + } + + public void setTaxRate(Double taxRate) { + _taxRate = taxRate; + } + + public Double getShortfallUnitAmount() { + return _shortfallUnitAmount; + } + + public void setShortfallUnitAmount(Double shortfallUnitAmount) { + _shortfallUnitAmount = shortfallUnitAmount; + } + + public Double getShortfallTotal() { + return _shortfallTotal; + } + + public void setShortfallTotal(Double shortfallTotal) { + _shortfallTotal = shortfallTotal; + } + + public String getDetails() { + return _details; + } + + public void setDetails(String description) { + _details = description; + } + + public Double getUnderinsuredAmount() { + return _underinsuredAmount; + } + + public void setUnderinsuredAmount(Double truncatedAmount) { + _underinsuredAmount = truncatedAmount; + } + + public Double getCoveredUnits() { + return _coveredUnits; + } + + public void setCoveredUnits(Double coveredUnits) { + _coveredUnits = coveredUnits; + } + + public String getDamageType() { + return _damageType; + } + + public void setDamageType(String damageType) { + _damageType = damageType; + } + + public Double getCoveredRatio() { + return _coveredRatio; + } + + public void setCoveredRatio(Double coveredRatio) { + _coveredRatio = coveredRatio; + } + + public String getExclusion() { + return _exclusion; + } + + public void setExclusion(String exclusion) { + _exclusion = exclusion; + } + + public Double getCoveredUnitAmountOverride() { + return _coveredUnitAmountOverride; + } + + public void setCoveredUnitAmountOverride(Double coveredUnitOverride) { + _coveredUnitAmountOverride = coveredUnitOverride; + } + + public Boolean isValidInspection() { + return _validInspection; + } + + public void setValidInspection(Boolean validInspection) { + _validInspection = validInspection; + } + + public Boolean isTaxExempt() { + return _taxExempt; + } + + public void setTaxExempt(Boolean taxExempt) { + _taxExempt = taxExempt; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Extension.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Extension.java new file mode 100644 index 000000000000..89a8e4970e3b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Extension.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.Date; + +public abstract class Extension { + public static final long serialVersionUID = 1L; + + private Long _id; + private Date _creationDate; + private Date _modifiedDate; + private Integer _version; + + private String _type; + + private Claim _claim; + + public Extension() { + String[] name = this.getClass().getName().split( "\\." ); + _type = name[name.length - 1]; + } + + public Long getId() { + return _id; + } + + protected void setId(Long id) { + _id = id; + } + + public Date getCreationDate() { + return _creationDate; + } + + public void setCreationDate(Date creationDate) { + _creationDate = creationDate; + } + + public Date getModifiedDate() { + return _modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + _modifiedDate = modifiedDate; + } + + public Integer getVersion() { + return _version; + } + + public void setVersion(Integer version) { + _version = version; + } + + public Claim getClaim() { + return _claim; + } + + public void setClaim(Claim claim) { + _claim = claim; + } + + public String getType() { + return _type; + } + + public void setType(String type) { + _type = type; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/GapAssessmentExtension.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/GapAssessmentExtension.java new file mode 100644 index 000000000000..7e9e05fd654e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/GapAssessmentExtension.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 . + */ +package org.hibernate.query.hhh12076; + +public class GapAssessmentExtension extends SettlementExtension { + public static final long serialVersionUID = 1L; + + private Double _insuredsObligation = 0.0; + private Double _eligibleAmount = 0.0; + private Double _assessedAmount = 0.0; + private Double _underinsuredAmount = 0.0; + + public Double getAssessedAmount() { + return _assessedAmount; + } + + public void setAssessedAmount(Double assessedAmount) { + _assessedAmount = assessedAmount; + } + + public Double getEligibleAmount() { + return _eligibleAmount; + } + + public void setEligibleAmount(Double eligible) { + _eligibleAmount = eligible; + } + + public Double getUnderinsuredAmount() { + return _underinsuredAmount; + } + + public void setUnderinsuredAmount(Double underinsuredAmount) { + _underinsuredAmount = underinsuredAmount; + } + + public Double getInsuredsObligation() { + return _insuredsObligation; + } + + public void setInsuredsObligation(Double insuredsObligation) { + _insuredsObligation = insuredsObligation; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/HbmMappingJoinClassTest.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/HbmMappingJoinClassTest.java new file mode 100644 index 000000000000..be9fb29624ce --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/HbmMappingJoinClassTest.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.query.Query; + +import org.hibernate.testing.FailureExpected; +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.assertNotNull; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-12076") +public class HbmMappingJoinClassTest extends BaseCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { + "Claim.hbm.xml", + "EwtAssessmentExtension.hbm.xml", + "Extension.hbm.xml", + "GapAssessmentExtension.hbm.xml", + "Settlement.hbm.xml", + "SettlementExtension.hbm.xml", + "SettlementTask.hbm.xml", + "Task.hbm.xml", + "TaskStatus.hbm.xml", + }; + } + + @Override + protected String getBaseForMappings() { + return "org/hibernate/query/hhh12076/"; + } + + @Override + protected void prepareTest() { + doInHibernate( this::sessionFactory, session -> { + TaskStatus taskStatus = new TaskStatus(); + taskStatus.setName("Enabled"); + taskStatus.setDisplayName("Enabled"); + session.save(taskStatus); + + for (long i = 0; i < 10; i++) { + SettlementTask settlementTask = new SettlementTask(); + settlementTask.setId(i); + Settlement settlement = new Settlement(); + settlementTask.setLinked(settlement); + settlementTask.setStatus(taskStatus); + + Claim claim = new Claim(); + claim.setId(i); + settlement.setClaim(claim); + + for (int j = 0; j < 2; j++) { + GapAssessmentExtension gapAssessmentExtension = new GapAssessmentExtension(); + gapAssessmentExtension.setSettlement(settlement); + EwtAssessmentExtension ewtAssessmentExtension = new EwtAssessmentExtension(); + ewtAssessmentExtension.setSettlement(settlement); + + settlement.getExtensions().add(gapAssessmentExtension); + settlement.getExtensions().add(ewtAssessmentExtension); + } + session.save(claim); + session.save(settlement); + session.save(settlementTask); + } + } ); + } + + @Test + @FailureExpected( jiraKey = "HHH-12076") + public void testClassExpressionInOnClause() { + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "select " + + " rootAlias.id, " + + " linked.id, " + + " extensions.id " + + "from SettlementTask as rootAlias " + + "join rootAlias.linked as linked " + + "left join linked.extensions as extensions " + + " on extensions.class = org.hibernate.query.hhh12076.EwtAssessmentExtension " + + "where linked.id = :claimId") + .setParameter("claimId", 1L) + .getResultList(); + + assertNotNull(results); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Settlement.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Settlement.java new file mode 100644 index 000000000000..bfde43973b41 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Settlement.java @@ -0,0 +1,256 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class Settlement { + + private Long _id; + private Integer _version; + private Date _creationDate; + private Date _modifiedDate; + + private Boolean _override = false; + private Boolean _started = false; + private Boolean _taxable = false; + + private Double _units = 0.0; + private Double _amount = 0.0; + private Double _subtotal = 0.0; + + private Double _taxRate = 0.0; + private Double _taxAmount = 0.0; + + private Double _goodwill = 0.0; + private Double _totalAmount = 0.0; + private Double _underinsuredAmount = 0.0; + + private Date _openDate; + private Date _allocateDate; + private Date _closeDate; + + private String _trackingId; + + private Claim _claim; + private SettlementStatus _status = SettlementStatus.RESERVED; + + private Set _extensions; + + private transient Map, SettlementExtension> _extensionMap; + + public Settlement() { + _extensions = new HashSet(); + } + + public Long getId() { + return _id; + } + + protected void setId(Long id) { + _id = id; + } + + public Integer getVersion() { + return _version; + } + + public void setVersion(Integer version) { + _version = version; + } + + public Date getCreationDate() { + return _creationDate; + } + + public void setCreationDate(Date creationDate) { + _creationDate = creationDate; + } + + public Date getModifiedDate() { + return _modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + _modifiedDate = modifiedDate; + } + + public Claim getClaim() { + return _claim; + } + + public void setClaim(Claim claim) { + _claim = claim; + } + + public SettlementStatus getStatus() { + return _status; + } + + public void setStatus(SettlementStatus status) { + _status = status; + } + + public String getTrackingId() { + return _trackingId; + } + + public void setTrackingId(String trackingId) { + _trackingId = trackingId; + } + + public Double getUnits() { + return _units; + } + + public void setUnits(Double units) { + _units = units; + } + + public Double getAmount() { + return _amount; + } + + public void setAmount(Double amount) { + _amount = amount; + } + + public Double getTotalAmount() { + return _totalAmount; + } + + public void setTotalAmount(Double totalAmount) { + _totalAmount = totalAmount; + } + + public Date getCloseDate() { + return _closeDate; + } + + public void setCloseDate(Date settlementDate) { + _closeDate = settlementDate; + } + + public Set getExtensions() { + return _extensions; + } + + public void setExtensions(Set extensions) { + _extensions = extensions; + } + + public void addExtension(SettlementExtension extension) { + if ( !hasExtension( extension.getClass() ) ) { + if ( extension.getOrderIndex() == null ) { + extension.setOrderIndex( _extensions.size() ); + } + extension.setSettlement( this ); + _extensions.add( extension ); + } + } + + @SuppressWarnings("unchecked") + public X getExtension(Class extensionType) { + if ( _extensionMap == null || _extensionMap.size() != _extensions.size() ) { + Map, SettlementExtension> map = new HashMap, SettlementExtension>( _extensions.size() ); + for ( SettlementExtension extension : _extensions ) { + map.put( extension.getClass(), extension ); + } + _extensionMap = map; + } + return (X) _extensionMap.get( extensionType ); + } + + public boolean hasExtension(Class extensionType) { + return getExtension( extensionType ) != null; + } + + public Boolean isOverride() { + return _override; + } + + public void setOverride(Boolean override) { + _override = override; + } + + public Double getGoodwill() { + return _goodwill; + } + + public void setGoodwill(Double goodwill) { + _goodwill = goodwill; + } + + public Date getOpenDate() { + return _openDate; + } + + public void setOpenDate(Date startDate) { + _openDate = startDate; + } + + public Date getAllocateDate() { + return _allocateDate; + } + + public void setAllocateDate(Date allocateDate) { + _allocateDate = allocateDate; + } + + public Double getSubtotal() { + return _subtotal; + } + + public void setSubtotal(Double subtotal) { + _subtotal = subtotal; + } + + public Double getTaxRate() { + return _taxRate; + } + + public void setTaxRate(Double taxRate) { + _taxRate = taxRate; + } + + public Double getTaxAmount() { + return _taxAmount; + } + + public void setTaxAmount(Double taxAmount) { + _taxAmount = taxAmount; + } + + public Double getUnderinsuredAmount() { + return _underinsuredAmount; + } + + public void setUnderinsuredAmount(Double underinsuredAmount) { + _underinsuredAmount = underinsuredAmount; + } + + public Boolean isStarted() { + return _started; + } + + public void setStarted(Boolean started) { + _started = started; + } + + public Boolean isTaxable() { + return _taxable; + } + + public void setTaxable(Boolean taxable) { + _taxable = taxable; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementExtension.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementExtension.java new file mode 100644 index 000000000000..24491121a6a6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementExtension.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.Date; + +public abstract class SettlementExtension { + + private Long _id; + private Integer _version; + private Date _creationDate; + private Date _modifiedDate; + + private Integer _orderIndex; + private Settlement _settlement; + + public SettlementExtension() { + } + + public Claim getClaim() { + return _settlement.getClaim(); + } + + public Long getId() { + return _id; + } + + protected void setId(Long id) { + _id = id; + } + + public Integer getVersion() { + return _version; + } + + public void setVersion(Integer version) { + _version = version; + } + + public Date getCreationDate() { + return _creationDate; + } + + public void setCreationDate(Date creationDate) { + _creationDate = creationDate; + } + + public Date getModifiedDate() { + return _modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + _modifiedDate = modifiedDate; + } + + public Settlement getSettlement() { + return _settlement; + } + + public void setSettlement(Settlement settlement) { + _settlement = settlement; + } + + public Integer getOrderIndex() { + return _orderIndex; + } + + public void setOrderIndex(Integer orderIndex) { + _orderIndex = orderIndex; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementStatus.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementStatus.java new file mode 100644 index 000000000000..4fd18a9f2449 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementStatus.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 . + */ +package org.hibernate.query.hhh12076; + +public enum SettlementStatus { + RESERVED, ALLOCATED, PAID, VOID, DENIED +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementTask.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementTask.java new file mode 100644 index 000000000000..ec1e53214afe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/SettlementTask.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.query.hhh12076; + +public class SettlementTask extends Task { + + private Settlement _linked; + + public Settlement getLinked() { + return _linked; + } + + public void setLinked(Settlement settlement) { + _linked = settlement; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Task.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Task.java new file mode 100644 index 000000000000..65c95c1adba7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/Task.java @@ -0,0 +1,182 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +public abstract class Task { + + private Long _id; + private Integer _version; + private Date _creationDate; + private Date _modifiedDate; + + private Date _startDate; + private Date _closeDate; + private Date _dueDate; + private Date _stateDueDate; + private Date _statusDueDate; + private Date _stateTransitionDate; + private Date _statusTransitionDate; + + private Task _parent; + private TaskStatus _status; + + private Set> _children; + private Set> _linkedTasks; + + public Task() { + _children = new HashSet>(); + _linkedTasks = new HashSet>(); + } + + public abstract T getLinked(); + + public abstract void setLinked(T linked); + + public void addChild(Task task) { + task.setParent( this ); + _children.add( task ); + } + + public Long getId() { + return _id; + } + + public void setId(Long id) { + _id = id; + } + + public Integer getVersion() { + return _version; + } + + public void setVersion(Integer version) { + _version = version; + } + + public Date getCreationDate() { + return _creationDate; + } + + public void setCreationDate(Date creationDate) { + _creationDate = creationDate; + } + + public Date getModifiedDate() { + return _modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + _modifiedDate = modifiedDate; + } + + public TaskStatus getStatus() { + return _status; + } + + public void setStatus(TaskStatus status) { + _status = status; + } + + @SuppressWarnings("unchecked") + public > Set getChildren(Class ofType) { + Set children = null; + + children = new LinkedHashSet(); + for ( Task child : _children ) { + if ( ofType.isInstance( child ) ) { + children.add( (X) child ); + } + } + return children; + } + + public Set> getChildren() { + return _children; + } + + public void setChildren(Set> links) { + _children = links; + } + + public Date getStartDate() { + return _startDate; + } + + public void setStartDate(Date openDate) { + _startDate = openDate; + } + + public Date getCloseDate() { + return _closeDate; + } + + public void setCloseDate(Date closeDate) { + _closeDate = closeDate; + } + + public Date getDueDate() { + return _dueDate; + } + + public void setDueDate(Date expiryDate) { + _dueDate = expiryDate; + } + + public Task getParent() { + return _parent; + } + + public void setParent(Task parentTask) { + _parent = parentTask; + } + + public Set> getLinkedTasks() { + return _linkedTasks; + } + + public void setLinkedTasks(Set> linkedTasks) { + _linkedTasks = linkedTasks; + } + + public Date getStateTransitionDate() { + return _stateTransitionDate; + } + + public void setStateTransitionDate(Date stateTransitionDate) { + _stateTransitionDate = stateTransitionDate; + } + + public Date getStatusTransitionDate() { + return _statusTransitionDate; + } + + public void setStatusTransitionDate(Date taskTransitionDate) { + _statusTransitionDate = taskTransitionDate; + } + + public Date getStateDueDate() { + return _stateDueDate; + } + + public void setStateDueDate(Date stateDueDate) { + _stateDueDate = stateDueDate; + } + + public Date getStatusDueDate() { + return _statusDueDate; + } + + public void setStatusDueDate(Date statusDueDate) { + _statusDueDate = statusDueDate; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/TaskStatus.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/TaskStatus.java new file mode 100644 index 000000000000..c1b136a35681 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/TaskStatus.java @@ -0,0 +1,131 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12076; + +import java.util.Date; + +public class TaskStatus { + + private Long _id; + private Integer _version; + private Date _creationDate; + private Date _modifiedDate; + + private boolean _active; + private Integer _orderIndex; + + private String _name; + private String _displayName; + + public TaskStatus() { + } + + public String getEntityName() { + return _displayName; + } + + public Long getId() { + return _id; + } + + public void setId(Long id) { + _id = id; + } + + public Integer getVersion() { + return _version; + } + + public void setVersion(Integer version) { + _version = version; + } + + public Date getCreationDate() { + return _creationDate; + } + + public void setCreationDate(Date creationDate) { + _creationDate = creationDate; + } + + public Date getModifiedDate() { + return _modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + _modifiedDate = modifiedDate; + } + + public String getName() { + return _name; + } + + public void setName(String name) { + _name = name; + } + + public String getDisplayName() { + return _displayName; + } + + public void setDisplayName(String displayName) { + _displayName = displayName; + } + + public boolean isActive() { + return _active; + } + + public void setActive(boolean active) { + _active = active; + } + + @Override + public String toString() { + return _name; + } + + public Integer getOrderIndex() { + return _orderIndex; + } + + public void setOrderIndex(Integer ordering) { + _orderIndex = ordering; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( _name == null ) ? 0 : _name.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( obj == null ) { + return false; + } + if ( obj == this ) { + return true; + } + if ( !( obj instanceof TaskStatus ) ) { + return false; + } + TaskStatus other = (TaskStatus) obj; + if ( _name == null ) { + if ( other._name != null ) { + return false; + } + } + else if ( !_name.equals( other._name ) ) { + return false; + } + return true; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12225/Contract.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/Contract.java new file mode 100644 index 000000000000..d6946861ef9d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/Contract.java @@ -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 . + */ +package org.hibernate.query.hhh12225; + +import java.util.Date; + +public class Contract { + public static final long serialVersionUID = 1L; + + private transient boolean overrideEnabled = false; + + private Long id; + private Date creationDate; + private Date modifiedDate; + private Integer version; + + private boolean rendered; + private boolean fixedPrice; + private boolean renewable; + private boolean emailDistributionRequested; + + private Integer financedTerm; + private Integer financedAmortizationPeriod; + private Integer coverageTerm; + + private Long trackingId; + private Long timeToCreate = 0L; + private Long templateId; + + private Double productPrice = 0.0; + private Double totalCost = 0.0; + private Double paymentAmount = 0.0; + private Double price = 0.0; + private Double financedAmount = 0.0; + private Double coverageBenefit = 0.0; + private Double coveragePaymentRelief = 0.0; + private Double coverageFinanced = 0.0; + private Double previousDeficiency = 0.0; + private Double coverageDeficiency = 0.0; + private Double interestRate = 0.0; + + private String externalId; + private String paymentMethod; + private String paymentFrequency; + private String accountNumber; + private String origin; + private String premiumFinanced; + private String locale; + + private Date effectiveDate; + private Date terminationDate; + private Date renewalDate; + private Date expiryDate; + + private Contract _endorsed; + + public Contract() { + } + + public boolean isNew() { + return this.id == null; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Date getEffectiveDate() { + return this.effectiveDate; + } + + public void setEffectiveDate(Date effectiveDate) { + this.effectiveDate = effectiveDate; + } + + public Date getTerminationDate() { + return this.terminationDate; + } + + public void setTerminationDate(Date terminationDate) { + this.terminationDate = terminationDate; + } + + public String getExternalId() { + return this.externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public Long getTemplateId() { + return this.templateId; + } + + public void setTemplateId(Long templateId) { + this.templateId = templateId; + } + + public Double getPrice() { + return this.price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Integer getVersion() { + return this.version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public String toString() { + return String.valueOf( id ); + } + + public Integer getFinancedTerm() { + return financedTerm; + } + + public void setFinancedTerm(Integer integer) { + financedTerm = integer; + } + + public Long getTrackingId() { + return trackingId; + } + + public void setTrackingId(Long id) { + trackingId = id; + } + + public Date getCreationDate() { + return creationDate; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setCreationDate(Date date) { + creationDate = date; + } + + public void setModifiedDate(Date date) { + modifiedDate = date; + } + + public Integer getCoverageTerm() { + return coverageTerm; + } + + public void setCoverageTerm(Integer integer) { + coverageTerm = integer; + } + + public String getAccountNumber() { + return accountNumber; + } + + public Double getFinancedAmount() { + return financedAmount; + } + + public void setAccountNumber(String string) { + accountNumber = string; + } + + public void setFinancedAmount(Double double1) { + financedAmount = double1; + } + + public Double getCoverageDeficiency() { + return coverageDeficiency; + } + + public Double getPreviousDeficiency() { + return previousDeficiency; + } + + public void setCoverageDeficiency(Double double1) { + coverageDeficiency = double1; + } + + public void setPreviousDeficiency(Double double1) { + previousDeficiency = double1; + } + + public Double getTotalCost() { + return totalCost; + } + + public void setTotalCost(Double double1) { + totalCost = double1; + } + + public Double getInterestRate() { + return interestRate; + } + + public void setInterestRate(Double double1) { + interestRate = double1; + } + + public boolean isRendered() { + return rendered; + } + + public void setRendered(boolean b) { + rendered = b; + } + + public Double getCoverageBenefit() { + return coverageBenefit; + } + + public Double getCoverageFinanced() { + return coverageFinanced; + } + + public void setCoverageBenefit(Double double1) { + coverageBenefit = double1; + } + + public void setCoverageFinanced(Double double1) { + coverageFinanced = double1; + } + + public boolean isFixedPrice() { + return fixedPrice; + } + + public void setFixedPrice(boolean fixedPrice) { + this.fixedPrice = fixedPrice; + } + + public Double getProductPrice() { + return productPrice; + } + + public void setProductPrice(Double productPrice) { + this.productPrice = productPrice; + } + + public Long getTimeToCreate() { + return timeToCreate; + } + + public void setTimeToCreate(Long timeToCreate) { + this.timeToCreate = timeToCreate; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } + + public String getPaymentFrequency() { + return paymentFrequency; + } + + public void setPaymentFrequency(String paymentFrequency) { + this.paymentFrequency = paymentFrequency; + } + + public Double getPaymentAmount() { + return paymentAmount; + } + + public void setPaymentAmount(Double paymentAmount) { + this.paymentAmount = paymentAmount; + } + + public Double getCoveragePaymentRelief() { + return coveragePaymentRelief; + } + + public void setCoveragePaymentRelief(Double coveragePaymentRelief) { + this.coveragePaymentRelief = coveragePaymentRelief; + } + + public Date getExpiryDate() { + return expiryDate; + } + + public void setExpiryDate(Date coverageTerminationDate) { + this.expiryDate = coverageTerminationDate; + } + + public String getPremiumFinanced() { + return premiumFinanced; + } + + public void setPremiumFinanced(String premiumFinanced) { + this.premiumFinanced = premiumFinanced; + } + + public Integer getFinancedAmortizationPeriod() { + return financedAmortizationPeriod; + } + + public void setFinancedAmortizationPeriod(Integer financedAmortizationPeriod) { + this.financedAmortizationPeriod = financedAmortizationPeriod; + } + + public Date getRenewalDate() { + return renewalDate; + } + + public void setRenewalDate(Date refinancingDate) { + this.renewalDate = refinancingDate; + } + + public boolean isRenewable() { + return renewable; + } + + public void setRenewable(boolean renewable) { + this.renewable = renewable; + } + + public boolean isEmailDistributionRequested() { + return emailDistributionRequested; + } + + public void setEmailDistributionRequested(boolean emailDistributionRequested) { + this.emailDistributionRequested = emailDistributionRequested; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public Contract getEndorsed() { + return _endorsed; + } + + public void setEndorsed(Contract endorsed) { + _endorsed = endorsed; + } + + public boolean isEndorsement() { + return _endorsed != null; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public boolean isOverrideEnabled() { + return overrideEnabled; + } + + public void setOverrideEnabled(boolean overrideEnabled) { + this.overrideEnabled = overrideEnabled; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12225/HQLTypeTest.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/HQLTypeTest.java new file mode 100644 index 000000000000..2a27fe7e686c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/HQLTypeTest.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12225; + +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.assertFalse; + +@TestForIssue(jiraKey = "HHH-12225") +public class HQLTypeTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + }; + } + + @Override + protected String[] getMappings() { + return new String[] { + "Contract.hbm.xml", + "Vehicle.hbm.xml" + }; + } + + @Override + protected String getBaseForMappings() { + return "org/hibernate/query/hhh12225/"; + } + + @Test + public void test() throws Exception { + VehicleContract contract = doInHibernate( this::sessionFactory, session -> { + VehicleContract firstCotract = null; + for ( long i = 0; i < 10; i++ ) { + VehicleContract vehicleContract = new VehicleContract(); + Vehicle vehicle1 = new Vehicle(); + vehicle1.setContract( vehicleContract ); + VehicleTrackContract vehicleTrackContract = new VehicleTrackContract(); + Vehicle vehicle2 = new Vehicle(); + vehicle2.setContract( vehicleTrackContract ); + + session.save( vehicle1 ); + session.save( vehicle2 ); + session.save( vehicleContract ); + session.save( vehicleTrackContract ); + if ( i == 0 ) { + firstCotract = vehicleContract; + } + } + return firstCotract; + } ); + + doInHibernate( this::sessionFactory, session -> { + List workingResults = session.createQuery( + "select rootAlias.id from Contract as rootAlias where rootAlias.id = :id" ) + .setParameter( "id", contract.getId() ) + .getResultList(); + + assertFalse( workingResults.isEmpty() ); + Long workingId = (Long) workingResults.get( 0 ); + assertEquals( Long.valueOf( contract.getId() ), workingId ); + + List failingResults = session.createQuery( + "select rootAlias.id, type(rootAlias) from Contract as rootAlias where rootAlias.id = :id" ) + .setParameter( "id", contract.getId() ) + .getResultList(); + + assertFalse( failingResults.isEmpty() ); + Long failingId = (Long) ( (Object[]) failingResults.get( 0 ) )[0]; + assertEquals( Long.valueOf( contract.getId() ), failingId ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12225/Vehicle.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/Vehicle.java new file mode 100644 index 000000000000..16bb6d28e051 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/Vehicle.java @@ -0,0 +1,285 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12225; + +import java.util.Date; + +public class Vehicle { + public static final long serialVersionUID = 1L; + public static final String STATUS_NEW = "new"; + public static final String STATUS_USED = "used"; + + private Long _id; + private Integer _version; + private Date _creationDate; + private Date _modifiedDate; + + private String _vin; + private boolean _dirty; + private Double _msrp; + private Double _residualValue; + private Double _invoicePrice; + + private Integer _decodeAttempts; + private String _model; + private String _modelDetail; + private Integer _year; + private Integer _odometer; + private String _license; + private String _status; + private String _vehicleType; + private String _classification; + + private String _country; + private String _engineType; + private String _assemblyPlant; + private Integer _sequenceNumber; + private String _bodyType; + private String _fuelType; + private String _driveLineType; + + private VehicleContract _contract; + + /** + * default constructor + */ + public Vehicle() { + _decodeAttempts = 0; + } + + public Long getId() { + return _id; + } + + public void setId(Long id) { + _id = id; + } + + public Integer getVersion() { + return _version; + } + + public void setVersion(Integer version) { + _version = version; + } + + public Date getCreationDate() { + return _creationDate; + } + + public void setCreationDate(Date creationDate) { + _creationDate = creationDate; + } + + public Date getModifiedDate() { + return _modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + _modifiedDate = modifiedDate; + } + + public String getVin() { + return _vin; + } + + public void setVin(String vin) { + _vin = vin; + } + + public boolean isDirty() { + return _dirty; + } + + public void setDirty(boolean dirty) { + _dirty = dirty; + } + + public Double getMsrp() { + return _msrp; + } + + public void setMsrp(Double msrp) { + _msrp = msrp; + } + + public Integer getDecodeAttempts() { + return _decodeAttempts; + } + + public void setDecodeAttempts(Integer decodeAttempts) { + _decodeAttempts = decodeAttempts; + } + + public String getModel() { + return _model; + } + + public void setModel(String model) { + _model = model; + } + + public String getModelDetail() { + return _modelDetail; + } + + public void setModelDetail(String modelDetail) { + _modelDetail = modelDetail; + } + + public Integer getYear() { + return _year; + } + + public void setYear(Integer year) { + _year = year; + } + + public Integer getOdometer() { + return _odometer; + } + + public void setOdometer(Integer odometer) { + _odometer = odometer; + } + + public String getLicense() { + return _license; + } + + public void setLicense(String license) { + _license = license; + } + + public String getStatus() { + return _status; + } + + public void setStatus(String status) { + _status = status; + } + + public String getVehicleType() { + return _vehicleType; + } + + public void setVehicleType(String vehicleType) { + _vehicleType = vehicleType; + } + + public String getCountry() { + return _country; + } + + public void setCountry(String country) { + _country = country; + } + + public String getEngineType() { + return _engineType; + } + + public void setEngineType(String engineType) { + _engineType = engineType; + } + + public String getAssemblyPlant() { + return _assemblyPlant; + } + + public void setAssemblyPlant(String assemblyPlant) { + _assemblyPlant = assemblyPlant; + } + + public Integer getSequenceNumber() { + return _sequenceNumber; + } + + public void setSequenceNumber(Integer sequenceNumber) { + _sequenceNumber = sequenceNumber; + } + + public String getBodyType() { + return _bodyType; + } + + public void setBodyType(String bodyType) { + _bodyType = bodyType; + } + + public String getFuelType() { + return _fuelType; + } + + public void setFuelType(String fuelType) { + _fuelType = fuelType; + } + + public String getDriveLineType() { + return _driveLineType; + } + + public void setDriveLineType(String driveLineType) { + _driveLineType = driveLineType; + } + + public VehicleContract getContract() { + return _contract; + } + + public void setContract(VehicleContract contract) { + _contract = contract; + } + + public String getClassification() { + return _classification; + } + + public void setClassification(String classification) { + _classification = classification; + } + + public Double getResidualValue() { + return _residualValue; + } + + public void setResidualValue(Double residualValue) { + _residualValue = residualValue; + } + + public Double getInvoicePrice() { + return _invoicePrice; + } + + public void setInvoicePrice(Double invoicePrice) { + _invoicePrice = invoicePrice; + } + + public String toString() { + return String.valueOf( _vin ); + } + + public boolean equals(Object obj) { + if ( obj == null ) { + return false; + } + if ( obj == this ) { + return true; + } + if ( obj instanceof Vehicle ) { + //TODO - needs to include contract equals comparision + return _vin.equals( ( (Vehicle) obj ).getVin() ); + } + return false; + } + + public int hashCode() { + return _vin.hashCode(); + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12225/VehicleContract.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/VehicleContract.java new file mode 100644 index 000000000000..f8891e438529 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/VehicleContract.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 . + */ +package org.hibernate.query.hhh12225; + +import java.util.ArrayList; +import java.util.List; + +public class VehicleContract extends Contract { + public static final long serialVersionUID = 1L; + + private List vehicles; + + public VehicleContract() { + vehicles = new ArrayList(); + } + + public void addVehicle(Vehicle vehicle) { + vehicle.setContract( this ); + getVehicles().add( vehicle ); + } + + public String toString() { + return String.valueOf( getId() ); + } + + public List getVehicles() { + return vehicles; + } + + public void setVehicles(List vehicles) { + this.vehicles = vehicles; + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12225/VehicleTrackContract.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/VehicleTrackContract.java new file mode 100644 index 000000000000..4fea2019245c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12225/VehicleTrackContract.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.query.hhh12225; + +public class VehicleTrackContract extends VehicleContract { + public static final long serialVersionUID = 1L; + private String _etchingId = null; + private boolean _original = false; + + public String getEtchingId() { + return _etchingId; + } + + public void setEtchingId(String etchingId) { + _etchingId = etchingId; + } + + public boolean isOriginal() { + return _original; + } + + public void setOriginal(boolean original) { + _original = original; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/secure/JaccIntegratorTest.java b/hibernate-core/src/test/java/org/hibernate/secure/JaccIntegratorTest.java new file mode 100644 index 000000000000..f3e0f7eadd59 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/secure/JaccIntegratorTest.java @@ -0,0 +1,172 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.secure; + +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.security.Provider; +import java.util.Collections; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.security.auth.Subject; +import javax.security.jacc.PolicyContext; +import javax.security.jacc.PolicyContextException; +import javax.security.jacc.PolicyContextHandler; + +import org.hibernate.cfg.AvailableSettings; +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.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-11805" ) +public class JaccIntegratorTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.JACC_ENABLED, Boolean.TRUE.toString() ); + options.put( AvailableSettings.JACC_CONTEXT_ID, "JACC_CONTEXT_ID" ); + options.put( "hibernate.jacc.allowed.org.hibernate.secure.Customer", "insert" ); + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + PolicyContextHandler policyContextHandler = new PolicyContextHandler() { + @Override + public Object getContext(String key, Object data) throws PolicyContextException { + Subject subject = new Subject( true, Collections.singleton(new java.security.Principal() { + + @Override + public String getName() { + return "org.hibernate.secure.JaccIntegratorTest$Person"; + } + + @Override + public boolean implies(Subject subject) { + return true; + } + }), Collections.emptySet(), Collections.emptySet()); + return subject; + } + + @Override + public String[] getKeys() throws PolicyContextException { + return new String[0]; + } + + @Override + public boolean supports(String key) throws PolicyContextException { + return true; + } + }; + try { + PolicyContext.registerHandler( "javax.security.auth.Subject.container", policyContextHandler, true); + } + catch (PolicyContextException e) { + fail(e.getMessage()); + } + } + + protected void setPolicy(boolean allow) { + Policy.setPolicy( new Policy() { + @Override + public Provider getProvider() { + return super.getProvider(); + } + + @Override + public String getType() { + return super.getType(); + } + + @Override + public Parameters getParameters() { + return super.getParameters(); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return super.getPermissions( codesource ); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return super.getPermissions( domain ); + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return allow; + } + + @Override + public void refresh() { + super.refresh(); + } + } ); + } + + @Test + public void testAllow() { + setPolicy( true ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.id = 1L; + person.name = "John Doe"; + + entityManager.persist( person ); + } ); + } + + @Test + public void testDisallow() { + setPolicy( false ); + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.id = 1L; + person.name = "John Doe"; + + entityManager.persist( person ); + } ); + + fail("Should have thrown SecurityException"); + } + catch (Exception e) { + assertTrue( e.getCause() instanceof SecurityException ); + } + } + + @Entity + public static class Person { + + @Id + private Long id; + + private String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java new file mode 100644 index 000000000000..566e3681166a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -0,0 +1,320 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.serialization; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.SerializationHelper; +import org.hibernate.proxy.AbstractLazyInitializer; +import org.hibernate.proxy.HibernateProxy; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Selaron + */ +public class EntityProxySerializationTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class, ChildEntity.class }; + } + + @Override + protected void configure(final Configuration configuration) { + // enable LL without TX, which used to cause problems when serializing proxies (see HHH-12720) + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, Boolean.TRUE.toString() ); + } + + /** + * Prepare and persist a {@link SimpleEntity} with two {@link ChildEntity}. + */ + @Before + public void prepare() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + + try { + final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + if (count.longValue() > 0L) { + // entity already added previously + return; + } + + final SimpleEntity entity = new SimpleEntity(); + entity.setId( 1L ); + entity.setName( "TheParent" ); + + final ChildEntity c1 = new ChildEntity(); + c1.setId( 1L ); + c1.setParent( entity ); + + final ChildEntity c2 = new ChildEntity(); + c2.setId( 2L ); + c2.setParent( entity ); + + s.save( entity ); + s.save( c1 ); + s.save( c2 ); + } + finally { + t.commit(); + s.close(); + } + } + + /** + * Tests that serializing an initialized proxy will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + public void testInitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Initialize the proxy + parent.getName(); + assertTrue( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.getName() ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that serializing a proxy which is not initialized + * but whose target has been (separately) added to the persistence context + * will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + public void testUninitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Load the target of the proxy without the proxy being made aware of it + s.detach( parent ); + s.find( SimpleEntity.class, 1L ); + s.update( parent ); + + // assert we still have an uninitialized proxy + assertFalse( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.getName() ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + public void testProxyInitializationWithoutTX() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + assertEquals( "TheParent", parent.getName() ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( parent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-12720") + public void testProxyInitializationWithoutTXAfterDeserialization() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // destroy AbstractLazyInitializer internal state + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( deserializedParent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( deserializedParent ) ); + + assertEquals( "TheParent", deserializedParent.getName() ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( deserializedParent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + @Entity(name = "SimpleEntity") + static class SimpleEntity implements Serializable { + + private Long id; + + private String name; + + Set children = new HashSet<>(); + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent") + @LazyCollection(LazyCollectionOption.EXTRA) + @Fetch(FetchMode.SELECT) + public Set getChildren() { + return children; + } + + public void setChildren(final Set children) { + this.children = children; + } + + } + + @Entity(name = "ChildEntity") + static class ChildEntity { + private Long id; + + private SimpleEntity parent; + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.PROXY) + public SimpleEntity getParent() { + return parent; + } + + public void setParent(final SimpleEntity parent) { + this.parent = parent; + } + + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/GetterSetterSerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/GetterSetterSerializationTest.java index 875119d8e220..e437336fea54 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/GetterSetterSerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/GetterSetterSerializationTest.java @@ -39,10 +39,11 @@ public class GetterSetterSerializationTest { public void testPrivateFieldGetter() throws Exception { final AnEntity entity = new AnEntity( new PK( 1L ) ); + final String propertyName = "pk"; final Getter getter = new GetterFieldImpl( AnEntity.class, - "pk", - ReflectHelper.findField( AnEntity.class, "pk") + propertyName, + ReflectHelper.findField( AnEntity.class, propertyName) ); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream(baos); @@ -60,15 +61,16 @@ public void testPrivateFieldGetter() throws Exception { public void testPrivateFieldSetter() throws Exception { AnEntity entity = new AnEntity( new PK( 1L ) ); + final String propertyName = "pk"; final Getter getter = new GetterFieldImpl( AnEntity.class, - "pk", - ReflectHelper.findField( AnEntity.class, "pk") + propertyName, + ReflectHelper.findField( AnEntity.class, propertyName) ); final Setter setter = new SetterFieldImpl( AnEntity.class, - "pk", - ReflectHelper.findField( AnEntity.class, "pk") + propertyName, + ReflectHelper.findField( AnEntity.class, propertyName) ); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java new file mode 100644 index 000000000000..0b8eb3993c15 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java @@ -0,0 +1,243 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.serialization; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.EntityMode; +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.SerializationHelper; +import org.hibernate.proxy.AbstractLazyInitializer; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.map.MapProxy; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Selaron + */ +public class MapProxySerializationTest extends BaseCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "serialization/DynamicMapMappings.hbm.xml" }; + } + + @Override + protected void configure(final Configuration configuration) { + // enable LL without TX, which used to cause problems when serializing proxies (see HHH-12720) + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, Boolean.TRUE.toString() ); + + // dynamic-map by default. + configuration.setProperty( AvailableSettings.DEFAULT_ENTITY_MODE, EntityMode.MAP.getExternalName() ); + } + + @Before + public void prepare() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + + try { + final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + if (count.longValue() > 0L) { + // entity already added previously + return; + } + + final Map entity = new HashMap<>(); + entity.put( "id", 1L ); + entity.put( "name", "TheParent" ); + + final Map c1 = new HashMap<>(); + c1.put( "id", 1L ); + c1.put( "parent", entity ); + + s.save( "SimpleEntity", entity ); + s.save( "ChildEntity", c1 ); + } + finally { + t.commit(); + s.close(); + } + } + + /** + * Tests that serializing an initialized proxy will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + public void testInitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Initialize the proxy + parent.get( "name" ); + assertTrue( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that serializing a proxy which is not initialized + * but whose target has been (separately) added to the persistence context + * will serialized the target instead. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + public void testUninitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Load the target of the proxy without the proxy being made aware of it + s.detach( parent ); + s.byId( "SimpleEntity" ).load( 1L ); + s.update( parent ); + + // assert we still have an uninitialized proxy + assertFalse( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + public void testProxyInitializationWithoutTX() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + assertEquals( "TheParent", parent.get( "name" ) ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( parent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + public void testProxyInitializationWithoutTXAfterDeserialization() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // destroy AbstractLazyInitializer internal state + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( deserializedParent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( deserializedParent ) ); + + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( deserializedParent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/service/ServiceContributorTest.java b/hibernate-core/src/test/java/org/hibernate/service/ServiceContributorTest.java new file mode 100644 index 000000000000..0429e67ff584 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/service/ServiceContributorTest.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.service; + +import java.util.Map; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cache.internal.RegionFactoryInitiator; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +import org.hibernate.testing.cache.CachingRegionFactory; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +public class ServiceContributorTest extends BaseUnitTestCase { + @Test + public void overrideCachingInitiator() { + StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder(); + ssrb.clearSettings(); + + final MyRegionFactoryInitiator initiator = new MyRegionFactoryInitiator(); + ssrb.addInitiator( initiator ); + + final ServiceRegistryImplementor registry = (ServiceRegistryImplementor) ssrb.build(); + try { + final RegionFactory regionFactory = registry.getService( RegionFactory.class ); + assertTrue( initiator.called ); + assertTyping( MyRegionFactory.class, regionFactory ); + } + finally { + StandardServiceRegistryBuilder.destroy( registry ); + } + } + @Test + public void overrideCachingInitiatorExplicitSet() { + StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder(); + + final MyRegionFactoryInitiator initiator = new MyRegionFactoryInitiator(); + ssrb.addInitiator( initiator ); + ssrb.applySetting( AvailableSettings.CACHE_REGION_FACTORY, new MyRegionFactory() ); + + final ServiceRegistryImplementor registry = (ServiceRegistryImplementor) ssrb.build(); + try { + registry.getService( RegionFactory.class ); + assertFalse( initiator.called ); + } + finally { + StandardServiceRegistryBuilder.destroy( registry ); + } + } + + class MyRegionFactoryInitiator extends RegionFactoryInitiator { + private boolean called = false; + + @Override + protected RegionFactory getFallback( + Map configurationValues, + ServiceRegistryImplementor registry) { + called = true; + return new MyRegionFactory(); + } + +// @Override +// public RegionFactory initiateService( +// Map configurationValues, +// ServiceRegistryImplementor registry) { +// called = true; +// return super.initiateService( configurationValues, registry ); +// } + } + + class MyRegionFactory extends CachingRegionFactory { + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/session/AssociateEntityWithTwoSessionsTest.java b/hibernate-core/src/test/java/org/hibernate/session/AssociateEntityWithTwoSessionsTest.java new file mode 100644 index 000000000000..4ce935e7cdd0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/session/AssociateEntityWithTwoSessionsTest.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.session; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Session; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.proxy.AbstractLazyInitializer; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +public class AssociateEntityWithTwoSessionsTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Location.class, + Event.class + }; + } + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AbstractLazyInitializer.class.getName() ) ); + + @Test + @TestForIssue( jiraKey = "HHH-12216" ) + public void test() { + + final Location location = new Location(); + location.setCity( "Cluj" ); + + final Event event = new Event(); + event.setLocation( location ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( location ); + entityManager.persist( event ); + } ); + + final Triggerable triggerable = logInspection.watchForLogMessages( "HHH000485" ); + triggerable.reset(); + + doInJPA( this::entityManagerFactory, entityManager -> { + Event event1 = entityManager.find( Event.class, event.id ); + Location location1 = event1.getLocation(); + + try { + doInJPA( this::entityManagerFactory, _entityManager -> { + _entityManager.unwrap( Session.class ).update( location1 ); + } ); + + fail("Should have thrown a HibernateException"); + } + catch (Exception expected) { + } + } ); + + assertEquals( + "HHH000485: Illegally attempted to associate a proxy for entity [org.hibernate.session.AssociateEntityWithTwoSessionsTest$Location] with id [1] with two open sessions.", + triggerable.triggerMessage() + ); + + } + + @Entity(name = "Location") + public static class Location { + + @Id + @GeneratedValue + public Long id; + + public String city; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + } + + @Entity(name = "Event") + public static class Event { + + @Id + @GeneratedValue + public Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Location location; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java b/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java index 6429ef5bddaa..703174f6f801 100644 --- a/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.sharedSession; +import org.hibernate.FlushMode; import org.hibernate.IrrelevantEntity; import org.hibernate.Session; import org.hibernate.engine.spi.SessionImplementor; @@ -15,10 +16,14 @@ import org.hibernate.event.spi.PostInsertEventListener; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.resource.jdbc.spi.JdbcSessionOwner; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; +import java.lang.reflect.Field; +import java.util.Collection; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -233,7 +238,7 @@ public void testChildSessionTwoTransactions() throws Exception { .autoClose( true ) .openSession(); - //the secondary session should be automatically closed afterQuery the commit + //the secondary session should be automatically closed after the commit session.getTransaction().commit(); assertFalse( secondarySession.isOpen() ); @@ -242,7 +247,51 @@ public void testChildSessionTwoTransactions() throws Exception { session.getTransaction().begin(); session.getTransaction().commit(); } - + + @Test + @TestForIssue(jiraKey = "HHH-11830") + public void testSharedSessionTransactionObserver() throws Exception { + Session session = openSession(); + + session.getTransaction().begin(); + + Field field = null; + Class clazz = ((JdbcSessionOwner) session).getTransactionCoordinator().getClass(); + while (clazz != null) { + try { + field = clazz.getDeclaredField("observers"); + field.setAccessible(true); + break; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + assertNotNull("Observers field was not found", field); + + assertEquals(0, ((Collection) field.get(((SessionImplementor) session).getTransactionCoordinator())).size()); + + //open secondary sessions with managed options and immediately close + Session secondarySession; + for (int i = 0; i < 10; i++){ + secondarySession = session.sessionWithOptions() + .connection() + .flushMode( FlushMode.COMMIT ) + .autoClose( true ) + .openSession(); + + //when the shared session is opened it should register an observer + assertEquals(1, ((Collection) field.get(((JdbcSessionOwner) session).getTransactionCoordinator())).size()); + + //observer should be released + secondarySession.close(); + + assertEquals(0, ((Collection) field.get(((JdbcSessionOwner) session).getTransactionCoordinator())).size()); + } + } + + @Override protected Class[] getAnnotatedClasses() { return new Class[] { IrrelevantEntity.class }; diff --git a/hibernate-core/src/test/java/org/hibernate/stat/internal/ConcurrentQueryStatisticsTest.java b/hibernate-core/src/test/java/org/hibernate/stat/internal/ConcurrentQueryStatisticsTest.java index c5a215f5cb67..c21b75b70f41 100644 --- a/hibernate-core/src/test/java/org/hibernate/stat/internal/ConcurrentQueryStatisticsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/stat/internal/ConcurrentQueryStatisticsTest.java @@ -18,8 +18,7 @@ */ public class ConcurrentQueryStatisticsTest extends BaseUnitTestCase { - private ConcurrentQueryStatisticsImpl stats = new ConcurrentQueryStatisticsImpl( - "test" ); + private QueryStatisticsImpl stats = new QueryStatisticsImpl( "test" ); @Test public void testStats() { diff --git a/hibernate-core/src/test/java/org/hibernate/stat/internal/ConcurrentStatisticsTest.java b/hibernate-core/src/test/java/org/hibernate/stat/internal/ConcurrentStatisticsTest.java new file mode 100644 index 000000000000..e843504cfa44 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/stat/internal/ConcurrentStatisticsTest.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.stat.internal; + +import org.hibernate.SessionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.stat.SecondLevelCacheStatistics; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +public class ConcurrentStatisticsTest extends BaseCoreFunctionalTestCase { + private static final String REGION_PREFIX = "my-app"; + private static final String TRIVIAL_REGION_NAME = "noname"; + + private StatisticsImpl statistics; + private SessionFactory sessionFactory; + + @Before + public void setUp() { + sessionFactory = sessionFactory(); + statistics = new StatisticsImpl( (SessionFactoryImplementor) sessionFactory ); + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.setProperty( AvailableSettings.CACHE_REGION_PREFIX, REGION_PREFIX ); + configuration.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + } + + @After + public void tearDown() { + sessionFactory.close(); + } + + @Test + public void testThatGetSecondLevelCacheStatisticsWhenSecondLevelCacheIsNotEnabledReturnsNull() { + final SecondLevelCacheStatistics secondLevelCacheStatistics = statistics + .getSecondLevelCacheStatistics( StringHelper.qualify( REGION_PREFIX, TRIVIAL_REGION_NAME ) ); + assertThat( secondLevelCacheStatistics, is( nullValue() ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/stat/internal/StatsNamedContainerNullComputedValueTest.java b/hibernate-core/src/test/java/org/hibernate/stat/internal/StatsNamedContainerNullComputedValueTest.java new file mode 100644 index 000000000000..afd5b1088d07 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/stat/internal/StatsNamedContainerNullComputedValueTest.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-13645") +public class StatsNamedContainerNullComputedValueTest { + + @Test + public void testNullComputedValue() { + final StatsNamedContainer statsNamedContainer = new StatsNamedContainer(); + assertNull( + statsNamedContainer.getOrCompute( + "key", + v -> { + return null; + } + ) + ); + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/.gitignore b/hibernate-core/src/test/java/org/hibernate/test/annotations/.gitignore new file mode 100644 index 000000000000..64559d67b338 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/.gitignore @@ -0,0 +1 @@ +!/target/ diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/ConfigurationTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/ConfigurationTest.java index 9a8d67a60da7..8b0c4376f749 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/ConfigurationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/ConfigurationTest.java @@ -8,20 +8,19 @@ //$Id$ package org.hibernate.test.annotations; -import javax.persistence.PersistenceException; - -import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.ast.QuerySyntaxException; import org.junit.Test; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -54,25 +53,37 @@ public void testIgnoringHbm() throws Exception { cfg.configure( "org/hibernate/test/annotations/hibernate.cfg.xml" ); cfg.setProperty( Environment.HBM2DDL_AUTO, "create-drop" ); cfg.setProperty( Configuration.ARTEFACT_PROCESSING_ORDER, "class" ); - SessionFactory sf = cfg.buildSessionFactory(); - assertNotNull( sf ); - Session s = sf.openSession(); - Transaction tx = s.beginTransaction(); - Query q; - try { - s.createQuery( "from Boat" ).list(); - fail( "Boat should not be mapped" ); - } - catch (IllegalArgumentException e) { - assertTyping( QuerySyntaxException.class, e.getCause()); - //all good + + try ( SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory() ) { + assertNotNull( sf ); + + inTransaction( + sf, + session -> { + try { + session.createQuery( "from Boat" ).list(); + fail( "Boat should not be mapped" ); + } + catch (IllegalArgumentException expected) { + assertTyping( QuerySyntaxException.class, expected.getCause() ); + // expected outcome + + // see org.hibernate.test.jpa.compliance.tck2_2.QueryApiTest#testInvalidQueryMarksTxnForRollback + // for testing of how this invalid query String case is handled in terms of transactions + } + } + ); + + + inTransaction( + sf, + session -> { + assertEquals( 0, session.createQuery( "from Plane" ).list().size() ); + } + ); } - q = s.createQuery( "from Plane" ); - assertEquals( 0, q.list().size() ); - tx.commit(); - s.close(); - sf.close(); } + @Test public void testPrecedenceHbm() throws Exception { Configuration cfg = new Configuration(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java index 3597d0d7f6a9..5dd684f87056 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java @@ -6,23 +6,27 @@ */ package org.hibernate.test.annotations; -import javax.persistence.PersistenceException; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.Date; import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.List; import java.util.TimeZone; +import javax.persistence.PersistenceException; import org.hibernate.HibernateException; -import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.StaleStateException; import org.hibernate.Transaction; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.Oracle10gDialect; +import org.hibernate.query.Query; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.schema.TargetType; import org.hibernate.type.StandardBasicTypes; @@ -33,6 +37,7 @@ 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.assertFalse; import static org.junit.Assert.assertNotNull; @@ -365,36 +370,38 @@ public void testNonGetter() throws Exception { @Test @SkipForDialect(value = Oracle10gDialect.class, comment = "oracle12c returns time in getDate. For now, skip.") public void testTemporalType() throws Exception { - Session s = openSession(); - Transaction tx = s.beginTransaction(); - Flight airFrance = new Flight(); - airFrance.setId( Long.valueOf( 747 ) ); - airFrance.setName( "Paris-Amsterdam" ); - airFrance.setDuration( Long.valueOf( 10 ) ); - airFrance.setDepartureDate( new Date( 05, 06, 21, 10, 0, 0 ) ); - airFrance.setAlternativeDepartureDate( new GregorianCalendar( 2006, 02, 03, 10, 00 ) ); - airFrance.getAlternativeDepartureDate().setTimeZone( TimeZone.getTimeZone( "GMT" ) ); - airFrance.setBuyDate( new java.sql.Timestamp( 122367443 ) ); - airFrance.setFactor( 25 ); - s.persist( airFrance ); - tx.commit(); - s.close(); - s = openSession(); - tx = s.beginTransaction(); - Query q = s.createQuery( "from Flight f where f.departureDate = :departureDate" ); - q.setParameter( "departureDate", airFrance.getDepartureDate(), StandardBasicTypes.DATE ); - Flight copyAirFrance = (Flight) q.uniqueResult(); - assertNotNull( copyAirFrance ); - assertEquals( - df.format( new Date( 05, 06, 21 ) ).toString(), - df.format( copyAirFrance.getDepartureDate() ).toString() - ); - assertEquals( df.format( airFrance.getBuyDate() ), df.format( copyAirFrance.getBuyDate() ) ); - - s.delete( copyAirFrance ); - tx.commit(); - s.close(); + final ZoneId zoneId = ( Dialect.getDialect() instanceof MySQL5Dialect ) ? ZoneId.of( "UTC") + : ZoneId.systemDefault(); + + Flight airFrance = doInHibernate( this::sessionFactory, session -> { + Flight _airFrance = new Flight(); + _airFrance.setId( Long.valueOf( 747 ) ); + _airFrance.setName( "Paris-Amsterdam" ); + _airFrance.setDuration( Long.valueOf( 10 ) ); + _airFrance.setDepartureDate( Date.from(LocalDate.of( 2005, 06, 21 ).atStartOfDay(zoneId).toInstant()) ); + _airFrance.setAlternativeDepartureDate( new GregorianCalendar( 2006, 02, 03, 10, 00 ) ); + _airFrance.getAlternativeDepartureDate().setTimeZone( TimeZone.getTimeZone( "GMT" ) ); + _airFrance.setBuyDate( new java.sql.Timestamp( 122367443 ) ); + _airFrance.setFactor( 25 ); + session.persist( _airFrance ); + + return _airFrance; + } ); + + doInHibernate( this::sessionFactory, session -> { + Query q = session.createQuery( "from Flight f where f.departureDate = :departureDate" ); + q.setParameter( "departureDate", airFrance.getDepartureDate(), StandardBasicTypes.DATE ); + Flight copyAirFrance = (Flight) q.uniqueResult(); + assertNotNull( copyAirFrance ); + assertEquals( + Date.from(LocalDate.of( 2005, 06, 21 ).atStartOfDay(zoneId).toInstant()), + copyAirFrance.getDepartureDate() + ); + assertEquals( df.format( airFrance.getBuyDate() ), df.format( copyAirFrance.getBuyDate() ) ); + + session.delete( copyAirFrance ); + } ); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/UpdateTimeStampInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/UpdateTimeStampInheritanceTest.java new file mode 100644 index 000000000000..944e8babde7b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/UpdateTimeStampInheritanceTest.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.test.annotations; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.Session; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.IsNull.nullValue; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11867") +public class UpdateTimeStampInheritanceTest extends BaseEntityManagerFunctionalTestCase { + private static final long SLEEP_MILLIS = 25; + + private static final String customerId = "1"; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Customer.class, AbstractPerson.class, Address.class }; + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( customerId ); + customer.addAddress( "address" ); + entityManager.persist( customer ); + } ); + sleep( SLEEP_MILLIS ); + } + + @Test + public void updateParentClassProperty() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasNotUpdated( customer ); + customer.setName( "xyz" ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasUpdated( customer ); + } ); + } + + @Test + public void updateSubClassProperty() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasNotUpdated( customer ); + customer.setEmail( "xyz@" ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasUpdated( customer ); + } ); + } + + @Test + public void updateParentClassOneToOneAssociation() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasNotUpdated( customer ); + Address a = new Address(); + a.setStreet( "Lollard street" ); + customer.setWorkAddress( a ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasUpdated( customer ); + } ); + } + + @Test + public void updateSubClassOnrToOneAssociation() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasNotUpdated( customer ); + Address a = new Address(); + a.setStreet( "Lollard Street" ); + customer.setHomeAddress( a ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasUpdated( customer ); + } ); + } + + @Test + public void replaceParentClassElementCollection() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasNotUpdated( customer ); + Set adresses = new HashSet<>(); + adresses.add( "another address" ); + customer.setAdresses( adresses ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasUpdated( customer ); + } ); + } + + @Test + public void replaceSubClassElementCollection() { + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasNotUpdated( customer ); + Set books = new HashSet<>(); + customer.setBooks( books ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, customerId ); + assertThat( customer.getCreatedAt(), is( not( nullValue() ) ) ); + assertThat( customer.getModifiedAt(), is( not( nullValue() ) ) ); + assertModifiedAtWasUpdated( customer ); + } ); + } + + @Test + public void updateDetachedEntity() { + + Customer customer = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Customer.class, customerId ); + } ); + + assertModifiedAtWasNotUpdated( customer ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.unwrap( Session.class ).update( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + assertModifiedAtWasUpdated( entityManager.find( Customer.class, customerId ) ); + } ); + } + + private void assertModifiedAtWasNotUpdated(Customer customer) { + assertTrue( (customer.getModifiedAt().getTime() - customer.getCreatedAt().getTime()) < 10 ); + } + + private void assertModifiedAtWasUpdated(Customer customer) { + assertTrue( (customer.getModifiedAt().getTime() - customer.getCreatedAt().getTime()) > 10 ); + } + + @Entity(name = "person") + @Inheritance(strategy = InheritanceType.JOINED) + public static abstract class AbstractPerson { + @Id + @Column(name = "id") + private String id; + + private String name; + + @CreationTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "created_at", updatable = false) + private Date createdAt; + + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modified_at") + private Date modifiedAt; + + @ElementCollection + private Set adresses = new HashSet<>(); + + @OneToOne(cascade = CascadeType.ALL) + private Address workAddress; + + + public void setId(String id) { + this.id = id; + } + + public Date getCreatedAt() { + return createdAt; + } + + public Date getModifiedAt() { + return modifiedAt; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void addAddress(String address) { + this.adresses.add( address ); + } + + public void setWorkAddress(Address workAddress) { + this.workAddress = workAddress; + } + + public void setAdresses(Set adresses) { + this.adresses = adresses; + } + } + + @Entity(name = "Address") + @Table(name = "address") + public static class Address { + @Id + @GeneratedValue + private Long id; + + private String street; + + public void setStreet(String street) { + this.street = street; + } + } + + @Entity(name = "Customer") + @Table(name = "customer") + public static class Customer extends AbstractPerson { + private String email; + + @ElementCollection + private Set books = new HashSet<>(); + + @OneToOne(cascade = CascadeType.ALL) + private Address homeAddress; + + public void setEmail(String email) { + this.email = email; + } + + public void addBook(String book) { + this.books.add( book ); + } + + public void setHomeAddress(Address homeAddress) { + this.homeAddress = homeAddress; + } + + public void setBooks(Set books) { + this.books = books; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/access/AttributeAccessorTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/access/AttributeAccessorTest.java new file mode 100644 index 000000000000..858e5f63fb1c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/access/AttributeAccessorTest.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.access; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.AttributeAccessor; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.cfg.Environment; +import org.hibernate.mapping.Property; +import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl; +import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.service.ServiceRegistry; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.ServiceRegistryBuilder; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12062") +public class AttributeAccessorTest { + private ServiceRegistry serviceRegistry; + + @Before + public void setUp() { + serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( Environment.getProperties() ); + } + + @After + public void cleanUp() { + if ( serviceRegistry != null ) { + ServiceRegistryBuilder.destroy( serviceRegistry ); + serviceRegistry = null; + } + } + + @Test + public void testAttributeAccessorConfiguration() { + final Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClass( Foo.class ) + .buildMetadata(); + + final Property property = metadata.getEntityBinding( Foo.class.getName() ).getProperty( "name" ); + assertEquals( BasicAttributeAccessor.class.getName(), property.getPropertyAccessorName() ); + } + + @Entity(name = "Foo") + public static class Foo { + private Integer id; + private String name; + + public Foo() { + + } + + public Foo(Integer id) { + this.id = id; + } + + public Foo(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @AttributeAccessor( "org.hibernate.test.annotations.access.AttributeAccessorTest$BasicAttributeAccessor" ) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class BasicAttributeAccessor extends PropertyAccessStrategyBasicImpl { + @Override + public PropertyAccess buildPropertyAccess(Class containerJavaType, String propertyName) { + return super.buildPropertyAccess( containerJavaType, propertyName ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/access/xml/XmlAccessTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/access/xml/XmlAccessTest.java index 1a6688db8b2e..ac3b150841f5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/access/xml/XmlAccessTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/access/xml/XmlAccessTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.annotations.access.xml; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; @@ -164,8 +165,12 @@ private SessionFactoryImplementor buildSessionFactory(List> classesUnde cfg.addAnnotatedClass( clazz ); } for ( String configFile : configFiles ) { - InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( configFile ); - cfg.addInputStream( is ); + try ( InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( configFile ) ) { + cfg.addInputStream( is ); + } + catch (IOException e) { + throw new IllegalArgumentException( e ); + } } return ( SessionFactoryImplementor ) cfg.buildSessionFactory(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java new file mode 100644 index 000000000000..b64902b8729f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java @@ -0,0 +1,213 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.any; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; + +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyMetaDef; +import org.hibernate.annotations.MetaValue; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +public class EmbeddedAnyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Foo.class, Bar1.class, Bar2.class }; + } + + @Test + public void testEmbeddedAny() { + doInJPA( this::entityManagerFactory, em -> { + Foo foo1 = new Foo(); + foo1.setId( 1 ); + + Bar1 bar1 = new Bar1(); + bar1.setId( 1 ); + bar1.setBar1( "bar 1" ); + bar1.setBarType( "1" ); + + FooEmbeddable foo1Embedded = new FooEmbeddable(); + foo1Embedded.setBar( bar1 ); + + foo1.setFooEmbedded( foo1Embedded ); + + em.persist( bar1 ); + em.persist( foo1 ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo2 = new Foo(); + foo2.setId( 2 ); + + Bar2 bar2 = new Bar2(); + bar2.setId( 2 ); + bar2.setBar2( "bar 2" ); + bar2.setBarType( "2" ); + + FooEmbeddable foo2Embedded = new FooEmbeddable(); + foo2Embedded.setBar( bar2 ); + + foo2.setFooEmbedded( foo2Embedded ); + + em.persist( bar2 ); + em.persist( foo2 ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo1 = em.find( Foo.class, 1 ); + + assertTrue( foo1.getFooEmbedded().getBar() instanceof Bar1 ); + assertEquals( "bar 1", ( (Bar1) foo1.getFooEmbedded().getBar() ).getBar1() ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo2 = em.find( Foo.class, 2 ); + + assertTrue( foo2.getFooEmbedded().getBar() instanceof Bar2 ); + assertEquals( "bar 2", ( (Bar2) foo2.getFooEmbedded().getBar() ).getBar2() ); + } ); + } + + @Entity(name = "Foo") + public static class Foo { + + @Id + private Integer id; + + @Embedded + private FooEmbeddable fooEmbedded; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public FooEmbeddable getFooEmbedded() { + return fooEmbedded; + } + + public void setFooEmbedded(FooEmbeddable fooEmbedded) { + this.fooEmbedded = fooEmbedded; + } + } + + @Embeddable + public static class FooEmbeddable { + + @AnyMetaDef(idType = "integer", metaType = "string", metaValues = { + @MetaValue(value = "1", targetEntity = Bar1.class), + @MetaValue(value = "2", targetEntity = Bar2.class) + }) + @Any(metaColumn = @Column(name = "bar_type")) + @JoinColumn(name = "bar_id") + private BarInt bar; + + public BarInt getBar() { + return bar; + } + + public void setBar(BarInt bar) { + this.bar = bar; + } + } + + public interface BarInt { + + String getBarType(); + } + + @Entity(name = "Bar1") + @Table(name = "bar") + public static class Bar1 implements BarInt { + + @Id + private Integer id; + + private String bar1; + + @Column(name = "bar_type") + private String barType; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getBar1() { + return bar1; + } + + public void setBar1(String bar1) { + this.bar1 = bar1; + } + + @Override + public String getBarType() { + return barType; + } + + public void setBarType(String barType) { + this.barType = barType; + } + } + + @Entity(name = "Bar2") + @Table(name = "bar") + public static class Bar2 implements BarInt { + + @Id + private Integer id; + + private String bar2; + + @Column(name = "bar_type") + private String barType; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getBar2() { + return bar2; + } + + public void setBar2(String bar2) { + this.bar2 = bar2; + } + + @Override + public String getBarType() { + return barType; + } + + public void setBarType(String barType) { + this.barType = barType; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/backquotes/BackquoteTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/backquotes/BackquoteTest.java index 7febabefc12d..0583b1bfd224 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/backquotes/BackquoteTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/backquotes/BackquoteTest.java @@ -35,7 +35,6 @@ * */ public class BackquoteTest extends BaseUnitTestCase { - private static final Logger log = Logger.getLogger( BackquoteTest.class ); private ServiceRegistry serviceRegistry; private SessionFactory sessionFactory; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdIdentityTest.java new file mode 100644 index 000000000000..9fb9037dcd90 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdIdentityTest.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.cid; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; + +import org.hibernate.Criteria; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.criterion.Disjunction; +import org.hibernate.criterion.Restrictions; +import org.hibernate.engine.spi.SessionImplementor; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.RequiresDialectFeature; +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.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +@TestForIssue( jiraKey = "HHH-9662" ) +public class CompositeIdIdentityTest extends BaseCoreFunctionalTestCase { + + @Test + @FailureExpected( jiraKey = "HHH-9662" ) + public void testCompositePkWithIdentity() throws Exception { + doInHibernate( this::sessionFactory, session -> { + Animal animal = new Animal(); + animal.setSubId( 123L ); + session.persist(animal); + } ); + } + + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Animal.class + }; + } + + @Entity + @Table(name = "animal") + @IdClass(IdWithSubId.class) + public static class Animal { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Id + @Column(name = "sub_id") + private Long subId; + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + + public Long getSubId() { + return subId; + } + public void setSubId(Long subId) { + this.subId = subId; + } + } + + public static class IdWithSubId implements Serializable { + private Long id; + + private Long subId; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSubId() { + return subId; + } + public void setSubId(Long subId) { + this.subId = subId; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/EmbeddableCollectionElementWithLazyManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/EmbeddableCollectionElementWithLazyManyToOneTest.java new file mode 100644 index 000000000000..ea094b0d13db --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/EmbeddableCollectionElementWithLazyManyToOneTest.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 . + */ +package org.hibernate.test.annotations.collectionelement; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; + +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.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class EmbeddableCollectionElementWithLazyManyToOneTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Test + @TestForIssue( jiraKey = "???") + public void testLazyManyToOneInEmbeddable() { + Parent p = new Parent(); + p.containedChild = new ContainedChild( new Child() ); + + doInHibernate( + this::sessionFactory, session -> { + session.persist( p ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Parent pRead = session.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( pRead.containedChild.child ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( p ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "???") + public void testLazyManyToOneInCollectionElementEmbeddable() { + Parent p = new Parent(); + p.containedChildren.add( new ContainedChild( new Child() ) ); + + doInHibernate( + this::sessionFactory, session -> { + session.persist( p ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Parent pRead = session.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren ) ); + assertEquals( 1, pRead.containedChildren.size() ); + assertTrue( Hibernate.isInitialized( pRead.containedChildren ) ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren.iterator().next().child ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( p ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "???") + public void testLazyBoth() { + Parent p = new Parent(); + ContainedChild containedChild = new ContainedChild( new Child() ); + p.containedChild = containedChild; + p.containedChildren.add( containedChild ); + + doInHibernate( + this::sessionFactory, session -> { + session.persist( p ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Parent pRead = session.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( pRead.containedChild.child ) ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren ) ); + assertEquals( 1, pRead.containedChildren.size() ); + assertTrue( Hibernate.isInitialized( pRead.containedChildren ) ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren.iterator().next().child ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( p ); + } + ); + } + + @Entity(name = "Parent") + public static class Parent { + @Id + @GeneratedValue + private int id; + + private ContainedChild containedChild; + + @ElementCollection + private Set containedChildren = new HashSet(); + } + + @Entity(name = "Child") + public static class Child { + @Id + @GeneratedValue + private int id; + + } + + @Embeddable + public static class ContainedChild { + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private Child child; + + ContainedChild() { + } + + ContainedChild(Child child) { + this.child = child; + } + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java index f28566b87776..72d957992748 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/CompositeIdDerivedIdWithIdClassTest.java @@ -29,6 +29,9 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class CompositeIdDerivedIdWithIdClassTest extends BaseCoreFunctionalTestCase { @Override @@ -94,7 +97,7 @@ public void testMergeDetachedIdManyToOne() throws Exception { // merge lineItem with an ID with detached many-to-one s = openSession(); s.getTransaction().begin(); - s.merge(lineItem); + s.merge( lineItem ); s.getTransaction().commit(); s.close(); @@ -107,8 +110,116 @@ public void testMergeDetachedIdManyToOne() throws Exception { s.close(); } + @Test + @TestForIssue( jiraKey = "HHH-12007") + public void testBindTransientEntityWithTransientKeyManyToOne() { + + ShoppingCart cart = new ShoppingCart( "cart" ); + LineItem item = new LineItem( 0, "desc", cart ); + + Session s = openSession(); + s.getTransaction().begin(); + + String cartId = s.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( s.contains( item ) ); + assertFalse( s.contains( cart ) ); + + s.getTransaction().commit(); + s.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12007") + public void testBindTransientEntityWithPersistentKeyManyToOne() { + ShoppingCart cart = new ShoppingCart( "cart" ); + LineItem item = new LineItem( 0, "desc", cart ); + + Session s = openSession(); + s.getTransaction().begin(); + + session.persist( cart ); + String cartId = s.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( s.contains( item ) ); + assertTrue( s.contains( cart ) ); + + s.getTransaction().commit(); + s.close(); + + } + + @Test + @TestForIssue( jiraKey = "HHH-12007") + public void testBindTransientEntityWithDetachedKeyManyToOne() { + Session s = openSession(); + s.getTransaction().begin(); + + ShoppingCart cart = new ShoppingCart( "cart" ); + + s.getTransaction().commit(); + s.close(); + + LineItem item = new LineItem( 0, "desc", cart ); + + s = openSession(); + s.getTransaction().begin(); + + String cartId = s.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( s.contains( item ) ); + assertFalse( s.contains( cart ) ); + + s.getTransaction().commit(); + s.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12007") + public void testBindTransientEntityWithCopiedKeyManyToOne() { + Session s = openSession(); + s.getTransaction().begin(); + + ShoppingCart cart = new ShoppingCart( "cart" ); + s.getTransaction().commit(); + s.close(); + + LineItem item = new LineItem( 0, "desc", new ShoppingCart( "cart" ) ); + + s = openSession(); + s.getTransaction().begin(); + + String cartId = s.createQuery( + "select c.id from Cart c left join c.lineItems i where i = :item", + String.class + ).setParameter( "item", item ).uniqueResult(); + + assertNull( cartId ); + + assertFalse( s.contains( item ) ); + assertFalse( s.contains( cart ) ); + + s.getTransaction().commit(); + s.close(); + } + @Entity(name = "Cart") - public static class ShoppingCart { + public static class ShoppingCart implements Serializable{ @Id @Column(name = "id", nullable = false) private String id; @@ -147,7 +258,7 @@ public int hashCode() { @Entity(name = "LineItem") @IdClass(LineItem.Pk.class) - public static class LineItem { + public static class LineItem implements Serializable { @Id @Column(name = "item_seq_number", nullable = false) diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/OneToOneWithDerivedIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/OneToOneWithDerivedIdentityTest.java index 6618032ac7a1..6b079e569ea5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/OneToOneWithDerivedIdentityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/OneToOneWithDerivedIdentityTest.java @@ -17,13 +17,17 @@ import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.engine.spi.SessionImplementor; + +import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; public class OneToOneWithDerivedIdentityTest extends BaseCoreFunctionalTestCase { @Test - @TestForIssue(jiraKey = "HHH-5695") + //@TestForIssue(jiraKey = "HHH-5695") + @TestForIssue(jiraKey = "HHH-11903") + @FailureExpected(jiraKey = "HHH-11903") public void testInsertFooAndBarWithDerivedId() { Session s = openSession(); s.beginTransaction(); @@ -42,6 +46,8 @@ public void testInsertFooAndBarWithDerivedId() { .setParameter( "id", foo.getId() ) .uniqueResult(); assertNotNull( newBar ); + assertNotNull( newBar.getFoo() ); + assertEquals( foo.getId(), newBar.getFoo().getId() ); assertEquals( "Some details", newBar.getDetails() ); s.getTransaction().rollback(); s.close(); @@ -95,6 +101,8 @@ public void testSelectWithDerivedId() { s.clear(); Foo newFoo = (Foo) s.createQuery( "SELECT f FROM Foo f" ).uniqueResult(); assertNotNull( newFoo ); + assertNotNull( newFoo.getBar() ); + assertSame( newFoo, newFoo.getBar().getFoo() ); assertEquals( "Some details", newFoo.getBar().getDetails() ); s.getTransaction().rollback(); s.close(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b2/EmployeeId.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b2/EmployeeId.java index 07c716991640..a6a9d92a2efd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b2/EmployeeId.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b2/EmployeeId.java @@ -6,6 +6,8 @@ @Embeddable public class EmployeeId implements Serializable { + @Column(length = 32) String firstName; + @Column(length = 32) String lastName; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/DependentId.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/DependentId.java index 987ca79b5d2c..6e8de003c733 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/DependentId.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/DependentId.java @@ -1,10 +1,12 @@ package org.hibernate.test.annotations.derivedidentities.e3.b3; +import javax.persistence.Column; import javax.persistence.Embeddable; import java.io.Serializable; @Embeddable public class DependentId implements Serializable { + @Column(length = 32) String name; EmployeeId empPK; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/EmployeeId.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/EmployeeId.java index 270e2106d457..da7c33ff664a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/EmployeeId.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e3/b3/EmployeeId.java @@ -1,10 +1,13 @@ package org.hibernate.test.annotations.derivedidentities.e3.b3; +import javax.persistence.Column; import javax.persistence.Embeddable; import java.io.Serializable; @Embeddable public class EmployeeId implements Serializable { + @Column(length = 32) String firstName; + @Column(length = 32) String lastName; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java index 242086784a39..face0b9e25c0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java @@ -25,7 +25,7 @@ public class MedicalHistory { @MapsId @JoinColumn(name = "FK") - @OneToOne(cascade= CascadeType.ALL) + @OneToOne(cascade= CascadeType.PERSIST) Person patient; @Temporal(TemporalType.DATE) diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/EmbeddableIntegratorTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/EmbeddableIntegratorTest.java index 3e265257d3d4..fb8b8e6cd5c4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/EmbeddableIntegratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/EmbeddableIntegratorTest.java @@ -86,12 +86,12 @@ public void testWithTypeContributor() { Investor inv = (Investor) sess.get( Investor.class, 2L ); assertEquals( new BigDecimal( "100" ), inv.getInvestments().get( 0 ).getAmount().getAmount() ); - - sess.close(); }catch (Exception e){ sess.getTransaction().rollback(); + throw e; } finally { + sess.close(); sf.close(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/attributeOverrides/AttributeOverrideEnhancedUserTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/attributeOverrides/AttributeOverrideEnhancedUserTypeTest.java new file mode 100644 index 000000000000..101db1b0a1b5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/attributeOverrides/AttributeOverrideEnhancedUserTypeTest.java @@ -0,0 +1,176 @@ +package org.hibernate.test.annotations.embeddables.attributeOverrides; + +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.annotations.Columns; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.IntegerType; +import org.hibernate.usertype.UserType; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import javax.persistence.*; +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.YearMonth; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author András Eisenberger + */ +public class AttributeOverrideEnhancedUserTypeTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + TypeValue.class, + AggregatedTypeValue.class + }; + } + + @Test + @TestForIssue( jiraKey = "HHH-11465" ) + public void testIt() { + AggregatedTypeValue _e1 = doInHibernate( this::sessionFactory, session -> { + AggregatedTypeValue e1 = new AggregatedTypeValue(); + e1.id = 1L; + TypeValue t1 = new TypeValue(); + t1.time = YearMonth.of(2017, 5); + e1.oneValue = t1; + TypeValue t2 = new TypeValue(); + t2.time = YearMonth.of(2016, 4); + e1.otherValue = t2; + session.save( e1 ); + + return e1; + } ); + + doInHibernate( this::sessionFactory, session -> { + AggregatedTypeValue e1 = session.get( AggregatedTypeValue.class, _e1.id ); + assertEquals(e1.oneValue.time, YearMonth.of(2017, 5)); + assertEquals(e1.otherValue.time, YearMonth.of(2016, 4)); + session.delete( e1 ); + } ); + } + + @Embeddable + public static class TypeValue { + @Type(type = "year_month") + @Columns(columns = { + @Column(name = "year", nullable = true), + @Column(name = "month", nullable = true) + }) + YearMonth time; + } + + @Entity + @Table( name = "AGG_TYPE" ) + @TypeDef( + name = "year_month", + typeClass = YearMonthUserType.class + ) + public static class AggregatedTypeValue { + @Id + private Long id; + + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "time", column = @Column(name = "one_year")), + @AttributeOverride(name = "time", column = @Column(name = "one_month")) + }) + private TypeValue oneValue; + + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "time", column = @Column(name = "other_year")), + @AttributeOverride(name = "time", column = @Column(name = "other_month")) + }) + private TypeValue otherValue; + } + + public static class YearMonthUserType implements UserType, Serializable { + @Override + public int[] sqlTypes() { + return new int[]{ + IntegerType.INSTANCE.sqlType(), + IntegerType.INSTANCE.sqlType(), + }; + } + + @Override + public Class returnedClass() { + return YearMonth.class; + } + + @Override + public boolean equals(final Object x, final Object y) throws HibernateException { + if (x == y) { + return true; + } + if (x == null || y == null) { + return false; + } + final YearMonth mtx = (YearMonth) x; + final YearMonth mty = (YearMonth) y; + return mtx.equals(mty); + } + + @Override + public int hashCode(final Object x) throws HibernateException { + return x.hashCode(); + } + + @Override + public Object nullSafeGet(final ResultSet rs, final String[] names, final SharedSessionContractImplementor session, final Object owner) throws HibernateException, SQLException { + assert names.length == 2; + final Integer year = IntegerType.INSTANCE.nullSafeGet(rs, names[0], session); + final Integer month = IntegerType.INSTANCE.nullSafeGet(rs, names[1], session); + return year == null || month == null ? null : YearMonth.of(year, month); + } + + @Override + public void nullSafeSet(final PreparedStatement st, final Object value, final int index, final SharedSessionContractImplementor session) throws HibernateException, SQLException { + if (value == null) { + IntegerType.INSTANCE.set(st, null, index, session); + IntegerType.INSTANCE.set(st, null, index + 1, session); + } else { + final YearMonth YearMonth = (YearMonth) value; + + IntegerType.INSTANCE.set(st, YearMonth.getYear(), index, session); + IntegerType.INSTANCE.set(st, YearMonth.getMonthValue(), index + 1, session); + } + } + + @Override + public Object deepCopy(final Object value) throws HibernateException { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(final Object value) throws HibernateException { + return (Serializable) value; + } + + @Override + public Object assemble(final Serializable cached, final Object value) throws HibernateException { + return cached; + } + + @Override + public Object replace(final Object original, final Object target, final Object owner) throws HibernateException { + return original; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java index 076ba9951b25..ee345f544215 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java @@ -26,8 +26,6 @@ public class EmbeddableWithOneToMany_HHH_11302_xml_Test extends BaseEntityManagerFunctionalTestCase { - PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); - @Override public String[] getEjb3DD() { return new String[] { @@ -35,7 +33,7 @@ public String[] getEjb3DD() { }; } - public void buildEntityManagerFactory() throws Exception { + public void buildEntityManagerFactory() { try { super.buildEntityManagerFactory(); fail( "Should throw AnnotationException!" ); @@ -45,15 +43,6 @@ public void buildEntityManagerFactory() throws Exception { "@OneToMany, @ManyToMany or @ElementCollection cannot be used inside an @Embeddable that is also contained within an @ElementCollection" ) ); } - finally { - connectionProvider.stop(); - } - } - - protected Map buildSettings() { - Map settings = super.buildSettings(); - settings.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); - return settings; } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java index 1b59d47c9d54..526a7e062d81 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java @@ -18,14 +18,18 @@ import org.hibernate.Transaction; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import org.hibernate.test.annotations.embedded.FloatLeg.RateIndex; import org.hibernate.test.annotations.embedded.Leg.Frequency; import org.hibernate.test.util.SchemaUtil; +import org.junit.After; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -38,11 +42,19 @@ * @author Emmanuel Bernard */ public class EmbeddedTest extends BaseNonConfigCoreFunctionalTestCase { + + @After + public void cleanup() { + TransactionUtil.doInHibernate( this::sessionFactory, session ->{ + for ( Person person : session.createQuery( "from Person", Person.class ).getResultList() ) { + session.delete( person ); + } + } ); + } + @Test public void testSimple() throws Exception { - Session s; - Transaction tx; - Person p = new Person(); + Person person = new Person(); Address a = new Address(); Country c = new Country(); Country bornCountry = new Country(); @@ -54,28 +66,16 @@ public void testSimple() throws Exception { a.address1 = "colorado street"; a.city = "Springfield"; a.country = c; - p.address = a; - p.bornIn = bornCountry; - p.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - s.persist( p ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + person.address = a; + person.bornIn = bornCountry; + person.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - p = (Person) s.get( Person.class, p.id ); + TransactionUtil.doInHibernate( this::sessionFactory, session ->{ + session.persist( person ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session ->{ + Person p = session.get( Person.class, person.id ); assertNotNull( p ); assertNotNull( p.address ); assertEquals( "Springfield", p.address.city ); @@ -83,24 +83,13 @@ public void testSimple() throws Exception { assertEquals( "DM", p.address.country.getIso2() ); assertNotNull( p.bornIn ); assertEquals( "US", p.bornIn.getIso2() ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + } ); } @Test @TestForIssue(jiraKey = "HHH-8172") public void testQueryWithEmbeddedIsNull() throws Exception { - Session s; - Transaction tx; - Person p = new Person(); + Person person = new Person(); Address a = new Address(); Country c = new Country(); Country bornCountry = new Country(); @@ -112,44 +101,22 @@ public void testQueryWithEmbeddedIsNull() throws Exception { a.address1 = "colorado street"; a.city = "Springfield"; a.country = c; - p.address = a; - p.bornIn = bornCountry; - p.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - s.persist( p ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } - - s = openSession(); - tx = s.beginTransaction(); - try { - p = (Person) s.createQuery( "from Person p where p.bornIn is null" ).uniqueResult(); + person.address = a; + person.bornIn = bornCountry; + person.name = "Homer"; + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Person p = (Person) session.createQuery( "from Person p where p.bornIn is null" ).uniqueResult(); assertNotNull( p ); assertNotNull( p.address ); assertEquals( "Springfield", p.address.city ); assertNotNull( p.address.country ); assertEquals( "DM", p.address.country.getIso2() ); assertNull( p.bornIn ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + } ); } @Test @@ -158,7 +125,7 @@ public void testQueryWithEmbeddedIsNull() throws Exception { public void testQueryWithEmbeddedParameterAllNull() throws Exception { Session s; Transaction tx; - Person p = new Person(); + Person person = new Person(); Address a = new Address(); Country c = new Country(); Country bornCountry = new Country(); @@ -168,29 +135,17 @@ public void testQueryWithEmbeddedParameterAllNull() throws Exception { a.address1 = "colorado street"; a.city = "Springfield"; a.country = c; - p.address = a; - p.bornIn = bornCountry; - p.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - s.persist( p ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + person.address = a; + person.bornIn = bornCountry; + person.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - p = (Person) s.createQuery( "from Person p where p.bornIn = :b" ) - .setParameter( "b", p.bornIn ) + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Person p = (Person) session.createQuery( "from Person p where p.bornIn = :b" ) + .setParameter( "b", person.bornIn ) .uniqueResult(); assertNotNull( p ); assertNotNull( p.address ); @@ -198,25 +153,15 @@ public void testQueryWithEmbeddedParameterAllNull() throws Exception { assertNotNull( p.address.country ); assertEquals( "DM", p.address.country.getIso2() ); assertNull( p.bornIn ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + } ); } @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 { - Session s; - Transaction tx; - Person p = new Person(); + Person person = new Person(); Address a = new Address(); Country c = new Country(); Country bornCountry = new Country(); @@ -228,29 +173,17 @@ public void testQueryWithEmbeddedParameterOneNull() throws Exception { a.address1 = "colorado street"; a.city = "Springfield"; a.country = c; - p.address = a; - p.bornIn = bornCountry; - p.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - s.persist( p ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + person.address = a; + person.bornIn = bornCountry; + person.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - p = (Person) s.createQuery( "from Person p where p.bornIn = :b" ) - .setParameter( "b", p.bornIn ) + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Person p = (Person) session.createQuery( "from Person p where p.bornIn = :b" ) + .setParameter( "b", person.bornIn ) .uniqueResult(); assertNotNull( p ); assertNotNull( p.address ); @@ -259,24 +192,13 @@ public void testQueryWithEmbeddedParameterOneNull() throws Exception { assertEquals( "DM", p.address.country.getIso2() ); assertEquals( "US", p.bornIn.getIso2() ); assertNull( p.bornIn.getName() ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + } ); } @Test @TestForIssue(jiraKey = "HHH-8172") public void testQueryWithEmbeddedWithNullUsingSubAttributes() throws Exception { - Session s; - Transaction tx; - Person p = new Person(); + Person person = new Person(); Address a = new Address(); Country c = new Country(); Country bornCountry = new Country(); @@ -288,32 +210,19 @@ public void testQueryWithEmbeddedWithNullUsingSubAttributes() throws Exception { a.address1 = "colorado street"; a.city = "Springfield"; a.country = c; - p.address = a; - p.bornIn = bornCountry; - p.name = "Homer"; - s = openSession(); - tx = s.beginTransaction(); - try { - s.persist( p ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } - - s = openSession(); - tx = s.beginTransaction(); - try { - p = (Person) s.createQuery( "from Person p " + - "where ( p.bornIn.iso2 is null or p.bornIn.iso2 = :i ) and " + - "( p.bornIn.name is null or p.bornIn.name = :n )" - ).setParameter( "i", p.bornIn.getIso2() ) - .setParameter( "n", p.bornIn.getName() ) + person.address = a; + person.bornIn = bornCountry; + person.name = "Homer"; + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Person p = (Person) session.createQuery( "from Person p " + + "where ( p.bornIn.iso2 is null or p.bornIn.iso2 = :i ) and " + + "( p.bornIn.name is null or p.bornIn.name = :n )" + ).setParameter( "i", person.bornIn.getIso2() ) + .setParameter( "n", person.bornIn.getName() ) .uniqueResult(); assertNotNull( p ); assertNotNull( p.address ); @@ -322,16 +231,7 @@ public void testQueryWithEmbeddedWithNullUsingSubAttributes() throws Exception { assertEquals( "DM", p.address.country.getIso2() ); assertEquals( "US", p.bornIn.getIso2() ); assertNull( p.bornIn.getName() ); - tx.commit(); - } - catch (Exception e) { - if ( tx != null && tx.isActive() ) { - tx.rollback(); - } - } - finally { - s.close(); - } + } ); } @Test @@ -715,7 +615,7 @@ public void testEmbeddedAndOneToManyHql() throws Exception { public void testDefaultCollectionTable() throws Exception { //are the tables correct? assertTrue( SchemaUtil.isTablePresent( "WealthyPerson_vacationHomes", metadata() ) ); - assertTrue( SchemaUtil.isTablePresent( "WealthyPerson_legacyVacationHomes", metadata() ) ); + assertTrue( SchemaUtil.isTablePresent( "WelPers_LegacyVacHomes", metadata() ) ); assertTrue( SchemaUtil.isTablePresent( "WelPers_VacHomes", metadata() ) ); //just to make sure, use the mapping diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/RegionalArticle.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/RegionalArticle.java index da1eaae98607..1295864d4374 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/RegionalArticle.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/RegionalArticle.java @@ -40,12 +40,12 @@ public void setName(String name) { } public int hashCode() { - //a NPE can occurs, but I don't expect hashcode to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect hashcode to be used before pk is set return getPk().hashCode(); } public boolean equals(Object obj) { - //a NPE can occurs, but I don't expect equals to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect equals to be used before pk is set if ( obj != null && obj instanceof RegionalArticle ) { return getPk().equals( ( (RegionalArticle) obj ).getPk() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java index 3b24b37908a7..05df8f1df60b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java @@ -19,6 +19,7 @@ public class WealthyPerson extends Person { protected Set

    vacationHomes = new HashSet
    (); @ElementCollection + @CollectionTable(name = "WelPers_LegacyVacHomes") protected Set
    legacyVacationHomes = new HashSet
    (); @ElementCollection diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/entity/Java5FeaturesTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/entity/Java5FeaturesTest.java index f294fe71b91e..03096319209d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/entity/Java5FeaturesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/entity/Java5FeaturesTest.java @@ -92,7 +92,7 @@ public void testEnums() throws Exception { s.clear(); communityBid = (CommunityBid) s.createSQLQuery( "select {b.*} from Bid b where b.id = ?" ) .addEntity( "b", CommunityBid.class ) - .setInteger( 0, communityBid.getId() ).uniqueResult(); + .setInteger( 1, communityBid.getId() ).uniqueResult(); assertEquals( Starred.OK, communityBid.getCommunityNote() ); s.delete( communityBid ); tx.commit(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EnumeratedTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EnumeratedTypeTest.java index a398c5dfc054..439a7d9ff868 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EnumeratedTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/enumerated/EnumeratedTypeTest.java @@ -376,8 +376,8 @@ public void testTrimmedEnumChar() throws SQLException { assertEquals( resultList.get(1).getTrimmed(), Trimmed.B ); // ensure querying works - final Query query = s.createQuery("select e from EntityEnum e where e.trimmed=?"); - query.setParameter( 0, Trimmed.A ); + final Query query = s.createQuery("select e from EntityEnum e where e.trimmed=?1"); + query.setParameter( 1, Trimmed.A ); resultList = query.list(); assertEquals( resultList.size(), 1 ); assertEquals( resultList.get(0).getTrimmed(), Trimmed.A ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/fetchprofile/FetchProfileTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/fetchprofile/FetchProfileTest.java index 8bead575f646..5b507892d7a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/fetchprofile/FetchProfileTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/fetchprofile/FetchProfileTest.java @@ -37,7 +37,6 @@ */ @TestForIssue( jiraKey = "HHH-4812" ) public class FetchProfileTest extends BaseUnitTestCase { - private static final Logger log = Logger.getLogger( FetchProfileTest.class ); private ServiceRegistry serviceRegistry; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/filter/secondarytable/SecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/filter/secondarytable/SecondaryTableTest.java index 27cb44e5d63b..5ff4405d06a6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/filter/secondarytable/SecondaryTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/filter/secondarytable/SecondaryTableTest.java @@ -6,9 +6,11 @@ */ package org.hibernate.test.annotations.filter.secondarytable; + import org.hibernate.Session; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import org.junit.Assert; import org.junit.Test; @@ -21,35 +23,43 @@ protected Class[] getAnnotatedClasses() { @Override protected void prepareTest() throws Exception { - Session s = openSession(); - s.beginTransaction(); - try { - insertUser( "q@s.com", 21, false, "a1", "b" ); - insertUser( "r@s.com", 22, false, "a2", "b" ); - insertUser( "s@s.com", 23, true, "a3", "b" ); - insertUser( "t@s.com", 24, false, "a4", "b" ); - session.flush(); - s.getTransaction().commit(); - }catch (Exception e){ - s.getTransaction().rollback(); - } + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + insertUser( s, "q@s.com", 21, false, "a1", "b" ); + insertUser( s, "r@s.com", 22, false, "a2", "b" ); + insertUser( s, "s@s.com", 23, true, "a3", "b" ); + insertUser( s, "t@s.com", 24, false, "a4", "b" ); + } ); } - + @Test - public void testFilter(){ - Assert.assertEquals(Long.valueOf(4), session.createQuery("select count(u) from User u").uniqueResult()); - session.enableFilter("ageFilter").setParameter("age", 24); - Assert.assertEquals(Long.valueOf(2), session.createQuery("select count(u) from User u").uniqueResult()); + public void testFilter() { + try (Session session = openSession()) { + Assert.assertEquals( + Long.valueOf( 4 ), + session.createQuery( "select count(u) from User u" ).uniqueResult() + ); + session.enableFilter( "ageFilter" ).setParameter( "age", 24 ); + Assert.assertEquals( + Long.valueOf( 2 ), + session.createQuery( "select count(u) from User u" ).uniqueResult() + ); + } } - - private void insertUser(String emailAddress, int age, boolean lockedOut, String username, String password){ + + private void insertUser( + Session session, + String emailAddress, + int age, + boolean lockedOut, + String username, + String password) { User user = new User(); - user.setEmailAddress(emailAddress); - user.setAge(age); - user.setLockedOut(lockedOut); - user.setUsername(username); - user.setPassword(password); - session.persist(user); + user.setEmailAddress( emailAddress ); + user.setAge( age ); + user.setLockedOut( lockedOut ); + user.setUsername( username ); + user.setPassword( password ); + session.persist( user ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/FormulaWithAliasTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/FormulaWithAliasTest.java new file mode 100755 index 000000000000..45ffc2b17ba0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/FormulaWithAliasTest.java @@ -0,0 +1,130 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.formula; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.annotations.Formula; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.query.Query; +import org.hibernate.test.annotations.formula.FormulaWithColumnTypesTest.ExtendedDialect; +import org.hibernate.test.locking.A; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * @author Yanming Zhou* + */ +@RequiresDialect(H2Dialect.class) +public class FormulaWithAliasTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Customer.class }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( + Environment.DIALECT, + ExtendedDialect.class.getName() + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12280") + public void testFormulaWithAlias() throws Exception { + doInHibernate( this::sessionFactory, session -> { + Customer company1 = new Customer(); + company1.setBalance(new BigDecimal(100)); + company1.setVip(true); + session.persist(company1); + + Customer company2 = new Customer(); + company2.setBalance(new BigDecimal(1000)); + company2.setVip(false); + session.persist(company2); + } ); + + doInHibernate( this::sessionFactory, session -> { + List customers = session.createQuery( + "select c " + + "from Customer c ", Customer.class) + .getResultList(); + + assertEquals(2, customers.size()); + assertEquals(1d, customers.get(0).getPercentage().doubleValue(), 0); + assertEquals(1d, customers.get(1).getPercentage().doubleValue(), 0); + } ); + } + + @Entity(name = "Customer") + public static class Customer implements Serializable{ + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private BigDecimal balance; + + @Formula("balance/(select sum(c.balance) from Customer c where c.vip = {alias}.vip)") + private BigDecimal percentage; + + private boolean vip; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public BigDecimal getBalance() { + return balance; + } + + public void setBalance(BigDecimal balance) { + this.balance = balance; + } + + public BigDecimal getPercentage() { + return percentage; + } + + public void setPercentage(BigDecimal percentage) { + this.percentage = percentage; + } + + public boolean isVip() { + return vip; + } + + public void setVip(boolean vip) { + this.vip = vip; + } + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinColumnOrFormulaTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinColumnOrFormulaTest.java index 6a753f3e7b0b..375c5e46479d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinColumnOrFormulaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinColumnOrFormulaTest.java @@ -55,7 +55,7 @@ public void testUseOfJoinColumnOrFormula() { .addAnnotatedClass( D.class ) .buildMetadata(); - // Binding to the mapping model works afterQuery the simple change for HHH-9897 + // Binding to the mapping model works after the simple change for HHH-9897 // But building the SessionFactory fails in the collection persister trying to // use the formula (it expects Columns too) metadata.buildSessionFactory().close(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/EnumIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/EnumIdTest.java index 4693f29d6970..f32cf527fa8d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/EnumIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/EnumIdTest.java @@ -28,7 +28,6 @@ @SuppressWarnings("unchecked") @TestForIssue( jiraKey = "ANN-744" ) public class EnumIdTest extends BaseCoreFunctionalTestCase { - private static final Logger log = Logger.getLogger( EnumIdTest.class ); @Test public void testEnumAsId() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/JoinColumnOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/JoinColumnOverrideTest.java index 1349d103d863..10a742255b3d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/JoinColumnOverrideTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/JoinColumnOverrideTest.java @@ -33,7 +33,6 @@ */ @SuppressWarnings("unchecked") public class JoinColumnOverrideTest extends BaseUnitTestCase { - private static final Logger log = Logger.getLogger( JoinColumnOverrideTest.class ); private static final String expectedSqlPointyTooth = "create table PointyTooth (id numeric(128,0) not null, " + "bunny_id numeric(128,0), primary key (id))"; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/UUIDGenerator.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/UUIDGenerator.java index a6f189a4faea..0fe98e7e801b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/UUIDGenerator.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/UUIDGenerator.java @@ -18,7 +18,7 @@ * Unlike Hibernate's UUID generator. This avoids * meaningless synchronization and has less * than a chance of an asteroid hitting you on the head - * even afterQuery trillions of rows are inserted. I know + * even after trillions of rows are inserted. I know * this to be true because it says so in Wikipedia(haha). * http://en.wikipedia.org/wiki/UUID#Random_UUID_probability_of_duplicates */ diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/EnumIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/EnumIdTest.java index caed05b00d9d..9c0adaca22e8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/EnumIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/EnumIdTest.java @@ -27,7 +27,6 @@ @SuppressWarnings("unchecked") @TestForIssue( jiraKey = "ANN-744" ) public class EnumIdTest extends BaseCoreFunctionalTestCase { - private static final Logger log = Logger.getLogger( EnumIdTest.class ); @Test public void testEnumAsId() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/JoinColumnOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/JoinColumnOverrideTest.java index f0997c71b14b..ce52e419c17a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/JoinColumnOverrideTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/JoinColumnOverrideTest.java @@ -35,7 +35,6 @@ */ @SuppressWarnings("unchecked") public class JoinColumnOverrideTest extends BaseUnitTestCase { - private static final Logger log = Logger.getLogger( JoinColumnOverrideTest.class ); private static final String expectedSqlPointyTooth = "create table PointyTooth (id numeric(128,0) not null, " + "bunny_id numeric(128,0), primary key (id))"; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/UUIDGenerator.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/UUIDGenerator.java index 444db010ab76..e357ae524c64 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/UUIDGenerator.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/UUIDGenerator.java @@ -18,7 +18,7 @@ * Unlike Hibernate's UUID generator. This avoids * meaningless synchronization and has less * than a chance of an asteroid hitting you on the head - * even afterQuery trillions of rows are inserted. I know + * even after trillions of rows are inserted. I know * this to be true because it says so in Wikipedia(haha). * http://en.wikipedia.org/wiki/UUID#Random_UUID_probability_of_duplicates */ diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/idclass/IdClassMappedSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/idclass/IdClassMappedSuperclassTest.java new file mode 100644 index 000000000000..d39320f968f6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/idclass/IdClassMappedSuperclassTest.java @@ -0,0 +1,152 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.idclass; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Session; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-9114") +public class IdClassMappedSuperclassTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Simple.class }; + } + + @Test + public void testIdClassWithMappedSuperclassAndId() throws Exception { + Session session = openSession(); + try { + // Persist the entity + Simple simple = new Simple(); + simple.setSimpleId( "1" ); + simple.setCategoryId( "2" ); + session.getTransaction().begin(); + session.save( simple ); + session.getTransaction().commit(); + session.clear(); + + // Query the entity. + session.getTransaction().begin(); + simple = session.createQuery( "FROM Simple", Simple.class ).getSingleResult(); + session.getTransaction().commit(); + + // tests. + assertNotNull( simple ); + assertEquals( "1", simple.getSimpleId() ); + assertEquals( "2", simple.getCategoryId() ); + + } + catch ( Throwable t ) { + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + throw t; + } + finally { + session.close(); + } + } + + @MappedSuperclass + public abstract static class AbstractMappedSuperclass { + @Id + private String categoryId; + + public String getCategoryId() { + return categoryId; + } + + public void setCategoryId(String categoryId) { + this.categoryId = categoryId; + } + } + + @Entity(name = "Simple") + @IdClass(SimpleId.class) + public static class Simple extends AbstractMappedSuperclass { + @Id + private String simpleId; + private String data; + + public String getSimpleId() { + return simpleId; + } + + public void setSimpleId(String simpleId) { + this.simpleId = simpleId; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + } + + public static class SimpleId implements Serializable { + private String simpleId; + private String categoryId; + + public String getSimpleId() { + return simpleId; + } + + public void setSimpleId(String simpleId) { + this.simpleId = simpleId; + } + + public String getCategoryId() { + return categoryId; + } + + public void setCategoryId(String categoryId) { + this.categoryId = categoryId; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + SimpleId simpleId1 = (SimpleId) o; + + if ( getSimpleId() != null ? !getSimpleId().equals( simpleId1.getSimpleId() ) : simpleId1.getSimpleId() != null ) { + return false; + } + return getCategoryId() != null ? getCategoryId().equals( simpleId1.getCategoryId() ) : simpleId1.getCategoryId() == null; + } + + @Override + public int hashCode() { + int result = getSimpleId() != null ? getSimpleId().hashCode() : 0; + result = 31 * result + ( getCategoryId() != null ? getCategoryId().hashCode() : 0 ); + return result; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java new file mode 100644 index 000000000000..32412378a29c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.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.annotations.immutable; + +import java.util.Map; + +import javax.persistence.PersistenceException; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12387" ) +public class ImmutableEntityUpdateQueryHandlingModeExceptionTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Country.class, State.class, Photo.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE, "exception" ); + } + + @Test + public void testBulkUpdate(){ + Country _country = doInHibernate( this::sessionFactory, session -> { + Country country = new Country(); + country.setName("Germany"); + session.persist(country); + + return country; + } ); + + try { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( + "update Country " + + "set name = :name" ) + .setParameter( "name", "N/A" ) + .executeUpdate(); + } ); + fail("Should throw PersistenceException"); + } + catch (PersistenceException e) { + assertTrue( e.getCause() instanceof HibernateException ); + assertEquals( + "The query: [update Country set name = :name] attempts to update an immutable entity: [Country]", + e.getCause().getMessage() + ); + } + + doInHibernate( this::sessionFactory, session -> { + Country country = session.find(Country.class, _country.getId()); + assertEquals( "Germany", country.getName() ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeWarningTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeWarningTest.java new file mode 100644 index 000000000000..b557f6908283 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeWarningTest.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.immutable; + +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.SessionImpl; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12387" ) +public class ImmutableEntityUpdateQueryHandlingModeWarningTest extends BaseNonConfigCoreFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, SessionImpl.class.getName() ) ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Country.class, State.class, Photo.class }; + } + + @Test + public void testBulkUpdate(){ + Country _country = doInHibernate( this::sessionFactory, session -> { + Country country = new Country(); + country.setName("Germany"); + session.persist(country); + + return country; + } ); + + Triggerable triggerable = logInspection.watchForLogMessages( "HHH000487" ); + triggerable.reset(); + + doInHibernate( this::sessionFactory, session -> { + session.createQuery( + "update Country " + + "set name = :name" ) + .setParameter( "name", "N/A" ) + .executeUpdate(); + } ); + + assertEquals( "HHH000487: The query: [update Country set name = :name] attempts to update an immutable entity: [Country]", triggerable.triggerMessage() ); + + doInHibernate( this::sessionFactory, session -> { + Country country = session.find(Country.class, _country.getId()); + assertEquals( "N/A", country.getName() ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableTest.java index 86580a60a8cd..e8b1169ec640 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableTest.java @@ -36,7 +36,6 @@ */ @SuppressWarnings("unchecked") public class ImmutableTest extends BaseCoreFunctionalTestCase { - private static final Logger log = Logger.getLogger( ImmutableTest.class ); @Test public void testImmutableEntity() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/list/ListMappingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/list/ListMappingTest.java index a3b023a6dc3a..91c947177e92 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/list/ListMappingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/list/ListMappingTest.java @@ -84,7 +84,7 @@ class TargetImpl extends GenerationTargetToStdout { @Override public void accept(String action) { super.accept( action ); - if ( action.startsWith( "create table t_line_item" ) ) { + if ( action.matches( "^create( (column|row))? table t_line_item.+" ) ) { if ( action.contains( "position" ) ) { found = true; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/LoaderWithInvalidQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/LoaderWithInvalidQueryTest.java new file mode 100644 index 000000000000..c6b8687f8b92 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/LoaderWithInvalidQueryTest.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 . + */ +package org.hibernate.test.annotations.loader; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.HibernateException; +import org.hibernate.annotations.Loader; +import org.hibernate.annotations.NamedNativeQueries; +import org.hibernate.annotations.NamedQuery; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.util.ExceptionUtil; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class LoaderWithInvalidQueryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Override + public void buildEntityManagerFactory() { + try { + super.buildEntityManagerFactory(); + } + catch (Exception expected) { + HibernateException rootCause = (HibernateException) ExceptionUtil.rootCause( expected ); + assertTrue(rootCause.getMessage().contains( "could not resolve property: valid" )); + assertTrue(rootCause.getMessage().contains( "_Person is not mapped" )); + } + } + + @Test + public void test() { + } + + + @Entity(name = "Person") + @Loader(namedQuery = "invalid_sql") + @NamedQuery( + name = "invalid_sql", + query = "SELECT p " + + "FROM Person p " + + "WHERE p.id = ?1 and p.valid = true" + ) + @NamedQuery( + name = "another_invalid_sql", + query = "SELECT p " + + "FROM _Person p " + + "WHERE p.id = ?1" + ) + public static class Person { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "full_name") + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + //end::sql-custom-crud-example[] + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/AbstractLobTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/AbstractLobTest.java index b6d58c64873a..76c07e31d629 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/AbstractLobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/AbstractLobTest.java @@ -6,12 +6,17 @@ */ package org.hibernate.test.annotations.lob; +import org.hibernate.boot.jaxb.SourceType; +import org.hibernate.dialect.*; import org.junit.Test; import org.hibernate.Session; import org.hibernate.Transaction; + +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +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; @@ -55,62 +60,49 @@ public void testSerializableToBlob() throws Exception { editor.setName( "O'Reilly" ); book.setEditor( editor ); book.setCode2( new char[] { 'r' } ); - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - s.persist( book ); - tx.commit(); - s.close(); - s = openSession(); - tx = s.beginTransaction(); - B loadedBook = getBookClass().cast( s.get( getBookClass(), getId( book ) ) ); - assertNotNull( loadedBook.getEditor() ); - assertEquals( book.getEditor().getName(), loadedBook.getEditor().getName() ); - loadedBook.setEditor( null ); - tx.commit(); - s.close(); - s = openSession(); - tx = s.beginTransaction(); - loadedBook = getBookClass().cast( s.get( getBookClass(), getId( book ) ) ); - assertNull( loadedBook.getEditor() ); - tx.commit(); - s.close(); + doInHibernate( this::sessionFactory, session -> { + session.persist( book ); + } ); + + doInHibernate( this::sessionFactory, session -> { + B loadedBook = getBookClass().cast( session.get( getBookClass(), getId( book ) ) ); + assertNotNull( loadedBook.getEditor() ); + assertEquals( book.getEditor().getName(), loadedBook.getEditor().getName() ); + loadedBook.setEditor( null ); + } ); + + doInHibernate( this::sessionFactory, session -> { + B loadedBook = getBookClass().cast( session.get( getBookClass(), getId( book ) ) ); + assertNull( loadedBook.getEditor() ); + } ); } @Test public void testClob() throws Exception { - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - B b = createBook(); - b.setShortDescription( "Hibernate Bible" ); - b.setFullText( "Hibernate in Action aims to..." ); - b.setCode( new Character[] { 'a', 'b', 'c' } ); - b.setCode2( new char[] { 'a', 'b', 'c' } ); - s.persist( b ); - tx.commit(); - s.close(); - - s = openSession(); - tx = s.beginTransaction(); - B b2 = getBookClass().cast( s.get( getBookClass(), getId( b ) ) ); - assertNotNull( b2 ); - assertEquals( b2.getFullText(), b.getFullText() ); - assertEquals( b2.getCode()[1].charValue(), b.getCode()[1].charValue() ); - assertEquals( b2.getCode2()[2], b.getCode2()[2] ); - tx.commit(); - s.close(); + + B book = createBook(); + book.setShortDescription( "Hibernate Bible" ); + book.setFullText( "Hibernate in Action aims to..." ); + book.setCode( new Character[] { 'a', 'b', 'c' } ); + book.setCode2( new char[] { 'a', 'b', 'c' } ); + + doInHibernate( this::sessionFactory, session -> { + session.persist( book ); + } ); + + doInHibernate( this::sessionFactory, session -> { + B b2 = getBookClass().cast( session.get( getBookClass(), getId( book ) ) ); + assertNotNull( b2 ); + assertEquals( b2.getFullText(), book.getFullText() ); + assertEquals( b2.getCode()[1].charValue(), book.getCode()[1].charValue() ); + assertEquals( b2.getCode2()[2], book.getCode2()[2] ); + } ); } @Test public void testBlob() throws Exception { - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); + C cc = createCompiledCode(); Byte[] header = new Byte[2]; header[0] = new Byte( ( byte ) 3 ); @@ -122,37 +114,35 @@ public void testBlob() throws Exception { full[i] = ( byte ) ( 1 + i ); } cc.setFullCode( full ); - s.persist( cc ); - tx.commit(); - s.close(); - s = openSession(); - tx = s.beginTransaction(); - C recompiled = getCompiledCodeClass().cast( s.get( getCompiledCodeClass(), getId( cc ) ) ); - assertEquals( recompiled.getHeader()[1], cc.getHeader()[1] ); - assertEquals( recompiled.getFullCode()[codeSize - 1], cc.getFullCode()[codeSize - 1] ); - tx.commit(); - s.close(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( cc ); + } ); + + doInHibernate( this::sessionFactory, session -> { + C recompiled = getCompiledCodeClass().cast( session.get( getCompiledCodeClass(), getId( cc ) ) ); + assertEquals( recompiled.getHeader()[1], cc.getHeader()[1] ); + assertEquals( recompiled.getFullCode()[codeSize - 1], cc.getFullCode()[codeSize - 1] ); + } ); } @Test + @SkipForDialect( SybaseDialect.class ) public void testBinary() throws Exception { - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); + C cc = createCompiledCode(); byte[] metadata = new byte[2]; metadata[0] = ( byte ) 3; metadata[1] = ( byte ) 0; cc.setMetadata( metadata ); - s.persist( cc ); - tx.commit(); - s.close(); - s = openSession(); - tx = s.beginTransaction(); - C recompiled = getCompiledCodeClass().cast( s.get( getCompiledCodeClass(), getId( cc ) ) ); - assertEquals( recompiled.getMetadata()[1], cc.getMetadata()[1] ); - tx.commit(); - s.close(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( cc ); + } ); + + doInHibernate( this::sessionFactory, session -> { + C recompiled = getCompiledCodeClass().cast( session.get( getCompiledCodeClass(), getId( cc ) ) ); + assertEquals( recompiled.getMetadata()[1], cc.getMetadata()[1] ); + } ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTest.java index 6609bcb6f90d..2db8952ec07c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTest.java @@ -23,8 +23,6 @@ @RequiresDialect( Oracle8iDialect.class ) @TestForIssue( jiraKey = "HHH-4635" ) public class LobTest extends BaseCoreFunctionalTestCase { - - private static final Logger LOG = Logger.getLogger( LobTest.class ); @Test public void hibernateTest() { @@ -61,6 +59,6 @@ private void printConfig() { Query query = session.createSQLQuery( sql ); String s = (String) query.uniqueResult(); - LOG.debug( "Using Oracle charset " + s ); + log.debug( "Using Oracle charset " + s ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Man.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Man.java index 46d2fe9c3e50..e62f49b23c10 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Man.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Man.java @@ -52,12 +52,12 @@ public void setCarName(String carName) { } public int hashCode() { - //a NPE can occurs, but I don't expect hashcode to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect hashcode to be used before pk is set return getId().hashCode(); } public boolean equals(Object obj) { - //a NPE can occurs, but I don't expect equals to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect equals to be used before pk is set if ( obj != null && obj instanceof Man ) { return getId().equals( ( (Man) obj ).getId() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Woman.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Woman.java index 3a44f0d52d91..d58774c3c657 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Woman.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytomany/Woman.java @@ -80,12 +80,12 @@ public void setCarName(String carName) { public int hashCode() { - //a NPE can occurs, but I don't expect hashcode to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect hashcode to be used before pk is set return getId().hashCode(); } public boolean equals(Object obj) { - //a NPE can occurs, but I don't expect equals to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect equals to be used before pk is set if ( obj != null && obj instanceof Woman ) { return getId().equals( ( (Woman) obj ).getId() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/Parent.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/Parent.java index 98025d85e33b..d55c106ffe6e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/Parent.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/Parent.java @@ -23,12 +23,12 @@ public class Parent implements Serializable { public int age; public int hashCode() { - //a NPE can occurs, but I don't expect hashcode to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect hashcode to be used before pk is set return id.hashCode(); } public boolean equals(Object obj) { - //a NPE can occurs, but I don't expect equals to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect equals to be used before pk is set if ( obj != null && obj instanceof Parent ) { return id.equals( ( (Parent) obj ).id ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/LongIdentifierNamingStrategy.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/LongIdentifierNamingStrategy.java new file mode 100644 index 000000000000..f37d1fff17a9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/LongIdentifierNamingStrategy.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 . + */ +package org.hibernate.test.annotations.namingstrategy; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.ImplicitForeignKeyNameSource; +import org.hibernate.boot.model.naming.ImplicitIndexNameSource; +import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.boot.model.naming.ImplicitUniqueKeyNameSource; + +@SuppressWarnings("serial") +public class LongIdentifierNamingStrategy + extends ImplicitNamingStrategyJpaCompliantImpl { + + @Override + public Identifier determineForeignKeyName(ImplicitForeignKeyNameSource source) { + return limitIdentifierName(super.determineForeignKeyName( source )); + } + + @Override + public Identifier determineUniqueKeyName(ImplicitUniqueKeyNameSource source) { + return limitIdentifierName(super.determineUniqueKeyName( source )); + } + + @Override + public Identifier determineIndexName(ImplicitIndexNameSource source) { + return limitIdentifierName(super.determineIndexName( source )); + } + + public Identifier limitIdentifierName(Identifier identifier) { + String text = identifier.getText(); + if(text.length() > 30) { + return new Identifier( text.substring( 0, 30 ), identifier.isQuoted() ); + } + return identifier; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/LongKeyNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/LongKeyNamingStrategyTest.java new file mode 100644 index 000000000000..7fd014a440b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/LongKeyNamingStrategyTest.java @@ -0,0 +1,148 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +// $Id$ +package org.hibernate.test.annotations.namingstrategy; + +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.cfg.Environment; +import org.hibernate.mapping.UniqueKey; +import org.hibernate.service.ServiceRegistry; + +import org.hibernate.testing.ServiceRegistryBuilder; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.junit.Assert.assertEquals; + +/** + * Test harness for HHH-11089. + * + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-11089" ) +public class LongKeyNamingStrategyTest extends BaseUnitTestCase { + + private ServiceRegistry serviceRegistry; + + @Before + public void setUp() { + serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( Environment.getProperties() ); + } + + @After + public void tearDown() { + if ( serviceRegistry != null ) { + ServiceRegistryBuilder.destroy( serviceRegistry ); + } + } + @Test + public void testWithCustomNamingStrategy() throws Exception { + Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClass(Address.class) + .addAnnotatedClass(Person.class) + .getMetadataBuilder() + .applyImplicitNamingStrategy( new LongIdentifierNamingStrategy() ) + .build(); + + org.hibernate.mapping.ForeignKey foreignKey = + (org.hibernate.mapping.ForeignKey) metadata.getEntityBinding( Address.class.getName()).getTable().getForeignKeyIterator().next(); + assertEquals( "FK_way_longer_than_the_30_char", foreignKey.getName() ); + + UniqueKey uniqueKey = metadata.getEntityBinding( Address.class.getName()).getTable().getUniqueKeyIterator().next(); + assertEquals( "UK_way_longer_than_the_30_char", uniqueKey.getName() ); + + org.hibernate.mapping.Index index = metadata.getEntityBinding( Address.class.getName()).getTable().getIndexIterator().next(); + assertEquals( "IDX_way_longer_than_the_30_cha", index.getName() ); + } + + @Entity(name = "Address") + @Table(uniqueConstraints = @UniqueConstraint( + name = "UK_way_longer_than_the_30_characters_limit", + columnNames = { + "city", "streetName", "streetNumber" + }), + indexes = @Index( name = "IDX_way_longer_than_the_30_characters_limit", columnList = "city, streetName, streetNumber") + ) + public class Address { + + @Id + private Long id; + + private String city; + + private String streetName; + + private String streetNumber; + + @ManyToOne + @JoinColumn(name = "person_id", foreignKey = @ForeignKey(name = "FK_way_longer_than_the_30_characters_limit")) + private Person person; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getStreetName() { + return streetName; + } + + public void setStreetName(String streetName) { + this.streetName = streetName; + } + + public String getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber(String streetNumber) { + this.streetNumber = streetNumber; + } + } + + @Entity + public class Person { + + @Id + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/NamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/NamingStrategyTest.java index 72efb4c3ae0a..43f8409c71d1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/NamingStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/NamingStrategyTest.java @@ -11,9 +11,14 @@ import java.util.Locale; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.cfg.Environment; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.mapping.Collection; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Selectable; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.ServiceRegistryBuilder; @@ -32,7 +37,6 @@ * @author Hardy Ferentschik */ public class NamingStrategyTest extends BaseUnitTestCase { - private static final Logger log = Logger.getLogger( NamingStrategyTest.class ); private ServiceRegistry serviceRegistry; @@ -47,6 +51,7 @@ public void tearDown() { ServiceRegistryBuilder.destroy( serviceRegistry ); } } + @Test public void testWithCustomNamingStrategy() throws Exception { new MetadataSources( serviceRegistry ) @@ -57,6 +62,27 @@ public void testWithCustomNamingStrategy() throws Exception { .build(); } + @Test + public void testWithUpperCaseNamingStrategy() throws Exception { + Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClass(A.class) + .getMetadataBuilder() + .applyPhysicalNamingStrategy( new PhysicalNamingStrategyStandardImpl() { + @Override + public Identifier toPhysicalColumnName( + Identifier name, JdbcEnvironment context) { + return new Identifier( name.getText().toUpperCase(), name.isQuoted() ); + } + } ) + .build(); + + PersistentClass entityBinding = metadata.getEntityBinding( A.class.getName() ); + assertEquals("NAME", + ((Selectable) entityBinding.getProperty( "name" ).getColumnIterator().next()).getText()); + assertEquals("VALUE", + ((Selectable) entityBinding.getProperty( "value" ).getColumnIterator().next()).getText()); + } + @Test public void testWithJpaCompliantNamingStrategy() throws Exception { Metadata metadata = new MetadataSources( serviceRegistry ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/AbstractCharsetNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/AbstractCharsetNamingStrategyTest.java new file mode 100644 index 000000000000..044b569e65c9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/AbstractCharsetNamingStrategyTest.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 . + */ + +// $Id$ +package org.hibernate.test.annotations.namingstrategy.charset; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.mapping.UniqueKey; +import org.hibernate.service.ServiceRegistry; + +import org.hibernate.testing.ServiceRegistryBuilder; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.test.annotations.namingstrategy.LongIdentifierNamingStrategy; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12357" ) +public abstract class AbstractCharsetNamingStrategyTest extends BaseUnitTestCase { + + protected ServiceRegistry serviceRegistry; + + @Before + public void setUp() { + Map properties = new HashMap<>( Environment.getProperties() ); + properties.put( AvailableSettings.HBM2DDL_CHARSET_NAME, charsetName() ); + serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( properties ); + } + + protected abstract String charsetName(); + + @After + public void tearDown() { + if ( serviceRegistry != null ) { + ServiceRegistryBuilder.destroy( serviceRegistry ); + } + } + @Test + public void testWithCustomNamingStrategy() throws Exception { + Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClass(Address.class) + .addAnnotatedClass(Person.class) + .getMetadataBuilder() + .applyImplicitNamingStrategy( new LongIdentifierNamingStrategy() ) + .build(); + + UniqueKey uniqueKey = metadata.getEntityBinding( Address.class.getName()).getTable().getUniqueKeyIterator().next(); + assertEquals( expectedUniqueKeyName(), uniqueKey.getName() ); + + org.hibernate.mapping.ForeignKey foreignKey = + (org.hibernate.mapping.ForeignKey) metadata.getEntityBinding( Address.class.getName()).getTable().getForeignKeyIterator().next(); + assertEquals( expectedForeignKeyName(), foreignKey.getName() ); + + org.hibernate.mapping.Index index = metadata.getEntityBinding( Address.class.getName()).getTable().getIndexIterator().next(); + assertEquals( expectedIndexName(), index.getName() ); + } + + protected abstract String expectedUniqueKeyName(); + + protected abstract String expectedForeignKeyName(); + + protected abstract String expectedIndexName(); + + @Entity(name = "Address") + @Table(uniqueConstraints = @UniqueConstraint( + columnNames = { + "city", "stradă" + }), + indexes = @Index( columnList = "city, stradă") + ) + public class Address { + + @Id + private Long id; + + private String city; + + private String stradă; + + @ManyToOne + private Person personă; + } + + @Entity(name = "Person") + public class Person { + + @Id + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/Iso88591CharsetNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/Iso88591CharsetNamingStrategyTest.java new file mode 100644 index 000000000000..7ae6a2f401e6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/Iso88591CharsetNamingStrategyTest.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +// $Id$ +package org.hibernate.test.annotations.namingstrategy.charset; + +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; + +/** + * @author Vlad Mihalcea + */ +public class Iso88591CharsetNamingStrategyTest extends AbstractCharsetNamingStrategyTest { + + @Override + protected String charsetName() { + return "ISO-8859-1"; + } + + @Override + protected String expectedUniqueKeyName() { + if ( this.serviceRegistry.getService( JdbcServices.class ).getDialect() instanceof AbstractHANADialect ) { + return "UK38xspy14r49kkcmmyltias1j4"; // Non-ASCII, non-alphanumeric identifiers are quoted on HANA + } + else { + return "UKq2jxex2hrvg4139p85npyj71g"; + } + } + + @Override + protected String expectedForeignKeyName() { + if ( this.serviceRegistry.getService( JdbcServices.class ).getDialect() instanceof AbstractHANADialect ) { + return "FKdvmx00nr88d03v6xhrjyujrq2"; // Non-ASCII, non-alphanumeric identifiers are quoted on HANA + } + else { + return "FKdeqq4y6cesc2yfgi97u2hp61g"; + } + } + + @Override + protected String expectedIndexName() { + if ( this.serviceRegistry.getService( JdbcServices.class ).getDialect() instanceof AbstractHANADialect ) { + return "IDX38xspy14r49kkcmmyltias1j4"; // Non-ASCII, non-alphanumeric identifiers are quoted on HANA + } + else { + return "IDXq2jxex2hrvg4139p85npyj71g"; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/Utf8CharsetNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/Utf8CharsetNamingStrategyTest.java new file mode 100644 index 000000000000..d35ca0cc755b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/namingstrategy/charset/Utf8CharsetNamingStrategyTest.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +// $Id$ +package org.hibernate.test.annotations.namingstrategy.charset; + +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; + +/** + * @author Vlad Mihalcea + */ +public class Utf8CharsetNamingStrategyTest extends AbstractCharsetNamingStrategyTest { + + @Override + protected String charsetName() { + return "UTF8"; + } + + @Override + protected String expectedUniqueKeyName() { + if ( this.serviceRegistry.getService( JdbcServices.class ).getDialect() instanceof AbstractHANADialect ) { + return "UKinnacp0woeltj5l0k4vgabf8k"; // Non-ASCII, non-alphanumeric identifiers are quoted on HANA + } + else { + return "UKpm66tdjkgtsca5x2uwux487t5"; + } + } + + @Override + protected String expectedForeignKeyName() { + if ( this.serviceRegistry.getService( JdbcServices.class ).getDialect() instanceof AbstractHANADialect ) { + return "FKe1lr9dd16cmmon53r7m736yev"; // Non-ASCII, non-alphanumeric identifiers are quoted on HANA + } + else { + return "FKgvrnki5fwp3qo0hfp1bu1jj0q"; + } + } + + @Override + protected String expectedIndexName() { + if ( this.serviceRegistry.getService( JdbcServices.class ).getDialect() instanceof AbstractHANADialect ) { + return "IDXinnacp0woeltj5l0k4vgabf8k"; // Non-ASCII, non-alphanumeric identifiers are quoted on HANA + } + else { + return "IDXpm66tdjkgtsca5x2uwux487t5"; + } + } +} 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 d648eb0a1664..1c1f8be165a8 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 @@ -36,7 +36,6 @@ @SuppressWarnings("unchecked") @TestForIssue( jiraKey = "ANN-750" ) public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase { - private static final Logger log = Logger.getLogger( NaturalIdOnSingleManyToOneTest.class ); @After public void cleanupData() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdTest.java index 8fefe6c109c1..53ca5022f983 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdTest.java @@ -149,7 +149,7 @@ public void testNaturalIdLoaderCached() { assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() ); - //Try NaturalIdLoadAccess afterQuery insert + //Try NaturalIdLoadAccess after insert Session s = openSession(); Transaction tx = s.beginTransaction(); @@ -195,7 +195,7 @@ public void testNaturalIdLoaderCached() { s.close(); - //Try NaturalIdLoadAccess afterQuery load + //Try NaturalIdLoadAccess after load s = openSession(); tx = s.beginTransaction(); 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 new file mode 100644 index 000000000000..c940b3faecc6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.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 . + */ +package org.hibernate.test.annotations.notfound; + +import java.io.Serializable; +import javax.persistence.ConstraintMode; +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.OneToOne; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNull; + +/** + * @author Emmanuel Bernard + * @author Gail Badner + */ +public class NotFoundLogicalOneToOneTest extends BaseCoreFunctionalTestCase { + @Test + public void testLogicalOneToOne() throws Exception { + Currency euro = new Currency(); + euro.setName( "Euro" ); + Coin fiveC = new Coin(); + fiveC.setName( "Five cents" ); + fiveC.setCurrency( euro ); + + doInHibernate( + this::sessionFactory, session -> { + session.persist( euro ); + session.persist( fiveC ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( euro ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Coin coin = session.get( Coin.class, fiveC.getId() ); + assertNull( coin.getCurrency() ); + + session.delete( coin ); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Coin.class, Currency.class }; + } + + @Entity(name = "Coin") + public static class Coin { + private Integer id; + private String name; + private Currency currency; + + @Id + @GeneratedValue + 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) + @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + @NotFound(action = NotFoundAction.IGNORE) + 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; + + @Id + @GeneratedValue + 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/annotations/notfound/NotFoundOneToOneNonInsertableNonUpdateableTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundOneToOneNonInsertableNonUpdateableTest.java new file mode 100644 index 000000000000..df457847ced2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundOneToOneNonInsertableNonUpdateableTest.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.notfound; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Gail Badner + */ +public class NotFoundOneToOneNonInsertableNonUpdateableTest extends BaseCoreFunctionalTestCase { + private static final int ID = 1; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + PersonInfo.class + }; + } + + @Test + public void testOneToOne() { + + doInHibernate( + this::sessionFactory, session -> { + Person person = new Person(); + person.id = ID; + person.personInfo = new PersonInfo(); + person.personInfo.id = ID; + session.persist( person ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( session.get( PersonInfo.class, ID ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Person person = session.get( Person.class, ID ); + assertNotNull( person ); + assertNull( person.personInfo ); + + session.delete( person ); + } + ); + } + + @Entity(name="Person") + public static class Person { + + @Id + private int id; + + @OneToOne(optional = true, cascade = CascadeType.ALL) + @JoinColumn( + name = "id", + updatable = false, + insertable = false + ) + @NotFound(action = NotFoundAction.IGNORE) + private PersonInfo personInfo; + } + + @Entity(name = "PersonInfo") + public static class PersonInfo { + @Id + private int id; + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundTest.java index 887e8ca3b9ac..8142eb964437 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundTest.java @@ -9,16 +9,15 @@ import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; import javax.persistence.ManyToOne; -import javax.persistence.OneToOne; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -28,6 +27,7 @@ /** * @author Emmanuel Bernard */ +@RequiresDialectFeature(value = DialectChecks.SupportsIdentityColumns.class) public class NotFoundTest extends BaseCoreFunctionalTestCase { @Test @@ -56,25 +56,9 @@ public void testManyToOne() throws Exception { } ); } - @Test - public void testOneToOne() throws Exception { - doInHibernate( this::sessionFactory, session -> { - Show show = new Show(); - session.save( show ); - - ShowDescription showDescription = new ShowDescription(); - session.save( showDescription ); - } ); - } - @Override protected Class[] getAnnotatedClasses() { - return new Class[] { - Coin.class, - Currency.class, - Show.class, - ShowDescription.class - }; + return new Class[] {Coin.class, Currency.class}; } @Entity(name = "Coin") @@ -142,63 +126,4 @@ public void setName(String name) { } } - @Entity(name = "T_Show") - public static class Show { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - @OneToOne() - @NotFound(action = NotFoundAction.IGNORE) - @JoinTable(name = "Show_Description", - joinColumns = @JoinColumn(name = "show_id"), - inverseJoinColumns = @JoinColumn(name = "description_id")) - private ShowDescription description; - - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public ShowDescription getDescription() { - return description; - } - - public void setDescription(ShowDescription description) { - this.description = description; - } - } - - @Entity(name = "ShowDescription") - public static class ShowDescription { - - @Id - @GeneratedValue - private Integer id; - - @NotFound(action = NotFoundAction.IGNORE) - @OneToOne(mappedBy = "description") - private Show show; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public Show getShow() { - return show; - } - - public void setShow(Show show) { - this.show = show; - } - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/OneToOneNotFoundTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/OneToOneNotFoundTest.java new file mode 100644 index 000000000000..c7362215d316 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/OneToOneNotFoundTest.java @@ -0,0 +1,160 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.notfound; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.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.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11591") +public class OneToOneNotFoundTest extends BaseCoreFunctionalTestCase { + + @Override + protected boolean createSchema() { + return false; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Show.class, ShowDescription.class}; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.doWork( connection -> { + connection.createStatement().execute("create table SHOW_DESCRIPTION ( ID integer not null, primary key (ID) )" ); + connection.createStatement().execute("create table T_SHOW ( id integer not null, primary key (id) )" ); + connection.createStatement().execute("create table TSHOW_SHOWDESCRIPTION ( DESCRIPTION_ID integer, SHOW_ID integer not null, primary key (SHOW_ID) )" ); + + } ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Show show = new Show(); + show.setId( 1 ); + ShowDescription showDescription = new ShowDescription(); + showDescription.setId( 2 ); + show.setDescription( showDescription ); + session.save( showDescription ); + session.save( show ); + + } ); + + doInHibernate( this::sessionFactory, session -> { + session.doWork( connection -> { + connection.createStatement().execute( "delete from SHOW_DESCRIPTION where ID = 2" ); + + } ); + } ); + } + + @After + public void tearDow() { + doInHibernate( this::sessionFactory, session -> { + session.doWork( connection -> { + connection.createStatement().execute( "drop table TSHOW_SHOWDESCRIPTION" ); + connection.createStatement().execute( "drop table SHOW_DESCRIPTION" ); + connection.createStatement().execute( "drop table T_SHOW" ); + + } ); + } ); + } + + @Test + public void testOneToOne() throws Exception { + doInHibernate( this::sessionFactory, session -> { + final Show show2 = session.find( Show.class, 1 ); + assertNotNull( show2 ); + assertNull( show2.getDescription() ); + } ); + } + + @Entity(name = "Show") + @Table(name = "T_SHOW") + public static class Show { + + @Id + private Integer id; + + @OneToOne + @NotFound(action = NotFoundAction.IGNORE) + @JoinTable(name = "TSHOW_SHOWDESCRIPTION", + joinColumns = @JoinColumn(name = "SHOW_ID"), + inverseJoinColumns = @JoinColumn(name = "DESCRIPTION_ID"), foreignKey = @ForeignKey(name = "FK_DESC")) + private ShowDescription description; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public ShowDescription getDescription() { + return description; + } + + public void setDescription(ShowDescription description) { + this.description = description; + description.setShow( this ); + } + } + + @Entity(name = "ShowDescription") + @Table(name = "SHOW_DESCRIPTION") + public static class ShowDescription { + + @Id + @Column(name = "ID") + private Integer id; + + @NotFound(action = NotFoundAction.IGNORE) + @OneToOne(mappedBy = "description", cascade = CascadeType.ALL) + private Show show; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Show getShow() { + return show; + } + + public void setShow(Show show) { + this.show = show; + } + } +} 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 093ab3b0398c..0027981c70c4 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 @@ -6,25 +6,39 @@ */ package org.hibernate.test.annotations.onetomany; -import javax.persistence.PersistenceException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.PersistenceException; +import org.hibernate.AnnotationException; import org.hibernate.Hibernate; -import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.mapping.Column; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.annotations.Customer; @@ -36,6 +50,7 @@ import org.junit.Test; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -345,6 +360,47 @@ public void testCascadeDelete() throws Exception { s.close(); } + @Test + @RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class) + public void testCascadeDeleteWithUnidirectionalAssociation() throws Exception { + OnDeleteUnidirectionalOneToManyChild child = new OnDeleteUnidirectionalOneToManyChild(); + + doInHibernate( this::sessionFactory, session -> { + OnDeleteUnidirectionalOneToManyParent parent = new OnDeleteUnidirectionalOneToManyParent(); + parent.children = Collections.singletonList( child); + session.persist( parent ); + } ); + + doInHibernate( this::sessionFactory, session -> { + session.createQuery("delete from OnDeleteUnidirectionalOneToManyParent").executeUpdate(); + } ); + + doInHibernate( this::sessionFactory, session -> { + OnDeleteUnidirectionalOneToManyChild e1 = session.get( OnDeleteUnidirectionalOneToManyChild.class, child.id ); + assertNull( "delete cascade should work", e1 ); + } ); + } + + @Test + public void testOnDeleteWithoutJoinColumn() throws Exception { + StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + .build(); + + try { + new MetadataSources( serviceRegistry ) + .addAnnotatedClass( OnDeleteUnidirectionalOneToMany.class ) + .addAnnotatedClass( ParentUnawareChild.class ) + .getMetadataBuilder() + .build(); + } + catch ( AnnotationException e ) { + assertTrue(e.getMessage().contains( "Unidirectional one-to-many associations annotated with @OnDelete must define @JoinColumn" )); + } + finally { + StandardServiceRegistryBuilder.destroy( serviceRegistry ); + } + } + @Test public void testSimpleOneToManySet() throws Exception { Session s; @@ -503,7 +559,9 @@ protected Class[] getAnnotatedClasses() { Person.class, Organisation.class, OrganisationUser.class, - Model.class + Model.class, + OnDeleteUnidirectionalOneToManyParent.class, + OnDeleteUnidirectionalOneToManyChild.class }; } @@ -511,4 +569,47 @@ protected Class[] getAnnotatedClasses() { protected String[] getXmlFiles() { return new String[] { "org/hibernate/test/annotations/onetomany/orm.xml" }; } + + @Entity(name = "OnDeleteUnidirectionalOneToManyParent") + @javax.persistence.Table(name = "OneToManyParent") + public static class OnDeleteUnidirectionalOneToManyParent { + + @Id + @GeneratedValue + Long id; + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn(name = "a_id") + @OnDelete(action = OnDeleteAction.CASCADE) + List children; + } + + @Entity(name = "OnDeleteUnidirectionalOneToManyChild") + @javax.persistence.Table(name = "OneToManyChild") + public static class OnDeleteUnidirectionalOneToManyChild { + + @Id + @GeneratedValue + Long id; + } + + @Entity(name = "OnDeleteUnidirectionalOneToMany") + @javax.persistence.Table(name = "OneToMany") + public static class OnDeleteUnidirectionalOneToMany { + + @Id + Long id; + + @OneToMany(cascade = CascadeType.ALL) + @OnDelete(action = OnDeleteAction.CASCADE) + List children; + } + + @Entity(name = "ParentUnawareChild") + @javax.persistence.Table(name = "Child") + public static class ParentUnawareChild { + + @Id + Long id; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Parent.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Parent.java index 980eee6a96a4..4f83edc2c707 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Parent.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Parent.java @@ -32,12 +32,12 @@ public class Parent implements Serializable { public Set children; public int hashCode() { - //a NPE can occurs, but I don't expect hashcode to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect hashcode to be used before pk is set return id.hashCode(); } public boolean equals(Object obj) { - //a NPE can occurs, but I don't expect equals to be used beforeQuery pk is set + //a NPE can occurs, but I don't expect equals to be used before pk is set if ( obj != null && obj instanceof Parent ) { return id.equals( ( (Parent) obj ).id ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneJoinTableNonOptionalTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneJoinTableNonOptionalTest.java new file mode 100644 index 000000000000..e863d625d320 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneJoinTableNonOptionalTest.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 . + */ +package org.hibernate.test.annotations.onetoone; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.PropertyValueException; + +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.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11596") +public class OneToOneJoinTableNonOptionalTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Show.class, ShowDescription.class}; + } + + @Test + public void testSavingEntitiesWithANullOneToOneAssociationValue() { + doInHibernate( this::sessionFactory, session -> { + Show show = new Show(); + session.save( show ); + } ); + + try { + doInHibernate( this::sessionFactory, session -> { + ShowDescription showDescription = new ShowDescription(); + session.save( showDescription ); + } ); + fail(); + } + catch (PropertyValueException expected) { + assertTrue( expected.getMessage().startsWith( "not-null property references a null or transient value" ) ); + } + } + + @Entity(name = "Show") + @Table(name = "T_SHOW") + public static class Show { + + @Id + @GeneratedValue + private Integer id; + + @OneToOne + @JoinTable(name = "TSHOW_SHOWDESCRIPTION", + joinColumns = @JoinColumn(name = "SHOW_ID"), + inverseJoinColumns = @JoinColumn(name = "DESCRIPTION_ID"), foreignKey = @ForeignKey(name = "FK_DESC")) + private ShowDescription description; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public ShowDescription getDescription() { + return description; + } + + public void setDescription(ShowDescription description) { + this.description = description; + description.setShow( this ); + } + } + + @Entity(name = "ShowDescription") + @Table(name = "SHOW_DESCRIPTION") + public static class ShowDescription { + + @Id + @Column(name = "ID") + @GeneratedValue + private Integer id; + + @OneToOne(mappedBy = "description", cascade = CascadeType.ALL, optional = false) + private Show show; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Show getShow() { + return show; + } + + public void setShow(Show show) { + this.show = show; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneJoinTableOptionalTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneJoinTableOptionalTest.java new file mode 100644 index 000000000000..6beef319f300 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneJoinTableOptionalTest.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.annotations.onetoone; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11596") +public class OneToOneJoinTableOptionalTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Show.class, ShowDescription.class}; + } + + @Test + public void testSavingEntitiesWithANullOneToOneAssociationValue() { + doInHibernate( this::sessionFactory, session -> { + Show show = new Show(); + session.save( show ); + } ); + + doInHibernate( this::sessionFactory, session -> { + ShowDescription showDescription = new ShowDescription(); + session.save( showDescription ); + } ); + } + + @Entity(name = "Show") + @Table(name = "T_SHOW") + public static class Show { + + @Id + @GeneratedValue + private Integer id; + + @OneToOne + @JoinTable(name = "TSHOW_SHOWDESCRIPTION", + joinColumns = @JoinColumn(name = "SHOW_ID"), + inverseJoinColumns = @JoinColumn(name = "DESCRIPTION_ID"), foreignKey = @ForeignKey(name = "FK_DESC")) + private ShowDescription description; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public ShowDescription getDescription() { + return description; + } + + public void setDescription(ShowDescription description) { + this.description = description; + description.setShow( this ); + } + } + + @Entity(name = "ShowDescription") + @Table(name = "SHOW_DESCRIPTION") + public static class ShowDescription { + + @Id + @Column(name = "ID") + @GeneratedValue + private Integer id; + + @OneToOne(mappedBy = "description", cascade = CascadeType.ALL) + private Show show; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Show getShow() { + return show; + } + + public void setShow(Show show) { + this.show = show; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneMapsIdJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneMapsIdJoinColumnTest.java new file mode 100644 index 000000000000..31c22b5b88ad --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneMapsIdJoinColumnTest.java @@ -0,0 +1,117 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.onetoone; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class OneToOneMapsIdJoinColumnTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + PersonDetails.class + }; + } + + @Test + public void testLifecycle() { + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( "ABC-123" ); + + PersonDetails details = new PersonDetails(); + details.setNickName( "John Doe" ); + + person.setDetails( details ); + entityManager.persist( person ); + + return person; + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = entityManager.find( Person.class, _person.getId() ); + + PersonDetails details = entityManager.find( PersonDetails.class, _person.getId() ); + } ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private String id; + + @OneToOne(mappedBy = "person", cascade = CascadeType.PERSIST, optional = false) + private PersonDetails details; + + public Person() {} + + public Person(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public void setDetails(PersonDetails details) { + this.details = details; + details.setPerson( this ); + } + } + + @Entity(name = "PersonDetails") + public static class PersonDetails { + + @Id + private String id; + + private String nickName; + + @OneToOne + @MapsId + @JoinColumn(name = "person_id") + private Person person; + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneMapsIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneMapsIdTest.java new file mode 100644 index 000000000000..79ae11cb3a57 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneMapsIdTest.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.annotations.onetoone; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class OneToOneMapsIdTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + PersonDetails.class + }; + } + + @Test + public void testLifecycle() { + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( "ABC-123" ); + entityManager.persist( person ); + + return person; + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = entityManager.find( Person.class, _person.getId() ); + + PersonDetails personDetails = new PersonDetails(); + personDetails.setNickName( "John Doe" ); + personDetails.setPerson( person ); + + entityManager.persist( personDetails ); + } ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String registrationNumber; + + public Person() {} + + public Person(String registrationNumber) { + this.registrationNumber = registrationNumber; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getRegistrationNumber() { + return registrationNumber; + } + } + + @Entity(name = "PersonDetails") + public static class PersonDetails { + + @Id + private Long id; + + private String nickName; + + @OneToOne + @MapsId + private Person person; + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneTest.java index cd6d127e0552..3c4b84e5baf9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OneToOneTest.java @@ -21,12 +21,16 @@ import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import org.hibernate.test.annotations.Customer; import org.hibernate.test.annotations.Discount; import org.hibernate.test.annotations.Passport; import org.hibernate.test.annotations.Ticket; import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -37,257 +41,261 @@ public class OneToOneTest extends BaseNonConfigCoreFunctionalTestCase { @Test public void testEagerFetching() throws Exception { - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - Client c = new Client(); - c.setName( "Emmanuel" ); - Address a = new Address(); - a.setCity( "Courbevoie" ); - c.setAddress( a ); - s.persist( c ); - tx.commit(); - s.close(); - - s = openSession(); - tx = s.beginTransaction(); - Query q = s.createQuery( "select c from Client c where c.name = :name" ); - q.setString( "name", c.getName() ); - c = ( Client ) q.uniqueResult(); - //c = (Client) s.get(Client.class, c.getId()); - assertNotNull( c ); - tx.commit(); - s.close(); - assertNotNull( c.getAddress() ); + final String clientName = "Emmanuel"; + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Client c = new Client(); + c.setName( clientName ); + Address a = new Address(); + a.setCity( "Courbevoie" ); + c.setAddress( a ); + session.persist( c ); + } ); + + final Client client = TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Query q = session.createQuery( "select c from Client c where c.name = :name" ); + q.setString( "name", clientName ); + Client c = (Client) q.uniqueResult(); + //c = (Client) s.get(Client.class, c.getId()); + + assertNotNull( c ); + return c; + } ); + + assertNotNull( client.getAddress() ); //assertTrue( "Should be eager fetched", Hibernate.isInitialized( c.getAddress() ) ); - } @Test public void testDefaultOneToOne() throws Exception { //test a default one to one and a mappedBy in the other side - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - Customer c = new Customer(); - c.setName( "Hibernatus" ); - Passport p = new Passport(); - p.setNumber( "123456789" ); - s.persist( c ); //we need the id to assigned it to passport - c.setPassport( p ); - p.setOwner( c ); - p.setId( c.getId() ); - tx.commit(); - s.close(); - s = openSession(); - tx = s.beginTransaction(); - c = ( Customer ) s.get( Customer.class, c.getId() ); - assertNotNull( c ); - p = c.getPassport(); - assertNotNull( p ); - assertEquals( "123456789", p.getNumber() ); - assertNotNull( p.getOwner() ); - assertEquals( "Hibernatus", p.getOwner().getName() ); - tx.commit(); // commit or rollback is the same, we don't care for read queries - s.close(); + Long customerId = TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Customer c = new Customer(); + c.setName( "Hibernatus" ); + Passport p = new Passport(); + p.setNumber( "123456789" ); + session.persist( c ); //we need the id to assigned it to passport + c.setPassport( p ); + p.setOwner( c ); + p.setId( c.getId() ); + return c.getId(); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Customer c = session.get( Customer.class, customerId ); + assertNotNull( c ); + Passport p = c.getPassport(); + + assertNotNull( p ); + assertEquals( "123456789", p.getNumber() ); + assertNotNull( p.getOwner() ); + assertEquals( "Hibernatus", p.getOwner().getName() ); + } ); } @Test public void testOneToOneWithExplicitFk() throws Exception { - Client c = new Client(); + final Client c = new Client(); Address a = new Address(); a.setCity( "Paris" ); c.setName( "Emmanuel" ); c.setAddress( a ); - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - s.persist( c ); - tx.commit(); - s.close(); - - s = openSession(); - tx = s.beginTransaction(); - c = ( Client ) s.get( Client.class, c.getId() ); - assertNotNull( c ); - assertNotNull( c.getAddress() ); - assertEquals( "Paris", c.getAddress().getCity() ); - tx.commit(); - s.close(); + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + session.persist( c ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Client client = session.get( Client.class, c.getId() ); + + assertNotNull( client ); + assertNotNull( client.getAddress() ); + assertEquals( "Paris", client.getAddress().getCity() ); + } ); } @Test public void testOneToOneWithExplicitSecondaryTableFk() throws Exception { - Client c = new Client(); + final Client c = new Client(); Address a = new Address(); a.setCity( "Paris" ); c.setName( "Emmanuel" ); c.setSecondaryAddress( a ); - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - s.persist( c ); - tx.commit(); - s.close(); - - s = openSession(); - tx = s.beginTransaction(); - c = ( Client ) s.get( Client.class, c.getId() ); - assertNotNull( c ); - assertNotNull( c.getSecondaryAddress() ); - assertEquals( "Paris", c.getSecondaryAddress().getCity() ); - tx.commit(); - s.close(); + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + session.persist( c ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + final Client client = session.get( Client.class, c.getId() ); + + assertNotNull( client ); + assertNotNull( client.getSecondaryAddress() ); + assertEquals( "Paris", client.getSecondaryAddress().getCity() ); + } ); } @Test public void testUnidirectionalTrueOneToOne() throws Exception { - Body b = new Body(); - Heart h = new Heart(); + final Body b = new Body(); + final Heart h = new Heart(); b.setHeart( h ); b.setId( 1 ); h.setId( b.getId() ); //same PK - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - s.persist( h ); - s.persist( b ); - tx.commit(); - s.close(); - - s = openSession(); - tx = s.beginTransaction(); - b = ( Body ) s.get( Body.class, b.getId() ); - assertNotNull( b ); - assertNotNull( b.getHeart() ); - assertEquals( h.getId(), b.getHeart().getId() ); - tx.commit(); - s.close(); + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + session.persist( h ); + session.persist( b ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + final Body body = session.get( Body.class, b.getId() ); + + assertNotNull( body ); + assertNotNull( body.getHeart() ); + assertEquals( h.getId(), body.getHeart().getId() ); + } ); } @Test public void testCompositePk() throws Exception { - Session s; - Transaction tx; - s = openSession(); - tx = s.beginTransaction(); - ComputerPk cid = new ComputerPk(); - cid.setBrand( "IBM" ); - cid.setModel( "ThinkPad" ); - Computer c = new Computer(); - c.setId( cid ); - c.setCpu( "2 GHz" ); - SerialNumberPk sid = new SerialNumberPk(); - sid.setBrand( cid.getBrand() ); - sid.setModel( cid.getModel() ); - SerialNumber sn = new SerialNumber(); - sn.setId( sid ); - sn.setValue( "REZREZ23424" ); - c.setSerial( sn ); - s.persist( c ); - tx.commit(); - s.close(); - - s = openSession(); - tx = s.beginTransaction(); - c = ( Computer ) s.get( Computer.class, cid ); - assertNotNull( c ); - assertNotNull( c.getSerial() ); - assertEquals( sn.getValue(), c.getSerial().getValue() ); - tx.commit(); - s.close(); + final ComputerPk cid = new ComputerPk(); + final SerialNumber sn = new SerialNumber(); + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + cid.setBrand( "IBM" ); + cid.setModel( "ThinkPad" ); + Computer c = new Computer(); + c.setId( cid ); + c.setCpu( "2 GHz" ); + SerialNumberPk sid = new SerialNumberPk(); + sid.setBrand( cid.getBrand() ); + sid.setModel( cid.getModel() ); + sn.setId( sid ); + sn.setValue( "REZREZ23424" ); + c.setSerial( sn ); + session.persist( c ); + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Computer c = session.get( Computer.class, cid ); + assertNotNull( c ); + assertNotNull( c.getSerial() ); + assertEquals( sn.getValue(), c.getSerial().getValue() ); + } ); } @Test public void testBidirectionalTrueOneToOne() throws Exception { - Session s = openSession(); - s.getTransaction().begin(); - Party party = new Party(); - PartyAffiliate affiliate = new PartyAffiliate(); - affiliate.partyId = "id"; - party.partyId = "id"; - party.partyAffiliate = affiliate; - affiliate.party = party; - - s.persist( party ); - s.getTransaction().commit(); - - s.clear(); - - Transaction tx = s.beginTransaction(); - affiliate = ( PartyAffiliate ) s.get( PartyAffiliate.class, "id" ); - assertNotNull( affiliate.party ); - assertEquals( affiliate.partyId, affiliate.party.partyId ); - - s.clear(); - - party = ( Party ) s.get( Party.class, "id" ); - assertNotNull( party.partyAffiliate ); - assertEquals( party.partyId, party.partyAffiliate.partyId ); - - s.delete( party ); - s.delete( party.partyAffiliate ); - tx.commit(); - s.close(); + try (Session s = openSession()) { + Party party = new Party(); + PartyAffiliate affiliate = new PartyAffiliate(); + affiliate.partyId = "id"; + party.partyId = "id"; + party.partyAffiliate = affiliate; + affiliate.party = party; + + s.getTransaction().begin(); + try { + + s.persist( party ); + s.getTransaction().commit(); + } + catch (Exception e) { + if ( s.getTransaction() != null && s.getTransaction().isActive() ) { + s.getTransaction().rollback(); + } + throw e; + } + + s.clear(); + + Transaction tx = s.beginTransaction(); + try { + affiliate = s.get( PartyAffiliate.class, "id" ); + assertNotNull( affiliate.party ); + assertEquals( affiliate.partyId, affiliate.party.partyId ); + + s.clear(); + + party = s.get( Party.class, "id" ); + assertNotNull( party.partyAffiliate ); + assertEquals( party.partyId, party.partyAffiliate.partyId ); + + s.delete( party ); + s.delete( party.partyAffiliate ); + tx.commit(); + } + catch (Exception e) { + if ( s.getTransaction() != null && s.getTransaction().isActive() ) { + s.getTransaction().rollback(); + } + throw e; + } + } } @Test public void testBidirectionalFkOneToOne() throws Exception { - Session s = openSession(); - s.getTransaction().begin(); - Trousers trousers = new Trousers(); - TrousersZip zip = new TrousersZip(); - trousers.id = 1; - zip.id = 2; - trousers.zip = zip; - zip.trousers = trousers; - s.persist( trousers ); - s.persist( zip ); - s.getTransaction().commit(); - - s.clear(); - - Transaction tx = s.beginTransaction(); - trousers = ( Trousers ) s.get( Trousers.class, trousers.id ); - assertNotNull( trousers.zip ); - assertEquals( zip.id, trousers.zip.id ); - - s.clear(); - - zip = ( TrousersZip ) s.get( TrousersZip.class, zip.id ); - assertNotNull( zip.trousers ); - assertEquals( trousers.id, zip.trousers.id ); - - s.delete( zip ); - s.delete( zip.trousers ); - tx.commit(); - s.close(); + try (Session s = openSession()) { + s.getTransaction().begin(); + Trousers trousers = new Trousers(); + TrousersZip zip = new TrousersZip(); + try { + trousers.id = 1; + zip.id = 2; + trousers.zip = zip; + zip.trousers = trousers; + s.persist( trousers ); + s.persist( zip ); + s.getTransaction().commit(); + } + catch (Exception e) { + if ( s.getTransaction() != null && s.getTransaction().isActive() ) { + s.getTransaction().rollback(); + } + throw e; + } + + s.clear(); + + Transaction tx = s.beginTransaction(); + try { + trousers = s.get( Trousers.class, trousers.id ); + assertNotNull( trousers.zip ); + assertEquals( zip.id, trousers.zip.id ); + + s.clear(); + + zip = s.get( TrousersZip.class, zip.id ); + assertNotNull( zip.trousers ); + assertEquals( trousers.id, zip.trousers.id ); + + s.delete( zip ); + s.delete( zip.trousers ); + tx.commit(); + } + catch (Exception e) { + if ( s.getTransaction() != null && s.getTransaction().isActive() ) { + s.getTransaction().rollback(); + } + throw e; + } + } } @Test public void testForeignGenerator() { - Session s = openSession(); - Transaction tx = s.beginTransaction(); - Owner owner = new Owner(); - OwnerAddress address = new OwnerAddress(); - owner.setAddress( address ); - address.setOwner( owner ); - s.persist( owner ); - s.flush(); - s.clear(); - owner = ( Owner ) s.get( Owner.class, owner.getId() ); - assertNotNull( owner ); - assertNotNull( owner.getAddress() ); - assertEquals( owner.getId(), owner.getAddress().getId() ); - tx.rollback(); - s.close(); + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Owner owner = new Owner(); + OwnerAddress address = new OwnerAddress(); + owner.setAddress( address ); + address.setOwner( owner ); + session.persist( owner ); + session.flush(); + session.clear(); + owner = session.get( Owner.class, owner.getId() ); + assertNotNull( owner ); + assertNotNull( owner.getAddress() ); + assertEquals( owner.getId(), owner.getAddress().getId() ); + } ); } @Test @@ -314,58 +322,88 @@ public void testJoinColumnConfiguredInXml() { } @Test - @TestForIssue( jiraKey = "HHH-6723" ) + @TestForIssue(jiraKey = "HHH-6723") public void testPkOneToOneSelectStatementDoesNotGenerateExtraJoin() { // This test uses an interceptor to verify that correct number of joins are generated. - Session s = openSession(new JoinCounter(1)); - Transaction tx = s.beginTransaction(); - Owner owner = new Owner(); - OwnerAddress address = new OwnerAddress(); - owner.setAddress( address ); - address.setOwner( owner ); - s.persist( owner ); - s.flush(); - s.clear(); - - owner = ( Owner ) s.get( Owner.class, owner.getId() ); - assertNotNull( owner ); - assertNotNull( owner.getAddress() ); - assertEquals( owner.getId(), owner.getAddress().getId() ); - s.flush(); - s.clear(); - - address = ( OwnerAddress ) s.get( OwnerAddress.class, address.getId() ); - assertNotNull( address ); - assertNotNull( address.getOwner() ); - assertEquals( address.getId(), address.getOwner().getId() ); - - s.flush(); - s.clear(); - - owner = ( Owner ) s.createCriteria( Owner.class ) - .add( Restrictions.idEq( owner.getId() ) ) - .uniqueResult(); - - assertNotNull( owner ); - assertNotNull( owner.getAddress() ); - assertEquals( owner.getId(), owner.getAddress().getId() ); - s.flush(); - s.clear(); - - address = ( OwnerAddress ) s.createCriteria( OwnerAddress.class ) - .add( Restrictions.idEq( address.getId() ) ) - .uniqueResult(); - - address = ( OwnerAddress ) s.get( OwnerAddress.class, address.getId() ); - assertNotNull( address ); - assertNotNull( address.getOwner() ); - assertEquals( address.getId(), address.getOwner().getId() ); - - s.flush(); - s.clear(); - - tx.rollback(); - s.close(); + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + + Owner owner = new Owner(); + OwnerAddress address = new OwnerAddress(); + owner.setAddress( address ); + address.setOwner( owner ); + s.persist( owner ); + s.flush(); + s.clear(); + + owner = s.get( Owner.class, owner.getId() ); + assertNotNull( owner ); + assertNotNull( owner.getAddress() ); + assertEquals( owner.getId(), owner.getAddress().getId() ); + s.flush(); + s.clear(); + + address = s.get( OwnerAddress.class, address.getId() ); + assertNotNull( address ); + assertNotNull( address.getOwner() ); + assertEquals( address.getId(), address.getOwner().getId() ); + + s.flush(); + s.clear(); + + owner = (Owner) s.createCriteria( Owner.class ) + .add( Restrictions.idEq( owner.getId() ) ) + .uniqueResult(); + + assertNotNull( owner ); + assertNotNull( owner.getAddress() ); + assertEquals( owner.getId(), owner.getAddress().getId() ); + s.flush(); + s.clear(); + + address = (OwnerAddress) s.createCriteria( OwnerAddress.class ) + .add( Restrictions.idEq( address.getId() ) ) + .uniqueResult(); + + address = s.get( OwnerAddress.class, address.getId() ); + assertNotNull( address ); + assertNotNull( address.getOwner() ); + assertEquals( address.getId(), address.getOwner().getId() ); + + s.flush(); + s.clear(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-5757") + public void testHqlQuery() throws Exception { + //test a default one to one and a mappedBy in the other side + final Passport passport = TransactionUtil.doInHibernate( this::sessionFactory, session -> { + Customer c = new Customer(); + c.setName( "Hibernatus" ); + Passport p = new Passport(); + p.setNumber( "123456789" ); + session.persist( c ); //we need the id to assigned it to passport + c.setPassport( p ); + p.setOwner( c ); + p.setId( c.getId() ); + return p; + } ); + + final Customer customer = TransactionUtil.doInHibernate( this::sessionFactory, session -> { + final Customer c = (Customer) session.createQuery( "from Customer c where c.passport = :passport " ) + .setParameter( "passport", passport ).getSingleResult(); + + assertThat( c, is( notNullValue() ) ); + return c; + } ); + + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + final Passport p = (Passport) session.createQuery( "from Passport p where p.owner = :owner " ) + .setParameter( "owner", customer ).getSingleResult(); + + assertThat( p, is( notNullValue() ) ); + } ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMappedByTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMappedByTest.java index f74477deb8ee..db6c732357b0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMappedByTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMappedByTest.java @@ -6,17 +6,18 @@ */ package org.hibernate.test.annotations.onetoone; +import java.util.concurrent.atomic.AtomicReference; import javax.persistence.PersistenceException; -import org.junit.Test; - -import org.hibernate.Session; -import org.hibernate.Transaction; import org.hibernate.criterion.Restrictions; import org.hibernate.id.IdentifierGenerationException; + +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +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; @@ -31,84 +32,133 @@ public class OptionalOneToOneMappedByTest extends BaseCoreFunctionalTestCase { // @OneToOne(mappedBy="address") with foreign generator @Test public void testBidirForeignIdGenerator() { - Session s = openSession(); - Transaction tx = s.beginTransaction(); - OwnerAddress address = new OwnerAddress(); - address.setOwner( null ); try { - s.persist( address ); - s.flush(); - fail( "should have failed with IdentifierGenerationException" ); + doInHibernate( this::sessionFactory, session -> { + OwnerAddress address = new OwnerAddress(); + address.setOwner( null ); + + session.persist( address ); + session.flush(); + fail( "should have failed with IdentifierGenerationException" ); + } ); } catch (PersistenceException ex) { - assertTyping(IdentifierGenerationException.class, ex.getCause()); + assertTyping( IdentifierGenerationException.class, ex.getCause() ); // expected } - finally { - tx.rollback(); - } - s.close(); } @Test public void testBidirAssignedId() throws Exception { - Session s = openSession(); - s.getTransaction().begin(); - PartyAffiliate affiliate = new PartyAffiliate(); - affiliate.partyId = "id"; - - s.persist( affiliate ); - s.getTransaction().commit(); - - s.clear(); - - Transaction tx = s.beginTransaction(); - - affiliate = ( PartyAffiliate ) s.createCriteria(PartyAffiliate.class) - .add( Restrictions.idEq( "id" ) ) - .uniqueResult(); - assertNotNull( affiliate ); - assertEquals( "id", affiliate.partyId ); - assertNull( affiliate.party ); - - s.clear(); - - affiliate = ( PartyAffiliate ) s.get( PartyAffiliate.class, "id" ); - assertNull( affiliate.party ); - - s.delete( affiliate ); - tx.commit(); - s.close(); + doInHibernate( this::sessionFactory, session -> { + PartyAffiliate affiliate = new PartyAffiliate(); + affiliate.partyId = "id"; + + session.persist( affiliate ); + } ); + + doInHibernate( this::sessionFactory, session -> { + PartyAffiliate affiliate = (PartyAffiliate) session.createCriteria( + PartyAffiliate.class ) + .add( Restrictions.idEq( "id" ) ) + .uniqueResult(); + assertNotNull( affiliate ); + assertEquals( "id", affiliate.partyId ); + assertNull( affiliate.party ); + } ); + + doInHibernate( this::sessionFactory, session -> { + PartyAffiliate affiliate = session.get( + PartyAffiliate.class, + "id" + ); + assertNull( affiliate.party ); + + session.delete( affiliate ); + } ); } @Test public void testBidirDefaultIdGenerator() throws Exception { - Session s = openSession(); - s.getTransaction().begin(); - PersonAddress personAddress = new PersonAddress(); - personAddress.setPerson( null ); - - s.persist( personAddress ); - s.getTransaction().commit(); - - s.clear(); - - Transaction tx = s.beginTransaction(); - - personAddress = ( PersonAddress ) s.createCriteria(PersonAddress.class) - .add( Restrictions.idEq( personAddress.getId() ) ) - .uniqueResult(); - assertNotNull( personAddress ); - assertNull( personAddress.getPerson() ); - - s.clear(); - - personAddress = ( PersonAddress ) s.get( PersonAddress.class, personAddress.getId() ); - assertNull( personAddress.getPerson() ); + PersonAddress _personAddress = doInHibernate( + this::sessionFactory, + session -> { + PersonAddress personAddress = new PersonAddress(); + personAddress.setPerson( null ); + + session.persist( personAddress ); + + return personAddress; + } + ); + + doInHibernate( this::sessionFactory, session -> { + PersonAddress personAddress = (PersonAddress) session.createCriteria( + PersonAddress.class ) + .add( Restrictions.idEq( _personAddress.getId() ) ) + .uniqueResult(); + assertNotNull( personAddress ); + assertNull( personAddress.getPerson() ); + } ); + + doInHibernate( this::sessionFactory, session -> { + PersonAddress personAddress = session.get( + PersonAddress.class, + _personAddress.getId() + ); + assertNull( personAddress.getPerson() ); + + session.delete( personAddress ); + } ); + } - s.delete( personAddress ); - tx.commit(); - s.close(); + @Test + @TestForIssue(jiraKey = "HHH-5757") + public void testBidirQueryEntityProperty() throws Exception { + + AtomicReference personHolder = new AtomicReference<>(); + + PersonAddress _personAddress = doInHibernate( + this::sessionFactory, + session -> { + PersonAddress personAddress = new PersonAddress(); + Person person = new Person(); + personAddress.setPerson( person ); + person.setPersonAddress( personAddress ); + + session.persist( person ); + session.persist( personAddress ); + + personHolder.set( person ); + + return personAddress; + } + ); + + doInHibernate( this::sessionFactory, session -> { + PersonAddress personAddress = (PersonAddress) session.createCriteria( + PersonAddress.class ) + .add( Restrictions.idEq( _personAddress.getId() ) ) + .uniqueResult(); + assertNotNull( personAddress ); + assertNotNull( personAddress.getPerson() ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person person = personHolder.get(); + // this call throws GenericJDBCException + PersonAddress personAddress = (PersonAddress) session.createQuery( + "select pa from PersonAddress pa where pa.person = :person", PersonAddress.class ) + .setParameter( "person", person ) + .getSingleResult(); + + // the other way should also work + person = (Person) session.createCriteria( Person.class ) + .add( Restrictions.eq( "personAddress", personAddress ) ) + .uniqueResult(); + + session.delete( personAddress ); + } ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCTest.java index 05ac0f992173..c895f37f791f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCTest.java @@ -60,7 +60,7 @@ public void testNotFoundBidirForeignIdGenerator() { person.setPersonAddress( null ); person.setId( 1 ); try { - // Hibernate resets the ID to null beforeQuery executing the foreign generator + // Hibernate resets the ID to null before executing the foreign generator s.persist( person ); s.flush(); fail( "should have thrown IdentifierGenerationException."); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OwnerAddress.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OwnerAddress.java index f401fac3a617..0af8420a9e1e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OwnerAddress.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OwnerAddress.java @@ -20,8 +20,8 @@ */ @Entity public class OwnerAddress { - @Id @GeneratedValue(generator = "fk") - @GenericGenerator(strategy = "foreign", name = "fk", parameters = @Parameter(name="property", value="owner")) + @Id @GeneratedValue(generator = "fk_1") + @GenericGenerator(strategy = "foreign", name = "fk_1", parameters = @Parameter(name="property", value="owner")) private Integer id; @OneToOne(mappedBy="address") diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/primarykey/NullablePrimaryKeyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/primarykey/NullablePrimaryKeyTest.java index 0123e58b35ae..f09d3584d8dc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/primarykey/NullablePrimaryKeyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/primarykey/NullablePrimaryKeyTest.java @@ -21,6 +21,7 @@ import org.hibernate.tool.schema.internal.SchemaCreatorImpl; import org.hibernate.testing.ServiceRegistryBuilder; +import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Assert; import org.junit.Test; @@ -32,8 +33,7 @@ * @author Hardy Ferentschik * */ -public class NullablePrimaryKeyTest { - private static final Logger log = Logger.getLogger( NullablePrimaryKeyTest.class ); +public class NullablePrimaryKeyTest extends BaseUnitTestCase { @Test @SuppressWarnings("unchecked") diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java new file mode 100644 index 000000000000..7ed667757d8f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.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.annotations.override.inheritance; + +import static org.junit.Assert.assertTrue; + +import javax.persistence.AttributeOverride; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.cfg.AnnotationBinder; +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.jboss.logging.Logger; +import org.junit.Rule; +import org.junit.Test; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12609, HHH-12654, HHH-13172" ) +public class EntityInheritanceAttributeOverrideTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) ); + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ + CategoryEntity.class, + TaxonEntity.class, + AbstractEntity.class + }; + } + + @Override + public void buildEntityManagerFactory() { + Triggerable warningLogged = logInspection.watchForLogMessages( "HHH000499:" ); + + super.buildEntityManagerFactory(); + + assertTrue("A warning should have been logged for this unsupported configuration", warningLogged.wasTriggered()); + } + + @Test + public void test() { + } + + @Entity(name = "AbstractEntity") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static class AbstractEntity { + + @Id + private Long id; + + @Column(name = "code", nullable = false, unique = true) + private String code; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + } + + @Entity(name = "Category") + public static class CategoryEntity extends AbstractEntity { + + } + + @Entity(name = "Taxon") + @Table( + name = "taxon", + uniqueConstraints = @UniqueConstraint(name = "category_code", columnNames = { "catalog_version_id", "code" }) + ) + @AttributeOverride(name = "code", column = @Column(name = "code", nullable = false, unique = false)) + public static class TaxonEntity extends CategoryEntity { + + @Column(name = "catalog_version_id") + private String catalogVersion; + + public String getCatalogVersion() { + return catalogVersion; + } + + public void setCatalogVersion(String catalogVersion) { + this.catalogVersion = catalogVersion; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/MappedSuperclassAttributeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/MappedSuperclassAttributeOverrideTest.java new file mode 100644 index 000000000000..7e41c8b60d70 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/MappedSuperclassAttributeOverrideTest.java @@ -0,0 +1,118 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.override.inheritance; + +import javax.persistence.AttributeOverride; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.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 Vlad Mihalcea + */ +public class MappedSuperclassAttributeOverrideTest extends BaseEntityManagerFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ + CategoryEntity.class, + TaxonEntity.class + }; + } + + @Test + @TestForIssue( jiraKey = "HHH-12609" ) + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + TaxonEntity taxon1 = new TaxonEntity(); + taxon1.setId( 1L ); + taxon1.setCode( "Taxon" ); + taxon1.setCatalogVersion( "C1" ); + + entityManager.persist( taxon1 ); + + TaxonEntity taxon2 = new TaxonEntity(); + taxon2.setId( 2L ); + taxon2.setCode( "Taxon" ); + taxon2.setCatalogVersion( "C2" ); + + entityManager.persist( taxon2 ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + assertEquals( + 2, + ((Number) entityManager.createQuery( + "select count(t) " + + "from Taxon t " + + "where t.code = :code" ) + .setParameter( "code", "Taxon" ) + .getSingleResult()).intValue() + ); + } ); + } + + @MappedSuperclass + public static class AbstractEntity { + + @Id + private Long id; + + @Column(name = "code", nullable = false, unique = true) + private String code; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + } + + @Entity(name = "Category") + public static class CategoryEntity extends AbstractEntity { + + } + + @Entity(name = "Taxon") + @Table(name = "taxon", uniqueConstraints = @UniqueConstraint(columnNames = { "catalog_version_id", "code" })) + @AttributeOverride(name = "code", column = @Column(name = "code", nullable = false, unique = false)) + public static class TaxonEntity extends AbstractEntity { + + @Column(name = "catalog_version_id") + private String catalogVersion; + + public String getCatalogVersion() { + return catalogVersion; + } + + public void setCatalogVersion(String catalogVersion) { + this.catalogVersion = catalogVersion; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/CollectionPersister.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/CollectionPersister.java index a80c11ecdf0e..38824b8d5ba2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/CollectionPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/CollectionPersister.java @@ -8,7 +8,7 @@ import org.hibernate.MappingException; import org.hibernate.cache.CacheException; -import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.mapping.Collection; import org.hibernate.persister.collection.OneToManyPersister; import org.hibernate.persister.spi.PersisterCreationContext; @@ -19,7 +19,7 @@ public class CollectionPersister extends OneToManyPersister { public CollectionPersister( Collection collectionBinding, - CollectionRegionAccessStrategy cacheAccessStrategy, + CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) throws MappingException, CacheException { super( collectionBinding, cacheAccessStrategy, creationContext ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/EntityPersister.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/EntityPersister.java index 3a6a0a205edb..dc55bbf41a91 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/EntityPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/persister/EntityPersister.java @@ -7,8 +7,8 @@ package org.hibernate.test.annotations.persister; import org.hibernate.HibernateException; -import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; -import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.mapping.PersistentClass; import org.hibernate.persister.entity.SingleTableEntityPersister; import org.hibernate.persister.spi.PersisterCreationContext; @@ -19,8 +19,8 @@ public class EntityPersister extends SingleTableEntityPersister { public EntityPersister( PersistentClass persistentClass, - EntityRegionAccessStrategy cache, - NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy, + EntityDataAccess cache, + NaturalIdDataAccess naturalIdRegionAccessStrategy, PersisterCreationContext creationContext) throws HibernateException { super( persistentClass, cache, naturalIdRegionAccessStrategy, creationContext ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/NamedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/NamedQueryTest.java index c4fbdaced245..2c3337a79954 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/NamedQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/NamedQueryTest.java @@ -57,10 +57,10 @@ public void tearDown() { } @Test - public void testNamedQueriesOrdinalParametersAreZeroBased() { + public void testNamedQueriesOrdinalParametersAreOneBased() { doInHibernate( this::sessionFactory, session -> { Query query = session.getNamedQuery( "NamedQuery" ); - query.setParameter( 0, GAME_TITLES[0] ); + query.setParameter( 1, GAME_TITLES[0] ); List list = query.getResultList(); assertEquals( 1, list.size() ); } @@ -68,10 +68,10 @@ public void testNamedQueriesOrdinalParametersAreZeroBased() { } @Test - public void testNativeNamedQueriesOrdinalParametersAreZeroBased() { + public void testNativeNamedQueriesOrdinalParametersAreOneBased() { doInHibernate( this::sessionFactory, session -> { Query query = session.getNamedNativeQuery( "NamedNativeQuery" ); - query.setParameter( 0, GAME_TITLES[0] ); + query.setParameter( 1, GAME_TITLES[0] ); List list = query.getResultList(); assertEquals( 1, list.size() ); } @@ -79,7 +79,7 @@ public void testNativeNamedQueriesOrdinalParametersAreZeroBased() { } @Entity(name = "Game") - @NamedQueries(@NamedQuery(name = "NamedQuery", query = "select g from Game g where title = ?")) + @NamedQueries(@NamedQuery(name = "NamedQuery", query = "select g from Game g where title = ?1")) @NamedNativeQueries(@NamedNativeQuery(name = "NamedNativeQuery", query = "select * from Game g where title = ?")) public static class Game { private Long id; 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 91ee827904e0..97d48e9c660c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java @@ -31,8 +31,6 @@ import org.hibernate.dialect.function.SQLFunction; import org.hibernate.stat.Statistics; import org.hibernate.type.StandardBasicTypes; - -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -95,9 +93,17 @@ public void testNativeQueryWithFormulaAttribute() { } @Test - @FailureExpected(jiraKey = "HHH-2225") public void testNativeQueryWithFormulaAttributeWithoutAlias() { - String sql = "select TABLE_NAME , sysdate() from all_tables where TABLE_NAME = 'AUDIT_ACTIONS' "; + SQLFunction dateFunction = getDialect().getFunctions().get( "current_date" ); + String dateFunctionRendered = dateFunction.render( + null, + java.util.Collections.EMPTY_LIST, + sessionFactory() + ); + String sql = String.format( + "select TABLE_NAME , %s from ALL_TABLES where TABLE_NAME = 'AUDIT_ACTIONS' ", + dateFunctionRendered + ); Session s = openSession(); s.beginTransaction(); s.createSQLQuery( sql ).addEntity( "t", AllTables.class ).list(); @@ -351,7 +357,7 @@ public void testSQLQuery() { s.clear(); tx = s.beginTransaction(); Query q = s.getNamedQuery( "night.getAll.bySQL" ); - q.setParameter( 0, 9990 ); + q.setParameter( 1, 9990 ); List result = q.list(); assertEquals( 1, result.size() ); Night n2 = (Night) result.get( 0 ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/Company.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/Company.java new file mode 100644 index 000000000000..7a6c27f88a84 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/Company.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 . + */ +package org.hibernate.test.annotations.reflection; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Vlad Mihalcea + */ +@Entity +public class Company { + + @Id + private Long id; + + private List organizations = new ArrayList<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getOrganizations() { + return organizations; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/ElementCollectionConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/ElementCollectionConverterTest.java new file mode 100644 index 000000000000..2eeb459eaeab --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/ElementCollectionConverterTest.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.annotations.reflection; + +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; + +@TestForIssue( jiraKey = "HHH-11924") +public class ElementCollectionConverterTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Company.class, + }; + } + + @Override + protected String[] getXmlFiles() { + return new String[] { "org/hibernate/test/annotations/reflection/element-collection-converter-orm.xml" }; + } + + + @Test + public void testConverterIsAppliedToElementCollection() { + doInHibernate( this::sessionFactory, session -> { + Company company = new Company(); + company.setId( 1L ); + + Organization org1 = new Organization(); + org1.setOrganizationId( "ACME" ); + + company.getOrganizations().add( org1 ); + + session.persist( company ); + } ); + + doInHibernate( this::sessionFactory, session -> { + String organizationId = (String) session + .createNativeQuery( "select organizations from Company_organizations" ) + .getSingleResult(); + assertEquals( "ORG-ACME", organizationId ); + + Company company = session.find( Company.class, 1L ); + + assertEquals( 1, company.getOrganizations().size() ); + assertEquals( "ACME" , company.getOrganizations().get( 0 ).getOrganizationId()); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java index dac067954821..c79a60e708d2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java @@ -6,59 +6,8 @@ */ package org.hibernate.test.annotations.reflection; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import javax.persistence.AssociationOverrides; -import javax.persistence.AttributeOverrides; -import javax.persistence.Basic; -import javax.persistence.Column; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Embedded; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.EntityListeners; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.ExcludeDefaultListeners; -import javax.persistence.ExcludeSuperclassListeners; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.JoinColumns; -import javax.persistence.JoinTable; -import javax.persistence.Lob; -import javax.persistence.ManyToMany; -import javax.persistence.MapKey; -import javax.persistence.MappedSuperclass; -import javax.persistence.NamedNativeQueries; -import javax.persistence.NamedQueries; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; -import javax.persistence.OrderBy; -import javax.persistence.PostLoad; -import javax.persistence.PostPersist; -import javax.persistence.PrePersist; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.PrimaryKeyJoinColumns; -import javax.persistence.SecondaryTable; -import javax.persistence.SecondaryTables; -import javax.persistence.SequenceGenerator; -import javax.persistence.SqlResultSetMappings; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; -import javax.persistence.Version; - +import org.dom4j.DocumentException; +import org.dom4j.io.SAXReader; import org.hibernate.annotations.Columns; import org.hibernate.cfg.EJB3DTDEntityResolver; import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; @@ -66,22 +15,23 @@ import org.hibernate.internal.util.xml.ErrorLogger; import org.hibernate.internal.util.xml.XMLHelper; +import org.hibernate.testing.boot.BootstrapContextImpl; import org.hibernate.testing.junit4.BaseUnitTestCase; -import org.hibernate.testing.boot.ClassLoaderAccessTestingImpl; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; import org.junit.Test; - -import org.dom4j.DocumentException; -import org.dom4j.io.SAXReader; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXNotSupportedException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import javax.persistence.*; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.junit.Assert.*; /** * @author Emmanuel Bernard @@ -92,14 +42,14 @@ public void testMappedSuperclassAnnotations() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/metadata-complete.xml" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( Organization.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( Organization.class, context, BootstrapContextImpl.INSTANCE ); assertTrue( reader.isAnnotationPresent( MappedSuperclass.class ) ); } @Test public void testEntityRelatedAnnotations() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( Administration.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( Administration.class, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Entity.class ) ); assertEquals( "Default value in xml entity should not override @Entity.name", "JavaAdministration", @@ -133,7 +83,7 @@ public void testEntityRelatedAnnotations() throws Exception { assertEquals( "wrong tble name", "tablehilo", reader.getAnnotation( TableGenerator.class ).table() ); assertEquals( "no schema overriding", "myschema", reader.getAnnotation( TableGenerator.class ).schema() ); - reader = new JPAOverriddenAnnotationReader( Match.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( Match.class, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Table.class ) ); assertEquals( "Java annotation not taken into account", "matchtable", reader.getAnnotation( Table.class ).name() @@ -181,10 +131,10 @@ public void testEntityRelatedAnnotations() throws Exception { assertNotNull( reader.getAnnotation( ExcludeSuperclassListeners.class ) ); assertNotNull( reader.getAnnotation( ExcludeDefaultListeners.class ) ); - reader = new JPAOverriddenAnnotationReader( Competition.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( Competition.class, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( MappedSuperclass.class ) ); - reader = new JPAOverriddenAnnotationReader( TennisMatch.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( TennisMatch.class, context, BootstrapContextImpl.INSTANCE ); assertNull( "Mutualize PKJC into PKJCs", reader.getAnnotation( PrimaryKeyJoinColumn.class ) ); assertNotNull( reader.getAnnotation( PrimaryKeyJoinColumns.class ) ); assertEquals( @@ -211,7 +161,7 @@ public void testEntityRelatedAnnotations() throws Exception { ); - reader = new JPAOverriddenAnnotationReader( SocialSecurityPhysicalAccount.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( SocialSecurityPhysicalAccount.class, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( IdClass.class ) ); assertEquals( "id-class not used", SocialSecurityNumber.class, reader.getAnnotation( IdClass.class ).value() ); assertEquals( @@ -232,7 +182,7 @@ public void testEntityRelatedAnnotationsMetadataComplete() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/metadata-complete.xml" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( Administration.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( Administration.class, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Entity.class ) ); assertEquals( "Metadata complete should ignore java annotations", "", reader.getAnnotation( Entity.class ).name() @@ -241,7 +191,7 @@ public void testEntityRelatedAnnotationsMetadataComplete() throws Exception { assertEquals( "@Table should not be used", "", reader.getAnnotation( Table.class ).name() ); assertEquals( "Default schema not overriden", "myschema", reader.getAnnotation( Table.class ).schema() ); - reader = new JPAOverriddenAnnotationReader( Match.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( Match.class, context, BootstrapContextImpl.INSTANCE ); 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() ); @@ -252,14 +202,14 @@ public void testEntityRelatedAnnotationsMetadataComplete() throws Exception { assertNull( reader.getAnnotation( NamedQueries.class ) ); assertNull( reader.getAnnotation( NamedNativeQueries.class ) ); - reader = new JPAOverriddenAnnotationReader( TennisMatch.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( TennisMatch.class, context, BootstrapContextImpl.INSTANCE ); assertNull( reader.getAnnotation( PrimaryKeyJoinColumn.class ) ); assertNull( reader.getAnnotation( PrimaryKeyJoinColumns.class ) ); - reader = new JPAOverriddenAnnotationReader( Competition.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( Competition.class, context, BootstrapContextImpl.INSTANCE ); assertNull( reader.getAnnotation( MappedSuperclass.class ) ); - reader = new JPAOverriddenAnnotationReader( SocialSecurityMoralAccount.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( SocialSecurityMoralAccount.class, context, BootstrapContextImpl.INSTANCE ); assertNull( reader.getAnnotation( IdClass.class ) ); assertNull( reader.getAnnotation( DiscriminatorValue.class ) ); assertNull( reader.getAnnotation( DiscriminatorColumn.class ) ); @@ -271,11 +221,11 @@ public void testEntityRelatedAnnotationsMetadataComplete() throws Exception { public void testIdRelatedAnnotations() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); Method method = Administration.class.getDeclaredMethod( "getId" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( method, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); assertNull( reader.getAnnotation( Id.class ) ); assertNull( reader.getAnnotation( Column.class ) ); Field field = Administration.class.getDeclaredField( "id" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Id.class ) ); assertNotNull( reader.getAnnotation( GeneratedValue.class ) ); assertEquals( GenerationType.SEQUENCE, reader.getAnnotation( GeneratedValue.class ).strategy() ); @@ -292,23 +242,23 @@ public void testIdRelatedAnnotations() throws Exception { "org/hibernate/test/annotations/reflection/metadata-complete.xml" ); method = Administration.class.getDeclaredMethod( "getId" ); - reader = new JPAOverriddenAnnotationReader( method, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); assertNotNull( "Default access type when not defined in metadata complete should be property", reader.getAnnotation( Id.class ) ); field = Administration.class.getDeclaredField( "id" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNull( "Default access type when not defined in metadata complete should be property", reader.getAnnotation( Id.class ) ); method = BusTrip.class.getDeclaredMethod( "getId" ); - reader = new JPAOverriddenAnnotationReader( method, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); assertNull( reader.getAnnotation( EmbeddedId.class ) ); field = BusTrip.class.getDeclaredField( "id" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( EmbeddedId.class ) ); assertNotNull( reader.getAnnotation( AttributeOverrides.class ) ); assertEquals( 1, reader.getAnnotation( AttributeOverrides.class ).value().length ); @@ -320,22 +270,22 @@ public void testBasicRelatedAnnotations() throws Exception { "org/hibernate/test/annotations/reflection/metadata-complete.xml" ); Field field = BusTrip.class.getDeclaredField( "status" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Enumerated.class ) ); assertEquals( EnumType.STRING, reader.getAnnotation( Enumerated.class ).value() ); assertEquals( false, reader.getAnnotation( Basic.class ).optional() ); field = BusTrip.class.getDeclaredField( "serial" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Lob.class ) ); assertEquals( "serialbytes", reader.getAnnotation( Columns.class ).columns()[0].name() ); field = BusTrip.class.getDeclaredField( "terminusTime" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Temporal.class ) ); assertEquals( TemporalType.TIMESTAMP, reader.getAnnotation( Temporal.class ).value() ); assertEquals( FetchType.LAZY, reader.getAnnotation( Basic.class ).fetch() ); field = BusTripPk.class.getDeclaredField( "busDriver" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.isAnnotationPresent( Basic.class ) ); } @@ -343,11 +293,10 @@ public void testBasicRelatedAnnotations() throws Exception { public void testVersionRelatedAnnotations() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); Method method = Administration.class.getDeclaredMethod( "getVersion" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( method, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Version.class ) ); Field field = Match.class.getDeclaredField( "version" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Version.class ) ); } @@ -356,12 +305,12 @@ public void testTransientAndEmbeddedRelatedAnnotations() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); Field field = Administration.class.getDeclaredField( "transientField" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Transient.class ) ); assertNull( reader.getAnnotation( Basic.class ) ); field = Match.class.getDeclaredField( "playerASSN" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( Embedded.class ) ); } @@ -370,7 +319,7 @@ public void testAssociationRelatedAnnotations() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); Field field = Administration.class.getDeclaredField( "defaultBusTrip" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( OneToOne.class ) ); assertNull( reader.getAnnotation( JoinColumns.class ) ); assertNotNull( reader.getAnnotation( PrimaryKeyJoinColumns.class ) ); @@ -383,7 +332,7 @@ public void testAssociationRelatedAnnotations() throws Exception { "org/hibernate/test/annotations/reflection/metadata-complete.xml" ); field = BusTrip.class.getDeclaredField( "players" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( OneToMany.class ) ); assertNotNull( reader.getAnnotation( JoinColumns.class ) ); assertEquals( 2, reader.getAnnotation( JoinColumns.class ).value().length ); @@ -392,7 +341,7 @@ public void testAssociationRelatedAnnotations() throws Exception { assertEquals( "name", reader.getAnnotation( MapKey.class ).name() ); field = BusTrip.class.getDeclaredField( "roads" ); - reader = new JPAOverriddenAnnotationReader( field, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); assertNotNull( reader.getAnnotation( ManyToMany.class ) ); assertNotNull( reader.getAnnotation( JoinTable.class ) ); assertEquals( "bus_road", reader.getAnnotation( JoinTable.class ).name() ); @@ -403,25 +352,39 @@ public void testAssociationRelatedAnnotations() throws Exception { assertEquals( "maxSpeed", reader.getAnnotation( OrderBy.class ).value() ); } + @Test + @TestForIssue(jiraKey = "HHH-11924") + public void testElementCollectionConverter() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + + Field field = Company.class.getDeclaredField( "organizations" ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( ElementCollection.class ) ); + assertNotNull( reader.getAnnotation( Converts.class ) ); + assertNotNull( reader.getAnnotation( Converts.class ).value() ); + assertTrue( reader.getAnnotation( Converts.class ).value().length == 1 ); + assertEquals(OrganizationConverter.class, reader.getAnnotation( Converts.class ).value()[0].converter()); + } + @Test public void testEntityListeners() throws Exception { XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); Method method = Administration.class.getDeclaredMethod( "calculate" ); - JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( method, context, ClassLoaderAccessTestingImpl.INSTANCE ); + JPAOverriddenAnnotationReader reader = new JPAOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); assertTrue( reader.isAnnotationPresent( PrePersist.class ) ); - reader = new JPAOverriddenAnnotationReader( Administration.class, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( Administration.class, context, BootstrapContextImpl.INSTANCE ); assertTrue( reader.isAnnotationPresent( EntityListeners.class ) ); assertEquals( 1, reader.getAnnotation( EntityListeners.class ).value().length ); assertEquals( LogListener.class, reader.getAnnotation( EntityListeners.class ).value()[0] ); method = LogListener.class.getDeclaredMethod( "noLog", Object.class ); - reader = new JPAOverriddenAnnotationReader( method, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); assertTrue( reader.isAnnotationPresent( PostLoad.class ) ); method = LogListener.class.getDeclaredMethod( "log", Object.class ); - reader = new JPAOverriddenAnnotationReader( method, context, ClassLoaderAccessTestingImpl.INSTANCE ); + reader = new JPAOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); assertTrue( reader.isAnnotationPresent( PrePersist.class ) ); assertFalse( reader.isAnnotationPresent( PostPersist.class ) ); @@ -430,10 +393,10 @@ public void testEntityListeners() throws Exception { } private XMLContext buildContext(String ormfile) throws SAXException, DocumentException, IOException { - XMLHelper xmlHelper = new XMLHelper( ClassLoaderServiceTestingImpl.INSTANCE ); + XMLHelper xmlHelper = new XMLHelper(); InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( ormfile ); assertNotNull( "ORM.xml not found: " + ormfile, is ); - XMLContext context = new XMLContext( ClassLoaderAccessTestingImpl.INSTANCE ); + XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); ErrorLogger errorLogger = new ErrorLogger(); SAXReader saxReader = xmlHelper.createSAXReader( errorLogger, EJB3DTDEntityResolver.INSTANCE ); //saxReader.setValidation( false ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/OrganizationConverter.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/OrganizationConverter.java new file mode 100644 index 000000000000..3a68b3e914d6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/OrganizationConverter.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 . + */ +package org.hibernate.test.annotations.reflection; + +import javax.persistence.AttributeConverter; + +/** + * Converts {@link Organization} <=> String + */ +public class OrganizationConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(Organization organization) { + return "ORG-" + organization.getOrganizationId(); + } + + @Override + public Organization convertToEntityAttribute(String organizationId) { + Organization organization = new Organization(); + organization.setOrganizationId(organizationId.replace("ORG-", "")); + return organization; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java index b4195e2c5db3..235fe820e4a3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java @@ -21,7 +21,7 @@ import org.hibernate.internal.util.xml.ErrorLogger; import org.hibernate.internal.util.xml.XMLHelper; -import org.hibernate.testing.boot.ClassLoaderAccessTestingImpl; +import org.hibernate.testing.boot.BootstrapContextImpl; import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; /** @@ -30,8 +30,8 @@ public class XMLContextTest { @Test public void testAll() throws Exception { - final XMLHelper xmlHelper = new XMLHelper( ClassLoaderServiceTestingImpl.INSTANCE ); - final XMLContext context = new XMLContext( ClassLoaderAccessTestingImpl.INSTANCE ); + final XMLHelper xmlHelper = new XMLHelper(); + final XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( "org/hibernate/test/annotations/reflection/orm.xml" diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/secondarytable/SecondaryTableSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/secondarytable/SecondaryTableSchemaTest.java new file mode 100644 index 000000000000..28de8472e6ef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/secondarytable/SecondaryTableSchemaTest.java @@ -0,0 +1,146 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.secondarytable; + +import java.io.Serializable; +import java.net.URL; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; + +import org.hibernate.Criteria; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.OptimisticLockType; +import org.hibernate.annotations.OptimisticLocking; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.transaction.TransactionUtil; +import org.hibernate.test.schemaupdate.foreignkeys.definition.AbstractForeignKeyDefinitionTest; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(value = H2Dialect.class) +public class SecondaryTableSchemaTest + extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Cluster.class, + }; + } + + protected void addConfigOptions(Map options) { + options.put( + AvailableSettings.URL, + options.get( AvailableSettings.URL ) + ";INIT=CREATE SCHEMA IF NOT EXISTS schema1\\;CREATE SCHEMA IF NOT EXISTS schema2;" + ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + List clusters = entityManager.createQuery( "select c from Cluster c" ).getResultList(); + + assertTrue(clusters.isEmpty()); + } ); + } + + @Entity(name = "Cluster") + @Table(name = "cluster", schema = "schema1") + @SecondaryTable(name = "Cluster", schema="schema2", pkJoinColumns = { @PrimaryKeyJoinColumn(name = "clusterid") }) + @org.hibernate.annotations.Table(appliesTo = "Cluster", optional = false) + @OptimisticLocking(type = OptimisticLockType.DIRTY) + @DynamicUpdate + public static class Cluster implements Serializable { + private static final long serialVersionUID = 3965099001305947412L; + + @Id + @Column(name = "objid") + private Long id; + + private String uuid; + + private String resourceKey; + + private String name; + + @Column(name = "lastSync", table = "Cluster") + private Long lastSync; + + @Column(name = "healthStatus", table = "Cluster") + private Integer healthStatus; + + 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 getResourceKey() { + return resourceKey; + } + + public void setResourceKey(String resourceKey) { + this.resourceKey = resourceKey; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public Long getLastSync() { + return lastSync; + } + + public void setLastSync(Long lastSync) { + this.lastSync = lastSync; + } + + public Integer getHealthStatus() { + return healthStatus; + } + + public void setHealthStatus(Integer healthStatus) { + this.healthStatus = healthStatus; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java new file mode 100644 index 000000000000..483c84e12d12 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.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.annotations.tuplizer.bytebuddysubclass; + +import java.io.Serializable; + +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.tuple.Instantiator; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.implementation.FixedValue; +import net.bytebuddy.matcher.ElementMatchers; + +/** + * @author Florian Bien + */ +public class MyEntityInstantiator implements Instantiator { + private final PersistentClass persistentClass; + + public MyEntityInstantiator(PersistentClass persistentClass) { + this.persistentClass = persistentClass; + } + + @Override + public Object instantiate(Serializable id) { + return instantiate(); + } + + @Override + public Object instantiate() { + return createInstance( persistentClass.getMappedClass() ); + } + + public static E createInstance(Class entityClass) { + Class loaded = new ByteBuddy() + .subclass( entityClass ) + .method( ElementMatchers.named( "toString" ) ) + .intercept( FixedValue.value( "transformed" ) ) + .make() + // we use our internal helper to get a class loading strategy suitable for the JDK used + .load( entityClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( entityClass ) ) + .getLoaded(); + + try { + return loaded.newInstance(); + } + catch (Exception e) { + throw new RuntimeException( "Unable to create new instance of " + entityClass.getSimpleName(), e ); + } + } + + @Override + public boolean isInstance(Object object) { + return true; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyTuplizer.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyTuplizer.java new file mode 100644 index 000000000000..971e32a17043 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyTuplizer.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.test.annotations.tuplizer.bytebuddysubclass; + +import org.hibernate.EntityNameResolver; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.tuple.Instantiator; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.tuple.entity.PojoEntityTuplizer; + +/** + * @author Florian Bien + */ +public class MyTuplizer extends PojoEntityTuplizer { + public MyTuplizer( + EntityMetamodel entityMetamodel, + PersistentClass mappedEntity) { + super( entityMetamodel, mappedEntity ); + } + + public EntityNameResolver[] getEntityNameResolvers() { + return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE }; + } + + @Override + protected Instantiator buildInstantiator(EntityMetamodel entityMetamodel, PersistentClass persistentClass) { + return new MyEntityInstantiator( persistentClass ); + } + + public static class MyEntityNameResolver implements EntityNameResolver { + public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver(); + + public String resolveEntityName(Object entity) { + if ( entity.getClass().getName().contains( "$ByteBuddy$" ) ) { + return entity.getClass().getSuperclass().getName(); + } + else { + return entity.getClass().getName(); + } + + } + + public boolean equals(Object obj) { + return getClass().equals( obj.getClass() ); + } + + public int hashCode() { + return getClass().hashCode(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/TuplizerInstantiatesByteBuddySubclassTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/TuplizerInstantiatesByteBuddySubclassTest.java new file mode 100644 index 000000000000..bd72290e8a04 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/TuplizerInstantiatesByteBuddySubclassTest.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 . + */ +package org.hibernate.test.annotations.tuplizer.bytebuddysubclass; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.Tuplizer; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Florian Bien + */ +public class TuplizerInstantiatesByteBuddySubclassTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class }; + } + + @Test + public void hhh11655Test() throws Exception { + Session session = openSession(); + session.beginTransaction(); + + SimpleEntity simpleEntityNonProxy = new SimpleEntity(); + Assert.assertFalse( session.contains( simpleEntityNonProxy ) ); + + SimpleEntity simpleEntity = MyEntityInstantiator.createInstance( SimpleEntity.class ); + Assert.assertFalse( session.contains( simpleEntity ) ); + + session.persist( simpleEntity ); + Assert.assertTrue( session.contains( simpleEntity ) ); + + session.getTransaction().rollback(); + session.close(); + } + + @Entity(name = "SimpleEntity") + @Tuplizer(impl = MyTuplizer.class) + public static class SimpleEntity { + protected SimpleEntity() { + } + + @Id + @GeneratedValue + private Long id; + + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java index f39328e4ef15..34f4911f54b4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java @@ -16,19 +16,23 @@ import javax.persistence.Id; import javax.persistence.Table; +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.MySQL5InnoDBDialect; +import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertTrue; @@ -37,17 +41,36 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-11236") -@RequiresDialect(MySQL5InnoDBDialect.class) +@RequiresDialect(MySQL5Dialect.class) +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class MySQLDropConstraintThrowsExceptionTest extends BaseUnitTestCase { - @After - public void releaseResources() { + @Before + public void setUp() { + final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + .enableAutoClose() + .applySetting( AvailableSettings.HBM2DDL_AUTO, "drop" ) + .build(); + SessionFactoryImplementor sessionFactory = null; + + try { + final Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClass( Customer.class ) + .buildMetadata(); + sessionFactory = (SessionFactoryImplementor) metadata.buildSessionFactory(); + } + finally { + if ( sessionFactory != null ) { + sessionFactory.close(); + } + StandardServiceRegistryBuilder.destroy( serviceRegistry ); + } } - @Test - public void testEnumTypeInterpretation() { - StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + @After + public void tearDown() { + final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() .enableAutoClose() .applySetting( AvailableSettings.HBM2DDL_AUTO, "drop" ) .build(); @@ -67,9 +90,13 @@ public void testEnumTypeInterpretation() { StandardServiceRegistryBuilder.destroy( serviceRegistry ); } - PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); + } + + @Test + public void testEnumTypeInterpretation() { + final PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - serviceRegistry = new StandardServiceRegistryBuilder() + final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() .enableAutoClose() .applySetting( AvailableSettings.HBM2DDL_AUTO, "update" ) .applySetting( @@ -78,17 +105,19 @@ public void testEnumTypeInterpretation() { ) .build(); + SessionFactory sessionFactory = null; try { final Metadata metadata = new MetadataSources( serviceRegistry ) .addAnnotatedClass( Customer.class ) .buildMetadata(); - sessionFactory = (SessionFactoryImplementor) metadata.buildSessionFactory(); + sessionFactory = metadata.buildSessionFactory(); List alterStatements = connectionProvider.getExecuteStatements().stream() - .filter( - sql -> sql.toLowerCase().contains( "alter " ) - ).map( String::trim ).collect( Collectors.toList() ); - assertTrue(alterStatements.get(0).matches( "alter table CUSTOMER\\s+drop index .*?" )); - assertTrue(alterStatements.get(1).matches( "alter table CUSTOMER\\s+add constraint .*? unique \\(CUSTOMER_ID\\)" )); + .filter( + sql -> sql.toLowerCase().contains( "alter " ) + ).map( String::trim ).collect( Collectors.toList() ); + assertTrue( alterStatements.get( 0 ).matches( "alter table CUSTOMER\\s+drop index .*?" ) ); + assertTrue( alterStatements.get( 1 ) + .matches( "alter table CUSTOMER\\s+add constraint .*? unique \\(CUSTOMER_ID\\)" ) ); } finally { if ( sessionFactory != null ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintBatchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintBatchingTest.java new file mode 100644 index 000000000000..f5ba7f2b531e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintBatchingTest.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.uniqueconstraint; + +import java.util.Map; +import javax.persistence.PersistenceException; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-12688") +@RequiresDialect(H2Dialect.class) +public class UniqueConstraintBatchingTest extends BaseEntityManagerFunctionalTestCase { + + protected Class[] getAnnotatedClasses() { + return new Class[] { + Room.class, + Building.class, + House.class + }; + } + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, SqlExceptionHelper.class.getName() ) ); + + private Triggerable triggerable; + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.STATEMENT_BATCH_SIZE, 5 ); + triggerable = logInspection.watchForLogMessages( "Unique index" ); + triggerable.reset(); + } + + @Test + public void testBatching() throws Exception { + Room livingRoom = new Room(); + + doInJPA( this::entityManagerFactory, entityManager -> { + livingRoom.setId( 1l ); + livingRoom.setName( "livingRoom" ); + entityManager.persist( livingRoom ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + House house = new House(); + house.setId( 1l ); + house.setCost( 100 ); + house.setHeight( 1000l ); + house.setRoom( livingRoom ); + entityManager.persist( house ); + } ); + + try { + doInJPA( this::entityManagerFactory, entityManager -> { + House house2 = new House(); + house2.setId( 2l ); + house2.setCost( 100 ); + house2.setHeight( 1001l ); + house2.setRoom( livingRoom ); + entityManager.persist( house2 ); + } ); + fail( "Should throw exception" ); + } + catch (PersistenceException e) { + assertEquals( 1, triggerable.triggerMessages().size() ); + assertTrue( triggerable.triggerMessage().startsWith( "Unique index or primary key violation" ) ); + } + } + +} 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 bcc8b402a16b..7393215cdcba 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 @@ -38,10 +38,6 @@ protected Class[] getAnnotatedClasses() { return new Class[] { Customer.class }; } - protected void configure(Configuration configuration) { - configuration.setProperty( Environment.HBM2DDL_AUTO, "update" ); - } - @Test public void testUniqueConstraintWithEmptyColumnName() { doInHibernate( this::sessionFactory, session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ColumnTransformerTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ColumnTransformerTest.java index 55097e968a06..85b4a84e560a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ColumnTransformerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ColumnTransformerTest.java @@ -6,12 +6,14 @@ */ package org.hibernate.test.annotations.various.readwriteexpression; -import org.junit.Test; - import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.criterion.Restrictions; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -19,7 +21,15 @@ * @author Emmanuel Bernard */ public class ColumnTransformerTest extends BaseCoreFunctionalTestCase { + /** + * @implNote Limited to H2 because getting a good expression to use for + * {@link Staff#kooky} that works on all databases is challenging and + * really what happens on the "database side" here is not relevant - + * the issue being tested is how Hibernate applies the table aliases to + * column references in the expression. + */ @Test + @RequiresDialect(H2Dialect.class ) public void testCustomColumnReadAndWrite() throws Exception{ Session s = openSession(); Transaction t = s.beginTransaction(); @@ -59,9 +69,9 @@ public void testCustomColumnReadAndWrite() throws Exception{ assertEquals(HEIGHT_INCHES, staff.getSizeInInches(), 0.01d); // Test predicate and entity load via HQL - staff = (Staff)s.createQuery("from Staff s where s.sizeInInches between ? and ?") - .setDouble(0, HEIGHT_INCHES - 0.01d) - .setDouble(1, HEIGHT_INCHES + 0.01d) + staff = (Staff)s.createQuery("from Staff s where s.sizeInInches between ?1 and ?2") + .setDouble(1, HEIGHT_INCHES - 0.01d) + .setDouble(2, HEIGHT_INCHES + 0.01d) .uniqueResult(); assertEquals(HEIGHT_INCHES, staff.getSizeInInches(), 0.01d); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java index 3b027924b5d5..a210407885dd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java @@ -55,4 +55,12 @@ public Staff(double sizeInInches, double radius, double diameter, Integer id) { public double getDiameter() { return diameter; } public void setDiameter(double diameter) { this.diameter = diameter; } private double diameter; + + @Column(name="kooky") + @ColumnTransformer( + read = "cast( kooky as VARCHAR )" + ) + public String getKooky() { return kooky; } + public void setKooky(String kooky) { this.kooky = kooky; } + private String kooky; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java index acc9d01c4362..3c96ba4df1cb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java @@ -15,9 +15,9 @@ import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; import org.hibernate.cfg.annotations.reflection.XMLContext; -import org.hibernate.testing.junit4.BaseUnitTestCase; -import org.hibernate.testing.boot.ClassLoaderAccessTestingImpl; +import org.hibernate.testing.boot.BootstrapContextImpl; +import org.hibernate.testing.junit4.BaseUnitTestCase; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -49,7 +49,7 @@ protected JPAOverriddenAnnotationReader getReader(Class entityClass, String f throws Exception { AnnotatedElement el = getAnnotatedElement( entityClass, fieldName ); XMLContext xmlContext = getContext( ormResourceName ); - return new JPAOverriddenAnnotationReader( el, xmlContext, ClassLoaderAccessTestingImpl.INSTANCE ); + return new JPAOverriddenAnnotationReader( el, xmlContext, BootstrapContextImpl.INSTANCE ); } protected AnnotatedElement getAnnotatedElement(Class entityClass, String fieldName) throws Exception { @@ -63,8 +63,12 @@ protected XMLContext getContext(String resourceName) throws Exception { } protected XMLContext getContext(InputStream is) throws Exception { - XMLContext xmlContext = new XMLContext( ClassLoaderAccessTestingImpl.INSTANCE ); - Document doc = new SAXReader().read( is ); + XMLContext xmlContext = new XMLContext( BootstrapContextImpl.INSTANCE ); + SAXReader reader = new SAXReader(); + reader.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false ); + reader.setFeature( "http://xml.org/sax/features/external-general-entities", false ); + reader.setFeature( "http://xml.org/sax/features/external-parameter-entities", false ); + Document doc = reader.read( is ); xmlContext.addDocument( doc ); return xmlContext; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java index e3558de12055..fcf80d24429f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java @@ -13,4 +13,8 @@ public interface A extends java.io.Serializable { public Integer getAId(); public void setAId(Integer aId); + + String getDescription(); + + void setDescription(String description); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java index 34bab4b1dda5..3b13900d80b3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java @@ -23,6 +23,7 @@ public class AImpl implements A { private static final long serialVersionUID = 1L; private Integer aId = 0; + private String description; public AImpl() { } @@ -37,4 +38,13 @@ public Integer getAId() { public void setAId(Integer aId) { this.aId = aId; } + + @Column( name = "description" ) + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/HbmWithIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/HbmWithIdentityTest.java index 987071b434fe..864146824723 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/HbmWithIdentityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/HbmWithIdentityTest.java @@ -9,8 +9,10 @@ import org.junit.Test; import org.hibernate.Session; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; /** @@ -19,6 +21,7 @@ @RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) public class HbmWithIdentityTest extends BaseCoreFunctionalTestCase { @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") public void testManyToOneAndInterface() throws Exception { Session s = openSession(); s.getTransaction().begin(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/array/ArrayTest.java b/hibernate-core/src/test/java/org/hibernate/test/array/ArrayTest.java index b478260ca686..c7e8ee75d9f7 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/array/ArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/array/ArrayTest.java @@ -10,6 +10,8 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.junit.Assert.assertEquals; @@ -24,6 +26,7 @@ public String[] getMappings() { } @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") public void testArrayJoinFetch() throws Exception { Session s; Transaction tx; diff --git a/hibernate-core/src/test/java/org/hibernate/test/ast/ASTIteratorTest.java b/hibernate-core/src/test/java/org/hibernate/test/ast/ASTIteratorTest.java index ccd015b403b8..1c278d718c56 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/ast/ASTIteratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/ast/ASTIteratorTest.java @@ -18,6 +18,8 @@ import org.hibernate.hql.internal.ast.util.ASTParentsFirstIterator; import org.hibernate.hql.internal.ast.util.ASTPrinter; import org.hibernate.hql.internal.ast.util.ASTUtil; +import org.hibernate.hql.internal.ast.util.TokenPrinters; + import org.hibernate.testing.junit4.BaseUnitTestCase; import static org.junit.Assert.assertEquals; @@ -36,7 +38,7 @@ public void testSimpleTree() throws Exception { HqlParser parser = HqlParser.getInstance( input ); parser.statement(); AST ast = parser.getAST(); - ASTPrinter printer = new ASTPrinter( HqlTokenTypes.class ); + ASTPrinter printer = TokenPrinters.HQL_TOKEN_PRINTER; printer.showAst( ast, new PrintWriter( System.out ) ); ASTIterator iterator = new ASTIterator( ast ); int count = 0; diff --git a/hibernate-core/src/test/java/org/hibernate/test/batch/BatchingInheritanceDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/test/batch/BatchingInheritanceDeleteTest.java new file mode 100644 index 000000000000..48904cc1e084 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batch/BatchingInheritanceDeleteTest.java @@ -0,0 +1,235 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.batch; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +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.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12470" ) +public class BatchingInheritanceDeleteTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Foo.class, + Bar.class, + Baz.class + }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.setProperty( AvailableSettings.STATEMENT_BATCH_SIZE, "25" ); + } + + @Test + //@FailureExpected( jiraKey = "HHH-12470" ) + public void testDelete() { + doInHibernate( this::sessionFactory, s -> { + Bar bar = new Bar("bar"); + + Foo foo = new Foo("foo"); + foo.setBar(bar); + + s.persist(foo); + s.persist(bar); + + s.flush(); + + s.remove(foo); + s.remove(bar); + + s.flush(); + + assertThat(s.find(Foo.class, foo.getId()), nullValue()); + assertThat(s.find(Bar.class, bar.getId()), nullValue()); + } ); + } + + @Entity(name = "Bar") + public static class Bar extends AbstractBar { + + @Column(nullable = false) + private String name; + + @OneToMany(cascade = CascadeType.ALL) + List bazList = new ArrayList<>(); + + public Bar() { + super(); + } + + public Bar(final String name) { + super(); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public List getBazList() { + return bazList; + } + + public void setBazList(final List bazList) { + this.bazList = bazList; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Bar [name=").append(name).append("]"); + return builder.toString(); + } + } + + @Entity(name = "Baz") + public static class Baz { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + @Column(nullable = false) + private String name; + + public Baz() { + super(); + } + + public Baz(final String name) { + super(); + 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; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Bar [name=").append(name).append("]"); + return builder.toString(); + } + } + + @Entity(name = "Foo") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Foo extends AbstractFoo { + + public Foo() { + super(); + } + + public Foo(final String name) { + super(); + this.name = name; + } + + @Column(name = "NAME") + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "BAR_ID") + private Bar bar; + + public Bar getBar() { + return bar; + } + + public void setBar(final Bar bar) { + this.bar = bar; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + } + + @Entity(name = "AbstractBar") + @Inheritance(strategy = InheritanceType.JOINED) + public static class AbstractBar { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + } + + @MappedSuperclass + public static class AbstractFoo { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + 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/batch/NonBatchingBatchFailureTest.java b/hibernate-core/src/test/java/org/hibernate/test/batch/NonBatchingBatchFailureTest.java index 6882af767778..6561d37645b5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batch/NonBatchingBatchFailureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/batch/NonBatchingBatchFailureTest.java @@ -71,10 +71,7 @@ public void testBasicInsertion() { Field field = sessionImplementor.getJdbcCoordinator().getClass().getDeclaredField( "currentBatch" ); field.setAccessible( true ); Batch batch = (Batch) field.get( sessionImplementor.getJdbcCoordinator() ); - if ( batch == null ) { - throw new Exception( "Current batch was null" ); - } - else { + if ( batch != null ) { //make sure it's actually a batching impl assertEquals( NonBatchingBatch.class, batch.getClass() ); field = AbstractBatchImpl.class.getDeclaredField( "statements" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchBootstrapTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchBootstrapTest.java new file mode 100644 index 000000000000..23ee50788ff8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchBootstrapTest.java @@ -0,0 +1,87 @@ +package org.hibernate.test.batchfetch; + +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.MappedSuperclass; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +public class BatchFetchBootstrapTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + JafSid.class, UserGroup.class + }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "30"); + } + + @Override + protected void buildSessionFactory() { + } + + @Test + public void test() { + super.buildSessionFactory(); + } + + + @MappedSuperclass + public abstract static class DatabaseEntity { + private int id; + + @Id + @GeneratedValue + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + } + + @Entity(name = "JafSid") + public static class JafSid extends DatabaseEntity { + + private Set groups = new LinkedHashSet<>(); + + @ManyToMany(mappedBy = "members", fetch = FetchType.EAGER) + public Set getGroups() { + return groups; + } + + public void setGroups(Set groups) { + this.groups = groups; + } + } + + @Entity(name = "UserGroup") + public static class UserGroup extends DatabaseEntity { + + private Set members = new LinkedHashSet<>(); + + @ManyToMany + public Set getMembers() { + return members; + } + + public void setMembers(Set members) { + this.members = members; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java new file mode 100644 index 000000000000..01871c44c27c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java @@ -0,0 +1,339 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.hibernate.Session; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.BatchFetchQueue; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +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; + +/** + * @author Gail Badner + * @author Stephen Fikes + */ +public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctionalTestCase { + private static final AStatementInspector statementInspector = new AStatementInspector(); + private static final int NUMBER_OF_EMPLOYEES = 8; + + private List tasks = new ArrayList<>(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Employee.class, Task.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.STATEMENT_INSPECTOR, statementInspector ); + } + + @Before + public void createData() { + tasks.clear(); + tasks = doInHibernate( + this::sessionFactory, session -> { + for (int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++) { + Task task = new Task(); + task.id = i; + tasks.add( task ); + session.persist( task ); + Employee e = new Employee("employee0" + i); + e.task = task; + session.persist(e); + } + return tasks; + } + ); + } + + @After + public void deleteData() { + doInHibernate( + this::sessionFactory, session -> { + session.createQuery( "delete from Task" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + } + ); + } + + @Test + public void testSeveralNotFoundFromQuery() { + + doInHibernate( + this::sessionFactory, session -> { + // delete 2nd and 8th Task so that the non-found Task entities will be queried + // in 2 different batches. + session.delete( tasks.get( 1 ) ); + session.delete( tasks.get( 7 ) ); + } + ); + + statementInspector.clear(); + + final List employees = doInHibernate( + this::sessionFactory, session -> { + List results = + session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); + for ( int i = 0 ; i < tasks.size() ; i++ ) { + checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + } + return results; + } + ); + + final List paramterCounts = statementInspector.parameterCounts; + + // there should be 4 SQL statements executed + assertEquals( 4, paramterCounts.size() ); + + // query loading Employee entities shouldn't have any parameters + assertEquals( 0, paramterCounts.get( 0 ).intValue() ); + + // query specifically for Task with ID == 0 will result in 1st batch; + // query should have 5 parameters for [0,1,2,3,4]; + // Task with ID == 1 won't be found; the rest will be found. + assertEquals( 5, paramterCounts.get( 1 ).intValue() ); + + // query specifically for Task with ID == 1 will result in 2nd batch; + // query should have 4 parameters [1,5,6,7]; + // Task with IDs == [1,7] won't be found; the rest will be found. + assertEquals( 4, paramterCounts.get( 2 ).intValue() ); + + // no extra queries required to load entities with IDs [2,3,4] because they + // were already loaded from 1st batch + + // no extra queries required to load entities with IDs [5,6] because they + // were already loaded from 2nd batch + + // query specifically for Task with ID == 7 will result in just querying + // Task with ID == 7 (because the batch is empty). + // query should have 1 parameter [7]; + // Task with ID == 7 won't be found. + assertEquals( 1, paramterCounts.get( 3 ).intValue() ); + + assertEquals( NUMBER_OF_EMPLOYEES, employees.size() ); + for ( int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++ ) { + if ( i == 1 || i == 7 ) { + assertNull( employees.get( i ).task ); + } + else { + assertEquals( tasks.get( i ).id, employees.get( i ).task.id ); + } + } + } + + @Test + public void testMostNotFoundFromQuery() { + + doInHibernate( + this::sessionFactory, session -> { + // delete all but last Task entity + for ( int i = 0; i < 7; i++ ) { + session.delete( tasks.get( i ) ); + } + } + ); + + statementInspector.clear(); + + final List employees = doInHibernate( + this::sessionFactory, session -> { + List results = + session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); + for ( int i = 0 ; i < tasks.size() ; i++ ) { + checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + } + return results; + } + ); + + final List paramterCounts = statementInspector.parameterCounts; + + // there should be 8 SQL statements executed + assertEquals( 8, paramterCounts.size() ); + + // query loading Employee entities shouldn't have any parameters + assertEquals( 0, paramterCounts.get( 0 ).intValue() ); + + // query specifically for Task with ID == 0 will result in 1st batch; + // query should have 5 parameters for [0,1,2,3,4]; + // Task with IDs == [0,1,2,3,4] won't be found + assertEquals( 5, paramterCounts.get( 1 ).intValue() ); + + // query specifically for Task with ID == 1 will result in 2nd batch; + // query should have 4 parameters [1,5,6,7]; + // Task with IDs == [1,5,6] won't be found; Task with ID == 7 will be found. + assertEquals( 4, paramterCounts.get( 2 ).intValue() ); + + // query specifically for Task with ID == 2 will result in just querying + // Task with ID == 2 (because the batch is empty). + // query should have 1 parameter [2]; + // Task with ID == 2 won't be found. + assertEquals( 1, paramterCounts.get( 3 ).intValue() ); + + // query specifically for Task with ID == 3 will result in just querying + // Task with ID == 3 (because the batch is empty). + // query should have 1 parameter [3]; + // Task with ID == 3 won't be found. + assertEquals( 1, paramterCounts.get( 4 ).intValue() ); + + // query specifically for Task with ID == 4 will result in just querying + // Task with ID == 4 (because the batch is empty). + // query should have 1 parameter [4]; + // Task with ID == 4 won't be found. + assertEquals( 1, paramterCounts.get( 5 ).intValue() ); + + // query specifically for Task with ID == 5 will result in just querying + // Task with ID == 5 (because the batch is empty). + // query should have 1 parameter [5]; + // Task with ID == 5 won't be found. + assertEquals( 1, paramterCounts.get( 6 ).intValue() ); + + // query specifically for Task with ID == 6 will result in just querying + // Task with ID == 6 (because the batch is empty). + // query should have 1 parameter [6]; + // Task with ID == 6 won't be found. + assertEquals( 1, paramterCounts.get( 7 ).intValue() ); + + // no extra queries required to load entity with ID == 7 because it + // was already loaded from 2nd batch + + assertEquals( NUMBER_OF_EMPLOYEES, employees.size() ); + + for ( int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++ ) { + if ( i == 7 ) { + assertEquals( tasks.get( i ).id, employees.get( i ).task.id ); + } + else { + assertNull( employees.get( i ).task ); + } + } + } + + @Test + public void testNotFoundFromGet() { + + doInHibernate( + this::sessionFactory, session -> { + // delete task so it is not found later when getting the Employee. + session.delete( tasks.get( 0 ) ); + } + ); + + statementInspector.clear(); + + doInHibernate( + this::sessionFactory, session -> { + Employee employee = session.get( Employee.class, "employee00" ); + checkInBatchFetchQueue( tasks.get( 0 ).id, session, false ); + assertNotNull( employee ); + assertNull( employee.task ); + } + ); + + final List paramterCounts = statementInspector.parameterCounts; + + // there should be 2 SQL statements executed + // 1) query to load Employee entity by ID (associated Tasks is registered for batch loading) + // 2) batch will only contain the ID for the associated Task (which will not be found) + assertEquals( 2, paramterCounts.size() ); + + // query loading Employee entities shouldn't have any parameters + assertEquals( 1, paramterCounts.get( 0 ).intValue() ); + + // Will result in just querying a single Task (because the batch is empty). + // query should have 1 parameter; + // Task won't be found. + assertEquals( 1, paramterCounts.get( 1 ).intValue() ); + } + + private static void checkInBatchFetchQueue(long id, Session session, boolean expected) { + final SessionImplementor sessionImplementor = (SessionImplementor) session; + final EntityPersister persister = + sessionImplementor.getFactory().getMetamodel().entityPersister( Task.class ); + final BatchFetchQueue batchFetchQueue = + sessionImplementor.getPersistenceContext().getBatchFetchQueue(); + assertEquals( expected, batchFetchQueue.containsEntityKey( new EntityKey( id, persister ) ) ); + } + + @Entity(name = "Employee") + public static class Employee { + @Id + private String name; + + @OneToOne(optional = true) + @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + @NotFound(action = NotFoundAction.IGNORE) + private Task task; + + private Employee() { + } + + private Employee(String name) { + this.name = name; + } + } + + @Entity(name = "Task") + @BatchSize(size = 5) + public static class Task { + @Id + private long id; + + public Task() { + } + } + + public static class AStatementInspector implements StatementInspector { + private List parameterCounts = new ArrayList<>(); + + public String inspect(String sql) { + parameterCounts.add( countParameters( sql ) ); + return sql; + } + private void clear() { + parameterCounts.clear(); + } + private int countParameters(String sql) { + int count = 0; + int parameterIndex = sql.indexOf( '?' ); + while ( parameterIndex >= 0 ) { + count++; + parameterIndex = sql.indexOf( '?', parameterIndex + 1 ); + } + return count; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java new file mode 100644 index 000000000000..18aebd94a178 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.loader.BatchFetchStyle; + +/** + * @author Gail Badner + */ +public class BatchFetchNotFoundIgnoreDynamicStyleTest extends BatchFetchNotFoundIgnoreDefaultStyleTest { + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.BATCH_FETCH_STYLE, BatchFetchStyle.DYNAMIC ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnorePaddedStyleTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnorePaddedStyleTest.java new file mode 100644 index 000000000000..e02a726612c3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnorePaddedStyleTest.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 . + */ +package org.hibernate.test.batchfetch; + +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.loader.BatchFetchStyle; + +/** + * @author Gail Badner + */ +public class BatchFetchNotFoundIgnorePaddedStyleTest extends BatchFetchNotFoundIgnoreDefaultStyleTest { + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java new file mode 100644 index 000000000000..c161191188fe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java @@ -0,0 +1,146 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import java.time.ZonedDateTime; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Test; + +public class BatchFetchReferencedColumnNameTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Child.class, Parent.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + + configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); + configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); + + configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "64" ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13059") + public void test() throws Exception { + doInHibernate( this::sessionFactory, session -> { + Parent p = new Parent(); + p.setId( 1L ); + session.save( p ); + + Child c1 = new Child(); + c1.setCreatedOn( ZonedDateTime.now() ); + c1.setParentId( 1L ); + c1.setId( 10L ); + session.save( c1 ); + + Child c2 = new Child(); + c2.setCreatedOn( ZonedDateTime.now() ); + c2.setParentId( 1L ); + c2.setId( 11L ); + session.save( c2 ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Parent p = session.get( Parent.class, 1L ); + Assert.assertNotNull( p ); + + Assert.assertEquals( 2, p.getChildren().size() ); + } ); + } + + @Entity + @Table(name = "CHILD") + public static class Child { + + @Id + @Column(name = "CHILD_ID") + private Long id; + + @Column(name = "PARENT_ID") + private Long parentId; + + @Column(name = "CREATED_ON") + private ZonedDateTime createdOn; + + public ZonedDateTime getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(ZonedDateTime createdOn) { + this.createdOn = createdOn; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + } + + @Entity + @Table(name = "PARENT") + public static class Parent { + + @Id + @Column(name = "PARENT_ID") + private Long id; + + @OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL }) + @JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID") + @OrderBy("createdOn desc") + private List children; + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java new file mode 100644 index 000000000000..12c0d79bc0f4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java @@ -0,0 +1,181 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.LockModeType; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.*; + +public class BatchFetchRefreshTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + + public void testRefreshWithBatch() { + + doInHibernate( this::sessionFactory, session -> { + + // Retrieve one of the parents into the session. + Parent parent = session.find(Parent.class, 1); + Assert.assertNotNull(parent); + + // Retrieve children but keep their parents lazy! + // This allows batch fetching to do its thing when we refresh below. + session.createQuery( "FROM Child" ).getResultList(); + + session.refresh( parent, LockModeType.PESSIMISTIC_WRITE ); + + // Just something to force delazification of children on parent entity + // The parent is obviously attached to the session (we just refreshed it!) + parent.getChildren().size(); + + // Another interesting thing to note - em.getLockMode returns an incorrect value after the above refresh + Assert.assertEquals( LockModeType.PESSIMISTIC_WRITE, session.getLockMode( parent ) ); + }); + } + + @Before + public void setupData() { + final int numParents = 5; + final int childrenPerParent = 2; + + doInHibernate( this::sessionFactory, session -> { + int k = 1; + for ( int i = 1; i <= numParents; i++ ) { + Parent parent = new Parent(); + parent.parentId = i; + parent.name = "Parent_" + i; + + session.persist( parent ); + + // Create some children for each parent... + for ( int j = 0; j < childrenPerParent; j++,k++ ) { + Child child = new Child(); + child.childId = k; + child.name = "Child_" + i + "_" + j; + child.age = 15; + child.parent = parent; + parent.getChildren().add( child ); + session.persist( child ); + } + } + }); + + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + settings.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "8" ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + @Column(name = "parent_id") + private int parentId; + + @Column(name = "name") + private String name; + + @OneToMany(mappedBy = "parent") + private Set children = new LinkedHashSet<>(); + + public int getParentId() { + return parentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getChildren() { + return children; + } + + public void setChildren(Set children) { + this.children = children; + } + + } + + @Entity(name = "Child") + public static class Child { + + @Id + @Column(name = "child_id") + private int childId; + + @Column(name = "name") + private String name; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "parent_id") + private Parent parent; + + @Column(name = "age") + private int age; + + public int getChildId() { + return childId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java new file mode 100644 index 000000000000..461e57c7a831 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java @@ -0,0 +1,114 @@ +package org.hibernate.test.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.annotations.Fetch; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.metamodel.spi.MetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.junit.Test; + +public class BatchingEntityLoaderInitializationWithNoLockModeTest extends BaseEntityManagerFunctionalTestCase { + + private Long mainId; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { MainEntity.class, SubEntity.class }; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + protected Map buildSettings() { + Map settings = super.buildSettings(); + settings.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.LEGACY ); + settings.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, 5 ); + return settings; + } + + @Test + public void testJoin() { + doInJPA( this::entityManagerFactory, em -> { + SubEntity sub = new SubEntity(); + em.persist( sub ); + + MainEntity main = new MainEntity(); + main.setSub( sub ); + em.persist( main ); + + this.mainId = main.getId(); + }); + + doInJPA( this::entityManagerFactory, em -> { + EntityPersister entityPersister = ( (MetamodelImplementor) em.getMetamodel() ) + .entityPersister( MainEntity.class ); + + // use some specific lock options to trigger the creation of a loader with lock options + LockOptions lockOptions = new LockOptions( LockMode.NONE ); + lockOptions.setTimeOut( 10 ); + + MainEntity main = (MainEntity) entityPersister.load( this.mainId, null, lockOptions, + (SharedSessionContractImplementor) em ); + assertNotNull( main.getSub() ); + } ); + } + + @Entity(name = "MainEntity") + public static class MainEntity { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @Fetch(org.hibernate.annotations.FetchMode.JOIN) + private SubEntity sub; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public SubEntity getSub() { + return sub; + } + + public void setSub(SubEntity sub) { + this.sub = sub; + } + } + + @Entity(name = "SubEntity") + public static class SubEntity { + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return id; + } + + + public void setId(Long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java new file mode 100644 index 000000000000..83a800a26706 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "city") +public class City { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Integer id; + + @Column(name = "name") + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "country_id") + private Country country; + + public City() { + } + + public City(String name, Country country) { + super(); + this.name = name; + this.country = country; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + + @Override + public String toString() { + return name + " (" + ( country == null ? "?" : country.getName() ) + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java new file mode 100644 index 000000000000..76e40d62ca6b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "country") +public class Country { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Integer id; + + @Column(name = "name") + private String name; + + @OneToMany(mappedBy = "country") + private List cities; + + public Country() { + } + + public Country(String name) { + super(); + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getCities() { + return cities; + } + + @Override + public String toString() { + return name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java new file mode 100644 index 000000000000..08c5f471b0cf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.stream.IntStream; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +public class PaddedBatchFetchTestCase extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Country.class, City.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + + configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); + configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); + + configuration.setProperty( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.name() ); + configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "15" ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12835") + public void paddedBatchFetchTest() throws Exception { + doInHibernate( this::sessionFactory, session -> { + // Having DEFAULT_BATCH_FETCH_SIZE=15 + // results in batchSizes = [15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + // Let's create 11 countries so batch size 15 will be used with padded values, + // this causes to have to remove 4 elements from list + int numberOfCountries = 11; + + IntStream.range( 0, numberOfCountries ).forEach( i -> { + Country c = new Country( "Country " + i ); + session.save( c ); + session.save( new City( "City " + i, c ) ); + } ); + } ); + + doInHibernate( this::sessionFactory, session -> { + List allCities = session.createQuery( "from City", City.class ).list(); + + // this triggers countries to be fetched in batch + assertNotNull( allCities.get( 0 ).getCountry().getName() ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/binding/cacheable/CacheableHbmXmlTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/binding/cacheable/CacheableHbmXmlTest.java index 2e70dfc1e0ff..a0bb5c61bb51 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/binding/cacheable/CacheableHbmXmlTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/binding/cacheable/CacheableHbmXmlTest.java @@ -19,6 +19,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.XmlMappingBinderAccess; +import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -38,8 +39,7 @@ * * @author Steve Ebersole */ -public class CacheableHbmXmlTest { - private static final Logger log = Logger.getLogger( CacheableHbmXmlTest.class ); +public class CacheableHbmXmlTest extends BaseUnitTestCase { private static final String HBM_RESOURCE_NAME = "org/hibernate/test/boot/binding/cacheable/SimpleEntity.hbm.xml"; 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/qualfiedTableNaming/NamespaceTest.java new file mode 100644 index 000000000000..97fc459b7f10 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/NamespaceTest.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.boot.database.qualfiedTableNaming; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; + +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11625") +public class NamespaceTest { + + private static final String EXPECTED_CATALOG_PHYSICAL_NAME = "catalog"; + private static final String EXPECTED_SCHEMA_PHYSICAL_NAME = "schema"; + + private final Database mockDatabase = mock( Database.class ); + private Namespace.Name name; + + @Before + public void setUp() { + when( mockDatabase.getPhysicalNamingStrategy() ).thenReturn( new TestNamingStrategy() ); + name = new Namespace.Name( + Identifier.toIdentifier( "DB1" ), + Identifier.toIdentifier( "PUBLIC" ) + ); + } + + @Test + public void testPhysicalNameSchemaAndCatalog() { + Namespace namespace = new Namespace( mockDatabase, name ); + + final Namespace.Name physicalName = namespace.getPhysicalName(); + + assertThat( physicalName.getSchema().getText(), is( EXPECTED_SCHEMA_PHYSICAL_NAME ) ); + assertThat( physicalName.getCatalog().getText(), is( EXPECTED_CATALOG_PHYSICAL_NAME ) ); + } + + public static class TestNamingStrategy implements PhysicalNamingStrategy { + @Override + public Identifier toPhysicalCatalogName( + Identifier name, JdbcEnvironment jdbcEnvironment) { + return new Identifier( EXPECTED_CATALOG_PHYSICAL_NAME, false ); + } + + @Override + public Identifier toPhysicalSchemaName( + Identifier name, JdbcEnvironment jdbcEnvironment) { + return new Identifier( EXPECTED_SCHEMA_PHYSICAL_NAME, false ); + } + + @Override + public Identifier toPhysicalTableName( + Identifier name, JdbcEnvironment jdbcEnvironment) { + return name; + } + + @Override + public Identifier toPhysicalSequenceName( + Identifier name, JdbcEnvironment jdbcEnvironment) { + return null; + } + + @Override + public Identifier toPhysicalColumnName( + Identifier name, JdbcEnvironment jdbcEnvironment) { + return name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java index dc4bfcaa93d4..a21b27896239 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java @@ -3,7 +3,6 @@ import java.io.Serializable; import java.util.Objects; import javax.persistence.Entity; -import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; @@ -12,8 +11,6 @@ import org.hibernate.cfg.Configuration; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; import org.junit.Test; @@ -77,7 +74,7 @@ public void setUp() { } protected int entityCount() { - return 100; + return 4; } @Test @@ -95,9 +92,12 @@ public void testUpdate() { @Test public void testDeleteFromPerson() { doInHibernate( this::sessionFactory, session -> { - int updateCount = session.createQuery( "delete from Person where employed = :employed" ) - .setParameter( "employed", false ) - .executeUpdate(); + //tag::batch-bulk-hql-temp-table-delete-query-example[] + int updateCount = session.createQuery( + "delete from Person where employed = :employed" ) + .setParameter( "employed", false ) + .executeUpdate(); + //end::batch-bulk-hql-temp-table-delete-query-example[] assertEquals( entityCount(), updateCount ); }); } @@ -112,6 +112,7 @@ public void testDeleteFromEngineer() { }); } + //tag::batch-bulk-hql-temp-table-base-class-example[] @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) public static class Person implements Serializable { @@ -126,6 +127,10 @@ public static class Person implements Serializable { private boolean employed; + //Getters and setters are omitted for brevity + + //end::batch-bulk-hql-temp-table-base-class-example[] + public Integer getId() { return id; } @@ -175,8 +180,11 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId(), getCompanyName() ); } + //tag::batch-bulk-hql-temp-table-base-class-example[] } + //end::batch-bulk-hql-temp-table-base-class-example[] + //tag::batch-bulk-hql-temp-table-sub-classes-example[] @Entity(name = "Doctor") public static class Doctor extends Person { } @@ -186,6 +194,10 @@ public static class Engineer extends Person { private boolean fellow; + //Getters and setters are omitted for brevity + + //end::batch-bulk-hql-temp-table-sub-classes-example[] + public boolean isFellow() { return fellow; } @@ -193,5 +205,7 @@ public boolean isFellow() { public void setFellow(boolean fellow) { this.fellow = fellow; } + //tag::batch-bulk-hql-temp-table-sub-classes-example[] } + //end::batch-bulk-hql-temp-table-sub-classes-example[] } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkIdTest.java index 2c193d34a1b9..eeef7b9a676b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkIdTest.java @@ -10,8 +10,6 @@ import org.hibernate.cfg.Configuration; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; import org.junit.Test; @@ -89,12 +87,10 @@ public void testUpdate() { @Test public void testDeleteFromPerson() { doInHibernate( this::sessionFactory, session -> { - //tag::batch-bulk-hql-temp-table-delete-query-example[] int updateCount = session.createQuery( "delete from Person where employed = :employed" ) .setParameter( "employed", false ) .executeUpdate(); - //end::batch-bulk-hql-temp-table-delete-query-example[] assertEquals( entityCount(), updateCount ); }); } @@ -109,7 +105,6 @@ public void testDeleteFromEngineer() { }); } - //tag::batch-bulk-hql-temp-table-base-class-example[] @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) public static class Person { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java new file mode 100644 index 000000000000..2ca13e346f7b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java @@ -0,0 +1,157 @@ +package org.hibernate.test.bulkid; + +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.hql.spi.id.inline.InlineIdsOrClauseBulkIdStrategy; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12561" ) +public class GlobalQuotedIdentifiersBulkIdTest + extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Doctor.class, + Engineer.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE ); + options.put( AvailableSettings.HQL_BULK_ID_STRATEGY, InlineIdsOrClauseBulkIdStrategy.class.getName() ); + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, entityManager -> { + for ( int i = 0; i < entityCount(); i++ ) { + Doctor doctor = new Doctor(); + doctor.setEmployed( ( i % 2 ) == 0 ); + doctor.setEmployedOn( Timestamp.valueOf( "2018-06-01 00:00:00" ) ); + entityManager.persist( doctor ); + } + + for ( int i = 0; i < entityCount(); i++ ) { + Engineer engineer = new Engineer(); + engineer.setEmployed( ( i % 2 ) == 0 ); + engineer.setEmployedOn( Timestamp.valueOf( "2018-06-01 00:00:00" ) ); + engineer.setFellow( ( i % 2 ) == 1 ); + entityManager.persist( engineer ); + } + }); + } + + protected int entityCount() { + return 5; + } + + @Test + public void testBulkUpdate() { + doInJPA( this::entityManagerFactory, entityManager -> { + int updateCount = entityManager.createQuery( + "UPDATE Person u " + + "SET u.employedOn = :date " + + "WHERE u.id IN :userIds" + ) + .setParameter( "date", Timestamp.valueOf( "2018-06-03 00:00:00" ) ) + .setParameter( "userIds", Arrays.asList(1L, 2L, 3L ) ) + .executeUpdate(); + + assertEquals(3, updateCount); + }); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + private boolean employed; + + @Temporal( TemporalType.TIMESTAMP ) + private Date employedOn; + + 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 Date getEmployedOn() { + return employedOn; + } + + public void setEmployedOn(Date employedOn) { + this.employedOn = employedOn; + } + + public boolean isEmployed() { + return employed; + } + + public void setEmployed(boolean employed) { + this.employed = employed; + } + } + + @Entity(name = "Doctor") + public static class Doctor extends Person { + } + + @Entity(name = "Engineer") + public static class Engineer extends Person { + + private boolean fellow; + + public boolean isFellow() { + return fellow; + } + + public void setFellow(boolean fellow) { + this.fellow = fellow; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bulkid/GlobalTemporaryTableBulkCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/bulkid/GlobalTemporaryTableBulkCompositeIdTest.java new file mode 100644 index 000000000000..5fe3df49e6f2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bulkid/GlobalTemporaryTableBulkCompositeIdTest.java @@ -0,0 +1,22 @@ +package org.hibernate.test.bulkid; + +import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; +import org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(PostgreSQL82Dialect.class) +public class GlobalTemporaryTableBulkCompositeIdTest extends AbstractBulkCompositeIdTest { + + @Override + protected Class getMultiTableBulkIdStrategyClass() { + return GlobalTemporaryTableBulkIdStrategy.class; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java index 2ec09c75e113..2df2a00dbbe3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java @@ -6,17 +6,16 @@ */ package org.hibernate.test.bytecode; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import org.hibernate.bytecode.internal.javassist.BulkAccessor; import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; - +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.Test; /** * @author Steve Ebersole @@ -55,10 +54,48 @@ public void testReflectionOptimization() { assertEquivalent( values, BeanReflectionHelper.TEST_VALUES ); } + @Test + @TestForIssue(jiraKey = "HHH-12584") + public void testAbstractClass() { + BytecodeProvider provider = Environment.getBytecodeProvider(); + ReflectionOptimizer reflectionOptimizer = provider.getReflectionOptimizer( AbstractClass.class, new String[]{ "getProperty" }, + new String[]{ "setProperty" }, new Class[]{ String.class } ); + assertNotNull( reflectionOptimizer ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12584") + public void testInterface() { + BytecodeProvider provider = Environment.getBytecodeProvider(); + ReflectionOptimizer reflectionOptimizer = provider.getReflectionOptimizer( Interface.class, new String[]{ "getProperty" }, + new String[]{ "setProperty" }, new Class[]{ String.class } ); + assertNotNull( reflectionOptimizer ); + } + private void assertEquivalent(Object[] checkValues, Object[] values) { assertEquals( "Different lengths", checkValues.length, values.length ); for ( int i = 0; i < checkValues.length; i++ ) { assertEquals( "different values at index [" + i + "]", checkValues[i], values[i] ); } } + + public static abstract class AbstractClass { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } + + public interface Interface { + + String getProperty(); + + void setProperty(String property); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/AbstractEnhancerTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/AbstractEnhancerTestTask.java deleted file mode 100644 index f23a52a73695..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/AbstractEnhancerTestTask.java +++ /dev/null @@ -1,64 +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.bytecode.enhancement; - -import org.hibernate.SessionFactory; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.service.ServiceRegistry; - -import org.hibernate.testing.ServiceRegistryBuilder; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestTask; - -/** - * @author Luis Barreiro - */ -public abstract class AbstractEnhancerTestTask implements EnhancerTestTask { - - private ServiceRegistry serviceRegistry; - private SessionFactory factory; - - public final void prepare(Configuration config) { - config.setProperty( Environment.HBM2DDL_AUTO, "create-drop" ); - - Class[] resources = getAnnotatedClasses(); - for ( Class resource : resources ) { - config.addAnnotatedClass( resource ); - } - - StandardServiceRegistryBuilder serviceBuilder = new StandardServiceRegistryBuilder( ); - serviceBuilder.addService( ClassLoaderService.class, new ClassLoaderServiceImpl( Thread.currentThread().getContextClassLoader() ) ); - - serviceBuilder.applySettings( config.getProperties() ); - serviceRegistry = serviceBuilder.build(); - factory = config.buildSessionFactory( serviceRegistry ); - } - - public final void complete() { - try { - cleanup(); - } - finally { - factory.close(); - factory = null; - if ( serviceRegistry != null ) { - ServiceRegistryBuilder.destroy( serviceRegistry ); - serviceRegistry = null; - } - } - } - - protected SessionFactory getFactory() { - return factory; - } - - protected abstract void cleanup(); - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java deleted file mode 100644 index 174aa1c6ccea..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java +++ /dev/null @@ -1,276 +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.bytecode.enhancement; - -import org.hibernate.bytecode.enhance.spi.UnloadedClass; - -import org.hibernate.test.bytecode.enhancement.detached.DetachedGetIdentifierTestTask; -import org.hibernate.test.bytecode.enhancement.cascade.CascadeWithFkConstraintTestTask; -import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.hibernate.testing.junit4.BaseUnitTestCase; -import org.hibernate.test.bytecode.enhancement.access.MixedAccessTestTask; -import org.hibernate.test.bytecode.enhancement.association.InheritedAttributeAssociationTestTask; -import org.hibernate.test.bytecode.enhancement.association.ManyToManyAssociationTestTask; -import org.hibernate.test.bytecode.enhancement.association.OneToManyAssociationTestTask; -import org.hibernate.test.bytecode.enhancement.association.OneToOneAssociationTestTask; -import org.hibernate.test.bytecode.enhancement.basic.BasicEnhancementTestTask; -import org.hibernate.test.bytecode.enhancement.basic.HHH9529TestTask; -import org.hibernate.test.bytecode.enhancement.cascade.CascadeDeleteTestTask; -import org.hibernate.test.bytecode.enhancement.dirty.DirtyTrackingCollectionTestTask; -import org.hibernate.test.bytecode.enhancement.dirty.DirtyTrackingTestTask; -import org.hibernate.test.bytecode.enhancement.eviction.EvictionTestTask; -import org.hibernate.test.bytecode.enhancement.extended.ExtendedAssociationManagementTestTasK; -import org.hibernate.test.bytecode.enhancement.extended.ExtendedEnhancementTestTask; -import org.hibernate.test.bytecode.enhancement.inherited.InheritedTestTask; -import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask1; -import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask2; -import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask3; -import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask4; -import org.hibernate.test.bytecode.enhancement.lazy.HHH_10708.UnexpectedDeleteOneTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.HHH_10708.UnexpectedDeleteThreeTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.HHH_10708.UnexpectedDeleteTwoTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.LazyBasicFieldNotInitializedTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.LazyCollectionLoadingTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.LazyCollectionNoTransactionLoadingTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.LazyLoadingIntegrationTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.LazyLoadingTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.LazyProxyOnEnhancedEntityTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.basic.LazyBasicFieldAccessTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.basic.LazyBasicPropertyAccessTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.cache.LazyInCacheTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.group.LazyGroupAccessTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.group.LazyGroupUpdateTestTask; -import org.hibernate.test.bytecode.enhancement.lazy.group.SimpleLazyGroupUpdateTestTask; -import org.hibernate.test.bytecode.enhancement.lazyCache.InitFromCacheTestTask; -import org.hibernate.test.bytecode.enhancement.mapped.MappedSuperclassTestTask; -import org.hibernate.test.bytecode.enhancement.merge.CompositeMergeTestTask; -import org.hibernate.test.bytecode.enhancement.ondemandload.LazyCollectionWithClearedSessionTestTask; -import org.hibernate.test.bytecode.enhancement.ondemandload.LazyCollectionWithClosedSessionTestTask; -import org.hibernate.test.bytecode.enhancement.ondemandload.LazyEntityLoadingWithClosedSessionTestTask; -import org.hibernate.test.bytecode.enhancement.otherentityentrycontext.OtherEntityEntryContextTestTask; -import org.hibernate.test.bytecode.enhancement.pk.EmbeddedPKTestTask; -import org.junit.Test; - -/** - * @author Luis Barreiro - */ -public class EnhancerTest extends BaseUnitTestCase { - - @Test - public void testBasic() { - EnhancerTestUtils.runEnhancerTestTask( BasicEnhancementTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-9529" ) - public void testFieldHHH9529() { - EnhancerTestUtils.runEnhancerTestTask( HHH9529TestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10851" ) - public void testAccess() { - EnhancerTestUtils.runEnhancerTestTask( MixedAccessTestTask.class ); - } - - @Test - public void testDirty() { - EnhancerTestUtils.runEnhancerTestTask( DirtyTrackingTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-11426" ) - public void testDetached() { - EnhancerTestUtils.runEnhancerTestTask( DetachedGetIdentifierTestTask.class ); - } - - @Test - public void testEviction() { - EnhancerTestUtils.runEnhancerTestTask( EvictionTestTask.class ); - } - - @Test - public void testOtherPersistenceContext() { - EnhancerTestUtils.runEnhancerTestTask( OtherEntityEntryContextTestTask.class ); - } - - @Test - public void testAssociation() { - EnhancerTestUtils.runEnhancerTestTask( OneToOneAssociationTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( OneToManyAssociationTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( ManyToManyAssociationTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( InheritedAttributeAssociationTestTask.class ); - } - - @Test - public void testLazy() { - EnhancerTestUtils.runEnhancerTestTask( LazyLoadingTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( LazyLoadingIntegrationTestTask.class ); - - EnhancerTestUtils.runEnhancerTestTask( LazyBasicPropertyAccessTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( LazyBasicFieldAccessTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10922" ) - public void testLazyProxyOnEnhancedEntity() { - EnhancerTestUtils.runEnhancerTestTask( LazyProxyOnEnhancedEntityTestTask.class, new EnhancerTestContext() { - @Override - public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { - return false; - } - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-11173" ) - public void testLazyCache() { - EnhancerTestUtils.runEnhancerTestTask( LazyInCacheTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10252" ) - public void testCascadeDelete() { - EnhancerTestUtils.runEnhancerTestTask( CascadeDeleteTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10252" ) - public void testCascadeFkDelete() { - EnhancerTestUtils.runEnhancerTestTask( CascadeWithFkConstraintTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10055" ) - public void testLazyCollectionHandling() { - EnhancerTestUtils.runEnhancerTestTask( LazyCollectionLoadingTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10267" ) - public void testLazyGroups() { - EnhancerTestUtils.runEnhancerTestTask( LazyGroupAccessTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-11155" ) - public void testLazyGroupsUpdate() { - EnhancerTestUtils.runEnhancerTestTask( LazyGroupUpdateTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-11155" ) - public void testLazyGroupsUpdateSimple() { - EnhancerTestUtils.runEnhancerTestTask( SimpleLazyGroupUpdateTestTask.class ); - } - - @Test - public void testLazyCollectionNoTransactionHandling() { - EnhancerTestUtils.runEnhancerTestTask( LazyCollectionNoTransactionLoadingTestTask.class ); - } - - @Test(timeout = 10000) - @TestForIssue( jiraKey = "HHH-10055" ) - @FailureExpected( jiraKey = "HHH-10055" ) - public void testOnDemand() { - EnhancerTestUtils.runEnhancerTestTask( LazyCollectionWithClearedSessionTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( LazyCollectionWithClosedSessionTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( LazyEntityLoadingWithClosedSessionTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10708" ) - public void testLazyUnexpectedDelete() { - EnhancerTestUtils.runEnhancerTestTask( UnexpectedDeleteOneTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( UnexpectedDeleteTwoTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( UnexpectedDeleteThreeTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-10646" ) - public void testMappedSuperclass() { - EnhancerTestUtils.runEnhancerTestTask( MappedSuperclassTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( MappedSuperclassTestTask.class, new EnhancerTestContext() { - @Override - public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { - // HHH-10981 - Without lazy loading, the generation of getters and setters has a different code path - return false; - } - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-11284" ) - public void testInherited() { - EnhancerTestUtils.runEnhancerTestTask( InheritedTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( InheritedTestTask.class, new EnhancerTestContext() { - @Override - public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { - // HHH-10981 - Without lazy loading, the generation of getters and setters has a different code path - return false; - } - } ); - } - - @Test - public void testMerge() { - EnhancerTestUtils.runEnhancerTestTask( CompositeMergeTestTask.class ); - } - - @Test - public void testEmbeddedPK() { - EnhancerTestUtils.runEnhancerTestTask( EmbeddedPKTestTask.class ); - } - - @Test - public void testExtendedEnhancement() { - EnhancerTestUtils.runEnhancerTestTask( ExtendedEnhancementTestTask.class ); - EnhancerTestUtils.runEnhancerTestTask( ExtendedAssociationManagementTestTasK.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-3949" ) - @FailureExpected( jiraKey = "HHH-3949" ) - public void testJoinFetchLazyToOneAttributeHql() { - EnhancerTestUtils.runEnhancerTestTask( HHH3949TestTask1.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-3949" ) - @FailureExpected( jiraKey = "HHH-3949" ) - public void testJoinFetchLazyToOneAttributeHql2() { - EnhancerTestUtils.runEnhancerTestTask( HHH3949TestTask2.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-3949" ) - @FailureExpected( jiraKey = "HHH-3949" ) - public void testHHH3949() { - EnhancerTestUtils.runEnhancerTestTask( HHH3949TestTask3.class ); - EnhancerTestUtils.runEnhancerTestTask( HHH3949TestTask4.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-9937") - public void testLazyBasicFieldNotInitialized() { - EnhancerTestUtils.runEnhancerTestTask( LazyBasicFieldNotInitializedTestTask.class ); - } - - @Test - @TestForIssue( jiraKey = "HHH-11293") - public void testDirtyCollection() { - EnhancerTestUtils.runEnhancerTestTask( DirtyTrackingCollectionTestTask.class ); - } - - @Test - public void testInitFromCache() { - EnhancerTestUtils.runEnhancerTestTask( InitFromCacheTestTask.class ); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/access/MixedAccessTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/access/MixedAccessTest.java new file mode 100644 index 000000000000..0264035f1b26 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/access/MixedAccessTest.java @@ -0,0 +1,191 @@ +package org.hibernate.test.bytecode.enhancement.access; + +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.stream.Collectors.joining; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * Requires a custom enhancement context to disable dirty checking as bytecode enhancement is not expected to fully work with AccessType.PROPERTY + * In particular, the properties changed are not marked dirty and therefore not updated in the DB (failing the checks in @After method) + * + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-10851" ) +@RunWith( BytecodeEnhancerRunner.class ) +@CustomEnhancementContext( MixedAccessTest.NoDirtyCheckingContext.class ) +public class MixedAccessTest extends BaseCoreFunctionalTestCase { + + private static final Pattern PARAM_PATTERN = Pattern.compile( "\\{\\\"(.*)\\\"\\:\\\"(.*)\\\"\\}" ); + private static final Function MAPPING_FUNCTION = e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\""; + private static final String ID = "foo", PARAM_KEY = "paramName", PARAM_VAL = "paramValue", PARAMS_AS_STR = "{\"" + PARAM_KEY + "\":\"" + PARAM_VAL + "\"}"; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{TestEntity.class, TestOtherEntity.class}; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + TestEntity testEntity = new TestEntity( ID ); + testEntity.setParamsAsString( PARAMS_AS_STR ); + s.persist( testEntity ); + + TestOtherEntity testOtherEntity = new TestOtherEntity( ID ); + testOtherEntity.setParamsAsString( PARAMS_AS_STR ); + s.persist( testOtherEntity ); + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + TestEntity testEntity = s.get( TestEntity.class, ID ); + Assert.assertEquals( PARAMS_AS_STR, testEntity.getParamsAsString() ); + + TestOtherEntity testOtherEntity = s.get( TestOtherEntity.class, ID ); + Assert.assertEquals( PARAMS_AS_STR, testOtherEntity.getParamsAsString() ); + + // Clean parameters + testEntity.setParamsAsString( "{}" ); + testOtherEntity.setParamsAsString( "{}" ); + } ); + } + + @After + public void cleanup() { + doInHibernate( this::sessionFactory, s -> { + TestEntity testEntity = s.get( TestEntity.class, ID ); + Assert.assertTrue( testEntity.getParams().isEmpty() ); + + TestOtherEntity testOtherEntity = s.get( TestOtherEntity.class, ID ); + Assert.assertTrue( testOtherEntity.getParams().isEmpty() ); + } ); + } + + // --- // + + @Entity + @Table( name = "TEST_ENTITY" ) + private static class TestEntity { + + @Id + String name; + + @Transient + Map params = new LinkedHashMap<>(); + + TestEntity(String name) { + this(); + this.name = name; + } + + TestEntity() { + } + + Map getParams() { + return Collections.unmodifiableMap( params ); + } + + void setParams(Map params) { + this.params.clear(); + this.params.putAll( params ); + } + + @Column( name = "params", length = 4000 ) + @Access( AccessType.PROPERTY ) + String getParamsAsString() { + return "{" + params.entrySet().stream().map( MAPPING_FUNCTION ).collect( joining( "," ) ) + "}"; + } + + @SuppressWarnings( "unchecked" ) + void setParamsAsString(String string) { + Matcher matcher = PARAM_PATTERN.matcher( string ); + + params.clear(); + if ( matcher.matches() && matcher.groupCount() > 1 ) { + params.put( matcher.group( 1 ), matcher.group( 2 ) ); + } + } + } + + @Entity + @Table( name = "OTHER_ENTITY" ) + @Access( AccessType.FIELD ) + private static class TestOtherEntity { + + @Id + String name; + + @Transient + Map params = new LinkedHashMap<>(); + + TestOtherEntity(String name) { + this(); + this.name = name; + } + + TestOtherEntity() { + } + + Map getParams() { + return Collections.unmodifiableMap( params ); + } + + void setParams(Map params) { + this.params.clear(); + this.params.putAll( params ); + } + + @Column( name = "params", length = 4000 ) + @Access( AccessType.PROPERTY ) + String getParamsAsString() { + return "{" + params.entrySet().stream().map( MAPPING_FUNCTION ).collect( joining( "," ) ) + "}"; + } + + @SuppressWarnings( "unchecked" ) + void setParamsAsString(String string) { + Matcher matcher = PARAM_PATTERN.matcher( string ); + + params.clear(); + if ( matcher.matches() && matcher.groupCount() > 1 ) { + params.put( matcher.group( 1 ), matcher.group( 2 ) ); + } + } + } + + // --- // + + public static class NoDirtyCheckingContext extends EnhancerTestContext { + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/access/MixedAccessTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/access/MixedAccessTestTask.java deleted file mode 100644 index 1de19dae14b0..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/access/MixedAccessTestTask.java +++ /dev/null @@ -1,217 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.access; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; -import javax.persistence.Access; -import javax.persistence.AccessType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -/** - * @author Luis Barreiro - */ -public class MixedAccessTestTask extends AbstractEnhancerTestTask { - - private static ScriptEngine engine = new ScriptEngineManager().getEngineByName( "javascript" ); - private static boolean cleanup = false; - - public Class[] getAnnotatedClasses() { - return new Class[]{TestEntity.class, TestOtherEntity.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - try { - TestEntity testEntity = new TestEntity( "foo" ); - testEntity.setParamsAsString( "{\"paramName\":\"paramValue\"}" ); - s.persist( testEntity ); - - TestOtherEntity testOtherEntity = new TestOtherEntity( "foo" ); - testOtherEntity.setParamsAsString( "{\"paramName\":\"paramValue\"}" ); - s.persist( testOtherEntity ); - - s.getTransaction().commit(); - } - catch ( Exception e ) { - s.getTransaction().rollback(); - throw e; - } - finally { - s.close(); - } - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - try { - TestEntity testEntity = s.get( TestEntity.class, "foo" ); - Assert.assertEquals( "{\"paramName\":\"paramValue\"}", testEntity.getParamsAsString() ); - - TestOtherEntity testOtherEntity = s.get( TestOtherEntity.class, "foo" ); - Assert.assertEquals( "{\"paramName\":\"paramValue\"}", testOtherEntity.getParamsAsString() ); - - // Clean parameters - cleanup = true; - testEntity.setParamsAsString( "{}" ); - testOtherEntity.setParamsAsString( "{}" ); - - s.getTransaction().commit(); - } - catch ( RuntimeException e ) { - s.getTransaction().rollback(); - throw e; - } - finally { - s.close(); - } - } - - protected void cleanup() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - try { - TestEntity testEntity = s.get( TestEntity.class, "foo" ); - Assert.assertTrue( testEntity.getParams().isEmpty() ); - - TestOtherEntity testOtherEntity = s.get( TestOtherEntity.class, "foo" ); - Assert.assertTrue( testOtherEntity.getParams().isEmpty() ); - - s.getTransaction().commit(); - } - catch ( RuntimeException e ) { - s.getTransaction().rollback(); - throw e; - } - finally { - s.close(); - } - } - - @Entity - private static class TestEntity { - - @Id - String name; - - @Transient - Map params = new LinkedHashMap<>(); - - public TestEntity(String name) { - this(); - this.name = name; - } - - protected TestEntity() { - } - - public Map getParams() { - return params; - } - - public void setParams(Map params) { - this.params = params; - } - - @Column( name = "params", length = 4000 ) - @Access( AccessType.PROPERTY ) - public String getParamsAsString() { - if ( params.size() > 0 ) { - // Convert to JSON - return "{" + params.entrySet().stream().map( - e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"" - ).collect( Collectors.joining( "," ) ) + "}"; - } - return null; - } - - public void setParamsAsString(String string) { - params.clear(); - - try { - params.putAll( (Map) engine.eval( "Java.asJSONCompatible(" + string + ")" ) ); - } catch ( ScriptException ignore ) { - // JDK 8u60 required --- use hard coded values to pass the test - if ( !cleanup ) { - params.put( "paramName", "paramValue" ); - } - } - } - } - - @Entity - @Table(name = "other") - @Access( AccessType.FIELD ) - private static class TestOtherEntity { - - @Id - String name; - - @Transient - Map params = new LinkedHashMap<>(); - - public TestOtherEntity(String name) { - this(); - this.name = name; - } - - protected TestOtherEntity() { - } - - public Map getParams() { - return params; - } - - public void setParams(Map params) { - this.params = params; - } - - @Column( name = "params", length = 4000 ) - @Access( AccessType.PROPERTY ) - public String getParamsAsString() { - if ( params.size() > 0 ) { - // Convert to JSON - return "{" + params.entrySet().stream().map( - e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"" - ).collect( Collectors.joining( "," ) ) + "}"; - } - return null; - } - - public void setParamsAsString(String string) { - params.clear(); - - try { - params.putAll( (Map) engine.eval( "Java.asJSONCompatible(" + string + ")" ) ); - } catch ( ScriptException ignore ) { - // JDK 8u60 required --- use hard coded values to pass the test - if ( !cleanup ) { - params.put( "paramName", "paramValue" ); - } - } - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/Customer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/Customer.java deleted file mode 100644 index bfc9fc9f5235..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/Customer.java +++ /dev/null @@ -1,123 +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.bytecode.enhancement.association; - -import java.util.ArrayList; -import java.util.List; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; -import javax.persistence.Version; - -/** - * @author Ståle W. Pedersen - */ -@Entity -public class Customer { - - @Id - private int id; - - @OneToOne - private User user; - - private String firstName; - - private String lastName; - - @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER) - private List customerInventories; - - @Version - private int version; - - public Customer() { - } - - public Integer getId() { - return id; - } - - public void setId(Integer customerId) { - this.id = customerId; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public List getInventories() { - if ( customerInventories == null ) { - customerInventories = new ArrayList(); - } - return customerInventories; - } - - public void setInventories (List inventories) { - this.customerInventories = inventories; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public void addInventory(CustomerInventory inventory) { - List list = getInventories(); - list.add( inventory ); - customerInventories = list; - } - - public CustomerInventory addInventory(String item) { - CustomerInventory inventory = new CustomerInventory( this, item ); - getInventories().add( inventory ); - return inventory; - } - - public int getVersion() { - return version; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( o == null || getClass() != o.getClass() ) { - return false; - } - return id == ( (Customer) o ).id; - } - - @Override - public int hashCode() { - return new Integer( id ).hashCode(); - } - - @Override - public String toString() { - return this.getFirstName() + " " + this.getLastName(); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/CustomerInventory.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/CustomerInventory.java deleted file mode 100644 index 4a34098c8f76..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/CustomerInventory.java +++ /dev/null @@ -1,103 +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.bytecode.enhancement.association; - -/** - * @author Ståle W. Pedersen - */ - -import java.util.Comparator; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.ManyToOne; -import javax.persistence.Version; - -@Entity -public class CustomerInventory implements Comparator { - - @Id - private Long id; - - @Id - private int custId; - - @ManyToOne(cascade = CascadeType.MERGE) - private Customer customer; - - @ManyToOne(cascade = CascadeType.MERGE) - private String vehicle; - - @Version - private int version; - - public CustomerInventory() { - } - - CustomerInventory(Customer customer, String vehicle) { - this.customer = customer; - this.vehicle = vehicle; - ; - } - - public String getVehicle() { - return vehicle; - } - - public Long getId() { - return id; - } - - public Customer getCustomer() { - return customer; - } - - public void setCustomer(Customer customer) { - this.customer = customer; - } - - public int getCustId() { - return custId; - } - - public int getVersion() { - return version; - } - - public int compare(CustomerInventory cdb1, CustomerInventory cdb2) { - return cdb1.id.compareTo( cdb2.id ); - } - - @Override - public boolean equals(Object obj) { - if ( obj == this ) { - return true; - } - if ( obj == null || !( obj instanceof CustomerInventory ) ) { - return false; - } - if ( this.id == ( (CustomerInventory) obj ).id ) { - return true; - } - if ( this.id != null && ( (CustomerInventory) obj ).id == null ) { - return false; - } - if ( this.id == null && ( (CustomerInventory) obj ).id != null ) { - return false; - } - - return this.id.equals( ( (CustomerInventory) obj ).id ); - } - - @Override - public int hashCode() { - int result = id.hashCode(); - result = 31 * result + custId; - return result; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/Group.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/Group.java deleted file mode 100644 index 713f1da24198..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/Group.java +++ /dev/null @@ -1,55 +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.bytecode.enhancement.association; - -import java.util.HashSet; -import java.util.Set; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.ManyToMany; - -/** - * @author Ståle W. Pedersen - */ -@Entity -public class Group { - - @Id - private int id; - - @Column - private String name; - - @ManyToMany(mappedBy = "groups") - private Set users = new HashSet(); - - public Group() { - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Set getUsers() { - return users; - } - - public void setUsers(Set users) { - this.users = users; - } - - public void removeUser(User user) { - Set set = this.users; - set.remove( user ); - this.users = set; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/InheritedAttributeAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/InheritedAttributeAssociationTest.java new file mode 100644 index 000000000000..69fcadfbebc4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/InheritedAttributeAssociationTest.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.test.bytecode.enhancement.association; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import java.util.List; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-11050" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class InheritedAttributeAssociationTest { + + @Test + public void test() { + // The mapping is wrong but the point is that the enhancement phase does not need to fail. See JIRA for further detail + + // If enhancement of 'items' attribute fails, 'name' won't be enhanced + Author author = new Author(); + author.name = "Bernardo Soares"; + EnhancerTestUtils.checkDirtyTracking( author, "name" ); + } + + // --- // + + @Entity + private static class Author { + + @Id + @GeneratedValue + Long id; + + @OneToMany( fetch = FetchType.LAZY, mappedBy = "author" ) + List items; + + // keep this field after 'items' + String name; + } + + @MappedSuperclass + @Inheritance( strategy = InheritanceType.SINGLE_TABLE ) + @DiscriminatorColumn( name = "type", discriminatorType = DiscriminatorType.STRING ) + private static abstract class Item { + + @Id + @GeneratedValue + Long id; + + @ManyToOne( fetch = FetchType.LAZY ) + Author author; + } + + @Entity + @DiscriminatorValue( "child" ) + private static class ChildItem extends Item { + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/InheritedAttributeAssociationTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/InheritedAttributeAssociationTestTask.java deleted file mode 100644 index d76eb2386cc3..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/InheritedAttributeAssociationTestTask.java +++ /dev/null @@ -1,86 +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.bytecode.enhancement.association; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; - -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.ManyToOne; -import javax.persistence.MappedSuperclass; -import javax.persistence.OneToMany; -import java.util.List; - -/** - * @author Luis Barreiro - */ -public class InheritedAttributeAssociationTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] { Author.class, Item.class, ChildItem.class }; - } - - @Override - public void prepare() { - } - - @Override - @TestForIssue( jiraKey = "HHH-11050") - public void execute() { - // The mapping is wrong but the point is that the enhancement phase does not need to fail. See JIRA for further detail - - // If enhancement of 'items' attribute fails, 'name' won't be enhanced - Author author = new Author(); - author.name = "Bernardo Soares"; - EnhancerTestUtils.checkDirtyTracking( author, "name" ); - } - - @Override - protected void cleanup() { - } - - @Entity - public static class Author { - - @Id @GeneratedValue - Long id; - - @OneToMany(fetch = FetchType.LAZY, mappedBy="author") - List items; - - // keep this field after 'items' - String name; - } - - @MappedSuperclass - @Inheritance(strategy = InheritanceType.SINGLE_TABLE) - @DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING) - public static abstract class Item { - - @Id - @GeneratedValue - Long id; - - @ManyToOne(fetch = FetchType.LAZY) - Author author; - } - - @Entity - @DiscriminatorValue("child") - public static class ChildItem extends Item { - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationTest.java new file mode 100644 index 000000000000..b4843d27eceb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationTest.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.bytecode.enhancement.association; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class ManyToManyAssociationTest { + + @Test + public void test() { + Group group = new Group(); + Group anotherGroup = new Group(); + + User user = new User(); + User anotherUser = new User(); + + user.addGroup( group ); + user.addGroup( anotherGroup ); + anotherUser.addGroup( group ); + + Assert.assertEquals( 2, group.getUsers().size() ); + Assert.assertEquals( 1, anotherGroup.getUsers().size() ); + + group.resetUsers(); + + Assert.assertEquals( 1, user.getGroups().size() ); + Assert.assertEquals( 0, anotherUser.getGroups().size() ); + + // Test remove + user.addGroup( group ); + anotherUser.addGroup( group ); + + Assert.assertEquals( 2, group.getUsers().size() ); + Assert.assertEquals( 1, anotherGroup.getUsers().size() ); + + Set groups = new HashSet<>( user.getGroups() ); + groups.remove( group ); + user.setGroups( groups ); + + Assert.assertEquals( 1, group.getUsers().size() ); + Assert.assertEquals( 1, anotherGroup.getUsers().size() ); + + groups.remove( anotherGroup ); + user.setGroups( groups ); + + Assert.assertEquals( 1, group.getUsers().size() ); + // This happens (and is expected) because there was no snapshot taken before remove + Assert.assertEquals( 1, anotherGroup.getUsers().size() ); + } + + // -- // + + @Entity + private static class Group { + + @Id + Long id; + + @Column + String name; + + @ManyToMany( mappedBy = "groups" ) + Set users = new HashSet<>(); + + Set getUsers() { + return Collections.unmodifiableSet( users ); + } + + void resetUsers() { + // this wouldn't trigger association management: users.clear(); + users = new HashSet<>(); + } + } + + @Entity + private static class User { + + @Id + Long id; + + String password; + + @ManyToMany + Set groups; + + void addGroup(Group group) { + Set groups = this.groups == null ? new HashSet<>() : this.groups; + groups.add( group ); + this.groups = groups; + } + + Set getGroups() { + return Collections.unmodifiableSet( groups ); + } + + void setGroups(Set groups) { + this.groups = groups; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationTestTask.java deleted file mode 100644 index f92a6a65bd43..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationTestTask.java +++ /dev/null @@ -1,70 +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.bytecode.enhancement.association; - -import java.util.HashSet; -import java.util.Set; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -/** - * @author Luis Barreiro - */ -public class ManyToManyAssociationTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {Group.class, User.class}; - } - - public void prepare() { - } - - public void execute() { - Group group = new Group(); - Group anotherGroup = new Group(); - - User user = new User(); - User anotherUser = new User(); - - user.addGroup( group ); - user.addGroup( anotherGroup ); - anotherUser.addGroup( group ); - - Assert.assertTrue( group.getUsers().size() == 2 ); - Assert.assertTrue( anotherGroup.getUsers().size() == 1 ); - - group.setUsers( new HashSet() ); - - Assert.assertTrue( user.getGroups().size() == 1 ); - Assert.assertTrue( anotherUser.getGroups().size() == 0 ); - - // Test remove - user.addGroup( group ); - anotherUser.addGroup( group ); - - Assert.assertTrue( group.getUsers().size() == 2 ); - Assert.assertTrue( anotherGroup.getUsers().size() == 1 ); - - Set groups = new HashSet( user.getGroups() ); - groups.remove( group ); - user.setGroups( groups ); - - Assert.assertTrue( group.getUsers().size() == 1 ); - Assert.assertTrue( anotherGroup.getUsers().size() == 1 ); - - groups.remove( anotherGroup ); - user.setGroups( groups ); - - Assert.assertTrue( group.getUsers().size() == 1 ); - // This happens (and is expected) because there was no snapshot taken beforeQuery remove - Assert.assertTrue( anotherGroup.getUsers().size() == 1 ); - } - - protected void cleanup() { - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToManyAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToManyAssociationTest.java new file mode 100644 index 000000000000..ac6f46c6d712 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToManyAssociationTest.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.association; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class OneToManyAssociationTest { + + @Test + public void test() { + Customer customer = new Customer(); + Assert.assertTrue( customer.getInventories().isEmpty() ); + + CustomerInventory customerInventory = new CustomerInventory(); + customerInventory.setCustomer( customer ); + + Assert.assertEquals( 1, customer.getInventories().size() ); + Assert.assertTrue( customer.getInventories().contains( customerInventory ) ); + + Customer anotherCustomer = new Customer(); + Assert.assertTrue( anotherCustomer.getInventories().isEmpty() ); + customerInventory.setCustomer( anotherCustomer ); + + Assert.assertTrue( customer.getInventories().isEmpty() ); + Assert.assertEquals( 1, anotherCustomer.getInventories().size() ); + Assert.assertSame( customerInventory, anotherCustomer.getInventories().get( 0 ) ); + + customer.addInventory( customerInventory ); + + Assert.assertSame( customer, customerInventory.getCustomer() ); + Assert.assertTrue( anotherCustomer.getInventories().isEmpty() ); + Assert.assertEquals( 1, customer.getInventories().size() ); + + customer.addInventory( new CustomerInventory() ); + Assert.assertEquals( 2, customer.getInventories().size() ); + + // Test remove + customer.removeInventory( customerInventory ); + Assert.assertEquals( 1, customer.getInventories().size() ); + + // This happens (and is expected) because there was no snapshot taken before remove + Assert.assertNotNull( customerInventory.getCustomer() ); + } + + // --- // + + @Entity + private static class Customer { + + @Id + Long id; + + String name; + + @OneToMany( mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER ) + List customerInventories = new ArrayList<>(); + + void addInventory(CustomerInventory inventory) { + List list = customerInventories; + list.add( inventory ); + customerInventories = list; + } + + List getInventories() { + return Collections.unmodifiableList( customerInventories ); + } + + void removeInventory(CustomerInventory inventory) { + customerInventories.remove( inventory ); + } + } + + @Entity + private static class CustomerInventory { + + @Id + Long id; + + @Id + Long custId; + + @ManyToOne( cascade = CascadeType.MERGE ) + Customer customer; + + @ManyToOne( cascade = CascadeType.MERGE ) + String vehicle; + + Customer getCustomer() { + return customer; + } + + void setCustomer(Customer customer) { + this.customer = customer; + } + + void setVehicle(String vehicle) { + this.vehicle = vehicle; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToManyAssociationTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToManyAssociationTestTask.java deleted file mode 100644 index 1149d45209e2..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToManyAssociationTestTask.java +++ /dev/null @@ -1,65 +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.bytecode.enhancement.association; - -import java.util.List; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -/** - * @author Luis Barreiro - */ -public class OneToManyAssociationTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {Customer.class, CustomerInventory.class, Group.class, User.class}; - } - - public void prepare() { - } - - public void execute() { - Customer customer = new Customer(); - Assert.assertTrue( customer.getInventories().isEmpty() ); - - CustomerInventory customerInventory = new CustomerInventory(); - customerInventory.setCustomer( customer ); - - Assert.assertTrue( customer.getInventories().size() == 1 ); - Assert.assertTrue( customer.getInventories().contains( customerInventory ) ); - - Customer anotherCustomer = new Customer(); - Assert.assertTrue( anotherCustomer.getInventories().isEmpty() ); - customerInventory.setCustomer( anotherCustomer ); - - Assert.assertTrue( customer.getInventories().isEmpty() ); - Assert.assertTrue( anotherCustomer.getInventories().size() == 1 ); - Assert.assertTrue( anotherCustomer.getInventories().get( 0 ) == customerInventory ); - - customer.addInventory( customerInventory ); - - Assert.assertTrue( customerInventory.getCustomer() == customer ); - Assert.assertTrue( anotherCustomer.getInventories().isEmpty() ); - Assert.assertTrue( customer.getInventories().size() == 1 ); - - customer.addInventory( new CustomerInventory() ); - Assert.assertTrue( customer.getInventories().size() == 2 ); - - // Test remove - - List inventories = customer.getInventories(); - inventories.remove( customerInventory ); - customer.setInventories( inventories ); - - // This happens (and is expected) because there was no snapshot taken beforeQuery remove - Assert.assertNotNull( customerInventory.getCustomer() ); - } - - protected void cleanup() { - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTest.java new file mode 100644 index 000000000000..d40f8591cadf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTest.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 . + */ +package org.hibernate.test.bytecode.enhancement.association; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import java.util.UUID; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class OneToOneAssociationTest { + + @Test + public void test() { + User user = new User(); + user.setLogin( UUID.randomUUID().toString() ); + + Customer customer = new Customer(); + customer.setUser( user ); + + Assert.assertEquals( customer, user.getCustomer() ); + + // check dirty tracking is set automatically with bi-directional association management + EnhancerTestUtils.checkDirtyTracking( user, "login", "customer" ); + + User anotherUser = new User(); + anotherUser.setLogin( UUID.randomUUID().toString() ); + + customer.setUser( anotherUser ); + + Assert.assertNull( user.getCustomer() ); + Assert.assertEquals( customer, anotherUser.getCustomer() ); + + user.setCustomer( new Customer() ); + + Assert.assertEquals( user, user.getCustomer().getUser() ); + } + + // --- // + + @Entity + private static class Customer { + + @Id + Long id; + + @OneToOne + User user; + + User getUser() { + return user; + } + + void setUser(User newUser) { + user = newUser; + } + } + + @Entity + private static class User { + + @Id + Long id; + + String login; + + String password; + + @OneToOne( mappedBy = "user" ) + Customer customer; + + void setLogin(String login) { + this.login = login; + } + + Customer getCustomer() { + return customer; + } + + void setCustomer(Customer customer) { + this.customer = customer; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTestTask.java deleted file mode 100644 index 1767b82a79dc..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTestTask.java +++ /dev/null @@ -1,54 +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.bytecode.enhancement.association; - -import java.util.UUID; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.junit.Assert; - -/** - * @author Luis Barreiro - */ -public class OneToOneAssociationTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {Customer.class, User.class}; - } - - public void prepare() { - } - - public void execute() { - User user = new User(); - user.setLogin( UUID.randomUUID().toString() ); - - Customer customer = new Customer(); - customer.setUser( user ); - - Assert.assertEquals( customer, user.getCustomer() ); - - // check dirty tracking is set automatically with bi-directional association management - EnhancerTestUtils.checkDirtyTracking( user, "login", "customer" ); - - User anotherUser = new User(); - anotherUser.setLogin( UUID.randomUUID().toString() ); - - customer.setUser( anotherUser ); - - Assert.assertNull( user.getCustomer() ); - Assert.assertEquals( customer, anotherUser.getCustomer() ); - - user.setCustomer( new Customer() ); - - Assert.assertEquals( user, user.getCustomer().getUser() ); - } - - protected void cleanup() { - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/User.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/User.java deleted file mode 100644 index 67049adabb84..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/User.java +++ /dev/null @@ -1,75 +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.bytecode.enhancement.association; - -import java.util.HashSet; -import java.util.Set; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.ManyToMany; -import javax.persistence.OneToOne; - -/** - * @author Ståle W. Pedersen - */ -@Entity -public class User { - - @Id - private int id; - - private String login; - - private String password; - - @OneToOne(mappedBy = "user") - private Customer customer; - - @ManyToMany - private Set groups; - - public User() { - } - - public Customer getCustomer() { - return customer; - } - - public void setCustomer(Customer customer) { - this.customer = customer; - } - - public String getLogin() { - return login; - } - - public void setLogin(String login) { - this.login = login; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public void addGroup(Group group) { - Set groups = ( this.groups == null ? new HashSet() : this.groups ); - groups.add( group ); - this.groups = groups; - } - - public Set getGroups() { - return groups; - } - - public void setGroups(Set groups) { - this.groups = groups; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java new file mode 100644 index 000000000000..fbe4ef03afea --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.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.bytecode.enhancement.basic; + +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class BasicEnhancementTest { + + @Test + public void basicManagedTest() { + SimpleEntity entity = new SimpleEntity(); + + // Call the new ManagedEntity methods + assertTyping( ManagedEntity.class, entity ); + ManagedEntity managedEntity = (ManagedEntity) entity; + assertSame( entity, managedEntity.$$_hibernate_getEntityInstance() ); + + assertNull( managedEntity.$$_hibernate_getEntityEntry() ); + managedEntity.$$_hibernate_setEntityEntry( EnhancerTestUtils.makeEntityEntry() ); + assertNotNull( managedEntity.$$_hibernate_getEntityEntry() ); + managedEntity.$$_hibernate_setEntityEntry( null ); + assertNull( managedEntity.$$_hibernate_getEntityEntry() ); + + managedEntity.$$_hibernate_setNextManagedEntity( managedEntity ); + managedEntity.$$_hibernate_setPreviousManagedEntity( managedEntity ); + assertSame( managedEntity, managedEntity.$$_hibernate_getNextManagedEntity() ); + assertSame( managedEntity, managedEntity.$$_hibernate_getPreviousManagedEntity() ); + } + + @Test + public void basicInterceptableTest() { + SimpleEntity entity = new SimpleEntity(); + + assertTyping( PersistentAttributeInterceptable.class, entity ); + PersistentAttributeInterceptable interceptableEntity = (PersistentAttributeInterceptable) entity; + + assertNull( interceptableEntity.$$_hibernate_getInterceptor() ); + interceptableEntity.$$_hibernate_setInterceptor( new ObjectAttributeMarkerInterceptor() ); + assertNotNull( interceptableEntity.$$_hibernate_getInterceptor() ); + + assertNull( EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ) ); + entity.setAnObject( new Object() ); + + assertSame( ObjectAttributeMarkerInterceptor.WRITE_MARKER, EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ) ); + assertSame( ObjectAttributeMarkerInterceptor.READ_MARKER, entity.getAnObject() ); + + entity.setAnObject( null ); + assertSame( ObjectAttributeMarkerInterceptor.WRITE_MARKER, EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ) ); + } + + @Test + public void basicExtendedEnhancementTest() { + // test uses ObjectAttributeMarkerInterceptor to ensure that field access is routed through enhanced methods + + SimpleEntity entity = new SimpleEntity(); + ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( new ObjectAttributeMarkerInterceptor() ); + + Object decoy = new Object(); + entity.anUnspecifiedObject = decoy; + + Object gotByReflection = EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ); + assertNotSame( decoy, gotByReflection ); + assertSame( ObjectAttributeMarkerInterceptor.WRITE_MARKER, gotByReflection ); + + Object entityObject = entity.anUnspecifiedObject; + + assertNotSame( decoy, entityObject ); + assertSame( ObjectAttributeMarkerInterceptor.READ_MARKER, entityObject ); + + // do some more calls on the various types, without the interceptor + ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( null ); + + entity.id = 1234567890L; + Assert.assertEquals( 1234567890L, (long) entity.getId() ); + + entity.name = "Entity Name"; + assertSame( "Entity Name", entity.name ); + + entity.active = true; + assertTrue( entity.getActive() ); + + entity.someStrings = Arrays.asList( "A", "B", "C", "D" ); + assertArrayEquals( new String[]{"A", "B", "C", "D"}, entity.someStrings.toArray() ); + } + + // --- // + + public static class ObjectAttributeMarkerInterceptor implements PersistentAttributeInterceptor { + + public static final Object READ_MARKER = new Object(); + public static final Object WRITE_MARKER = new Object(); + + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { + return oldValue; + } + + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + return newValue; + } + + @Override + public byte readByte(Object obj, String name, byte oldValue) { + return oldValue; + } + + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + return newValue; + } + + @Override + public char readChar(Object obj, String name, char oldValue) { + return oldValue; + } + + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { + return newValue; + } + + @Override + public short readShort(Object obj, String name, short oldValue) { + return oldValue; + } + + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { + return newValue; + } + + @Override + public int readInt(Object obj, String name, int oldValue) { + return oldValue; + } + + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { + return newValue; + } + + @Override + public float readFloat(Object obj, String name, float oldValue) { + return oldValue; + } + + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { + return newValue; + } + + @Override + public double readDouble(Object obj, String name, double oldValue) { + return oldValue; + } + + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { + return newValue; + } + + @Override + public long readLong(Object obj, String name, long oldValue) { + return oldValue; + } + + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { + return newValue; + } + + @Override + public Object readObject(Object obj, String name, Object oldValue) { + return READ_MARKER; + } + + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + return WRITE_MARKER; + } + } + + // --- // + + @Entity + private static class SimpleEntity { + + Object anUnspecifiedObject; + + @Id + Long id; + + String name; + + Boolean active; + + List someStrings; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + Object getAnObject() { + return anUnspecifiedObject; + } + + void setAnObject(Object providedObject) { + this.anUnspecifiedObject = providedObject; + } + + List getSomeStrings() { + return Collections.unmodifiableList( someStrings ); + } + + void setSomeStrings(List someStrings) { + this.someStrings = someStrings; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTestTask.java deleted file mode 100644 index 5c516c4fc357..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTestTask.java +++ /dev/null @@ -1,72 +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.bytecode.enhancement.basic; - -import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; - -import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; - -/** - * @author Luis Barreiro - */ -public class BasicEnhancementTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {SimpleEntity.class}; - } - - public void prepare() { - } - - public void execute() { - SimpleEntity entity = new SimpleEntity(); - - // Call the new ManagedEntity methods - assertTyping( ManagedEntity.class, entity ); - ManagedEntity managedEntity = (ManagedEntity) entity; - assertSame( entity, managedEntity.$$_hibernate_getEntityInstance() ); - - assertNull( managedEntity.$$_hibernate_getEntityEntry() ); - managedEntity.$$_hibernate_setEntityEntry( EnhancerTestUtils.makeEntityEntry() ); - assertNotNull( managedEntity.$$_hibernate_getEntityEntry() ); - managedEntity.$$_hibernate_setEntityEntry( null ); - assertNull( managedEntity.$$_hibernate_getEntityEntry() ); - - managedEntity.$$_hibernate_setNextManagedEntity( managedEntity ); - managedEntity.$$_hibernate_setPreviousManagedEntity( managedEntity ); - assertSame( managedEntity, managedEntity.$$_hibernate_getNextManagedEntity() ); - assertSame( managedEntity, managedEntity.$$_hibernate_getPreviousManagedEntity() ); - - // Add an attribute interceptor... - assertTyping( PersistentAttributeInterceptable.class, entity ); - PersistentAttributeInterceptable interceptableEntity = (PersistentAttributeInterceptable) entity; - - assertNull( interceptableEntity.$$_hibernate_getInterceptor() ); - interceptableEntity.$$_hibernate_setInterceptor( new ObjectAttributeMarkerInterceptor() ); - assertNotNull( interceptableEntity.$$_hibernate_getInterceptor() ); - - - assertNull( EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ) ); - entity.setAnObject( new Object() ); - - assertSame( EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ), ObjectAttributeMarkerInterceptor.WRITE_MARKER ); - assertSame( entity.getAnObject(), ObjectAttributeMarkerInterceptor.READ_MARKER ); - - entity.setAnObject( null ); - assertSame( EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ), ObjectAttributeMarkerInterceptor.WRITE_MARKER ); - } - - protected void cleanup() { - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicInSessionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicInSessionTest.java deleted file mode 100644 index 02ad8fb5eb4c..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicInSessionTest.java +++ /dev/null @@ -1,76 +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.bytecode.enhancement.basic; - -import java.net.URL; -import java.net.URLClassLoader; - -import org.hibernate.Session; - -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.junit.Assert; -import org.junit.Test; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; - -/** - * @author Steve Ebersole - */ -public class BasicInSessionTest extends BaseCoreFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] {MyEntity.class}; - } - - @Test - public void testIt() { - Session s = openSession(); - s.beginTransaction(); - s.save( new MyEntity( 1L ) ); - s.save( new MyEntity( 2L ) ); - s.getTransaction().commit(); - s.close(); - - s = openSession(); - s.beginTransaction(); - MyEntity myEntity1 = s.get( MyEntity.class, 1L ); - MyEntity myEntity2 = s.get( MyEntity.class, 2L ); - - assertNotNull( myEntity1.$$_hibernate_getEntityInstance() ); - assertSame( myEntity1, myEntity1.$$_hibernate_getEntityInstance() ); - assertNotNull( myEntity1.$$_hibernate_getEntityEntry() ); - assertNull( myEntity1.$$_hibernate_getPreviousManagedEntity() ); - assertNotNull( myEntity1.$$_hibernate_getNextManagedEntity() ); - - assertNotNull( myEntity2.$$_hibernate_getEntityInstance() ); - assertSame( myEntity2, myEntity2.$$_hibernate_getEntityInstance() ); - assertNotNull( myEntity2.$$_hibernate_getEntityEntry() ); - assertNotNull( myEntity2.$$_hibernate_getPreviousManagedEntity() ); - assertNull( myEntity2.$$_hibernate_getNextManagedEntity() ); - - s.createQuery( "delete MyEntity" ).executeUpdate(); - s.getTransaction().commit(); - s.close(); - - assertNull( myEntity1.$$_hibernate_getEntityEntry() ); - } - - @Test - public void enhacementTest() { - try { - EnhancerTestUtils.enhanceAndDecompile( SimpleEntity.class, new URLClassLoader( new URL[0] ) ); - } - catch (Exception e) { - e.printStackTrace(); - Assert.fail( "Unexpected exception in EnhancerTestUtils.enhanceAndDecompile(): " + e.getMessage() ); - } - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicSessionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicSessionTest.java new file mode 100644 index 000000000000..b32c7a94495e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicSessionTest.java @@ -0,0 +1,127 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.basic; + +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class BasicSessionTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ MyEntity.class}; + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + s.save( new MyEntity( 1L ) ); + s.save( new MyEntity( 2L ) ); + } ); + + MyEntity[] entities = new MyEntity[2]; + + doInHibernate( this::sessionFactory, s -> { + entities[0] = s.get( MyEntity.class, 1L ); + entities[1] = s.get( MyEntity.class, 2L ); + + assertNotNull( entities[0].$$_hibernate_getEntityInstance() ); + assertSame( entities[0], entities[0].$$_hibernate_getEntityInstance() ); + assertNotNull( entities[0].$$_hibernate_getEntityEntry() ); + assertNull( entities[0].$$_hibernate_getPreviousManagedEntity() ); + assertNotNull( entities[0].$$_hibernate_getNextManagedEntity() ); + + assertNotNull( entities[1].$$_hibernate_getEntityInstance() ); + assertSame( entities[1], entities[1].$$_hibernate_getEntityInstance() ); + assertNotNull( entities[1].$$_hibernate_getEntityEntry() ); + assertNotNull( entities[1].$$_hibernate_getPreviousManagedEntity() ); + assertNull( entities[1].$$_hibernate_getNextManagedEntity() ); + + s.createQuery( "delete MyEntity" ).executeUpdate(); + } ); + + assertNull( entities[0].$$_hibernate_getEntityEntry() ); + assertNull( entities[1].$$_hibernate_getEntityEntry() ); + } + + // --- // + + @Entity( name = "MyEntity" ) + @Table( name = "MY_ENTITY" ) + private static class MyEntity implements ManagedEntity { + + @Id + Long id; + + @Transient + private transient EntityEntry entityEntry; + @Transient + private transient ManagedEntity previous; + @Transient + private transient ManagedEntity next; + + MyEntity() { + } + + MyEntity(Long id) { + this.id = id; + } + + @Override + public Object $$_hibernate_getEntityInstance() { + return this; + } + + @Override + public EntityEntry $$_hibernate_getEntityEntry() { + return entityEntry; + } + + @Override + public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) { + this.entityEntry = entityEntry; + } + + @Override + public ManagedEntity $$_hibernate_getNextManagedEntity() { + return next; + } + + @Override + public void $$_hibernate_setNextManagedEntity(ManagedEntity next) { + this.next = next; + } + + @Override + public ManagedEntity $$_hibernate_getPreviousManagedEntity() { + return previous; + } + + @Override + public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) { + this.previous = previous; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/CrossEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/CrossEnhancementTest.java new file mode 100644 index 000000000000..d3f270918c6d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/CrossEnhancementTest.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.basic; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.Table; +import java.io.Serializable; + +import org.hibernate.SessionFactory; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-9529" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class CrossEnhancementTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.class, ChildKey.class}; + } + + @Test + public void test() { + sessionFactory().close(); + buildSessionFactory(); + } + + // --- // + + @Entity + @Table( name = "PARENT" ) + private static class Parent { + @Id + String id; + } + + @Embeddable + private static class ChildKey implements Serializable { + String parent; + String type; + } + + @Entity + @Table( name = "CHILD" ) + private static class Child { + @EmbeddedId + ChildKey id; + + @MapsId( "parent" ) + @ManyToOne + Parent parent; + + public String getfieldOnChildKeyParent() { + // Note that there are two GETFIELD ops here, one on the field 'id' that should be enhanced and another + // on the field 'parent' that may be or not (depending if 'extended enhancement' is enabled) + + // Either way, the field 'parent' on ChildKey should not be confused with the field 'parent' on Child + + return id.parent; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/ExtendedAssociationManagementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/ExtendedAssociationManagementTest.java new file mode 100644 index 000000000000..180c623a77a7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/ExtendedAssociationManagementTest.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.basic; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import java.util.UUID; + +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.getFieldByReflection; +import static org.junit.Assert.assertEquals; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class ExtendedAssociationManagementTest { + + @Test + public void test() { + User user = new User(); + user.login = UUID.randomUUID().toString(); + + Customer customer = new Customer(); + customer.user = user; + + assertEquals( customer, getFieldByReflection( user, "customer" ) ); + + // check dirty tracking is set automatically with bi-directional association management + EnhancerTestUtils.checkDirtyTracking( user, "login", "customer" ); + + User anotherUser = new User(); + anotherUser.login = UUID.randomUUID().toString(); + + customer.user = anotherUser; + + Assert.assertNull( user.customer ); + assertEquals( customer, getFieldByReflection( anotherUser, "customer" ) ); + + user.customer = new Customer(); + assertEquals( user, user.customer.user ); + } + + // --- // + + @Entity + private static class Customer { + + @Id + Long id; + + String firstName; + + String lastName; + + @OneToOne( fetch = FetchType.LAZY ) + User user; + } + + @Entity + private static class User { + + @Id + Long id; + + String login; + + String password; + + @OneToOne( mappedBy = "user", fetch = FetchType.LAZY ) + Customer customer; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/GenericReturnValueMappedSuperclassEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/GenericReturnValueMappedSuperclassEnhancementTest.java new file mode 100644 index 000000000000..a4c6a59f265e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/GenericReturnValueMappedSuperclassEnhancementTest.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.basic; + +import static org.junit.Assert.assertEquals; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(BytecodeEnhancerRunner.class) +public class GenericReturnValueMappedSuperclassEnhancementTest { + + @Test + @TestForIssue(jiraKey = "HHH-12579") + public void enhanceClassWithGenericReturnValueOnMappedSuperclass() { + SimpleEntity implementation = new SimpleEntity(); + + implementation.setEntity( SimpleEntity.Type.ONE ); + + assertEquals( SimpleEntity.Type.ONE, implementation.getEntity() ); + } + + @MappedSuperclass + @Cache(usage = CacheConcurrencyStrategy.NONE) + public static class AbstractMappedSuperclassWithGenericReturnValue { + + @Id + @GeneratedValue + public int id; + + @Access(AccessType.PROPERTY) + private T entity; + + public T getEntity() { + return entity; + } + + public void setEntity(T entity) { + this.entity = entity; + } + } + + public interface Marker { + } + + @Entity + @Cache(usage = CacheConcurrencyStrategy.NONE) + public static class SimpleEntity extends AbstractMappedSuperclassWithGenericReturnValue { + + public enum Type implements Marker { + ONE + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/HHH9529TestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/HHH9529TestTask.java deleted file mode 100644 index 70e48b53a16b..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/HHH9529TestTask.java +++ /dev/null @@ -1,74 +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.bytecode.enhancement.basic; - -import java.io.Serializable; -import javax.persistence.Embeddable; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.ManyToOne; -import javax.persistence.MapsId; - -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -/** - * @author Luis Barreiro - */ -public class HHH9529TestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class, Child.class, ChildKey.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - } - - public void execute() { - } - - protected void cleanup() { - } - - @Entity - public class Parent { - @Id - String id; - } - - @Embeddable - public class ChildKey implements Serializable { - String parent; - String type; - } - - @Entity - public class Child { - @EmbeddedId - ChildKey id; - - @MapsId("parent") - @ManyToOne - Parent parent; - - public String getfieldOnChildKeyParent() { - // Note that there are two GETFIELD ops here, one on the field 'id' that should be enhanced and another - // on the field 'parent' that may be or not (depending if 'extended enhancement' is enabled) - - // Either way, the field 'parent' on ChildKey should not be confused with the field 'parent' on Child - - return id.parent; - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/InheritedTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/InheritedTest.java new file mode 100644 index 000000000000..2a7cb8e17c14 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/InheritedTest.java @@ -0,0 +1,126 @@ +package org.hibernate.test.bytecode.enhancement.basic; + +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.clearDirtyTracking; + +/** + * @author Luis Barreiro + * @author Craig Andrews + */ +@TestForIssue( jiraKey = "HHH-11284" ) +@RunWith( BytecodeEnhancerRunner.class ) +@CustomEnhancementContext( {EnhancerTestContext.class, InheritedTest.EagerEnhancementContext.class} ) +public class InheritedTest { + + @Test + public void test() { + Employee charles = new Employee( "Charles", "Engineer" ); + charles.setOca( 1002 ); + + // Check that both types of class attributes are being dirty tracked + checkDirtyTracking( charles, "title", "oca" ); + clearDirtyTracking( charles ); + + // Let's give charles a promotion, this time using method references + charles.setOca( 99 ); + charles.setTitle( "Manager" ); + + checkDirtyTracking( charles, "title", "oca" ); + + Contractor bob = new Contractor( "Bob", 100 ); + bob.setOca( 1003 ); + + // Check that both types of class attributes are being dirty tracked + checkDirtyTracking( bob, "rate", "oca" ); + clearDirtyTracking( bob ); + + // Let's give bob a rate increase, this time using method references + bob.setOca( 88 ); + bob.setRate( 200 ); + + checkDirtyTracking( bob, "rate", "oca" ); + } + + // --- // + + @Entity + private static abstract class Person { + + @Id + String name; + + @Version + long oca; + + Person() { + } + + Person(String name) { + this(); + this.name = name; + } + + void setOca(long l) { + this.oca = l; + } + } + + @Entity + private static class Employee extends Person { + + String title; + + Employee() { + } + + Employee(String name, String title) { + super( name ); + this.title = title; + } + + void setTitle(String title) { + this.title = title; + } + } + + @Entity + private static class Contractor extends Person { + + Integer rate; + + Contractor() { + } + + Contractor(String name, Integer rate) { + super( name ); + this.rate = rate; + } + + void setRate(Integer rate) { + this.rate = rate; + } + } + + // --- // + + public static class EagerEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + // HHH-10981 - Without lazy loading, the generation of getters and setters has a different code path + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/MappedSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/MappedSuperclassTest.java new file mode 100644 index 000000000000..d52b4468925d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/MappedSuperclassTest.java @@ -0,0 +1,94 @@ +package org.hibernate.test.bytecode.enhancement.basic; + +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Version; + +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.clearDirtyTracking; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-10646" ) +@RunWith( BytecodeEnhancerRunner.class ) +@CustomEnhancementContext( {EnhancerTestContext.class, MappedSuperclassTest.EagerEnhancementContext.class} ) +public class MappedSuperclassTest { + + @Test + public void test() { + Employee charles = new Employee( "Charles", "Engineer" ); + charles.setOca( 1002 ); + + // Check that both types of class attributes are being dirty tracked + checkDirtyTracking( charles, "title", "oca" ); + clearDirtyTracking( charles ); + + // Let's give charles a promotion, this time using method references + charles.setOca( 99 ); + charles.setTitle( "Manager" ); + + checkDirtyTracking( charles, "title", "oca" ); + } + + // --- // + + @MappedSuperclass + private static class Person { + + @Id + String name; + + @Version + Long oca; + + Person(String name) { + this.name = name; + } + + Person() { + } + + void setOca(long l) { + this.oca = l; + } + } + + @Entity + private static class Employee extends Person { + + String title; + + Employee(String name, String title) { + super( name ); + this.title = title; + } + + Employee() { + } + + void setTitle(String title) { + this.title = title; + } + } + + // --- // + + public static class EagerEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + // HHH-10981 - Without lazy loading, the generation of getters and setters has a different code path + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/MyEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/MyEntity.java deleted file mode 100644 index 1f09c3322814..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/MyEntity.java +++ /dev/null @@ -1,91 +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.bytecode.enhancement.basic; - -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Transient; - -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.ManagedEntity; - -/** - * @author Steve Ebersole - */ -@Entity -public class MyEntity implements ManagedEntity { - - @Transient - private transient EntityEntry entityEntry; - @Transient - private transient ManagedEntity previous; - @Transient - private transient ManagedEntity next; - - @Id - private Long id; - - private String name; - - public MyEntity() { - } - - public MyEntity(Long id) { - this.id = 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; - } - - @Override - public Object $$_hibernate_getEntityInstance() { - return this; - } - - @Override - public EntityEntry $$_hibernate_getEntityEntry() { - return entityEntry; - } - - @Override - public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) { - this.entityEntry = entityEntry; - } - - @Override - public ManagedEntity $$_hibernate_getNextManagedEntity() { - return next; - } - - @Override - public void $$_hibernate_setNextManagedEntity(ManagedEntity next) { - this.next = next; - } - - @Override - public ManagedEntity $$_hibernate_getPreviousManagedEntity() { - return previous; - } - - @Override - public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) { - this.previous = previous; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/ObjectAttributeMarkerInterceptor.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/ObjectAttributeMarkerInterceptor.java deleted file mode 100644 index f49939737403..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/ObjectAttributeMarkerInterceptor.java +++ /dev/null @@ -1,127 +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.bytecode.enhancement.basic; - -import java.util.Set; - -import org.hibernate.engine.spi.PersistentAttributeInterceptor; - -/** - * Interceptor that stores a marker object on write, instead of the provided value. - * Also returns another marker object on read. Marks only non-primitive fields. - * - * @author Luis Barreiro - */ -public class ObjectAttributeMarkerInterceptor implements PersistentAttributeInterceptor { - - public static final Object READ_MARKER = new Object(); - public static final Object WRITE_MARKER = new Object(); - - public ObjectAttributeMarkerInterceptor() { - } - - @Override - public boolean readBoolean(Object obj, String name, boolean oldValue) { - return oldValue; - } - - @Override - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - return newValue; - } - - @Override - public byte readByte(Object obj, String name, byte oldValue) { - return oldValue; - } - - @Override - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - return newValue; - } - - @Override - public char readChar(Object obj, String name, char oldValue) { - return oldValue; - } - - @Override - public char writeChar(Object obj, String name, char oldValue, char newValue) { - return newValue; - } - - @Override - public short readShort(Object obj, String name, short oldValue) { - return oldValue; - } - - @Override - public short writeShort(Object obj, String name, short oldValue, short newValue) { - return newValue; - } - - @Override - public int readInt(Object obj, String name, int oldValue) { - return oldValue; - } - - @Override - public int writeInt(Object obj, String name, int oldValue, int newValue) { - return newValue; - } - - @Override - public float readFloat(Object obj, String name, float oldValue) { - return oldValue; - } - - @Override - public float writeFloat(Object obj, String name, float oldValue, float newValue) { - return newValue; - } - - @Override - public double readDouble(Object obj, String name, double oldValue) { - return oldValue; - } - - @Override - public double writeDouble(Object obj, String name, double oldValue, double newValue) { - return newValue; - } - - @Override - public long readLong(Object obj, String name, long oldValue) { - return oldValue; - } - - @Override - public long writeLong(Object obj, String name, long oldValue, long newValue) { - return newValue; - } - - @Override - public Object readObject(Object obj, String name, Object oldValue) { - return READ_MARKER; - } - - @Override - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - return WRITE_MARKER; - } - - @Override - public Set getInitializedLazyAttributeNames() { - return null; - } - - @Override - public void attributeInitialized(String name) { - - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/SimpleEntity.java deleted file mode 100644 index 652fd8b0ee47..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/SimpleEntity.java +++ /dev/null @@ -1,92 +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.bytecode.enhancement.basic; - -import java.util.List; -import java.util.Set; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -/** - * @author Steve Ebersole - */ -@Entity -public class SimpleEntity { - - @Id - private Long id; - - private String name; - - private boolean active; - - private long someNumber; - - Object anUnspecifiedObject; - - private List someStrings; - - @OneToMany - private Set someInts; - - 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 boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - public long getSomeNumber() { - return someNumber; - } - - public void setSomeNumber(long someNumber) { - this.someNumber = someNumber; - } - - public Object getAnObject() { - return anUnspecifiedObject; - } - - public void setAnObject(Object providedObject) { - this.anUnspecifiedObject = providedObject; - } - - public List getSomeStrings() { - return someStrings; - } - - public void setSomeStrings(List someStrings) { - this.someStrings = someStrings; - } - - public Set getSomeInts() { - return someInts; - } - - public void setSomeInts(Set someInts) { - this.someInts = someInts; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java new file mode 100644 index 000000000000..1f8860e41ca8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java @@ -0,0 +1,256 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.cascade; + +import 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.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.hibernate.Hibernate; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-10252" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class CascadeDeleteCollectionTest extends BaseCoreFunctionalTestCase { + private Parent originalParent; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.class}; + } + + @Before + public void prepare() { + // Create a Parent with one Child + originalParent = doInHibernate( this::sessionFactory, s -> { + Parent p = new Parent(); + p.setName( "PARENT" ); + p.setLazy( "LAZY" ); + p.makeChild(); + s.persist( p ); + return p; + } + ); + } + + @Test + public void testManagedWithUninitializedAssociation() { + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) + .setParameter( "name", "PARENT" ) + .uniqueResult(); + checkInterceptor( loadedParent, false ); + assertFalse( Hibernate.isPropertyInitialized( loadedParent, "children" ) ); + s.delete( loadedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testManagedWithInitializedAssociation() { + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) + .setParameter( "name", "PARENT" ) + .uniqueResult(); + checkInterceptor( loadedParent, false ); + loadedParent.getChildren(); + assertTrue( Hibernate.isPropertyInitialized( loadedParent, "children" ) ); + s.delete( loadedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedWithUninitializedAssociation() { + final Parent detachedParent = doInHibernate( this::sessionFactory, s -> { + return s.get( Parent.class, originalParent.getId() ); + } ); + + assertFalse( Hibernate.isPropertyInitialized( detachedParent, "children" ) ); + + checkInterceptor( detachedParent, false ); + + // Delete the detached Parent with uninitialized children + doInHibernate( this::sessionFactory, s -> { + s.delete( detachedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedWithInitializedAssociation() { + final Parent detachedParent = doInHibernate( this::sessionFactory, s -> { + Parent parent = s.get( Parent.class, originalParent.getId() ); + assertFalse( Hibernate.isPropertyInitialized( parent, "children" ) ); + + // initialize collection before detaching + parent.getChildren(); + return parent; + } ); + + assertTrue( Hibernate.isPropertyInitialized( detachedParent, "children" ) ); + + checkInterceptor( detachedParent, false ); + + // Delete the detached Parent with initialized children + doInHibernate( this::sessionFactory, s -> { + s.delete( detachedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedOriginal() { + + // originalParent#children should be initialized + assertTrue( Hibernate.isPropertyInitialized( originalParent, "children" ) ); + + checkInterceptor( originalParent, true ); + + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + s.delete( originalParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + private void checkInterceptor(Parent parent, boolean isNullExpected) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = + sessionFactory() + .getMetamodel() + .entityPersister( Parent.class ) + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + if ( isNullExpected ) { + // if a null Interceptor is expected, then there shouldn't be any uninitialized attributes + assertFalse( bytecodeEnhancementMetadata.hasUnFetchedAttributes( parent ) ); + assertNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) ); + } + else { + assertNotNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) ); + } + } + + // --- // + + @Entity( name = "Parent" ) + @Table( name = "PARENT" ) + public static class Parent { + + Long id; + + String name; + + List children = new ArrayList<>(); + + String lazy; + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + @OneToMany( mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY ) + List getChildren() { + return Collections.unmodifiableList( children ); + } + + void setChildren(List children) { + this.children = children; + } + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + @Basic( fetch = FetchType.LAZY ) + String getLazy() { + return lazy; + } + + void setLazy(String lazy) { + this.lazy = lazy; + } + + void makeChild() { + Child c = new Child(); + c.setParent( this ); + children.add( c ); + } + } + + @Entity + @Table( name = "CHILD" ) + private static class Child { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @ManyToOne( optional = false ) + @JoinColumn( name = "parent_id" ) + Parent parent; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + Parent getParent() { + return parent; + } + + void setParent(Parent parent) { + this.parent = parent; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java new file mode 100644 index 000000000000..5ba8d3a6016f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java @@ -0,0 +1,288 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.cascade; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Luis Barreiro + */ +@TestForIssue(jiraKey = "HHH-10252") +@RunWith(BytecodeEnhancerRunner.class) +public class CascadeDeleteManyToOneTest extends BaseCoreFunctionalTestCase { + private Child originalChild; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + @Before + public void prepare() { + // Create a Parent with one Child + originalChild = doInHibernate( + this::sessionFactory, s -> { + Child c = new Child(); + c.setName( "CHILD" ); + c.setLazy( "LAZY" ); + c.makeParent(); + s.persist( c ); + return c; + } + ); + } + + @Test + public void testManagedWithUninitializedAssociation() { + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + Child loadedChild = (Child) s.createQuery( "SELECT c FROM Child c WHERE name=:name" ) + .setParameter( "name", "CHILD" ) + .uniqueResult(); + checkInterceptor( loadedChild, false ); + assertFalse( Hibernate.isPropertyInitialized( loadedChild, "parent" ) ); + s.delete( loadedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testManagedWithInitializedAssociation() { + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + Child loadedChild = (Child) s.createQuery( "SELECT c FROM Child c WHERE name=:name" ) + .setParameter( "name", "CHILD" ) + .uniqueResult(); + checkInterceptor( loadedChild, false ); + loadedChild.getParent(); + assertTrue( Hibernate.isPropertyInitialized( loadedChild, "parent" ) ); + s.delete( loadedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedWithUninitializedAssociation() { + final Child detachedChild = doInHibernate( + this::sessionFactory, s -> { + return s.get( Child.class, originalChild.getId() ); + } + ); + + assertFalse( Hibernate.isPropertyInitialized( detachedChild, "parent" ) ); + + checkInterceptor( detachedChild, false ); + + // Delete the detached Child with uninitialized parent + doInHibernate( + this::sessionFactory, s -> { + s.delete( detachedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedWithInitializedAssociation() { + final Child detachedChild = doInHibernate( + this::sessionFactory, s -> { + Child child = s.get( Child.class, originalChild.getId() ); + assertFalse( Hibernate.isPropertyInitialized( child, "parent" ) ); + + // initialize parent before detaching + child.getParent(); + return child; + } + ); + + assertTrue( Hibernate.isPropertyInitialized( detachedChild, "parent" ) ); + + checkInterceptor( detachedChild, false ); + + // Delete the detached Child with initialized parent + doInHibernate( + this::sessionFactory, s -> { + s.delete( detachedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedOriginal() { + + // originalChild#parent should be initialized + assertTrue( Hibernate.isPropertyInitialized( originalChild, "parent" ) ); + + checkInterceptor( originalChild, true ); + + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + s.delete( originalChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + private void checkInterceptor(Child child, boolean isNullExpected) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = + sessionFactory() + .getMetamodel() + .entityPersister( Child.class ) + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + if ( isNullExpected ) { + // if a null Interceptor is expected, then there shouldn't be any uninitialized attributes + assertFalse( bytecodeEnhancementMetadata.hasUnFetchedAttributes( child ) ); + assertNull( bytecodeEnhancementMetadata.extractInterceptor( child ) ); + } + else { + assertNotNull( bytecodeEnhancementMetadata.extractInterceptor( child ) ); + } + } + + // --- // + + @Entity(name = "Parent") + @Table(name = "PARENT") + public static class Parent { + + Long id; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + @ManyToOne(optional = false, cascade = { + CascadeType.PERSIST, + CascadeType.MERGE, + CascadeType.REMOVE + }, fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @Basic(fetch = FetchType.LAZY) + String lazy; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + Parent getParent() { + return parent; + } + + void setParent(Parent parent) { + this.parent = parent; + } + + String getLazy() { + return lazy; + } + + void setLazy(String lazy) { + this.lazy = lazy; + } + + void makeParent() { + parent = new Parent(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTestTask.java deleted file mode 100644 index 67205321623b..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTestTask.java +++ /dev/null @@ -1,64 +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.bytecode.enhancement.cascade; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -/** - * @author Luis Barreiro - */ -public class CascadeDeleteTestTask extends AbstractEnhancerTestTask { - - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class, Child.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - // Create a Parent with one Child - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent p = new Parent(); - p.setName("PARENT"); - p.setLazy("LAZY"); - - Child child = p.makeChild(); - s.persist(p); - - s.getTransaction().commit(); - s.close(); - } - - public void execute() { - // Delete the Parent - Session s = getFactory().openSession(); - s.beginTransaction(); - Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) - .setParameter( "name", "PARENT" ) - .uniqueResult(); - - s.delete( loadedParent ); - s.getTransaction().commit(); - s.close(); - // If the lazy relation is not fetch on cascade there is a constraint violation on commit - } - - protected void cleanup() { - } - - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDetachedTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDetachedTest.java new file mode 100644 index 000000000000..8bb8095fc2ae --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDetachedTest.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 . + */ +package org.hibernate.test.bytecode.enhancement.cascade; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.List; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-10254" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class CascadeDetachedTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{Author.class, Book.class}; + } + + @Test + public void test() { + Book book = new Book( "978-1118063330", "Operating System Concepts 9th Edition" ); + book.addAuthor( new Author( "Abraham", "Silberschatz", new char[] { 'a', 'b' } ) ); + book.addAuthor( new Author( "Peter", "Galvin", new char[] { 'c', 'd' } ) ); + book.addAuthor( new Author( "Greg", "Gagne", new char[] { 'e', 'f' } ) ); + + doInJPA( this::sessionFactory, em -> { + em.persist( book ); + } ); + + doInJPA( this::sessionFactory, em -> { + em.merge( book ); + } ); + } + + // --- // + + @Entity + @Table( name = "BOOK" ) + public static class Book { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String isbn; + String title; + + @OneToMany( cascade = CascadeType.ALL, mappedBy = "book" ) + List authors = new ArrayList<>(); + + public Book() { + } + + public Book(String isbn, String title) { + this.isbn = isbn; + this.title = title; + } + + public void addAuthor(Author author) { + authors.add( author ); + author.book = this; + } + } + + @Entity + @Table( name = "AUTHOR" ) + public static class Author { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String firstName; + String lastName; + + @ManyToOne( fetch = FetchType.LAZY ) + @JoinColumn + Book book; + + @Basic( fetch = FetchType.LAZY ) + char[] charArrayCode; + + public Author() { + } + + public Author(String firstName, String lastName, char[] charArrayCode) { + this.firstName = firstName; + this.lastName = lastName; + this.charArrayCode = charArrayCode; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java new file mode 100644 index 000000000000..39a2637f5096 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java @@ -0,0 +1,286 @@ +package org.hibernate.test.bytecode.enhancement.cascade; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Bolek Ziobrowski + * @author Gail Badner + */ +@RunWith(BytecodeEnhancerRunner.class) +@TestForIssue(jiraKey = "HHH-13129") +public class CascadeOnUninitializedTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Address.class, + }; + } + + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + settings.put( AvailableSettings.SHOW_SQL, "true" ); + settings.put( AvailableSettings.FORMAT_SQL, "true" ); + } + + @Test + public void testMergeDetachedEnhancedEntityWithUninitializedManyToOne() { + + Person person = persistPersonWithManyToOne(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) ); + detachedPerson.setName( "newName" ); + + Person mergedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return (Person) session.merge( detachedPerson ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( mergedPerson, "primaryAddress" ) ); + assertEquals( "newName", mergedPerson.getName() ); + } + + @Test + public void testDeleteEnhancedEntityWithUninitializedManyToOne() { + Person person = persistPersonWithManyToOne(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) ); + + // deleting detachedPerson should result in detachedPerson.address being initialized, + // so that the DELETE operation can be cascaded to it. + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.delete( detachedPerson ); + } + ); + + // both the Person and its Address should be deleted + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + assertNull( session.get( Person.class, person.getId() ) ); + assertNull( session.get( Person.class, person.getPrimaryAddress().getId() ) ); + } + ); + } + + @Test + public void testMergeDetachedEnhancedEntityWithUninitializedOneToMany() { + + Person person = persistPersonWithOneToMany(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) ); + detachedPerson.setName( "newName" ); + + Person mergedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return (Person) session.merge( detachedPerson ); + } + ); + + // address should be initialized + assertTrue( Hibernate.isPropertyInitialized( mergedPerson, "addresses" ) ); + assertEquals( "newName", mergedPerson.getName() ); + } + + @Test + public void testDeleteEnhancedEntityWithUninitializedOneToMany() { + Person person = persistPersonWithOneToMany(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) ); + + // deleting detachedPerson should result in detachedPerson.address being initialized, + // so that the DELETE operation can be cascaded to it. + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.delete( detachedPerson ); + } + ); + + // both the Person and its Address should be deleted + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + assertNull( session.get( Person.class, person.getId() ) ); + assertNull( session.get( Person.class, person.getAddresses().iterator().next().getId() ) ); + } + ); + } + + public Person persistPersonWithManyToOne() { + Address address = new Address(); + address.setDescription( "ABC" ); + + final Person person = new Person(); + person.setName( "John Doe" ); + person.setPrimaryAddress( address ); + + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.persist( person ); + } + ); + + return person; + } + + public Person persistPersonWithOneToMany() { + Address address = new Address(); + address.setDescription( "ABC" ); + + final Person person = new Person(); + person.setName( "John Doe" ); + person.getAddresses().add( address ); + + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.persist( person ); + } + ); + + return person; + } + + @Entity + @Table(name = "TEST_PERSON") + public static class Person { + @Id + @GeneratedValue + private Long id; + + @Column(name = "NAME", length = 300, nullable = true) + private String name; + + @ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY) + @JoinColumn(name = "ADDRESS_ID") + @LazyToOne(LazyToOneOption.NO_PROXY) + private Address primaryAddress; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn + private Set
    addresses = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(Address primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public Set
    getAddresses() { + return addresses; + } + + public void setAddresses(Set
    addresses) { + this.addresses = addresses; + } + } + + @Entity + @Table(name = "TEST_ADDRESS") + public static class Address { + @Id + @GeneratedValue + private Long id; + + @Column(name = "DESCRIPTION", length = 300, nullable = true) + private String description; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } +} + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeWithFkConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeWithFkConstraintTest.java new file mode 100644 index 000000000000..723c481f94e1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeWithFkConstraintTest.java @@ -0,0 +1,117 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.cascade; + +import 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.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-10252" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class CascadeWithFkConstraintTest extends BaseCoreFunctionalTestCase { + + private String garageId, car1Id, car2Id; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Garage.class, Car.class}; + } + + @Before + public void prepare() { + // Create garage, add 2 cars to garage + doInJPA( this::sessionFactory, em -> { + + Garage garage = new Garage(); + Car car1 = new Car(); + Car car2 = new Car(); + garage.insert( car1 ); + garage.insert( car2 ); + + em.persist( garage ); + em.persist( car1 ); + em.persist( car2 ); + + garageId = garage.id; + car1Id = car1.id; + car2Id = car2.id; + } ); + } + + @Test + public void test() { + // Remove garage + doInJPA( this::sessionFactory, em -> { + Garage toRemoveGarage = em.find( Garage.class, garageId ); + em.remove( toRemoveGarage ); + } ); + + // Check if there is no garage but cars are still present + doInJPA( this::sessionFactory, em -> { + Garage foundGarage = em.find( Garage.class, garageId ); + Assert.assertNull( foundGarage ); + + Car foundCar1 = em.find( Car.class, car1Id ); + Assert.assertEquals( car1Id, foundCar1.id ); + + Car foundCar2 = em.find( Car.class, car2Id ); + Assert.assertEquals( car2Id, foundCar2.id ); + } ); + } + + // --- // + + @Entity + @Table( name = "GARAGE" ) + private static class Garage { + + @Id + String id; + + @OneToMany + @JoinColumn( name = "GARAGE_ID" ) + Set cars = new HashSet<>(); + + Garage() { + id = UUID.randomUUID().toString(); + } + + void insert(Car aCar) { + cars.add( aCar ); + } + } + + @Entity + @Table( name = "CAR" ) + public static class Car { + + @Id + String id; + + Car() { + id = UUID.randomUUID().toString(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeWithFkConstraintTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeWithFkConstraintTestTask.java deleted file mode 100644 index 965aaf4ca121..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeWithFkConstraintTestTask.java +++ /dev/null @@ -1,135 +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.bytecode.enhancement.cascade; - -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -import javax.persistence.Entity; -import javax.persistence.EntityManager; -import javax.persistence.EntityTransaction; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.OneToMany; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -/** - * @author Luis Barreiro - */ -public class CascadeWithFkConstraintTestTask extends AbstractEnhancerTestTask { - - private String garageId, car1Id, car2Id; - - public Class[] getAnnotatedClasses() { - return new Class[]{Garage.class, Car.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - // Create garage, add 2 cars to garage - EntityManager em = getFactory().createEntityManager(); - EntityTransaction tx = em.getTransaction(); - tx.begin(); - - Garage garage = new Garage(); - Car car1 = new Car(); - Car car2 = new Car(); - garage.insert( car1 ); - garage.insert( car2 ); - - em.persist( garage ); - em.persist( car1 ); - em.persist( car2 ); - - tx.commit(); - em.close(); - - garageId = garage.id; - car1Id = car1.id; - car2Id = car2.id; - } - - public void execute() { - - // Remove garage - - EntityManager em = getFactory().createEntityManager(); - EntityTransaction tx = em.getTransaction(); - tx.begin(); - - Garage toRemoveGarage = em.find( Garage.class, garageId ); - em.remove( toRemoveGarage ); - - tx.commit(); - em.close(); - - // Check if there is no garage but cars are still present - - EntityManager testEm = getFactory().createEntityManager(); - tx = testEm.getTransaction(); - tx.begin(); - - Garage foundGarage = testEm.find( Garage.class, garageId ); - Assert.assertNull( foundGarage ); - - Car foundCar1 = testEm.find( Car.class, car1Id ); - Assert.assertEquals( car1Id, foundCar1.id ); - - Car foundCar2 = testEm.find( Car.class, car2Id ); - Assert.assertEquals( car2Id, foundCar2.id ); - - tx.commit(); - testEm.close(); - - } - - protected void cleanup() { - } - - // --- // - - @Entity(name="Garage") - public static class Garage { - - @Id - String id; - - @OneToMany - @JoinColumn( name = "GARAGE_ID" ) - Set cars = new HashSet<>(); - - public Garage() { - this.id = UUID.randomUUID().toString(); - } - - public boolean insert(Car aCar) { - return cars.add( aCar ); - } - - } - - @Entity(name="Car") - public static class Car { - - @Id - String id; - - public Car() { - id = UUID.randomUUID().toString(); - } - - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/Child.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/Child.java deleted file mode 100644 index 7b0cb94ddca2..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/Child.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.cascade; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; - -/** - * Created by barreiro on 12/9/15. - */ -@Entity -public class Child { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - - @ManyToOne(optional = false) - @JoinColumn(name = "parent_id") - private Parent parent; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Parent getParent() { - return parent; - } - - public void setParent(Parent parent) { - this.parent = parent; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/Parent.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/Parent.java deleted file mode 100644 index a0d3757a2b31..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/Parent.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.cascade; - -import java.util.ArrayList; -import java.util.List; -import javax.persistence.Basic; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -/** - * Created by barreiro on 12/9/15. - */ -@Entity -public class Parent { - private Long id; - private String name; - private List children = new ArrayList(); - private String lazy; - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - @OneToMany(mappedBy = "parent", cascade = { - CascadeType.PERSIST, CascadeType.MERGE, - CascadeType.REFRESH, CascadeType.REMOVE - }, - fetch = FetchType.LAZY) - public List getChildren() { - return children; - } - - public void setChildren(List children) { - this.children = children; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Basic(fetch = FetchType.LAZY) - public String getLazy() { - return lazy; - } - - public void setLazy(String lazy) { - this.lazy = lazy; - } - - Child makeChild() { - final Child c = new Child(); - c.setParent( this ); - this.children.add( c ); - return c; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/detached/DetachedGetIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/detached/DetachedGetIdentifierTest.java new file mode 100644 index 000000000000..1a85ecbb7573 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/detached/DetachedGetIdentifierTest.java @@ -0,0 +1,59 @@ +package org.hibernate.test.bytecode.enhancement.detached; + +import org.hibernate.SessionFactory; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import static org.junit.Assert.assertNotNull; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-11426" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class DetachedGetIdentifierTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{SimpleEntity.class}; + } + + @Test + public void test() { + SimpleEntity[] entities = new SimpleEntity[2]; + entities[0] = new SimpleEntity(); + entities[0].name = "test"; + + TransactionUtil.doInJPA( this::sessionFactory, em -> { + entities[1] = em.merge( entities[0] ); + assertNotNull( em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier( entities[1] ) ); + } ); + + // Call as detached entity + try ( SessionFactory sessionFactory = sessionFactory() ) { + assertNotNull( sessionFactory.getPersistenceUnitUtil().getIdentifier( entities[1] ) ); + } + } + + // --- // + + @Entity + @Table( name = "SIMPLE_ENTITY" ) + private static class SimpleEntity { + + @Id + @GeneratedValue + Long id; + + String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/detached/DetachedGetIdentifierTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/detached/DetachedGetIdentifierTestTask.java deleted file mode 100644 index 76a263900e27..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/detached/DetachedGetIdentifierTestTask.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.detached; - -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -import javax.persistence.Entity; -import javax.persistence.EntityManager; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; - -/** - * @author Luis Barreiro - */ -public class DetachedGetIdentifierTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[]{SimpleEntity.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - } - - public void execute() { - EntityManager em = getFactory().createEntityManager(); - em.getTransaction().begin(); - - SimpleEntity se = new SimpleEntity(); - se.name = "test"; - se = em.merge( se ); - - Assert.assertNotNull( getFactory().getPersistenceUnitUtil().getIdentifier( se ) ); - - em.getTransaction().commit(); - em.close(); - - // Call as detached entity - Assert.assertNotNull( getFactory().getPersistenceUnitUtil().getIdentifier( se ) ); - } - - protected void cleanup() { - } - - @Entity - public static class SimpleEntity { - - @Id - @GeneratedValue - private Long id; - - private String name; - - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/Address.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/Address.java deleted file mode 100644 index 98ace502f5ae..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/Address.java +++ /dev/null @@ -1,88 +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.bytecode.enhancement.dirty; - -import java.io.Serializable; -import javax.persistence.Embeddable; -import javax.persistence.Embedded; - -/** - * @author Ståle W. Pedersen - */ -@Embeddable -public class Address implements Serializable { - - private String street1; - private String street2; - private String city; - private String state; - private String zip; - private String phone; - - @Embedded - private Country country; - - public Address() { - } - - public String getStreet1() { - return street1; - } - - public void setStreet1(String street1) { - this.street1 = street1; - } - - public String getStreet2() { - return street2; - } - - public void setStreet2(String street2) { - this.street2 = street2; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getState() { - return state; - } - - public void setState(String state) { - this.state = state; - } - - public Country getCountry() { - return country; - } - - public void setCountry(Country country) { - this.country = country; - } - - public String getZip() { - return zip; - } - - public void setZip(String zip) { - this.zip = zip; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/Country.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/Country.java deleted file mode 100644 index ea9ff9231ae8..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/Country.java +++ /dev/null @@ -1,26 +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.bytecode.enhancement.dirty; - -import javax.persistence.Embeddable; - -/** - * @author Ståle W. Pedersen - */ -@Embeddable -public class Country { - - private String name; - - 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/dirty/DirtyTrackingCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionTest.java new file mode 100644 index 000000000000..5b1c30927e97 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionTest.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.bytecode.enhancement.dirty; + +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.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-11293" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class DirtyTrackingCollectionTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{StringsEntity.class}; + } + + @Before + public void prepare() { + doInJPA( this::sessionFactory, em -> { + StringsEntity entity = new StringsEntity(); + entity.id = 1L; + entity.someStrings = new ArrayList<>( Arrays.asList( "a", "b", "c" ) ); + em.persist( entity ); + } ); + } + + @Test + public void test() { + doInJPA( this::sessionFactory, entityManager -> { + StringsEntity entity = entityManager.find( StringsEntity.class, 1L ); + entity.someStrings.clear(); + } ); + + doInJPA( this::sessionFactory, entityManager -> { + StringsEntity entity = entityManager.find( StringsEntity.class, 1L ); + assertEquals( 0, entity.someStrings.size() ); + entity.someStrings.add( "d" ); + } ); + + doInJPA( this::sessionFactory, entityManager -> { + StringsEntity entity = entityManager.find( StringsEntity.class, 1L ); + assertEquals( 1, entity.someStrings.size() ); + entity.someStrings = new ArrayList<>(); + } ); + + doInJPA( this::sessionFactory, entityManager -> { + StringsEntity entity = entityManager.find( StringsEntity.class, 1L ); + assertEquals( 0, entity.someStrings.size() ); + } ); + } + + // --- // + + @Entity + @Table( name = "STRINGS_ENTITY" ) + private static class StringsEntity { + + @Id + Long id; + + @ElementCollection + List someStrings; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionTestTask.java deleted file mode 100644 index e3f303eea7bb..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingCollectionTestTask.java +++ /dev/null @@ -1,66 +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.bytecode.enhancement.dirty; - -import java.util.ArrayList; -import java.util.Arrays; - -import org.hibernate.cfg.Configuration; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; - -/** - * @author Luis Barreiro - */ -public class DirtyTrackingCollectionTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {ParentChildEntity.class}; - } - - public void prepare() { - super.prepare( new Configuration() ); - } - - public void execute() { - - doInJPA( this::getFactory, entityManager -> { - ParentChildEntity entity = new ParentChildEntity(); - entity.setId( 1L ); - entity.setSomeStrings( new ArrayList<>( Arrays.asList( "a", "b", "c") ) ); - entityManager.persist( entity ); - } ); - - doInJPA( this::getFactory, entityManager -> { - ParentChildEntity entity = entityManager.find( ParentChildEntity.class, 1L ); - entity.getSomeStrings().clear(); - } ); - - doInJPA( this::getFactory, entityManager -> { - ParentChildEntity entity = entityManager.find( ParentChildEntity.class, 1L ); - assertEquals(0, entity.getSomeStrings().size()); - entity.getSomeStrings().add( "d" ); - } ); - - doInJPA( this::getFactory, entityManager -> { - ParentChildEntity entity = entityManager.find( ParentChildEntity.class, 1L ); - assertEquals(1, entity.getSomeStrings().size()); - entity.setSomeStrings( new ArrayList<>() ); - } ); - - doInJPA( this::getFactory, entityManager -> { - ParentChildEntity entity = entityManager.find( ParentChildEntity.class, 1L ); - assertEquals(0, entity.getSomeStrings().size()); - } ); - } - - protected void cleanup() { - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingNonUpdateableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingNonUpdateableTest.java new file mode 100644 index 000000000000..daa41972913e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingNonUpdateableTest.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.dirty; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Version; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-12051" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class DirtyTrackingNonUpdateableTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Thing.class}; + } + + @Test + public void test() { + doInJPA( this::sessionFactory, entityManager -> { + Thing thing = new Thing(); + entityManager.persist( thing ); + + entityManager + .createQuery( "update thing set special = :s, version = version + 1" ) + .setParameter( "s", "new" ) + .executeUpdate(); + + thing.special = "If I'm flush to the DB you get an OptimisticLockException"; + } ); + } + + // --- // + + @Entity( name = "thing" ) + @Table( name = "THING_ENTITY" ) + public class Thing { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + long id; + + @Version + long version; + + @Column( updatable = false ) + String special; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingTest.java new file mode 100644 index 000000000000..e16acebeae61 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingTest.java @@ -0,0 +1,144 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.dirty; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class DirtyTrackingTest { + + @Test + public void test() { + SimpleEntity entity = new SimpleEntity(); + EnhancerTestUtils.clearDirtyTracking( entity ); + + // Basic single field + Long number = entity.getSomeNumber(); + EnhancerTestUtils.checkDirtyTracking( entity ); + entity.setSomeNumber( number + 1L ); + EnhancerTestUtils.checkDirtyTracking( entity, "someNumber" ); + EnhancerTestUtils.clearDirtyTracking( entity ); + entity.setSomeNumber( entity.getSomeNumber() ); + EnhancerTestUtils.checkDirtyTracking( entity ); + + // Basic multi-field (Id properties are not flagged as dirty) + entity.id = 2L; + entity.active = !entity.active; + entity.someNumber = 193L; + EnhancerTestUtils.checkDirtyTracking( entity, "active", "someNumber" ); + EnhancerTestUtils.clearDirtyTracking( entity ); + + // Setting the same value should not make it dirty + entity.someNumber = 193L; + EnhancerTestUtils.checkDirtyTracking( entity ); + + // Collection + List stringList = new ArrayList<>(); + stringList.add( "FooBar" ); + entity.someStrings = stringList; + EnhancerTestUtils.checkDirtyTracking( entity, "someStrings" ); + EnhancerTestUtils.clearDirtyTracking( entity ); + + stringList.add( "BarFoo" ); + EnhancerTestUtils.checkDirtyTracking( entity, "someStrings" ); + EnhancerTestUtils.clearDirtyTracking( entity ); + + // Association: this should not set the entity to dirty + Set intSet = new HashSet<>(); + intSet.add( 42 ); + entity.someInts = intSet; + EnhancerTestUtils.checkDirtyTracking( entity ); + + // testing composite object + Address address = new Address(); + entity.address = address; + address.city = "Arendal"; + EnhancerTestUtils.checkDirtyTracking( entity, "address" ); + EnhancerTestUtils.clearDirtyTracking( entity ); + + // make sure that new composite instances are cleared + Address address2 = new Address(); + entity.address = address2; + address.street1 = "Heggedalveien"; + EnhancerTestUtils.checkDirtyTracking( entity, "address" ); + + Country country = new Country(); + address2.country = country; + country.name = "Norway"; + EnhancerTestUtils.checkDirtyTracking( entity, "address", "address.country" ); + + address.country = null; + entity.address = null; + } + + // --- // + + @Embeddable + private static class Address { + @Embedded + Country country; + String street1; + String street2; + String city; + String state; + String zip; + String phone; + } + + @Embeddable + private static class Country { + String name; + } + + @Entity + private static class SimpleEntity { + + @Id + Long id; + + String name; + + Boolean active = Boolean.FALSE; + + Long someNumber = 0L; + + List someStrings; + + @OneToMany + Set someInts; + + @Embedded + Address address; + + @Embedded + Address address2; + + public Long getSomeNumber() { + return someNumber; + } + + public void setSomeNumber(Long someNumber) { + this.someNumber = someNumber; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingTestTask.java deleted file mode 100644 index 8079a2aa814d..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/DirtyTrackingTestTask.java +++ /dev/null @@ -1,94 +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.bytecode.enhancement.dirty; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; - -/** - * @author Luis Barreiro - */ -public class DirtyTrackingTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {SimpleEntity.class}; - } - - public void prepare() { - } - - public void execute() { - SimpleEntity entity = new SimpleEntity(); - - // Basic single field - entity.getSomeNumber(); - EnhancerTestUtils.checkDirtyTracking( entity ); - entity.setSomeNumber( 1l ); - EnhancerTestUtils.checkDirtyTracking( entity, "someNumber" ); - EnhancerTestUtils.clearDirtyTracking( entity ); - entity.setSomeNumber( entity.getSomeNumber() ); - EnhancerTestUtils.checkDirtyTracking( entity ); - - // Basic multi-field (Id properties are not flagged as dirty) - entity.setId( 2l ); - entity.setActive( !entity.isActive() ); - entity.setSomeNumber( 193L ); - EnhancerTestUtils.checkDirtyTracking( entity, "active", "someNumber" ); - EnhancerTestUtils.clearDirtyTracking( entity ); - - // Setting the same value should not make it dirty - entity.setSomeNumber( 193L ); - EnhancerTestUtils.checkDirtyTracking( entity ); - - // Collection - List strings = new ArrayList(); - strings.add( "FooBar" ); - entity.setSomeStrings( strings ); - EnhancerTestUtils.checkDirtyTracking( entity, "someStrings" ); - EnhancerTestUtils.clearDirtyTracking( entity ); - - strings.add( "BarFoo" ); - EnhancerTestUtils.checkDirtyTracking( entity, "someStrings" ); - EnhancerTestUtils.clearDirtyTracking( entity ); - - // Association: this should not set the entity to dirty - Set intSet = new HashSet(); - intSet.add( 42 ); - entity.setSomeInts( intSet ); - EnhancerTestUtils.checkDirtyTracking( entity ); - - // testing composite object - Address address = new Address(); - entity.setAddress( address ); - address.setCity( "Arendal" ); - EnhancerTestUtils.checkDirtyTracking( entity, "address" ); - EnhancerTestUtils.clearDirtyTracking( entity ); - - // make sure that new composite instances are cleared - Address address2 = new Address(); - entity.setAddress( address2 ); - address.setStreet1( "Heggedalveien" ); - EnhancerTestUtils.checkDirtyTracking( entity, "address" ); - - Country country = new Country(); - address2.setCountry( country ); - country.setName( "Norway" ); - EnhancerTestUtils.checkDirtyTracking( entity, "address", "address.country" ); - - address.setCountry( null ); - entity.setAddress( null ); - - } - - protected void cleanup() { - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/ParentChildEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/ParentChildEntity.java deleted file mode 100644 index c78f6f998cb8..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/ParentChildEntity.java +++ /dev/null @@ -1,41 +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.bytecode.enhancement.dirty; - -import java.util.List; -import javax.persistence.ElementCollection; -import javax.persistence.Entity; -import javax.persistence.Id; - -/** - * @author Vlad Mihalcea - */ -@Entity -public class ParentChildEntity { - - @Id - private Long id; - - @ElementCollection - private List someStrings; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public List getSomeStrings() { - return someStrings; - } - - public void setSomeStrings(List someStrings) { - this.someStrings = someStrings; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/SimpleEntity.java deleted file mode 100644 index 5d438ef1e462..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/dirty/SimpleEntity.java +++ /dev/null @@ -1,99 +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.bytecode.enhancement.dirty; - -import java.util.List; -import java.util.Set; -import javax.persistence.Embeddable; -import javax.persistence.Embedded; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -/** - * @author Steve Ebersole - */ -@Entity -public class SimpleEntity { - - @Id - private Long id; - - private String name; - - private boolean active; - - private long someNumber; - - private List someStrings; - - @OneToMany - private Set someInts; - - @Embedded - private Address address; - - @Embedded - private Address address2; - - 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 boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - public long getSomeNumber() { - return someNumber; - } - - public void setSomeNumber(long someNumber) { - this.someNumber = someNumber; - } - - public List getSomeStrings() { - return someStrings; - } - - public void setSomeStrings(List someStrings) { - this.someStrings = someStrings; - } - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } - - public Set getSomeInts() { - return someInts; - } - - public void setSomeInts(Set someInts) { - this.someInts = someInts; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/EvictionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/EvictionTest.java new file mode 100644 index 000000000000..c4fb1f42aff6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/EvictionTest.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.eviction; + +import org.hibernate.engine.spi.ManagedEntity; +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.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class EvictionTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Parent.class}; + } + + @Before + public void prepare() { + // Create a Parent + doInHibernate( this::sessionFactory, s -> { + Parent p = new Parent(); + p.name = "PARENT"; + s.persist( p ); + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + + // Delete the Parent + Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) + .setParameter( "name", "PARENT" ) + .uniqueResult(); + assertTyping( ManagedEntity.class, loadedParent ); + ManagedEntity managedParent = (ManagedEntity) loadedParent; + + // before eviction + assertNotNull( managedParent.$$_hibernate_getEntityInstance() ); + assertNotNull( managedParent.$$_hibernate_getEntityEntry() ); + assertNull( managedParent.$$_hibernate_getPreviousManagedEntity() ); + assertNull( managedParent.$$_hibernate_getNextManagedEntity() ); + + assertTrue( s.contains( managedParent ) ); + s.evict( managedParent ); + + // after eviction + assertFalse( s.contains( managedParent ) ); + assertNotNull( managedParent.$$_hibernate_getEntityInstance() ); + assertNull( managedParent.$$_hibernate_getEntityEntry() ); + assertNull( managedParent.$$_hibernate_getPreviousManagedEntity() ); + assertNull( managedParent.$$_hibernate_getNextManagedEntity() ); + + // evict again + s.evict( managedParent ); + + assertFalse( s.contains( managedParent ) ); + assertNotNull( managedParent.$$_hibernate_getEntityInstance() ); + assertNull( managedParent.$$_hibernate_getEntityEntry() ); + assertNull( managedParent.$$_hibernate_getPreviousManagedEntity() ); + assertNull( managedParent.$$_hibernate_getNextManagedEntity() ); + + s.delete( managedParent ); + } ); + } + + // --- // + + @Entity( name = "Parent" ) + @Table( name = "PARENT" ) + private static class Parent { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/EvictionTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/EvictionTestTask.java deleted file mode 100644 index 1dc21fa1b193..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/EvictionTestTask.java +++ /dev/null @@ -1,94 +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.bytecode.enhancement.eviction; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -/** - * @author Gail Badner - */ -public class EvictionTestTask extends AbstractEnhancerTestTask { - - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - // Create a Parent - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent p = new Parent(); - p.setName( "PARENT" ); - s.persist( p ); - - s.getTransaction().commit(); - s.close(); - } - - public void execute() { - // Delete the Parent - Session s = getFactory().openSession(); - s.beginTransaction(); - Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) - .setParameter( "name", "PARENT" ) - .uniqueResult(); - assertTyping( ManagedEntity.class, loadedParent ); - ManagedEntity managedParent = (ManagedEntity) loadedParent; - // before eviction - assertNotNull( managedParent.$$_hibernate_getEntityInstance() ); - assertNotNull( managedParent.$$_hibernate_getEntityEntry() ); - assertNull( managedParent.$$_hibernate_getPreviousManagedEntity() ); - assertNull( managedParent.$$_hibernate_getNextManagedEntity() ); - - assertTrue( s.contains( managedParent ) ); - s.evict( managedParent ); - - // after eviction - assertFalse( s.contains( managedParent ) ); - assertNotNull( managedParent.$$_hibernate_getEntityInstance() ); - assertNull( managedParent.$$_hibernate_getEntityEntry() ); - assertNull( managedParent.$$_hibernate_getPreviousManagedEntity() ); - assertNull( managedParent.$$_hibernate_getNextManagedEntity() ); - - // evict again - s.evict( managedParent ); - - assertFalse( s.contains( managedParent ) ); - assertNotNull( managedParent.$$_hibernate_getEntityInstance() ); - assertNull( managedParent.$$_hibernate_getEntityEntry() ); - assertNull( managedParent.$$_hibernate_getPreviousManagedEntity() ); - assertNull( managedParent.$$_hibernate_getNextManagedEntity() ); - - s.delete( managedParent ); - - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/Parent.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/Parent.java deleted file mode 100644 index bea25eee584d..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/eviction/Parent.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.eviction; - -import java.util.ArrayList; -import java.util.List; -import javax.persistence.Basic; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -/** - * Created by barreiro on 12/9/15. - */ -@Entity -public class Parent { - private Long id; - private String name; - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - 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/test/bytecode/enhancement/extended/ExtendedAssociationManagementTestTasK.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/extended/ExtendedAssociationManagementTestTasK.java deleted file mode 100644 index 759c154acb66..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/extended/ExtendedAssociationManagementTestTasK.java +++ /dev/null @@ -1,82 +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.bytecode.enhancement.extended; - -import java.util.UUID; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.OneToOne; - -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -/** - * @author Luis Barreiro - */ -public class ExtendedAssociationManagementTestTasK extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {Customer.class, User.class}; - } - - public void prepare() { - } - - public void execute() { - User user = new User(); - user.login = UUID.randomUUID().toString(); - - Customer customer = new Customer(); - customer.user = user; - - Assert.assertEquals( customer, EnhancerTestUtils.getFieldByReflection( user, "customer" ) ); - - // check dirty tracking is set automatically with bi-directional association management - EnhancerTestUtils.checkDirtyTracking( user, "login", "customer" ); - - User anotherUser = new User(); - anotherUser.login = UUID.randomUUID().toString(); - - customer.user = anotherUser; - - Assert.assertNull( user.customer ); - Assert.assertEquals( customer, EnhancerTestUtils.getFieldByReflection( anotherUser, "customer" ) ); - - user.customer = new Customer(); - Assert.assertEquals( user, user.customer.user ); - } - - protected void cleanup() { - } - - @Entity public class Customer { - - @Id public int id; - - @OneToOne(fetch = FetchType.LAZY) public User user; - - public String firstName; - - public String lastName; - - public int version; - } - - @Entity public class User { - - @Id public int id; - - public String login; - - public String password; - - @OneToOne(mappedBy = "user", fetch = FetchType.LAZY) public Customer customer; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/extended/ExtendedEnhancementTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/extended/ExtendedEnhancementTestTask.java deleted file mode 100644 index 055d3f7b65d7..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/extended/ExtendedEnhancementTestTask.java +++ /dev/null @@ -1,90 +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.bytecode.enhancement.extended; - -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -import org.hibernate.engine.spi.PersistentAttributeInterceptable; - -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.test.bytecode.enhancement.basic.ObjectAttributeMarkerInterceptor; -import org.junit.Assert; - -/** - * @author Luis Barreiro - */ -public class ExtendedEnhancementTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {SimpleEntity.class}; - } - - public void prepare() { - } - - public void execute() { - // test uses ObjectAttributeMarkerInterceptor to ensure that field access is routed through enhanced methods - - SimpleEntity entity = new SimpleEntity(); - ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( new ObjectAttributeMarkerInterceptor() ); - - Object decoy = new Object(); - entity.anUnspecifiedObject = decoy; - - Object gotByReflection = EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ); - Assert.assertNotSame( gotByReflection, decoy ); - Assert.assertSame( gotByReflection, ObjectAttributeMarkerInterceptor.WRITE_MARKER ); - - Object entityObject = entity.anUnspecifiedObject; - - Assert.assertNotSame( entityObject, decoy ); - Assert.assertSame( entityObject, ObjectAttributeMarkerInterceptor.READ_MARKER ); - - // do some more calls on the various types, without the interceptor - ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( null ); - - entity.id = 1234567890l; - Assert.assertEquals( entity.id, 1234567890l ); - - entity.name = "Entity Name"; - Assert.assertSame( entity.name, "Entity Name" ); - - entity.active = true; - Assert.assertTrue( entity.active ); - - entity.someStrings = Arrays.asList( "A", "B", "C", "D" ); - Assert.assertArrayEquals( new String[] { "A", "B", "C", "D" }, entity.someStrings.toArray() ); - } - - protected void cleanup() { - } - - @Entity public class SimpleEntity { - - @Id public long id; - - public String name; - - public boolean active; - - public long someNumber; - - public int anInt; - - public Object anUnspecifiedObject; - - public List someStrings; - - @OneToMany public Set someInts; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/inherited/InheritedTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/inherited/InheritedTestTask.java deleted file mode 100644 index 2d2759f647b3..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/inherited/InheritedTestTask.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.inherited; - -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Version; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -/** - * @author Luis Barreiro - * @author Craig Andrews - */ -public class InheritedTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] { Person.class, Employee.class, Contractor.class }; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - } - - public void execute() { - Employee charles = new Employee( "Charles", "Engineer" ); - charles.setOca( 1002 ); - - // Check that both types of class attributes are being dirty tracked - EnhancerTestUtils.checkDirtyTracking( charles, "title", "oca" ); - EnhancerTestUtils.clearDirtyTracking( charles ); - - // Let's give charles a promotion, this time using method references - charles.setOca( 99 ); - charles.setTitle( "Manager" ); - - EnhancerTestUtils.checkDirtyTracking( charles, "title", "oca" ); - - Contractor bob = new Contractor( "Bob", 100 ); - bob.setOca( 1003 ); - - // Check that both types of class attributes are being dirty tracked - EnhancerTestUtils.checkDirtyTracking( bob, "rate", "oca" ); - EnhancerTestUtils.clearDirtyTracking( bob ); - - // Let's give bob a rate increase, this time using method references - bob.setOca( 88 ); - bob.setRate( 200 ); - - EnhancerTestUtils.checkDirtyTracking( bob, "rate", "oca" ); - } - - protected void cleanup() { - } - - @Entity private static abstract class Person { - - @Id private String name; - - @Version private long oca; - - public Person(String name) { - this(); - this.name = name; - } - - protected Person() {} - - protected void setOca(long l) { - this.oca = l; - } - } - - @Entity private static class Employee extends Person { - - private String title; - - public Employee(String name, String title) { - super(name); - this.title = title; - } - - public Employee() {} - - public void setTitle(String title) { - this.title = title; - } - } - - @Entity private static class Contractor extends Person { - - private Integer rate; - - public Contractor(String name, Integer rate) { - super(name); - this.rate = rate; - } - - public Contractor() {} - - public void setRate(Integer rate) { - this.rate = rate; - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/javassist/EnhancerFileNotFoundTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/javassist/EnhancerFileNotFoundTest.java index f7ac3ff130e2..b3fbb0c8dcb7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/javassist/EnhancerFileNotFoundTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/javassist/EnhancerFileNotFoundTest.java @@ -6,64 +6,60 @@ */ package org.hibernate.test.bytecode.enhancement.javassist; -import java.io.FileNotFoundException; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; - import javassist.CtClass; - import org.hibernate.bytecode.enhance.internal.javassist.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementContext; - import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; -import static org.junit.Assert.assertEquals; +import java.io.FileNotFoundException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; /** * @author Vlad Mihalcea */ -public class EnhancerFileNotFoundTest extends BaseUnitTestCase { - - public static class Enhancer extends EnhancerImpl { +public class EnhancerFileNotFoundTest { - public Enhancer(EnhancementContext enhancementContext) { - super( enhancementContext ); - } + @Test + @TestForIssue( jiraKey = "HHH-11307" ) + public void test() throws Exception { + Enhancer enhancer = new Enhancer( new DefaultEnhancementContext() ); + try { + String resourceName = Hidden.class.getName().replace( '.', '/' ) + ".class"; + URL url = getClass().getClassLoader().getResource( resourceName ); + if ( url != null ) { + Files.delete( Paths.get( url.toURI() ) ); + enhancer.loadCtClassFromClass( Hidden.class ); + } + fail( "Should throw FileNotFoundException!" ); + } catch ( Exception expected ) { + assertSame( FileNotFoundException.class, expected.getCause().getClass() ); + } + } - @Override - public CtClass loadCtClassFromClass(Class aClass) { - return super.loadCtClassFromClass( aClass ); - } - } + // --- // - @Test - @TestForIssue(jiraKey = "HHH-11307") - public void test() - throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - EnhancementContext enhancementContextMock = mock( EnhancementContext.class ); + private static class Enhancer extends EnhancerImpl { - Enhancer enhancer = new Enhancer( enhancementContextMock ); - try { - Class clazz = Hidden.class; - String resourceName = Hidden.class.getName().replace( '.', '/' ) + ".class"; - URL url = getClass().getClassLoader().getResource( resourceName ); - Files.delete( Paths.get(url.toURI()) ); - enhancer.loadCtClassFromClass( clazz ); - fail("Should throw FileNotFoundException!"); - } - catch ( Exception expected ) { - assertEquals( FileNotFoundException.class, expected.getCause().getClass() ); - } - } + public Enhancer(EnhancementContext enhancementContext) { + super( enhancementContext ); + } - private static class Hidden { + @Override + // change visibility protected -> public + public CtClass loadCtClassFromClass(Class aClass) { + return super.loadCtClassFromClass( aClass ); + } + } - } + // --- // + private static class Hidden { + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/AbstractHHH3949TestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/AbstractHHH3949TestTask.java deleted file mode 100644 index 8211875293f6..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/AbstractHHH3949TestTask.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.join; - -import org.hibernate.Session; -import org.hibernate.Transaction; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -public abstract class AbstractHHH3949TestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[] {Person.class, Vehicle.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); -// cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "false" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session session = getFactory().openSession(); - Transaction tx = session.beginTransaction(); - - // it is important that the data associations remain as follows: - // * Johnny <-> Volkswagen Golf - // * Ricky <-> Subaru Impreza - // * Rosy -> none - // * none <- Renault Truck - // - // see #shouldHaveVehicle and #shouldHaveDriver - - Person person1 = new Person( "Johnny" ); - Person person2 = new Person( "Ricky" ); - Person person3 = new Person( "Rosy" ); - session.save( person1 ); - session.save( person2 ); - session.save( person3 ); - - Vehicle vehicle1 = new Vehicle( "Volkswagen Golf" ); - vehicle1.setDriver( person1 ); - session.save( vehicle1 ); - - Vehicle vehicle2 = new Vehicle( "Subaru Impreza" ); - vehicle2.setDriver( person2 ); - person2.setVehicle( vehicle2 ); - session.save( vehicle2 ); - - Vehicle vehicle3 = new Vehicle( "Renault Truck" ); - - session.save( vehicle3 ); - - tx.commit(); - session.close(); - } - - protected boolean shouldHaveVehicle(Person person) { - return "Johnny".equals( person.getName() ) - || "Ricky".equals( person.getName() ); - } - - protected boolean shouldHaveDriver(Vehicle vehicle) { - return "Volkswagen Golf".equals( vehicle.getName() ) - || "Subaru Impreza".equals( vehicle.getName() ); - } - - protected void cleanup() { - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949Test.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949Test.java new file mode 100644 index 000000000000..e39604755e52 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949Test.java @@ -0,0 +1,224 @@ +package org.hibernate.test.bytecode.enhancement.join; + +import org.hibernate.FetchMode; +import org.hibernate.Session; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +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.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import java.util.List; + +import static org.hibernate.Hibernate.isInitialized; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@TestForIssue( jiraKey = "HHH-3949" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class HHH3949Test extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Person.class, Vehicle.class}; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + // see HHH-3949 for further details ^^^^^ + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + + // it is important that the data associations remain as follows: + // * Johnny <-> Volkswagen Golf + // * Ricky <-> Subaru Impreza + // * Rosy -> none + // * none <- Renault Truck + // + // see #shouldHaveVehicle and #shouldHaveDriver + + Person person1 = new Person( "Johnny" ); + Person person2 = new Person( "Ricky" ); + Person person3 = new Person( "Rosy" ); + s.save( person1 ); + s.save( person2 ); + s.save( person3 ); + + Vehicle vehicle1 = new Vehicle( "Volkswagen Golf" ); + vehicle1.setDriver( person1 ); + s.save( vehicle1 ); + + Vehicle vehicle2 = new Vehicle( "Subaru Impreza" ); + vehicle2.setDriver( person2 ); + person2.setVehicle( vehicle2 ); + s.save( vehicle2 ); + + Vehicle vehicle3 = new Vehicle( "Renault Truck" ); + s.save( vehicle3 ); + } ); + } + + @Test + public void test1() { + // verify the work around query + performQueryAndVerifyPersonResults( "from Person p fetch all properties left join fetch p.vehicle" ); + performQueryAndVerifyPersonResults( "from Person p left join fetch p.vehicle" ); + } + + @Test + public void test2() { + performQueryAndVerifyVehicleResults( "from Vehicle v fetch all properties left join fetch v.driver" ); + performQueryAndVerifyVehicleResults( "from Vehicle v left join fetch v.driver" ); + } + + @Test + @SuppressWarnings( "unchecked" ) + public void test3() { + doInHibernate( this::sessionFactory, s -> { + List persons = (List) s.createCriteria( Person.class ).setFetchMode( "vehicle", FetchMode.JOIN ).list(); + for ( Person person : persons ) { + if ( shouldHaveVehicle( person ) ) { + assertNotNull( person.getVehicle() ); + assertNotNull( person.getVehicle().getDriver() ); + } + } + } ); + } + + @Test + @SuppressWarnings( "unchecked" ) + public void test4() { + List vehicles; + + try ( Session s = openSession() ) { + vehicles = (List) s.createCriteria( Vehicle.class ).setFetchMode( "driver", FetchMode.JOIN ).list(); + } + + for ( Vehicle vehicle : vehicles ) { + if ( shouldHaveDriver( vehicle ) ) { + assertNotNull( vehicle.getDriver() ); + assertNotNull( vehicle.getDriver().getVehicle() ); + } + } + } + + // --- // + + @SuppressWarnings( "unchecked" ) + private void performQueryAndVerifyPersonResults(String query) { + List persons; + try ( Session s = openSession() ) { + persons = (List) s.createQuery( query ).list(); + } + for ( Person person : persons ) { + assertTrue( isInitialized( person ) ); + if ( shouldHaveVehicle( person ) ) { + assertNotNull( person.getVehicle() ); + assertTrue( isInitialized( person.getVehicle() ) ); + assertNotNull( person.getVehicle().getDriver() ); + } + } + } + + @SuppressWarnings( "unchecked" ) + private void performQueryAndVerifyVehicleResults(String query) { + List vehicles; + try ( Session s = openSession() ) { + vehicles = (List) s.createQuery( query ).list(); + } + for ( Vehicle vehicle : vehicles ) { + if ( shouldHaveDriver( vehicle ) ) { + assertNotNull( vehicle.getDriver() ); + assertNotNull( vehicle.getDriver().getVehicle() ); + } + } + } + + private boolean shouldHaveVehicle(Person person) { + return "Johnny".equals( person.name ) || "Ricky".equals( person.name ); + } + + private boolean shouldHaveDriver(Vehicle vehicle) { + return "Volkswagen Golf".equals( vehicle.name ) || "Subaru Impreza".equals( vehicle.name ); + } + + // --- // + + @Entity( name = "Person" ) + @Table( name = "PERSON" ) + private static class Person { + + @Id + @GeneratedValue + Long id; + + String name; + + @OneToOne( optional = true, mappedBy = "driver", fetch = FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + Vehicle vehicle; + + Person() { + } + + Person(String name) { + this.name = name; + } + + Vehicle getVehicle() { + return vehicle; + } + + void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity( name = "Vehicle" ) + @Table( name = "VEHICLE" ) + private static class Vehicle { + + @Id + @GeneratedValue + Long id; + + String name; + + @OneToOne( optional = true, fetch = FetchType.LAZY ) + Person driver; + + Vehicle() { + } + + Vehicle(String name) { + this.name = name; + } + + Person getDriver() { + return driver; + } + + void setDriver(Person driver) { + this.driver = driver; + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask1.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask1.java deleted file mode 100644 index 6c9747856a99..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask1.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.join; - -import java.util.List; - -import org.hibernate.Hibernate; -import org.hibernate.Session; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -public class HHH3949TestTask1 extends AbstractHHH3949TestTask { - - public void execute() { - // verify the work around query - performQueryAndVerifyResults( "from Person p fetch all properties left join fetch p.vehicle" ); - performQueryAndVerifyResults( "from Person p left join fetch p.vehicle" ); - } - - @SuppressWarnings("unchecked") - private void performQueryAndVerifyResults(String query) { - // 1) open session - Session session = getFactory().openSession(); - session.getTransaction().begin(); - // 2) perform the query - List persons = (List) session.createQuery( query ).list(); - // 3) close the session : this ensures that no more queries and/or data loading happen - session.getTransaction().commit(); - session.close(); - - // 4) verify the results - for ( Person person : persons ) { - assertTrue( Hibernate.isInitialized( person ) ); - if ( shouldHaveVehicle( person ) ) { - assertNotNull( person.getVehicle() ); - assertTrue( Hibernate.isInitialized( person.getVehicle() ) ); - assertNotNull( person.getVehicle().getDriver() ); - } - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask2.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask2.java deleted file mode 100644 index 2f5f6f8a49b8..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask2.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.join; - -import java.util.List; - -import org.hibernate.Session; - -import org.junit.Assert; - -public class HHH3949TestTask2 extends AbstractHHH3949TestTask { - - public void execute() { - performQueryAndVerifyResults( "from Vehicle v fetch all properties left join fetch v.driver" ); - performQueryAndVerifyResults( "from Vehicle v left join fetch v.driver" ); - } - - @SuppressWarnings("unchecked") - public void performQueryAndVerifyResults(String query) { - // 1) open session - Session session = getFactory().openSession(); - session.getTransaction().begin(); - // 2) perform the query - List vehicles = (List) session.createQuery( query ).list(); - // 3) close the session : this ensures that no more queries and/or data loading happen - session.getTransaction().commit(); - session.close(); - - // 4) verify the results - for ( Vehicle vehicle : vehicles ) { - if ( shouldHaveDriver( vehicle ) ) { - Assert.assertNotNull( vehicle.getDriver() ); - Assert.assertNotNull( vehicle.getDriver().getVehicle() ); - } - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask3.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask3.java deleted file mode 100644 index 0eb4448c5e60..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask3.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.join; - -import java.util.List; - -import org.hibernate.FetchMode; -import org.hibernate.Session; - -import org.junit.Assert; - -public class HHH3949TestTask3 extends AbstractHHH3949TestTask { - - @SuppressWarnings("unchecked") - public void execute() { - Session session = getFactory().openSession(); - session.getTransaction().begin(); - List persons = (List) session.createCriteria( Person.class ) - .setFetchMode( "vehicle", FetchMode.JOIN ) - .list(); - for ( Person person : persons ) { - if ( shouldHaveVehicle( person ) ) { - Assert.assertNotNull( person.getVehicle() ); - Assert.assertNotNull( person.getVehicle().getDriver() ); - } - } - session.getTransaction().commit(); - session.close(); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask4.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask4.java deleted file mode 100644 index d2f1985198ed..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/HHH3949TestTask4.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.join; - -import java.util.List; - -import org.hibernate.FetchMode; -import org.hibernate.Session; - -import org.junit.Assert; - -public class HHH3949TestTask4 extends AbstractHHH3949TestTask { - - @SuppressWarnings("unchecked") - public void execute() { - Session session = getFactory().openSession(); - session.getTransaction().begin(); - List vehicles = (List) session.createCriteria( Vehicle.class ) - .setFetchMode( "driver", FetchMode.JOIN ) - .list(); - session.getTransaction().commit(); - session.close(); - - for ( Vehicle vehicle : vehicles ) { - if ( shouldHaveDriver( vehicle ) ) { - Assert.assertNotNull( vehicle.getDriver() ); - Assert.assertNotNull( vehicle.getDriver().getVehicle() ); - } - } - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/Person.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/Person.java deleted file mode 100644 index beea94ec6f41..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/Person.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.join; - -import java.io.Serializable; -import javax.persistence.*; - -import org.hibernate.annotations.LazyToOne; -import org.hibernate.annotations.LazyToOneOption; - -@Entity -public class Person implements Serializable { - @Id - @GeneratedValue - private Integer id; - - private String name; - - @OneToOne(optional = true, mappedBy = "driver", fetch = FetchType.LAZY) - @LazyToOne(LazyToOneOption.NO_PROXY) - private Vehicle vehicle; - - public Vehicle getVehicle() { - return vehicle; - } - - public void setVehicle(Vehicle vehicle) { - this.vehicle = vehicle; - } - - public Integer getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Person() { - } - - public Person(String name) { - this.name = name; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/Vehicle.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/Vehicle.java deleted file mode 100644 index 9d12fa74ac65..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/join/Vehicle.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.hibernate.test.bytecode.enhancement.join; - -import java.io.Serializable; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.OneToOne; - -@Entity -public class Vehicle implements Serializable { - - @Id - @GeneratedValue - private Integer id; - - private String name; - - @OneToOne(optional = true, fetch = FetchType.LAZY) - private Person driver; - - public Integer getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Person getDriver() { - return driver; - } - - public void setDriver(Person driver) { - this.driver = driver; - } - - public Vehicle() { - } - - public Vehicle(String name) { - this.name = name; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java new file mode 100644 index 000000000000..5a636b08aeb7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java @@ -0,0 +1,424 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.SessionImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * Tests removing non-owning side of the bidirectional association, + * with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, // supports laziness and dirty-checking + BidirectionalLazyTest.NoDirtyCheckEnhancementContext.class // supports laziness; does not support dirty-checking +}) +public class BidirectionalLazyTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class, Unrelated.class }; + } + + @Test + public void testRemoveWithDeletedAssociatedEntity() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.get( Employer.class, "RedHat" ); + + // Delete the associated entity first + session.remove( employer ); + + for ( Employee employee : employer.getEmployees() ) { + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // Should be initialized because at least one entity was deleted beforehand + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + assertSame( employer, employee.getEmployer() ); + // employee.getEmployer was initialized, and should be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, employer, true ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employer.class, "RedHat" ) ); + assertTrue( session.createQuery( "from Employee e", Employee.class ).getResultList().isEmpty() ); + } + ); + } + + @Test + public void testRemoveWithNonAssociatedRemovedEntity() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employer.addEmployee( employee ); + session.persist( employee ); + session.persist( new Unrelated( 1 ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + // Delete an entity that is not associated with Employee + session.remove( session.get( Unrelated.class, 1 ) ); + final Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // Should be initialized because at least one entity was deleted beforehand + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // employee.getEmployer was initialized, and should not be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, employee.getEmployer(), false ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Unrelated.class, 1 ) ); + assertNull( session.find( Employee.class, "Jack" ) ); + session.remove( session.find( Employer.class, "RedHat" ) ); + } + ); + } + + @Test + public void testRemoveWithNoRemovedEntities() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employer.addEmployee( employee ); + session.persist( employee ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + // Don't delete any entities before deleting the Employee + final Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // There were no other deleted entities before employee was deleted, + // so there was no need to initialize employee.employer. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // employee.getEmployer was not initialized, and should not be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, LazyPropertyInitializer.UNFETCHED_PROPERTY, false ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employee.class, "Jack" ) ); + session.remove( session.find( Employer.class, "RedHat" ) ); + } + ); + } + + @Test + public void testRemoveEntityWithNullLazyManyToOne() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + session.persist( employee ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + + // Get and delete an Employer that is not associated with employee + Employer employer = session.get( Employer.class, "RedHat" ); + session.remove( employer ); + + // employee.employer is uninitialized. Since the column for employee.employer + // is a foreign key, and there is an Employer that has already been removed, + // employee.employer will need to be iniitialized to determine if + // employee.employer is nullifiable. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + ); + } + + /** + * @implSpec Same as {@link #testRemoveEntityWithNullLazyManyToOne} but + * deleting the Employer linked to the loaded Employee + */ + @Test + public void testRemoveEntityWithLinkedLazyManyToOne() { + inTransaction( + session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employee.setEmployer( employer ); + session.persist( employee ); + } + ); + + inTransaction( + session -> { + Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + + // Get and delete an Employer that is not associated with employee + Employer employer = session.get( Employer.class, "RedHat" ); + session.remove( employer ); + + // employee.employer is uninitialized. Since the column for employee.employer + // is a foreign key, and there is an Employer that has already been removed, + // employee.employer will need to be iniitialized to determine if + // employee.employer is nullifiable. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + ); + } + + private void checkEntityEntryState( + final Session session, + final Employee employee, + final Object employer, + final boolean isEmployerNullified) { + final SessionImplementor sessionImplementor = (SessionImplementor) session; + final EntityEntry entityEntry = sessionImplementor.getPersistenceContext().getEntry( employee ); + final int propertyNumber = entityEntry.getPersister().getEntityMetamodel().getPropertyIndex( "employer" ); + assertEquals( + employer, + entityEntry.getLoadedState()[propertyNumber] + ); + if ( isEmployerNullified ) { + assertEquals( null, entityEntry.getDeletedState()[propertyNumber] ); + } + else { + assertEquals( + employer, + entityEntry.getDeletedState()[propertyNumber] + ); + } + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employer", fetch = FetchType.LAZY) + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + employee.setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + private Employer employer; + + public Employee(String name) { + this(); + setName( name ); + } + + public long getId() { + return id; + } + + @Id + public String getName() { + return name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + @Entity(name = "Manager") + public static class Manager extends Employee { + } + + @Entity(name = "Unrelated") + public static class Unrelated { + private int id; + + public Unrelated() { + } + + public Unrelated(int id) { + setId( id ); + } + + @Id + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return true; + } + + @Override + public boolean isLazyLoadable(UnloadedField field) { + return true; + } + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/Child.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/Child.java deleted file mode 100644 index 99ceeb26ea80..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/Child.java +++ /dev/null @@ -1,68 +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.bytecode.enhancement.lazy; - -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.ManyToOne; - -import org.hibernate.annotations.LazyToOne; -import org.hibernate.annotations.LazyToOneOption; - -/** - * @author Luis Barreiro - */ - -@Entity -public class Child { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - Long id; - - String name; - - @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @LazyToOne(LazyToOneOption.NO_PROXY) - Parent parent; - - public Child() { - } - - public Child(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; - } - - public Parent getParent() { - return parent; - } - - public void setParent(Parent parent) { - this.parent = parent; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteOneTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteOneTestTask.java deleted file mode 100644 index 7bb68d346b57..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteOneTestTask.java +++ /dev/null @@ -1,96 +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.bytecode.enhancement.lazy.HHH_10708; - -import java.util.HashSet; -import java.util.Set; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; - -import org.hibernate.Session; -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.annotations.Cascade; -import org.hibernate.annotations.CascadeType; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -public class UnexpectedDeleteOneTestTask extends AbstractEnhancerTestTask { - - private int fooId; - - public Class[] getAnnotatedClasses() { - return new Class[] {Foo.class, Bar.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( AvailableSettings.SHOW_SQL, Boolean.FALSE.toString() ); - cfg.setProperty( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Bar bar1 = new Bar(); - Bar bar2 = new Bar(); - Foo foo = new Foo(); - s.save( bar1 ); - s.save( bar2 ); - s.save( foo ); - bar1.foo = foo; - bar2.foo = foo; - fooId = foo.id; - - s.getTransaction().commit(); - s.clear(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Foo foo = s.get( Foo.class, fooId ); - - // accessing the collection results in an exception - foo.bars.size(); - - s.flush(); - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - - @Entity(name = "Bar") static class Bar { - static final String FOO = "foo"; - - @Id @GeneratedValue - int id; - - @ManyToOne @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) - Foo foo; - } - - @Entity(name = "Foo") static class Foo { - - @Id @GeneratedValue - int id; - - @OneToMany(orphanRemoval = true, mappedBy = Bar.FOO, targetEntity = Bar.class) - @Cascade(CascadeType.ALL) - Set bars = new HashSet<>(); - } - -} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest1.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest1.java new file mode 100644 index 000000000000..7fa975bd7d02 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest1.java @@ -0,0 +1,94 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.HHH_10708; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; +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.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.HashSet; +import java.util.Set; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +@TestForIssue( jiraKey = "HHH-10708" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class UnexpectedDeleteTest1 extends BaseCoreFunctionalTestCase { + + private long fooId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Foo.class, Bar.class}; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Bar bar1 = new Bar(); + Bar bar2 = new Bar(); + Foo foo = new Foo(); + s.save( bar1 ); + s.save( bar2 ); + s.save( foo ); + bar1.foo = foo; + bar2.foo = foo; + fooId = foo.id; + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Foo foo = s.get( Foo.class, fooId ); + + // accessing the collection results in an exception + foo.bars.size(); + } ); + } + + // --- // + + @Entity + @Table( name = "BAR" ) + private static class Bar { + + @Id + @GeneratedValue + Long id; + + @ManyToOne + @Cache( usage = CacheConcurrencyStrategy.READ_WRITE ) + Foo foo; + } + + @Entity + @Table( name = "FOO" ) + private static class Foo { + + @Id + @GeneratedValue + Long id; + + @OneToMany( orphanRemoval = true, mappedBy = "foo", targetEntity = Bar.class ) + @Cascade( CascadeType.ALL ) + Set bars = new HashSet<>(); + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest2.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest2.java new file mode 100644 index 000000000000..882ecbec8bef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest2.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.test.bytecode.enhancement.lazy.HHH_10708; + +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.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import java.util.HashSet; +import java.util.Set; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +@TestForIssue( jiraKey = "HHH-10708" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class UnexpectedDeleteTest2 extends BaseCoreFunctionalTestCase { + + private Bar myBar; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Foo.class, Bar.class}; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Bar bar = new Bar(); + Foo foo1 = new Foo(); + Foo foo2 = new Foo(); + s.save( bar ); + s.save( foo1 ); + s.save( foo2 ); + + bar.foos.add( foo1 ); + bar.foos.add( foo2 ); + + myBar = bar; + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + s.refresh( myBar ); + Assert.assertFalse( myBar.foos.isEmpty() ); + + // The issue is that currently, for some unknown reason, foos are deleted on flush + } ); + + doInHibernate( this::sessionFactory, s -> { + Bar bar = s.get( Bar.class, myBar.id ); + Assert.assertFalse( bar.foos.isEmpty() ); + } ); + } + + // --- // + + @Entity + @Table( name = "BAR" ) + private static class Bar { + + @Id + @GeneratedValue + Long id; + + @ManyToMany( fetch = FetchType.LAZY, targetEntity = Foo.class ) + Set foos = new HashSet<>(); + } + + @Entity + @Table( name = "FOO" ) + private static class Foo { + + @Id + @GeneratedValue + Long id; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest3.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest3.java new file mode 100644 index 000000000000..28205ed5abf4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTest3.java @@ -0,0 +1,134 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.HHH_10708; + +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.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +@TestForIssue( jiraKey = "HHH-10708" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class UnexpectedDeleteTest3 extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.class}; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Child child = new Child(); + child.setId( 2L ); + s.save( child ); + + Parent parent = new Parent(); + parent.setId( 1L ); + parent.setNames( Collections.singleton( "name" ) ); + parent.addChild( child ); + + s.save( parent ); + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = s.get( Parent.class, 1L ); + + Child child = new Child(); + child.setId( 1L ); + s.save( child ); + parent.addChild( child ); + + // We need to leave at least one attribute unfetchd + //parent.getNames().size(); + s.save( parent ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Parent application = s.get( Parent.class, 1L ); + Assert.assertEquals( "Loaded Children collection has unexpected size", 2, application.getChildren().size() ); + } ); + } + + // --- // + + @Entity + @Table( name = "CHILD" ) + private static class Child { + + Long id; + + @Id + @Column( name = "id", unique = true, nullable = false ) + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + } + + @Entity + @Table( name = "PARENT" ) + private static class Parent { + + Long id; + Set names; + Set children; + + @Id + @Column( name = "id", unique = true, nullable = false ) + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + @ElementCollection + Set getNames() { + return Collections.unmodifiableSet( names ); + } + + void setNames(Set secrets) { + this.names = secrets; + } + + @ManyToMany( fetch = FetchType.LAZY, targetEntity = Child.class ) + Set getChildren() { + return Collections.unmodifiableSet( children ); + } + + void addChild(Child child) { + if (children == null) { + children = new HashSet<>(); + } + children.add( child ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteThreeTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteThreeTestTask.java deleted file mode 100644 index 360f2c5f004c..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteThreeTestTask.java +++ /dev/null @@ -1,152 +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.bytecode.enhancement.lazy.HHH_10708; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import javax.persistence.Column; -import javax.persistence.ElementCollection; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.ManyToMany; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -public class UnexpectedDeleteThreeTestTask extends AbstractEnhancerTestTask { - - @Override - public Class[] getAnnotatedClasses() { - return new Class[] { Parent.class, Child.class }; - } - - @Override - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Child child = new Child(); - child.setId( 2L ); - s.save(child); - - Parent parent = new Parent(); - parent.setId( 1L ); - parent.setNames( Collections.singleton( "name" ) ); - - Set children = new HashSet(); - children.add(child); - parent.setChildren( children ); - - s.save( parent ); - - s.getTransaction().commit(); - s.clear(); - s.close(); - } - - @Override - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent parent = s.get( Parent.class, 1L ); - Set children = parent.getChildren(); - if (children == null) { - children = new HashSet(); - parent.setChildren( children ); - } - Child child = new Child(); - child.setId( 1L ); - s.save(child); - children.add(child); - - // We need to leave at least one attribute unfetchd - //parent.getNames().size(); - s.save(parent); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - - Parent application = s.get( Parent.class, 1L ); - Assert.assertEquals( "Loaded Children collection has unexpected size", 2, application.getChildren().size() ); - - s.getTransaction().commit(); - s.close(); - } - - @Override - protected void cleanup() { - } - - // --- // - - @Entity(name = "UChild") public static class Child { - - private Long id; - - @Id - @Column(name = "id", unique = true, nullable = false) - public Long getId() { - return id; - } - - public void setId(Long id) - { - this.id = id; - } - - } - - @Entity(name = "UParent") public static class Parent { - - private Long id; - private Set names; - private Set children; - - @Id @Column(name = "id", unique = true, nullable = false) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - @ElementCollection - public Set getNames() { - return names; - } - - public void setNames(Set secrets) { - this.names = secrets; - } - - @ManyToMany(fetch = FetchType.LAZY, targetEntity = Child.class) - public Set getChildren() { - return children; - } - - public void setChildren(Set children) { - this.children = children; - } - - } - -} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTwoTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTwoTestTask.java deleted file mode 100644 index e304749fb5b5..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/HHH_10708/UnexpectedDeleteTwoTestTask.java +++ /dev/null @@ -1,102 +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.bytecode.enhancement.lazy.HHH_10708; - -import java.util.HashSet; -import java.util.Set; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.ManyToMany; - -import org.hibernate.Session; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - - -public class UnexpectedDeleteTwoTestTask extends AbstractEnhancerTestTask { - - private Bar myBar; - - public Class[] getAnnotatedClasses() { - return new Class[] {Foo.class, Bar.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( AvailableSettings.SHOW_SQL, Boolean.FALSE.toString() ); - cfg.setProperty( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); - cfg.setProperty( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Bar bar = new Bar(); - Foo foo1 = new Foo(); - Foo foo2 = new Foo(); - s.save( bar ); - s.save( foo1 ); - s.save( foo2 ); - - bar.foos.add( foo1 ); - bar.foos.add( foo2 ); - - s.getTransaction().commit(); - s.clear(); - s.close(); - - myBar = bar; - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - s.refresh( myBar ); - Assert.assertFalse( myBar.foos.isEmpty() ); - - // The issue is that currently, for some unknown reason, foos are deleted on flush - - s.flush(); - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - - Bar bar = s.get( Bar.class, myBar.id ); - Assert.assertFalse( bar.foos.isEmpty() ); - - s.flush(); - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - - @Entity(name = "Bar") static class Bar { - - @Id @GeneratedValue - int id; - - @ManyToMany(fetch = FetchType.LAZY, targetEntity = Foo.class) - Set foos = new HashSet<>(); - } - - @Entity(name = "Foo") static class Foo { - - @Id @GeneratedValue - int id; - } - -} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldMergeTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldMergeTest.java new file mode 100644 index 000000000000..f2cd996711b3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldMergeTest.java @@ -0,0 +1,166 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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 javax.persistence.Basic; +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.Lob; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.tuple.NonIdentifierAttribute; + +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 static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-11117") +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyBasicFieldMergeTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Company.class, + Manager.class, + }; + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, session -> { + Manager manager = new Manager(); + manager.setName("John Doe"); + manager.setResume(new byte[] {1, 2, 3}); + + Company company = new Company(); + company.setName("Company"); + company.setManager(manager); + + Company _company = (Company) session.merge( company); + assertEquals( company.getName(), _company.getName() ); + assertArrayEquals( company.getManager().getResume(), _company.getManager().getResume() ); + } ); + } + + @Entity(name = "Company") + @Table(name = "COMPANY") + public static class Company { + + @Id + @GeneratedValue + @Column(name = "COMP_ID") + private Long id; + + @Column(name = "NAME") + private String name; + + @OneToOne(mappedBy = "company", cascade = javax.persistence.CascadeType.ALL, orphanRemoval = true) + private Manager manager; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Manager getManager() { + return manager; + } + + public void setManager(Manager manager) { + this.manager = manager; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + } + + + @Entity(name = "Manager") + @Table(name = "MANAGER") + public static class Manager { + + @Id + @GeneratedValue + @Column(name = "MAN_ID") + private Long id; + + @Column(name = "NAME") + private String name; + + @Lob + @Column(name = "RESUME") + @Basic(fetch = FetchType.LAZY) + private byte[] resume; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMP_ID") + private Company company; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte[] getResume() { + return resume; + } + + public void setResume(byte[] resume) { + this.resume = resume; + } + + public Company getCompany() { + return company; + } + + public void setCompany(Company company) { + this.company = company; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldNotInitializedTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldNotInitializedTest.java new file mode 100644 index 000000000000..2d1bc80339c5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldNotInitializedTest.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +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 org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.tuple.NonIdentifierAttribute; + +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 static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@TestForIssue( jiraKey = "HHH-9937") +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyBasicFieldNotInitializedTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{TestEntity.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 -> { + TestEntity entity = new TestEntity(); + entity.description = "desc"; + s.persist( entity ); + entityId = entity.id; + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + TestEntity entity = s.get( TestEntity.class, entityId ); + Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); + + EntityPersister entityPersister = sessionFactory().getMetamodel().entityPersister( TestEntity.class ); + + boolean[] propertyLaziness = entityPersister.getPropertyLaziness(); + assertEquals( 1, propertyLaziness.length ); + assertTrue( propertyLaziness[0] ); + + // Make sure NonIdentifierAttribute#isLazy is consistent (HHH-10551) + NonIdentifierAttribute[] properties = entityPersister.getEntityMetamodel().getProperties(); + assertEquals( 1, properties.length ); + assertTrue( properties[0].isLazy() ); + } ); + } + + // --- // + + @Entity + @Table( name = "TEST_ENTITY" ) + private static class TestEntity { + + @Id + @GeneratedValue + Long id; + + @Basic( fetch = FetchType.LAZY ) + String description; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldNotInitializedTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldNotInitializedTestTask.java deleted file mode 100644 index f5a7b0f6d77e..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyBasicFieldNotInitializedTestTask.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.bytecode.enhancement.lazy; - - -import javax.persistence.Basic; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; - -import org.hibernate.Hibernate; -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.tuple.NonIdentifierAttribute; -import org.hibernate.tuple.entity.EntityMetamodel; - -import org.junit.Assert; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * @author Gail Badner - */ -public class LazyBasicFieldNotInitializedTestTask extends AbstractEnhancerTestTask { - - private Long entityId; - - public Class[] getAnnotatedClasses() { - return new Class[] {Entity.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Entity entity = new Entity(); - entity.setDescription( "desc" ); - s.persist( entity ); - entityId = entity.getId(); - - s.getTransaction().commit(); - s.clear(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - Entity entity = s.get( Entity.class, entityId ); - Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); - final EntityMetamodel entityMetamodel = - ( ( SessionFactoryImplementor) getFactory() ) - .getEntityPersister( Entity.class.getName() ) - .getEntityMetamodel(); - final boolean[] propertyLaziness = entityMetamodel.getPropertyLaziness(); - assertEquals( 1, propertyLaziness.length ); - assertTrue( propertyLaziness[0] ); - // Make sure NonIdentifierAttribute#isLazy is consistent (HHH-10551) - final NonIdentifierAttribute[] properties = entityMetamodel.getProperties(); - assertEquals( 1, properties.length ); - assertTrue( properties[0].isLazy() ); - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - - @javax.persistence.Entity - @Table(name = "lazy_field_not_init") - public static class Entity { - @Id - @GeneratedValue - private Long id; - - @Basic(fetch = FetchType.LAZY) - private String description; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - } - - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionDeletedTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionDeletedTest.java new file mode 100644 index 000000000000..fa05b01adac3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionDeletedTest.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.test.bytecode.enhancement.lazy; + +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.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Query; +import javax.persistence.Table; +import java.util.HashSet; +import java.util.Set; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-11576" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyCollectionDeletedTest extends BaseCoreFunctionalTestCase { + + private Long postId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Post.class, Tag.class, AdditionalDetails.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 -> { + Post post = new Post(); + + Tag tag1 = new Tag( "tag1" ); + Tag tag2 = new Tag( "tag2" ); + + Set tagSet = new HashSet<>(); + tagSet.add( tag1 ); + tagSet.add( tag2 ); + post.tags = tagSet; + + AdditionalDetails details = new AdditionalDetails(); + details.post = post; + details.details = "Some data"; + post.additionalDetails = details; + + postId = (Long) s.save( post ); + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from AdditionalDetails where id=" + postId ); + AdditionalDetails additionalDetails = (AdditionalDetails) query.getSingleResult(); + additionalDetails.details = "New data"; + s.persist( additionalDetails ); + + // additionalDetais.post.tags get deleted on commit + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post where id=" + postId ); + Post retrievedPost = (Post) query.getSingleResult(); + + assertFalse( "No tags found", retrievedPost.tags.isEmpty() ); + retrievedPost.tags.forEach( tag -> System.out.println( "Found tag: " + tag ) ); + } ); + } + + // --- // + + @Entity( name = "Tag" ) + @Table( name = "TAG" ) + private static class Tag { + + @Id + @GeneratedValue + Long id; + + String name; + + Tag() { + } + + Tag(String name) { + this.name = name; + } + } + + @Entity( name = "Post" ) + @Table( name = "POST" ) + private static class Post { + + @Id + @GeneratedValue + Long id; + + @ManyToMany( cascade = CascadeType.ALL ) + Set tags; + + @OneToOne( fetch = FetchType.LAZY, mappedBy = "post", cascade = CascadeType.ALL ) + AdditionalDetails additionalDetails; + } + + @Entity( name = "AdditionalDetails" ) + @Table( name = "ADDITIONAL_DETAILS" ) + private static class AdditionalDetails { + + @Id + Long id; + + String details; + + @OneToOne( optional = false ) + @MapsId + Post post; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionDetachTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionDetachTest.java new file mode 100644 index 000000000000..869bfd01aea2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionDetachTest.java @@ -0,0 +1,142 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.Hibernate.isPropertyInitialized; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.proxy.HibernateProxy; +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; + +@TestForIssue(jiraKey = "HHH-12260") +@RunWith(BytecodeEnhancerRunner.class) +public class LazyCollectionDetachTest extends BaseCoreFunctionalTestCase { + + private static final int CHILDREN_SIZE = 10; + private Long parentID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ Parent.class, Child.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = new Parent(); + parent.setChildren( new ArrayList<>() ); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + child.parent = parent; + s.persist( child ); + } + s.persist( parent ); + parentID = parent.id; + } ); + } + + @Test + public void testDetach() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = s.find( Parent.class, parentID ); + + assertThat( parent, notNullValue() ); + assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); + assertFalse( isPropertyInitialized( parent, "children" ) ); + checkDirtyTracking( parent ); + + s.detach( parent ); + + s.flush(); + } ); + } + + @Test + public void testDetachProxy() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = s.getReference( Parent.class, parentID ); + + checkDirtyTracking( parent ); + + s.detach( parent ); + + s.flush(); + } ); + } + + @Test + public void testRefresh() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = s.find( Parent.class, parentID ); + + assertThat( parent, notNullValue() ); + assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); + assertFalse( isPropertyInitialized( parent, "children" ) ); + checkDirtyTracking( parent ); + + s.refresh( parent ); + + s.flush(); + } ); + } + + + @Entity + @Table(name = "PARENT") + private static class Parent { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + List children; + + void setChildren(List children) { + this.children = children; + } + } + + @Entity + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + Parent parent; + + Child() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionLoadingTest.java new file mode 100644 index 000000000000..f61895cd5d22 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionLoadingTest.java @@ -0,0 +1,167 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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 org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.proxy.HibernateProxy; +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.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.Hibernate.isPropertyInitialized; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Simple test for lazy collection handling in the new bytecode support. + * Prior to HHH-10055 lazy collections were simply not handled. The tests + * initially added for HHH-10055 cover the more complicated case of handling + * lazy collection initialization outside of a transaction; that is a bigger + * fix, and I first want to get collection handling to work here in general. + * + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-10055" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyCollectionLoadingTest extends BaseCoreFunctionalTestCase { + private static final int CHILDREN_SIZE = 10; + private Long parentID; + private Parent parent; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.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 -> { + Parent parent = new Parent(); + parent.setChildren( new ArrayList<>() ); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + child.parent = parent; + s.persist( child ); + } + s.persist( parent ); + parentID = parent.id; + } ); + } + + @Test + public void testTransaction() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = s.load( Parent.class, parentID ); + assertThat( parent, notNullValue() ); + assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); + assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); + assertFalse( isPropertyInitialized( parent, "children" ) ); + checkDirtyTracking( parent ); + + List children1 = parent.children; + List children2 = parent.children; + + assertTrue( isPropertyInitialized( parent, "children" ) ); + checkDirtyTracking( parent ); + + assertThat( children1, sameInstance( children2 ) ); + assertThat( children1.size(), equalTo( CHILDREN_SIZE ) ); + } ); + } + + @Test + public void testNoTransaction() { + doInHibernate( this::sessionFactory, s -> { + parent = s.load( Parent.class, parentID ); + assertThat( parent, notNullValue() ); + assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); + assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); + assertFalse( isPropertyInitialized( parent, "children" ) ); + } ); + + List children1 = parent.children; + List children2 = parent.children; + + assertTrue( isPropertyInitialized( parent, "children" ) ); + + checkDirtyTracking( parent ); + assertThat( children1, sameInstance( children2 ) ); + assertThat( children1.size(), equalTo( CHILDREN_SIZE ) ); + } + + // --- // + + @Entity + @Table( name = "PARENT" ) + private static class Parent { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @OneToMany( mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + List children; + + void setChildren(List children) { + this.children = children; + } + } + + @Entity + @Table( name = "CHILD" ) + private static class Child { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @ManyToOne( cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + Parent parent; + + String name; + + Child() { + } + + Child(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionLoadingTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionLoadingTestTask.java deleted file mode 100644 index d7ab8c40012f..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionLoadingTestTask.java +++ /dev/null @@ -1,99 +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.bytecode.enhancement.lazy; - -import java.util.ArrayList; -import java.util.List; - -import org.hibernate.Hibernate; -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.proxy.HibernateProxy; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.sameInstance; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * Simple test for lazy collection handling in the new bytecode support. - * Prior to HHH-10055 lazy collections were simply not handled. The tests - * initially added for HHH-10055 cover the more complicated case of handling - * lazy collection initialization outside of a transaction; that is a bigger - * fix, and I first want to get collection handling to work here in general. - * - * @author Steve Ebersole - */ -public class LazyCollectionLoadingTestTask extends AbstractEnhancerTestTask { - private static final int CHILDREN_SIZE = 10; - private Long parentID; - private Long lastChildID; - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class, Child.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "false" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent parent = new Parent(); - parent.setChildren( new ArrayList() ); - for ( int i = 0; i < CHILDREN_SIZE; i++ ) { - final Child child = new Child(); - child.setParent( parent ); - s.persist( child ); - lastChildID = child.getId(); - } - s.persist( parent ); - parentID = parent.getId(); - - s.getTransaction().commit(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent parent = s.load( Parent.class, parentID ); - assertThat( parent, notNullValue() ); - assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); - assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); - assertFalse( Hibernate.isPropertyInitialized( parent, "children" ) ); - EnhancerTestUtils.checkDirtyTracking( parent ); - - List children1 = parent.getChildren(); - List children2 = parent.getChildren(); - - assertTrue( Hibernate.isPropertyInitialized( parent, "children" ) ); - EnhancerTestUtils.checkDirtyTracking( parent ); - - assertThat( children1, sameInstance( children2 ) ); - assertThat( children1.size(), equalTo( CHILDREN_SIZE ) ); - - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionNoTransactionLoadingTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionNoTransactionLoadingTestTask.java deleted file mode 100644 index 0e94667d23d9..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyCollectionNoTransactionLoadingTestTask.java +++ /dev/null @@ -1,102 +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.bytecode.enhancement.lazy; - -import org.hibernate.Hibernate; -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.proxy.HibernateProxy; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; - -import java.util.ArrayList; -import java.util.List; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsSame.sameInstance; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * Simple test for lazy collection handling in the new bytecode support. - * Prior to HHH-10055 lazy collections were simply not handled. The tests - * initially added for HHH-10055 cover the more complicated case of handling - * lazy collection initialization outside of a transaction; that is a bigger - * fix, and I first want to get collection handling to work here in general. - * - * @author Steve Ebersole - * @author John O'Hara - * - */ -public class LazyCollectionNoTransactionLoadingTestTask extends AbstractEnhancerTestTask { - private static final int CHILDREN_SIZE = 10; - private Long parentID; - private Long lastChildID; - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class, Child.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent parent = new Parent(); - parent.setChildren( new ArrayList() ); - for ( int i = 0; i < CHILDREN_SIZE; i++ ) { - final Child child = new Child(); - child.setParent( parent ); - s.persist( child ); - lastChildID = child.getId(); - } - s.persist( parent ); - parentID = parent.getId(); - - s.getTransaction().commit(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent parent = s.load( Parent.class, parentID ); - assertThat( parent, notNullValue() ); - assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); - assertThat( parent, not( instanceOf( HibernateProxy.class ) ) ); - assertFalse( Hibernate.isPropertyInitialized( parent, "children" ) ); - - s.getTransaction().commit(); - s.close(); - - s = null; - - List children1 = parent.getChildren(); - List children2 = parent.getChildren(); - - assertTrue( Hibernate.isPropertyInitialized( parent, "children" ) ); - - EnhancerTestUtils.checkDirtyTracking( parent ); - assertThat( children1, sameInstance( children2 ) ); - assertThat( children1.size(), equalTo( CHILDREN_SIZE ) ); - - } - - protected void cleanup() { - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyInitializationWithoutInlineDirtyTrackingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyInitializationWithoutInlineDirtyTrackingTest.java new file mode 100644 index 000000000000..669b2f7ba6da --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyInitializationWithoutInlineDirtyTrackingTest.java @@ -0,0 +1,107 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.Table; + +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author Guillaume Smet + */ +@TestForIssue(jiraKey = "HHH-12633") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext( {EnhancerTestContext.class, LazyInitializationWithoutInlineDirtyTrackingTest.NoInlineDirtyTrackingContext.class} ) +public class LazyInitializationWithoutInlineDirtyTrackingTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ File.class }; + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + File file = new File(); + file.setId( 1L ); + file.setName( "file" ); + file.setBytes( new byte[]{ 0 } ); + + s.persist( file ); + } ); + + doInHibernate( this::sessionFactory, s -> { + File file = s.find( File.class, 1L ); + file.setBytes( new byte[]{ 1 } ); + s.persist( file ); + } ); + } + + // --- // + + @Entity + @Table(name = "T_FILE") + public static class File { + + @Id + private Long id; + + private String name; + + @Column(name = "bytes") + @Lob + @Basic(fetch = FetchType.LAZY) + private byte[] bytes; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte[] getBytes() { + return bytes; + } + + public void setBytes(byte[] bytes) { + this.bytes = bytes; + } + } + + public static class NoInlineDirtyTrackingContext extends EnhancerTestContext { + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} 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 new file mode 100644 index 000000000000..05a50eb050cc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java @@ -0,0 +1,166 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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 org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.testing.FailureExpected; +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.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; +import java.util.HashMap; +import java.util.Map; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * This tests issues HHH-11624. The fix is also for HHH-10747 (and HHH-11476) and is a change on the enhanced setter. + * + * @author Luis Barreiro + */ + +@TestForIssue( jiraKey = "HHH-10747" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyLoadingByEnhancerSetterTest extends BaseCoreFunctionalTestCase { + + private Item item, mergedItem; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ItemField.class, ItemProperty.class}; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); + configuration.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + } + + @Before + public void prepare() { + + } + + @Test + public void testField() { + doInHibernate( this::sessionFactory, s -> { + ItemField input = new ItemField(); + input.name = "F"; + input.parameters = new HashMap<>(); + input.parameters.put( "aaa", "AAA" ); + input.parameters.put( "bbb", "BBB" ); + s.persist( input ); + } ); + + doInHibernate( this::sessionFactory, s -> { + // A parameters map is created with the class and is being compared to the persistent map (by the generated code) -- it shouldn't + item = s.find( ItemField.class, "F" ); + } ); + + doInHibernate( this::sessionFactory, s -> { + mergedItem = (Item) s.merge( item ); + } ); + + Assert.assertEquals( 2, mergedItem.getParameters().size() ); + } + + @Test + @FailureExpected( jiraKey = "HHH-10747" ) + public void testProperty() { + doInHibernate( this::sessionFactory, s -> { + ItemProperty input = new ItemProperty(); + input.setName( "P" ); + Map parameters = new HashMap<>(); + parameters.put( "ccc", "CCC" ); + parameters.put( "ddd", "DDD" ); + input.setParameters( parameters ); + s.persist( input ); + } ); + + doInHibernate( this::sessionFactory, s -> { + // A parameters map is created with the class and is being compared to the persistent map (by the generated code) -- it shouldn't + item = s.find( ItemProperty.class, "P" ); + } ); + + doInHibernate( this::sessionFactory, s -> { + mergedItem = (Item) s.merge( item ); + } ); + + Assert.assertEquals( 2, mergedItem.getParameters().size() ); + } + + // --- // + + private interface Item { + Map getParameters(); + } + + @Entity + @Table( name = "ITEM_F" ) + private static class ItemField implements Item { + + @Id + @Column( nullable = false ) + private String name; + + @ElementCollection( fetch = FetchType.EAGER ) + @MapKeyColumn( name = "NAME" ) + @Lob + @Column( name = "VALUE", length = 65535 ) + private Map parameters = new HashMap<>(); + + @Override + public Map getParameters() { + return parameters; + } + } + + @Entity + @Table( name = "ITEM_P" ) + private static class ItemProperty implements Item { + + private String aName; + + private Map parameterMap = new HashMap<>(); + + @Id + @Column( nullable = false ) + public String getName() { + return aName; + } + + public void setName(String name) { + this.aName = name; + } + + @ElementCollection( fetch = FetchType.EAGER ) + @MapKeyColumn( name = "NAME" ) + @Lob + @Column( name = "VALUE", length = 65535 ) + @Override + public Map getParameters() { + return parameterMap; + } + + public void setParameters(Map parameters) { + this.parameterMap = parameters; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingIntegrationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingIntegrationTest.java new file mode 100644 index 000000000000..cef8d2037363 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingIntegrationTest.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.bytecode.enhancement.lazy; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +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.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.List; + +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyLoadingIntegrationTest extends BaseCoreFunctionalTestCase { + + private static final int CHILDREN_SIZE = 10; + private Long lastChildID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.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 -> { + Parent parent = new Parent(); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + // Association management should kick in here + child.parent = parent; + s.persist( child ); + lastChildID = child.id; + } + s.persist( parent ); + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + checkDirtyTracking( loadedChild ); + + loadedChild.name = "Barrabas"; + checkDirtyTracking( loadedChild, "name" ); + + Parent loadedParent = loadedChild.parent; + checkDirtyTracking( loadedChild, "name" ); + checkDirtyTracking( loadedParent ); + + List loadedChildren = new ArrayList<>( loadedParent.children ); + loadedChildren.remove( 0 ); + loadedChildren.remove( loadedChild ); + loadedParent.setChildren( loadedChildren ); + + Assert.assertNull( loadedChild.parent ); + } ); + } + + // --- // + + @Entity + @Table( name = "PARENT" ) + private static class Parent { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @OneToMany( mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + List children; + + void setChildren(List children) { + this.children = children; + } + } + + @Entity + @Table( name = "CHILD" ) + private static class Child { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @ManyToOne( cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + Parent parent; + + String name; + + Child() { + } + + Child(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingIntegrationTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingIntegrationTestTask.java deleted file mode 100644 index 43ac5f9f984a..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingIntegrationTestTask.java +++ /dev/null @@ -1,88 +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.bytecode.enhancement.lazy; - -import java.util.ArrayList; -import java.util.List; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.junit.Assert; - -/** - * @author Luis Barreiro - */ -public class LazyLoadingIntegrationTestTask extends AbstractEnhancerTestTask { - - private static final int CHILDREN_SIZE = 10; - private Long parentID; - private Long lastChildID; - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class, Child.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent parent = new Parent(); - parent.setChildren( new ArrayList( CHILDREN_SIZE ) ); - for ( int i = 0; i < CHILDREN_SIZE; i++ ) { - final Child child = new Child(); - // Association management should kick in here - child.setParent( parent ); - s.persist( child ); - lastChildID = child.getId(); - } - s.persist( parent ); - parentID = parent.getId(); - - s.getTransaction().commit(); - s.clear(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Child loadedChild = s.load( Child.class, lastChildID ); - EnhancerTestUtils.checkDirtyTracking( loadedChild ); - - loadedChild.setName( "Barrabas" ); - EnhancerTestUtils.checkDirtyTracking( loadedChild, "name" ); - - Parent loadedParent = loadedChild.getParent(); - EnhancerTestUtils.checkDirtyTracking( loadedChild, "name" ); - EnhancerTestUtils.checkDirtyTracking( loadedParent ); - - List loadedChildren = new ArrayList( loadedParent.getChildren() ); - loadedChildren.remove( 0 ); - loadedChildren.remove( loadedChild ); - loadedParent.setChildren( loadedChildren ); - - EnhancerTestUtils.checkDirtyTracking( loadedParent, "children" ); - Assert.assertNull( loadedChild.parent ); - - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingTest.java new file mode 100644 index 000000000000..be6b56594275 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingTest.java @@ -0,0 +1,162 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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 org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.proxy.HibernateProxy; +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.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.getFieldByReflection; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyLoadingTest extends BaseCoreFunctionalTestCase { + + private static final int CHILDREN_SIZE = 10; + private Long parentID; + private Long lastChildID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.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 -> { + Parent parent = new Parent(); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child( "Child #" + i ); + child.parent = parent; + parent.addChild( child ); + s.persist( child ); + lastChildID = child.id; + } + s.persist( parent ); + parentID = parent.id; + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + + Object nameByReflection = getFieldByReflection( loadedChild, "name" ); + assertNotNull( "Non-lazy field 'name' was not loaded", nameByReflection ); + + Object parentByReflection = getFieldByReflection( loadedChild, "parent" ); + assertNull( "Lazy field 'parent' is initialized", parentByReflection ); + assertFalse( loadedChild instanceof HibernateProxy ); + + Parent loadedParent = loadedChild.parent; + assertThat( loadedChild.name, notNullValue() ); + assertThat( loadedParent, notNullValue() ); + assertThat( loadedChild.parent, notNullValue() ); + + checkDirtyTracking( loadedChild ); + + parentByReflection = getFieldByReflection( loadedChild, "parent" ); + Object childrenByReflection = getFieldByReflection( loadedParent, "children" ); + assertNotNull( "Lazy field 'parent' is not loaded", parentByReflection ); + assertNull( "Lazy field 'children' is initialized", childrenByReflection ); + assertFalse( loadedParent instanceof HibernateProxy ); + assertEquals( parentID, loadedParent.id ); + + Collection loadedChildren = loadedParent.children; + + checkDirtyTracking( loadedChild ); + checkDirtyTracking( loadedParent ); + + childrenByReflection = getFieldByReflection( loadedParent, "children" ); + assertNotNull( "Lazy field 'children' is not loaded", childrenByReflection ); + assertFalse( loadedChildren instanceof HibernateProxy ); + assertEquals( CHILDREN_SIZE, loadedChildren.size() ); + assertTrue( loadedChildren.contains( loadedChild ) ); + } ); + } + + // --- // + + @Entity + @Table( name = "PARENT" ) + private static class Parent { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @OneToMany( mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + List children; + + void addChild(Child child) { + if ( children == null ) { + children = new ArrayList<>(); + } + children.add( child ); + } + } + + @Entity + @Table( name = "CHILD" ) + private static class Child { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @ManyToOne( cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + Parent parent; + + String name; + + Child() { + } + + Child(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingTestTask.java deleted file mode 100644 index 3a10023c958c..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingTestTask.java +++ /dev/null @@ -1,108 +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.bytecode.enhancement.lazy; - -import java.util.ArrayList; -import java.util.Collection; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.proxy.HibernateProxy; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.junit.Assert; - -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.assertThat; - -/** - * @author Luis Barreiro - */ -public class LazyLoadingTestTask extends AbstractEnhancerTestTask { - - private static final int CHILDREN_SIZE = 10; - private Long parentID; - private Long lastChildID; - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class, Child.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Parent parent = new Parent(); - parent.setChildren(new ArrayList()); - for ( int i = 0; i < CHILDREN_SIZE; i++ ) { - final Child child = new Child( "Child #" + i ); - child.setParent( parent ); - parent.getChildren().add( child ); - s.persist( child ); - lastChildID = child.getId(); - } - s.persist( parent ); - parentID = parent.getId(); - - s.getTransaction().commit(); - s.clear(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Child loadedChild = s.load( Child.class, lastChildID ); - - Object nameByReflection = EnhancerTestUtils.getFieldByReflection( loadedChild, "name" ); - Assert.assertNotNull( "Non-lazy field 'name' was not loaded", nameByReflection ); - - Object parentByReflection = EnhancerTestUtils.getFieldByReflection( loadedChild, "parent" ); - Assert.assertNull( "Lazy field 'parent' is initialized", parentByReflection ); - Assert.assertFalse( loadedChild instanceof HibernateProxy ); - - Parent loadedParent = loadedChild.getParent(); - assertThat( loadedChild.getName(), notNullValue() ); - assertThat( loadedParent, notNullValue() ); - assertThat( loadedChild.getParent(), notNullValue() ); - - EnhancerTestUtils.checkDirtyTracking( loadedChild ); - - parentByReflection = EnhancerTestUtils.getFieldByReflection( loadedChild, "parent" ); - Object childrenByReflection = EnhancerTestUtils.getFieldByReflection( loadedParent, "children" ); - Assert.assertNotNull( "Lazy field 'parent' is not loaded", parentByReflection ); - Assert.assertNull( "Lazy field 'children' is initialized", childrenByReflection ); - Assert.assertFalse( loadedParent instanceof HibernateProxy ); - Assert.assertTrue( parentID.equals( loadedParent.id ) ); - - Collection loadedChildren = loadedParent.getChildren(); - - EnhancerTestUtils.checkDirtyTracking( loadedChild ); - EnhancerTestUtils.checkDirtyTracking( loadedParent ); - - childrenByReflection = EnhancerTestUtils.getFieldByReflection( loadedParent, "children" ); - Assert.assertNotNull( "Lazy field 'children' is not loaded", childrenByReflection ); - Assert.assertFalse( loadedChildren instanceof HibernateProxy ); - Assert.assertEquals( CHILDREN_SIZE, loadedChildren.size() ); - Assert.assertTrue( loadedChildren.contains( loadedChild ) ); - - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyProxyOnEnhancedEntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyProxyOnEnhancedEntityTest.java new file mode 100644 index 000000000000..cf3edb03846e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyProxyOnEnhancedEntityTest.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.bytecode.enhancement.lazy; + +import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.event.internal.DefaultFlushEventListener; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.LoadEvent; +import org.hibernate.event.spi.LoadEventListener; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.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.OneToOne; +import javax.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-10922" ) +@RunWith( BytecodeEnhancerRunner.class ) +@CustomEnhancementContext( {EnhancerTestContext.class, LazyProxyOnEnhancedEntityTest.NoLazyLoadingContext.class} ) +public class LazyProxyOnEnhancedEntityTest extends BaseCoreFunctionalTestCase { + + private Long parentID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.class}; + } + + @Before + public void prepare() { + doInJPA( this::sessionFactory, em -> { + Child c = new Child(); + em.persist( c ); + + Parent parent = new Parent(); + parent.setChild( c ); + em.persist( parent ); + parentID = parent.getId(); + } ); + } + + @Test + public void test() { + EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class ); + registry.prependListeners( EventType.LOAD, new ImmediateLoadTrap() ); + + doInJPA( this::sessionFactory, em -> { + + em.find( Parent.class, parentID ); + + // unwanted lazy load occurs on flush + } ); + } + + private static class ImmediateLoadTrap implements LoadEventListener { + @Override + public void onLoad(LoadEvent event, LoadType loadType) throws HibernateException { + if ( IMMEDIATE_LOAD == loadType ) { + String msg = loadType + ":" + event.getEntityClassName() + "#" + event.getEntityId(); + throw new RuntimeException( msg ); + } + } + } + + // --- // + + @Entity + @Table( name = "PARENT" ) + private static class Parent { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @OneToOne( fetch = FetchType.LAZY + ) + Child child; + + public Long getId() { + return id; + } + + public Child getChild() { + return child; + } + + public void setChild(Child child) { + this.child = child; + } + } + + @Entity + @Table( name = "CHILD" ) + private static class Child { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String name; + + Child() { + // No-arg constructor necessary for proxy factory + } + } + + // --- // + + public static class NoLazyLoadingContext extends EnhancerTestContext { + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyProxyOnEnhancedEntityTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyProxyOnEnhancedEntityTestTask.java deleted file mode 100644 index 8a51948c9cfd..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyProxyOnEnhancedEntityTestTask.java +++ /dev/null @@ -1,136 +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.bytecode.enhancement.lazy; - -import javax.persistence.Entity; -import javax.persistence.EntityManager; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToOne; -import javax.persistence.Table; - -import org.hibernate.HibernateException; -import org.hibernate.cfg.Configuration; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.service.spi.EventListenerRegistry; -import org.hibernate.event.spi.EventType; -import org.hibernate.event.spi.LoadEvent; -import org.hibernate.event.spi.LoadEventListener; -import org.hibernate.jpa.event.internal.core.JpaFlushEventListener; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -/** - * @author Luis Barreiro - */ -public class LazyProxyOnEnhancedEntityTestTask extends AbstractEnhancerTestTask { - - private Long parentID; - - public Class[] getAnnotatedClasses() { - return new Class[] {Parent.class, Child.class}; - } - - public void prepare() { - super.prepare( new Configuration() ); - - EntityManager em = getFactory().createEntityManager(); - em.getTransaction().begin(); - - Child c = new Child(); - em.persist( c ); - - Parent parent = new Parent(); - parent.setChild( c ); - em.persist( parent ); - parentID = parent.getId(); - - em.getTransaction().commit(); - em.clear(); - em.close(); - - } - - public void execute() { - EventListenerRegistry registry = getFactory().unwrap( SessionFactoryImplementor.class ).getServiceRegistry().getService( EventListenerRegistry.class ); - registry.prependListeners( EventType.FLUSH, new JpaFlushEventListener() ); - registry.prependListeners( EventType.LOAD, new ImmediateLoadTrap() ); - - EntityManager em = getFactory().createEntityManager(); - em.getTransaction().begin(); - - Parent p = em.find(Parent.class, parentID); - em.flush(); // unwanted lazy load occurs here - - em.getTransaction().commit(); - em.close(); - } - - protected void cleanup() { - } - - private static class ImmediateLoadTrap implements LoadEventListener { - @Override - public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType) throws HibernateException { - if ( loadType == IMMEDIATE_LOAD ) { - String msg = loadType + ":" + event.getEntityClassName() + "#" + event.getEntityId(); - throw new RuntimeException(msg); - } - } - } - - @Entity - @Table(name = "LazyProxyTask_Parent") - public static class Parent { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - - @OneToOne(fetch = FetchType.LAZY - ) - private Child child; - - public Long getId() { - return id; - } - - public Child getChild() { - return child; - } - - public void setChild(Child child) { - this.child = child; - } - } - - @Entity - @Table(name = "LazyProxyTask_Child") - public static class Child { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - - private String name; - - public Long getId() { - return 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/NaturalIdInUninitializedAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java new file mode 100644 index 000000000000..1f86b4aa83b3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NaturalId; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-13607" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class NaturalIdInUninitializedAssociationTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testImmutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isPropertyInitialized( e,"entityImmutableNaturalId" ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertEquals( "immutable name", e.entityImmutableNaturalId.name ); + } + ); + } + + @Test + public void testMutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isPropertyInitialized( e,"entityMutableNaturalId" ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertEquals( "mutable name", e.entityMutableNaturalId.name ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AnEntity.class ); + sources.addAnnotatedClass( EntityMutableNaturalId.class ); + sources.addAnnotatedClass( EntityImmutableNaturalId.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + EntityMutableNaturalId entityMutableNaturalId = new EntityMutableNaturalId( 1, "mutable name" ); + EntityImmutableNaturalId entityImmutableNaturalId = new EntityImmutableNaturalId( 2, "immutable name" ); + AnEntity anEntity = new AnEntity(); + anEntity.id = 3; + anEntity.entityImmutableNaturalId = entityImmutableNaturalId; + anEntity.entityMutableNaturalId = entityMutableNaturalId; + session.persist( anEntity ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.delete( session.get( AnEntity.class, 3 ) ); + } + ); + } + + @Entity(name = "AnEntity") + public static class AnEntity { + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY ) + private EntityMutableNaturalId entityMutableNaturalId; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY ) + private EntityImmutableNaturalId entityImmutableNaturalId; + } + + @Entity(name = "EntityMutableNaturalId") + public static class EntityMutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = true) + private String name; + + public EntityMutableNaturalId() { + } + + public EntityMutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity(name = "EntityImmutableNaturalId") + public static class EntityImmutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = false) + private String name; + + public EntityImmutableNaturalId() { + } + + public EntityImmutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/Parent.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/Parent.java deleted file mode 100644 index fd8aadbd3d1f..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/Parent.java +++ /dev/null @@ -1,48 +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.bytecode.enhancement.lazy; - -import java.util.List; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -/** - * @author Luis Barreiro - */ - -@Entity -public class Parent { - - Long id; - - List children; - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - public List getChildren() { - return children; - } - - public void setChildren(List children) { - this.children = children; - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceEagerManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceEagerManyToOneTest.java new file mode 100644 index 000000000000..7fc33ef7ade1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceEagerManyToOneTest.java @@ -0,0 +1,323 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.query.Query; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@RunWith(BytecodeEnhancerRunner.class) +public class QueryScrollingWithInheritanceEagerManyToOneTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( EmployeeParent.class ); + sources.addAnnotatedClass( Employee.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testScrollableWithStatelessSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + try { + statelessSession.beginTransaction(); + Query query = statelessSession.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( true ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + statelessSession.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( statelessSession.getTransaction().isActive() ) { + statelessSession.getTransaction().rollback(); + } + statelessSession.close(); + } + } + + @Test + public void testScrollableWithSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final Session session = sessionFactory().openSession(); + + try { + session.beginTransaction(); + Query query = session.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( true ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + session.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + session.close(); + } + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + Employee e1 = new Employee( "ENG1" ); + Employee e2 = new Employee( "ENG2" ); + OtherEntity other1 = new OtherEntity( "test1" ); + OtherEntity other2 = new OtherEntity( "test2" ); + e1.getOtherEntities().add( other1 ); + e1.getOtherEntities().add( other2 ); + e1.getParentOtherEntities().add( other1 ); + e1.getParentOtherEntities().add( other2 ); + other1.employee = e1; + other2.employee = e1; + other1.employeeParent = e1; + other2.employeeParent = e2; + session.persist( other1 ); + session.persist( other2 ); + session.persist( e1 ); + session.persist( e2 ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from EmployeeParent" ).executeUpdate(); + } + ); + } + + @Entity(name = "EmployeeParent") + @Table(name = "EmployeeParent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class EmployeeParent { + + @Id + private String dept; + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employeeParent", fetch = FetchType.LAZY) + protected Set parentOtherEntities = new HashSet<>(); + + public Set getParentOtherEntities() { + if ( parentOtherEntities == null ) { + parentOtherEntities = new LinkedHashSet(); + } + return parentOtherEntities; + } + + public void setOtherEntities(Set pParentOtherEntites) { + parentOtherEntities = pParentOtherEntites; + } + + public String getDept() { + return dept; + } + + protected void setDept(String dept) { + this.dept = dept; + } + + } + + @Entity(name = "Employee") + @Table(name = "Employee") + public static class Employee extends EmployeeParent { + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employee", fetch = FetchType.LAZY) + protected Set otherEntities = new HashSet<>(); + + public Employee(String dept) { + this(); + setDept( dept ); + } + + protected Employee() { + // this form used by Hibernate + } + + public Set getOtherEntities() { + if ( otherEntities == null ) { + otherEntities = new LinkedHashSet(); + } + return otherEntities; + } + + public void setOtherEntities(Set pOtherEntites) { + otherEntities = pOtherEntites; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "Employee_Id") + protected Employee employee = null; + + @ManyToOne(fetch = FetchType.EAGER) + //@LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "EmployeeParent_Id") + protected EmployeeParent employeeParent = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java new file mode 100644 index 000000000000..e9be4a677e48 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java @@ -0,0 +1,306 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.query.Query; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@RunWith(BytecodeEnhancerRunner.class) +public class QueryScrollingWithInheritanceTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( EmployeeParent.class ); + sources.addAnnotatedClass( Employee.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testScrollableWithStatelessSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + try { + statelessSession.beginTransaction(); + Query query = statelessSession.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( false ) ); + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + statelessSession.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( statelessSession.getTransaction().isActive() ) { + statelessSession.getTransaction().rollback(); + } + statelessSession.close(); + } + } + + @Test + public void testScrollableWithSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final Session session = sessionFactory().openSession(); + + try { + session.beginTransaction(); + Query query = session.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( false ) ); + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + session.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + session.close(); + } + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + Employee e1 = new Employee( "ENG1" ); + Employee e2 = new Employee( "ENG2" ); + OtherEntity other1 = new OtherEntity( "test1" ); + OtherEntity other2 = new OtherEntity( "test2" ); + e1.getOtherEntities().add( other1 ); + e1.getOtherEntities().add( other2 ); + e1.getParentOtherEntities().add( other1 ); + e1.getParentOtherEntities().add( other2 ); + other1.employee = e1; + other2.employee = e1; + other1.employeeParent = e1; + other2.employeeParent = e2; + session.persist( other1 ); + session.persist( other2 ); + session.persist( e1 ); + session.persist( e2 ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from EmployeeParent" ).executeUpdate(); + } + ); + } + + @Entity(name = "EmployeeParent") + @Table(name = "EmployeeParent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class EmployeeParent { + + @Id + private String dept; + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employeeParent", fetch = FetchType.LAZY) + protected Set parentOtherEntities = new HashSet<>(); + + public Set getParentOtherEntities() { + if ( parentOtherEntities == null ) { + parentOtherEntities = new LinkedHashSet(); + } + return parentOtherEntities; + } + + public void setOtherEntities(Set pParentOtherEntites) { + parentOtherEntities = pParentOtherEntites; + } + + public String getDept() { + return dept; + } + + protected void setDept(String dept) { + this.dept = dept; + } + + } + + @Entity(name = "Employee") + @Table(name = "Employee") + public static class Employee extends EmployeeParent { + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employee", fetch = FetchType.LAZY) + protected Set otherEntities = new HashSet<>(); + + public Employee(String dept) { + this(); + setDept( dept ); + } + + protected Employee() { + // this form used by Hibernate + } + + public Set getOtherEntities() { + if ( otherEntities == null ) { + otherEntities = new LinkedHashSet(); + } + return otherEntities; + } + + public void setOtherEntities(Set pOtherEntites) { + otherEntities = pOtherEntites; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "Employee_Id") + protected Employee employee = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "EmployeeParent_Id") + protected EmployeeParent employeeParent = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java new file mode 100644 index 000000000000..2aa0d1cdf448 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java @@ -0,0 +1,510 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.StatelessSession; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.query.Query; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +@RunWith(BytecodeEnhancerRunner.class) +public class StatelessQueryScrollingTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testDynamicFetchScroll() { + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + try { + final Query query = statelessSession.createQuery( "from Task t join fetch t.resource join fetch t.user" ); + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + statelessSession.close(); + } + } + + @Test + public void testDynamicFetchCollectionScroll() { + ScrollableResults scrollableResults = null; + StatelessSession statelessSession = sessionFactory().openStatelessSession(); + statelessSession.beginTransaction(); + + try { + final Query query = statelessSession.createQuery( "select p from Producer p join fetch p.products" ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + while ( scrollableResults.next() ) { + Producer producer = (Producer) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( producer ) ); + assertTrue( Hibernate.isPropertyInitialized( producer, "products" ) ); + assertTrue( Hibernate.isInitialized( producer.getProducts() ) ); + + for ( Product product : producer.getProducts() ) { + assertTrue( Hibernate.isInitialized( product ) ); + assertFalse( Hibernate.isInitialized( product.getVendor() ) ); + } + } + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + statelessSession.getTransaction().commit(); + statelessSession.close(); + } + } + + + @Before + public void createTestData() { + inTransaction( + session -> { + Date now = new Date(); + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + session.save( me ); + session.save( you ); + session.save( yourClock ); + session.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + session.save( u3 ); + session.save( u4 ); + session.save( it ); + session.save( task2 ); + } + ); + + inTransaction( + session -> { + Producer p1 = new Producer( 1, "Acme" ); + Producer p2 = new Producer( 2, "ABC" ); + + session.save( p1 ); + session.save( p2 ); + + Vendor v1 = new Vendor( 1, "v1" ); + Vendor v2 = new Vendor( 2, "v2" ); + + session.save( v1 ); + session.save( v2 ); + + final Product product1 = new Product( 1, "123", v1, p1 ); + final Product product2 = new Product( 2, "456", v1, p1 ); + final Product product3 = new Product( 3, "789", v1, p2 ); + + session.save( product1 ); + session.save( product2 ); + session.save( product3 ); + } + ); + } + + @After + public void deleteTestData() { + inTransaction( + s -> { + s.createQuery( "delete Task" ).executeUpdate(); + s.createQuery( "delete Resource" ).executeUpdate(); + s.createQuery( "delete User" ).executeUpdate(); + + s.createQuery( "delete Product" ).executeUpdate(); + s.createQuery( "delete Producer" ).executeUpdate(); + s.createQuery( "delete Vendor" ).executeUpdate(); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( Task.class ); + sources.addAnnotatedClass( User.class ); + sources.addAnnotatedClass( Resource.class ); + sources.addAnnotatedClass( Product.class ); + sources.addAnnotatedClass( Producer.class ); + sources.addAnnotatedClass( Vendor.class ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection fetch scrolling + + @Entity(name = "Producer") + public static class Producer { + @Id + private Integer id; + + private String name; + + @OneToMany(mappedBy = "producer", fetch = FetchType.LAZY) + private Set products = new HashSet<>(); + + public Producer() { + } + + public Producer(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + @Entity(name = "Product") + public static class Product { + @Id + private Integer id; + private String sku; + + @ManyToOne(fetch = FetchType.LAZY) + private Vendor vendor; + + @ManyToOne(fetch = FetchType.LAZY) + private Producer producer; + + public Product() { + } + + public Product(Integer id, String sku, Vendor vendor, Producer producer) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.producer = producer; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSku() { + return sku; + } + + public void setSku(String sku) { + this.sku = sku; + } + + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + public Producer getProducer() { + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } + } + + @Entity(name = "Vendor") + public static class Vendor { + @Id + private Integer id; + private String name; + + @OneToMany(mappedBy = "vendor", fetch = FetchType.LAZY) + private Set products = new HashSet<>(); + + public Vendor() { + } + + public Vendor(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity fetch scrolling + + @Entity(name = "Resource") + @Table(name = "resources") + public static class Resource { + @Id + @GeneratedValue(generator = "increment") + private Long id; + private String name; + @ManyToOne(fetch = FetchType.LAZY) + private User owner; + + public Resource() { + } + + public Resource(String name, User owner) { + this.name = name; + this.owner = owner; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + } + + @Entity(name = "User") + @Table(name = "users") + public static class User { + @Id + @GeneratedValue(generator = "increment") + private Long id; + private String name; + + public User() { + } + + public User(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Task") + public static class Task { + @Id + @GeneratedValue(generator = "increment") + private Long id; + private String description; + @ManyToOne(fetch = FetchType.LAZY) + private User user; + @ManyToOne(fetch = FetchType.LAZY) + private Resource resource; + private Date dueDate; + private Date startDate; + private Date completionDate; + + public Task() { + } + + public Task(User user, String description, Resource resource, Date dueDate) { + this( user, description, resource, dueDate, null, null ); + } + + public Task( + User user, + String description, + Resource resource, + Date dueDate, + Date startDate, + Date completionDate) { + this.user = user; + this.resource = resource; + this.description = description; + this.dueDate = dueDate; + this.startDate = startDate; + this.completionDate = completionDate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Resource getResource() { + return resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getCompletionDate() { + return completionDate; + } + + public void setCompletionDate(Date completionDate) { + this.completionDate = completionDate; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTest.java new file mode 100644 index 000000000000..4d07652d8c5d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTest.java @@ -0,0 +1,134 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +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.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.Hibernate.isPropertyInitialized; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyBasicFieldAccessTest extends BaseCoreFunctionalTestCase { + + private LazyEntity entity; + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{LazyEntity.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 -> { + LazyEntity entity = new LazyEntity(); + entity.setDescription( "desc" ); + s.persist( entity ); + entityId = entity.id; + } ); + } + + @Test + public void test() { + 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.setDescription( "desc1" ); + s.update( entity ); + + //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); + 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() ); + } ); + + doInHibernate( this::sessionFactory, s -> { + entity.setDescription( "desc2" ); + LazyEntity mergedEntity = (LazyEntity) s.merge( entity ); + + // Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); + checkDirtyTracking( mergedEntity, "description" ); + + assertEquals( "desc2", mergedEntity.getDescription() ); + assertTrue( isPropertyInitialized( mergedEntity, "description" ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "desc2", entity.getDescription() ); + } ); + } + + // --- // + + @Entity + @Table( name = "LAZY_FIELD_ENTITY" ) + private static class LazyEntity { + Long id; + String description; + + @Id + @GeneratedValue + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + @Basic( fetch = FetchType.LAZY ) + 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/LazyBasicFieldAccessTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTestTask.java deleted file mode 100644 index 83f8f9e4f824..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTestTask.java +++ /dev/null @@ -1,145 +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.bytecode.enhancement.lazy.basic; - - -import javax.persistence.Basic; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; - -import org.hibernate.Hibernate; -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; - -import org.junit.Assert; - -/** - * @author Gail Badner - */ -public class LazyBasicFieldAccessTestTask extends AbstractEnhancerTestTask { - - private Long entityId; - - public Class[] getAnnotatedClasses() { - return new Class[] {Entity.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Entity entity = new Entity(); - entity.setDescription( "desc" ); - s.persist( entity ); - entityId = entity.getId(); - - s.getTransaction().commit(); - s.clear(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Entity entity = s.get( Entity.class, entityId ); - - Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); - EnhancerTestUtils.checkDirtyTracking( entity ); - - Assert.assertEquals( "desc", entity.getDescription() ); - Assert.assertTrue( Hibernate.isPropertyInitialized( entity, "description" ) ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity.setDescription( "desc1" ); - s.update( entity ); - - //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); - EnhancerTestUtils.checkDirtyTracking( entity, "description" ); - - Assert.assertEquals( "desc1", entity.getDescription() ); - Assert.assertTrue( Hibernate.isPropertyInitialized( entity, "description" ) ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity = s.get( Entity.class, entityId ); - Assert.assertEquals( "desc1", entity.getDescription() ); - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity.setDescription( "desc2" ); - entity = (Entity) s.merge( entity ); - - //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); - EnhancerTestUtils.checkDirtyTracking( entity, "description" ); - - Assert.assertEquals( "desc2", entity.getDescription() ); - Assert.assertTrue( Hibernate.isPropertyInitialized( entity, "description" ) ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity = s.get( Entity.class, entityId ); - Assert.assertEquals( "desc2", entity.getDescription() ); - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - - @javax.persistence.Entity - @Table(name = "lazy_field_access") - public static class Entity { - private Long id; - - private String description; - - @Id - @GeneratedValue - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - @Basic(fetch = FetchType.LAZY) - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - } - - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTest.java new file mode 100644 index 000000000000..a41949fca951 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTest.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.test.bytecode.enhancement.lazy.basic; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +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.Access; +import javax.persistence.AccessType; +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.Hibernate.isPropertyInitialized; +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyBasicPropertyAccessTest extends BaseCoreFunctionalTestCase { + + private LazyEntity entity; + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{LazyEntity.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 -> { + LazyEntity entity = new LazyEntity(); + entity.description = "desc"; + s.persist( entity ); + entityId = entity.id; + } ); + } + + @Test + public void execute() { + 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.description = "desc1"; + s.update( entity ); + + // Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); + 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 ); + } ); + + doInHibernate( this::sessionFactory, s -> { + entity.description = "desc2"; + LazyEntity mergedEntity = (LazyEntity) s.merge( entity ); + + //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); + checkDirtyTracking( mergedEntity, "description" ); + + assertEquals( "desc2", mergedEntity.description ); + assertTrue( isPropertyInitialized( mergedEntity, "description" ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "desc2", entity.description ); + } ); + } + + // --- // + + @Entity + @Access( AccessType.FIELD ) + @Table( name = "LAZY_PROPERTY_ENTITY" ) + private static class LazyEntity { + + @Id + @GeneratedValue + Long id; + + @Basic( fetch = FetchType.LAZY ) + String description; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTestTask.java deleted file mode 100644 index d284e653352e..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTestTask.java +++ /dev/null @@ -1,147 +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.bytecode.enhancement.lazy.basic; - - -import javax.persistence.Access; -import javax.persistence.AccessType; -import javax.persistence.Basic; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; - -import org.hibernate.Hibernate; -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.junit.Assert; - -/** - * @author Gail Badner - */ -public class LazyBasicPropertyAccessTestTask extends AbstractEnhancerTestTask { - - private Long entityId; - - public Class[] getAnnotatedClasses() { - return new Class[] {Entity.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Entity entity = new Entity(); - entity.setDescription( "desc" ); - s.persist( entity ); - entityId = entity.getId(); - - s.getTransaction().commit(); - s.clear(); - s.close(); - } - - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Entity entity = s.get( Entity.class, entityId ); - - Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); - EnhancerTestUtils.checkDirtyTracking( entity ); - - Assert.assertEquals( "desc", entity.getDescription() ); - Assert.assertTrue( Hibernate.isPropertyInitialized( entity, "description" ) ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity.setDescription( "desc1" ); - s.update( entity ); - - // Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); - EnhancerTestUtils.checkDirtyTracking( entity, "description" ); - - Assert.assertEquals( "desc1", entity.getDescription() ); - Assert.assertTrue( Hibernate.isPropertyInitialized( entity, "description" ) ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity = s.get( Entity.class, entityId ); - Assert.assertEquals( "desc1", entity.getDescription() ); - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity.setDescription( "desc2" ); - entity = (Entity) s.merge( entity ); - - //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); - EnhancerTestUtils.checkDirtyTracking( entity, "description" ); - - Assert.assertEquals( "desc2", entity.getDescription() ); - Assert.assertTrue( Hibernate.isPropertyInitialized( entity, "description" ) ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.beginTransaction(); - entity = s.get( Entity.class, entityId ); - Assert.assertEquals( "desc2", entity.getDescription() ); - s.getTransaction().commit(); - s.close(); - } - - protected void cleanup() { - } - - @javax.persistence.Entity - @Access(AccessType.FIELD ) - @Table(name="lazy_property_access") - public static class Entity { - @Id - @GeneratedValue - private Long id; - - @Basic(fetch = FetchType.LAZY) - private String description; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - } - - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/LazyInCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/LazyInCacheTest.java new file mode 100644 index 000000000000..e2bac4a0539d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/LazyInCacheTest.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.cache; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Type; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +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.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyInCacheTest extends BaseCoreFunctionalTestCase { + + private Long orderId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Order.class, Product.class, Tag.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() { + Order order = new Order(); + Product product = new Product(); + order.products.add( product ); + order.data = "some data".getBytes( Charset.defaultCharset() ); + + doInJPA( this::sessionFactory, em -> { + em.persist( product ); + em.persist( order ); + } ); + + orderId = order.id; + } + + @Test + public void test() { + doInJPA( this::sessionFactory, em -> { + Order order = em.find( Order.class, orderId ); + Assert.assertEquals( 1, order.products.size() ); + } ); + } + + // --- // + + @Entity + @Table( name = "ORDER_TABLE" ) + @Cache( usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE ) + private static class Order { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @OneToMany + List products = new ArrayList<>(); + + @OneToMany + List tags = new ArrayList<>(); + + @Basic( fetch = FetchType.LAZY ) + @Type( type = "org.hibernate.type.BinaryType" ) + byte[] data; + } + + @Entity + @Table( name = "PRODUCT" ) + private static class Product { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String name; + } + + @Entity + @Table( name = "TAG" ) + private static class Tag { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/LazyInCacheTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/LazyInCacheTestTask.java deleted file mode 100644 index 38589e05f53c..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/LazyInCacheTestTask.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.bytecode.enhancement.lazy.cache; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.annotations.Type; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -import javax.persistence.Basic; -import javax.persistence.Cacheable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EntityManager; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToMany; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Luis Barreiro - */ -public class LazyInCacheTestTask extends AbstractEnhancerTestTask { - - public Class[] getAnnotatedClasses() { - return new Class[]{Order.class, Product.class, Tag.class}; - } - - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "true" ); - prepare( cfg ); - } - - public void execute() { - EntityManager entityManager = getFactory().createEntityManager(); - Order order = new Order(); - Product product = new Product(); - order.products.add( product ); - order.data = "some data".getBytes(); - entityManager.getTransaction().begin(); - entityManager.persist( product ); - entityManager.persist( order ); - entityManager.getTransaction().commit(); - - long orderId = order.id; - - entityManager = getFactory().createEntityManager(); - order = entityManager.find( Order.class, orderId ); - Assert.assertEquals( 1, order.products.size() ); - entityManager.close(); - - } - - protected void cleanup() { - } - - @Entity - @Cache( usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE ) - public static class Order { - - @Id - @GeneratedValue( strategy = GenerationType.IDENTITY ) - long id; - - @OneToMany - List products = new ArrayList<>(); - - @OneToMany - List tags = new ArrayList<>(); - - @Basic( fetch = FetchType.LAZY ) - @Column - @Type( type = "org.hibernate.type.BinaryType" ) - private byte[] data; - - } - - @Entity - public static class Product { - - @Id - @GeneratedValue( strategy = GenerationType.IDENTITY ) - long id; - - String name; - - } - - @Entity - public class Tag { - - @Id - @GeneratedValue( strategy = GenerationType.IDENTITY ) - long id; - - String name; - - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedAssociationsInCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedAssociationsInCacheTest.java new file mode 100644 index 000000000000..7878831fd822 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedAssociationsInCacheTest.java @@ -0,0 +1,166 @@ +package org.hibernate.test.bytecode.enhancement.lazy.cache; + +import javax.persistence.Basic; +import javax.persistence.Cacheable; +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 javax.validation.constraints.AssertTrue; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.stat.CacheRegionStatistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +@RunWith(BytecodeEnhancerRunner.class) +public class UninitializedAssociationsInCacheTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Employee.class}; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty(AvailableSettings.USE_SECOND_LEVEL_CACHE, "true"); + configuration.setProperty(AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "false"); + configuration.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11766") + public void attributeLoadingFromCache() { + final AtomicLong bossId = new AtomicLong(); + final AtomicLong teamleaderId = new AtomicLong(); + final AtomicLong teammemberId = new AtomicLong(); + + TransactionUtil.doInHibernate( + this::sessionFactory, (s) -> { + Employee boss = new Employee(); + Employee teamleader = new Employee(); + Employee teammember = new Employee(); + boss.regularString = "boss"; + teamleader.regularString = "leader"; + teammember.regularString = "member"; + s.persist( boss ); + s.persist( teamleader ); + s.persist( teammember ); + boss.subordinates.add( teamleader ); + teamleader.superior = boss; + teamleader.subordinates.add( teammember ); + teammember.superior = teamleader; + bossId.set( boss.id ); + teamleaderId.set( teamleader.id ); + teammemberId.set( teammember.id ); + } + ); + + sessionFactory().getCache().evictAll(); + sessionFactory().getStatistics().clear(); + CacheRegionStatistics regionStatistics = sessionFactory().getStatistics().getCacheRegionStatistics( "Employee" ); + + TransactionUtil.doInHibernate( + this::sessionFactory, (s) -> { + final Employee boss = s.find( Employee.class, bossId.get() ); + Assert.assertEquals( "boss", boss.regularString ); + final Employee leader = s.find( Employee.class, teamleaderId.get() ); + Assert.assertEquals( "leader", leader.regularString ); + final Employee member = s.find( Employee.class, teammemberId.get() ); + Assert.assertEquals( "member", member.regularString ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + } + ); + + assertEquals( 0, regionStatistics.getHitCount() ); + assertEquals( 3, regionStatistics.getMissCount() ); + assertEquals( 3, regionStatistics.getPutCount() ); + + TransactionUtil.doInHibernate( + this::sessionFactory, (s) -> { + final Employee boss = s.find( Employee.class, bossId.get() ); + final Employee leader = s.find( Employee.class, teamleaderId.get() ); + final Employee member = s.find( Employee.class, teammemberId.get() ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + Assert.assertNull( boss.superior ); + Assert.assertTrue( Hibernate.isPropertyInitialized( boss, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + Assert.assertEquals( leader, boss.subordinates.iterator().next() ); + Assert.assertTrue( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + Assert.assertEquals( boss, leader.superior ); + Assert.assertTrue( Hibernate.isPropertyInitialized( leader, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + Assert.assertEquals( member, leader.subordinates.iterator().next() ); + Assert.assertTrue( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + Assert.assertEquals( leader, member.superior ); + Assert.assertTrue( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + Assert.assertTrue( member.subordinates.isEmpty() ); + Assert.assertTrue( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + } + ); + + assertEquals( 3, regionStatistics.getHitCount() ); + assertEquals( 3, regionStatistics.getMissCount() ); + assertEquals( 3, regionStatistics.getPutCount() ); + } + + @Entity + @Table(name = "EMPLOYEE_TABLE") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Employee") + private static class Employee { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "SUPERIOR") + @LazyToOne( value = LazyToOneOption.NO_PROXY ) + Employee superior; + + @OneToMany(mappedBy = "superior") + List subordinates = new ArrayList<>(); + + @Basic + String regularString; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedLazyBasicCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedLazyBasicCacheTest.java new file mode 100644 index 000000000000..1152423920d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedLazyBasicCacheTest.java @@ -0,0 +1,131 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.cache; + +import javax.persistence.Basic; +import javax.persistence.Cacheable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.stat.CacheRegionStatistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Aaron Schmischke + * @author Gail Badner + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class UninitializedLazyBasicCacheTest extends BaseCoreFunctionalTestCase { + private Long personId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" ); + configuration.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Before + public void prepare() { + this.personId = doInHibernate( + this::sessionFactory, s -> { + + final Person person = new Person(); + person.setLazyAttribute( "does_not_matter" ); + s.persist( person ); + return person.getId(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11766") + public void test() { + + sessionFactory().getStatistics().clear(); + sessionFactory().getCache().evictAll(); + + doInHibernate( + this::sessionFactory, s -> { + final Person person = s.get( Person.class, personId ); + assertFalse( Hibernate.isPropertyInitialized( person, "lazyAttribute" ) ); + } + ); + + CacheRegionStatistics regionStatistics = sessionFactory().getStatistics().getCacheRegionStatistics( "Person" ); + assertEquals( 0, regionStatistics.getHitCount() ); + assertEquals( 1, regionStatistics.getMissCount() ); + assertEquals( 1, regionStatistics.getPutCount() ); + + doInHibernate( + this::sessionFactory, s -> { + final Person person = s.get( Person.class, personId ); + assertFalse( Hibernate.isPropertyInitialized( person, "lazyAttribute" ) ); + person.getLazyAttribute(); + assertTrue( Hibernate.isPropertyInitialized( person, "lazyAttribute" ) ); + } + ); + assertEquals( 1, regionStatistics.getHitCount() ); + assertEquals( 1, regionStatistics.getMissCount() ); + assertEquals( 1, regionStatistics.getPutCount() ); + } + + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "all", region = "Person") + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + @Column(name = "id") + private Long id; + + @Column(name = "lazyAttribute") + @Basic(fetch = FetchType.LAZY) + private String lazyAttribute; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLazyAttribute() { + return lazyAttribute; + } + + public void setLazyAttribute(String lazyAttribute) { + this.lazyAttribute = lazyAttribute; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java new file mode 100644 index 000000000000..2ff22a49adbe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java @@ -0,0 +1,227 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.group; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests removing non-owning side of the bidirectional association, + * where owning side is in an embeddable. + * + * Tests with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, + BidirectionalLazyGroupsInEmbeddableTest.NoDirtyCheckEnhancementContext.class +}) +public class BidirectionalLazyGroupsInEmbeddableTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class }; + } + + @Test + @Ignore("Test is failing with Javassist and also fails with ByteBuddy if the mappings are moved to the fields.") + public void test() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult(); + session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { + session.remove( employee ); + } + } + ); + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employerContainer.employer", fetch = FetchType.LAZY) + @LazyGroup("Employees") + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + if ( employee.getEmployerContainer() == null ) { + employee.setEmployerContainer( new EmployerContainer() ); + } + employee.getEmployerContainer().setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + public Employee(String name) { + this(); + setName( name ); + } + + @Id + @GeneratedValue + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public EmployerContainer employerContainer; + + protected Employee() { + // this form used by Hibernate + } + + public EmployerContainer getEmployerContainer() { + return employerContainer; + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployerContainer(EmployerContainer employerContainer) { + this.employerContainer = employerContainer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + @Embeddable + public static class EmployerContainer { + private Employer employer; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("EmployerForEmployee") + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java new file mode 100644 index 000000000000..8898e38d3008 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java @@ -0,0 +1,218 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.group; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests removing non-owning side of the bidirectional association, + * with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, + BidirectionalLazyGroupsTest.NoDirtyCheckEnhancementContext.class +}) +public class BidirectionalLazyGroupsTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class }; + } + + @Test + public void testRemoveCollectionOwnerNoCascade() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult(); + session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { + assertFalse( Hibernate.isPropertyInitialized( employee, "employer") ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employer.class, "RedHat" ) ); + assertNull( session.createQuery( "from Employee e", Employee.class ).uniqueResult() ); + } + ); + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employer", fetch = FetchType.LAZY) + @LazyGroup("Employees") + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + employee.setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + private Employer employer; + + public Employee(String name) { + this(); + setName( name ); + } + + @Id + @GeneratedValue + public long getId() { + return id; + } + + public String getName() { + return name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("EmployerForEmployee") + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/Child.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/Child.java deleted file mode 100644 index eb3efdd00e63..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/Child.java +++ /dev/null @@ -1,88 +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.bytecode.enhancement.lazy.group; - -import javax.persistence.Basic; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.ManyToOne; - -import org.hibernate.annotations.LazyGroup; -import org.hibernate.annotations.LazyToOne; -import org.hibernate.annotations.LazyToOneOption; - -/** - * @author Steve Ebersole - */ -@Entity -public class Child { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - Long id; - String name; - @Basic( fetch = FetchType.LAZY ) - String nickName; - @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @LazyToOne(LazyToOneOption.NO_PROXY) - Parent parent; - @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @LazyToOne(LazyToOneOption.NO_PROXY) - @LazyGroup( "SECONDARY" ) - Parent alternateParent; - - public Child() { - } - - public Child(String name, String nickName) { - this.name = name; - this.nickName = nickName; - } - - 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 getNickName() { - return nickName; - } - - public void setNickName(String nickName) { - this.nickName = nickName; - } - - public Parent getParent() { - return parent; - } - - public void setParent(Parent parent) { - this.parent = parent; - } - - public Parent getAlternateParent() { - return alternateParent; - } - - public void setAlternateParent(Parent alternateParent) { - this.alternateParent = alternateParent; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LGMB_From.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LGMB_From.java new file mode 100644 index 000000000000..edaf3808bf26 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LGMB_From.java @@ -0,0 +1,102 @@ +package org.hibernate.test.bytecode.enhancement.lazy.group; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +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.Lob; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * Source of a LazyToOne - relationship with FK on the other side + * + * @author Jan-Oliver Lustig, Sebastian Viefhaus + */ +@Entity +@Table(name = "LGMB_FROM") +@Access(AccessType.FIELD) +public class LGMB_From { + + @Column(length = 50, nullable = false) + private String name; + + // Lazy-Attribute without LazyGroup-Annotation (therefore Default-LazyGroup) + @Column(length = 65000) + @Basic(fetch = FetchType.LAZY) + @Lob + private String bigText; + + // Lazy-Association with mappdedBy in own LazyGroup + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "fromRelation", optional = true) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup(value = "toRelationLazyGroup") + private LGMB_To toRelation; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(nullable = false) + private Long id; + + /** + * Default Constructor + */ + public LGMB_From() { + super(); + } + + /** + * Constructor + */ + public LGMB_From(String name) { + super(); + this.name = name; + } + + /** + * @return the toRelation + */ + public LGMB_To getToRelation() { + return toRelation; + } + + /** + * @param toRelation the toRelation to set + */ + public void setToRelation(LGMB_To toRelation) { + this.toRelation = toRelation; + } + + public String getBigText() { + return bigText; + } + + /** + * @return id + */ + public Long getId() { + return id; + } + + /** + * @param id + */ + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LGMB_To.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LGMB_To.java new file mode 100644 index 000000000000..6f67c6b0a4dc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LGMB_To.java @@ -0,0 +1,78 @@ +package org.hibernate.test.bytecode.enhancement.lazy.group; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.AccessType; + +/** + * Target of a LazyToOne - relationship (Foreignkey on this side) + * + * @author Jan-Oliver Lustig, Sebastian Viefhaus + */ +@Entity +@Table(name = "LGMB_TO") +public class LGMB_To { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(nullable = false) + @AccessType("property") + private Long id; + + @OneToOne + LGMB_From fromRelation; + + @Column(length = 50, nullable = false) + private String name; + + /** + * Default Constructor + */ + public LGMB_To() { + super(); + } + + /** + * Constructor + */ + public LGMB_To(String name) { + super(); + this.name = name; + } + + /** + * @return the fromRelation + */ + public LGMB_From getFromRelation() { + return fromRelation; + } + + /** + * @param fromRelation the fromRelation to set + */ + public void setFromRelation(LGMB_From fromRelation) { + this.fromRelation = fromRelation; + } + + /** + * @return id + */ + public Long getId() { + return id; + } + + /** + * @param id + */ + public void setId(Long id) { + this.id = id; + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupAccessTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupAccessTestTask.java deleted file mode 100644 index f4683729f8c2..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupAccessTestTask.java +++ /dev/null @@ -1,132 +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.bytecode.enhancement.lazy.group; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.proxy.HibernateProxy; - -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.junit.Assert; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNull; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; - -/** - * @author Steve Ebersole - */ -public class LazyGroupAccessTestTask extends AbstractEnhancerTestTask { - - @Override - public Class[] getAnnotatedClasses() { - return new Class[] { Child.class, Parent.class }; - } - - @Override - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Child c1 = new Child( "steve", "hibernater" ); - Child c2 = new Child( "sally", "Joe Mama" ); - - Parent p1 = new Parent( "Hibernate" ); - Parent p2 = new Parent( "Swimming" ); - - c1.setParent( p1 ); - p1.getChildren().add( c1 ); - - c1.setAlternateParent( p2 ); - p2.getAlternateChildren().add( c1 ); - - c2.setAlternateParent( p1 ); - p1.getAlternateChildren().add( c2 ); - - c2.setParent( p2 ); - p2.getChildren().add( c2 ); - - s.save( p1 ); - s.save( p2 ); - - s.getTransaction().commit(); - s.close(); - } - - @Override - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Child c1 = (Child) s.createQuery( "from Child c where c.name = :name" ).setString( "name", "steve" ).uniqueResult(); - - // verify the expected initial loaded state - assertLoaded( c1, "name" ); - assertNotLoaded( c1, "nickName" ); - assertNotLoaded( c1, "parent" ); - assertNotLoaded( c1, "alternateParent" ); - - // Now lets access nickName which ought to initialize nickName and parent, but not alternateParent - c1.getNickName(); - assertLoaded( c1, "nickName" ); - assertLoaded( c1, "parent" ); - assertNotLoaded( c1, "alternateParent" ); - assertEquals( "Hibernate", c1.getParent().getNombre() ); - assertFalse( c1.getParent() instanceof HibernateProxy ); - - Child c2 = (Child) s.createQuery( "from Child c where c.name = :name" ).setString( "name", "sally" ).uniqueResult(); - - // verify the expected initial loaded state - assertLoaded( c2, "name" ); - assertNotLoaded( c2, "nickName" ); - assertNotLoaded( c2, "parent" ); - assertNotLoaded( c2, "alternateParent" ); - - // Now lets access alternateParent which ought to initialize alternateParent and nothing else - c2.getAlternateParent(); - assertNotLoaded( c2, "nickName" ); - assertNotLoaded( c2, "parent" ); - assertLoaded( c2, "alternateParent" ); - assertEquals( "Hibernate", c2.getAlternateParent().getNombre() ); - assertFalse( c2.getAlternateParent() instanceof HibernateProxy ); - - s.getTransaction().commit(); - s.close(); - } - - private void assertLoaded(Object owner, String name) { - // NOTE we assume null == not-loaded - Object fieldByReflection = EnhancerTestUtils.getFieldByReflection( owner, name ); - assertNotNull( "Expecting field '" + name + "' to be loaded, but it was not", fieldByReflection ); - } - - private void assertNotLoaded(Object owner, String name) { - // NOTE we assume null == not-loaded - Object fieldByReflection = EnhancerTestUtils.getFieldByReflection( owner, name ); - assertNull( "Expecting field '" + name + "' to be not loaded, but it was", fieldByReflection ); - } - - @Override - protected void cleanup() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - s.createQuery( "delete Child" ).executeUpdate(); - s.createQuery( "delete Parent" ).executeUpdate(); - - s.getTransaction().commit(); - s.close(); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupMappedByTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupMappedByTest.java new file mode 100644 index 000000000000..4e3197376515 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupMappedByTest.java @@ -0,0 +1,87 @@ +package org.hibernate.test.bytecode.enhancement.lazy.group; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +import org.hibernate.stat.SessionStatistics; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Testing OneToOne LazyToOne association + * + * @author Jan-Oliver Lustig, Sebastian Viefhaus + */ +@TestForIssue(jiraKey = "HHH-11986") +@RunWith(BytecodeEnhancerRunner.class) +public class LazyGroupMappedByTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { LGMB_From.class, LGMB_To.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-11986") + public void test() { + Long fromId = createEntities(); + + Statistics stats = sessionFactory().getStatistics(); + stats.setStatisticsEnabled( true ); + stats.clear(); + + doInHibernate( + this::sessionFactory, session -> { + + SessionStatistics sessionStats = session.getStatistics(); + + // Should be loaded lazy. + LGMB_From from = session.get( LGMB_From.class, fromId ); + assertEquals( 1, sessionStats.getEntityCount() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // Lazy text is accessed, toRelation should not be read yet. + String bigText = from.getBigText(); + assertEquals( 1, sessionStats.getEntityCount() ); + assertEquals( 2, stats.getPrepareStatementCount() ); + + // Second table is accessed and the lazy one should be reloaded. + LGMB_To to = from.getToRelation(); + assertEquals( 2, sessionStats.getEntityCount() ); + assertEquals( 3, stats.getPrepareStatementCount() ); + + to.getFromRelation().getName(); + assertEquals( 3, stats.getPrepareStatementCount() ); + } + ); + } + + /** + * Hilfsmethode: Eine Entität anlegen + * + * @return ID der Quell-Entität + */ + public Long createEntities() { + return doInHibernate( + this::sessionFactory, session -> { + session.createQuery( "delete from LGMB_To" ).executeUpdate(); + session.createQuery( "delete from LGMB_From" ).executeUpdate(); + + LGMB_From from = new LGMB_From( "A" ); + LGMB_To to = new LGMB_To( "B" ); + from.setToRelation( to ); + to.setFromRelation( from ); + + session.save( from ); + session.flush(); + + return from.getId(); + } + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupTest.java new file mode 100644 index 000000000000..275c16b5b3fa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupTest.java @@ -0,0 +1,276 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.List; + +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.getFieldByReflection; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-11155" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyGroupTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Child.class, Parent.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 -> { + Child c1 = new Child( "steve", "Hibernater" ); + Child c2 = new Child( "sally", "Joe Mama" ); + + Parent p1 = new Parent( "Hibernate" ); + Parent p2 = new Parent( "Swimming" ); + + c1.parent = p1; + p1.children.add( c1 ); + + c1.alternateParent = p2; + p2.alternateChildren.add( c1 ); + + c2.parent = p2; + p2.children.add( c2 ); + + c2.alternateParent = p1; + p1.alternateChildren.add( c2 ); + + s.save( p1 ); + s.save( p2 ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-10267" ) + public void testAccess() { + doInHibernate( this::sessionFactory, s -> { + Child c1 = (Child) s.createQuery( "from Child c where c.name = :name" ).setParameter( "name", "steve" ).uniqueResult(); + + // verify the expected initial loaded state + assertLoaded( c1, "name" ); + assertNotLoaded( c1, "nickName" ); + assertNotLoaded( c1, "parent" ); + assertNotLoaded( c1, "alternateParent" ); + + // Now lets access nickName which ought to initialize nickName and parent, but not alternateParent + c1.getNickName(); + assertLoaded( c1, "nickName" ); + assertLoaded( c1, "parent" ); + assertNotLoaded( c1, "alternateParent" ); + assertEquals( "Hibernate", c1.parent.nombre ); + assertFalse( c1.parent instanceof HibernateProxy ); + + Child c2 = (Child) s.createQuery( "from Child c where c.name = :name" ).setParameter( "name", "sally" ).uniqueResult(); + + // verify the expected initial loaded state + assertLoaded( c2, "name" ); + assertNotLoaded( c2, "nickName" ); + assertNotLoaded( c2, "parent" ); + assertNotLoaded( c2, "alternateParent" ); + + // Now lets access alternateParent which ought to initialize alternateParent and nothing else + c2.getAlternateParent(); + assertNotLoaded( c2, "nickName" ); + assertNotLoaded( c2, "parent" ); + assertLoaded( c2, "alternateParent" ); + assertEquals( "Hibernate", c2.alternateParent.nombre ); + assertFalse( c2.alternateParent instanceof HibernateProxy ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11155" ) + public void testUpdate() { + Parent p1New = new Parent(); + p1New.nombre = "p1New"; + + doInHibernate( this::sessionFactory, s -> { + Child c1 = (Child) s.createQuery( "from Child c where c.name = :name" ).setParameter( "name", "steve" ).uniqueResult(); + + // verify the expected initial loaded state + assertLoaded( c1, "name" ); + assertNotLoaded( c1, "nickName" ); + assertNotLoaded( c1, "parent" ); + assertNotLoaded( c1, "alternateParent" ); + + // Now lets update nickName which ought to initialize nickName and parent, but not alternateParent + c1.nickName = "new nickName"; + assertLoaded( c1, "nickName" ); + assertNotLoaded( c1, "parent" ); + assertNotLoaded( c1, "alternateParent" ); + assertEquals( "Hibernate", c1.parent.nombre ); + assertFalse( c1.parent instanceof HibernateProxy ); + + // Now update c1.parent + c1.parent.children.remove( c1 ); + c1.parent = p1New; + p1New.children.add( c1 ); + } ); + + // verify updates + doInHibernate( this::sessionFactory, s -> { + Child c1 = (Child) s.createQuery( "from Child c where c.name = :name" ).setParameter( "name", "steve" ).uniqueResult(); + assertEquals( "new nickName", c1.getNickName() ); + assertEquals( "p1New", c1.parent.nombre ); + assertFalse( c1.parent instanceof HibernateProxy ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child c2 = (Child) s.createQuery( "from Child c where c.name = :name" ).setParameter( "name", "sally" ).uniqueResult(); + + // verify the expected initial loaded state + assertLoaded( c2, "name" ); + assertNotLoaded( c2, "nickName" ); + assertNotLoaded( c2, "parent" ); + assertNotLoaded( c2, "alternateParent" ); + + // Now lets access and update alternateParent which ought to initialize alternateParent and nothing else + Parent p1 = c2.getAlternateParent(); + c2.alternateParent = p1New; + assertNotLoaded( c2, "nickName" ); + assertNotLoaded( c2, "parent" ); + assertLoaded( c2, "alternateParent" ); + assertEquals( "p1New", c2.getAlternateParent().nombre ); + assertFalse( c2.getAlternateParent() instanceof HibernateProxy ); + + p1.alternateChildren.remove( c2 ); + p1New.alternateChildren.add( c2 ); + } ); + + // verify update + doInHibernate( this::sessionFactory, s -> { + Child c2 = (Child) s.createQuery( "from Child c where c.name = :name" ).setParameter( "name", "sally" ).uniqueResult(); + assertEquals( "p1New", c2.getAlternateParent().nombre ); + } ); + } + + @After + public void cleanup() { + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete Child" ).executeUpdate(); + s.createQuery( "delete Parent" ).executeUpdate(); + } ); + } + + // --- // + + private void assertLoaded(Object owner, String name) { + // NOTE we assume null == not-loaded + Object fieldByReflection = getFieldByReflection( owner, name ); + assertNotNull( "Expecting field '" + name + "' to be loaded, but it was not", fieldByReflection ); + } + + private void assertNotLoaded(Object owner, String name) { + // NOTE we assume null == not-loaded + Object fieldByReflection = getFieldByReflection( owner, name ); + assertNull( "Expecting field '" + name + "' to be not loaded, but it was", fieldByReflection ); + } + + // --- // + + @Entity( name = "Parent" ) + @Table( name = "PARENT" ) + private static class Parent { + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String nombre; + + @OneToMany( mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + List children = new ArrayList<>(); + + @OneToMany( mappedBy = "alternateParent", cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + List alternateChildren = new ArrayList<>(); + + Parent() { + } + + Parent(String nombre) { + this.nombre = nombre; + } + } + + @Entity( name = "Child" ) + @Table( name = "CHILD" ) + private static class Child { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + String name; + + @Basic( fetch = FetchType.LAZY ) + String nickName; + + @ManyToOne( cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + Parent parent; + + @ManyToOne( cascade = CascadeType.ALL, fetch = FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + @LazyGroup( "SECONDARY" ) + Parent alternateParent; + + Child() { + } + + Child(String name, String nickName) { + this.name = name; + this.nickName = nickName; + } + + Parent getAlternateParent() { + return alternateParent; + } + + String getNickName() { + return nickName; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupUpdateTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupUpdateTestTask.java deleted file mode 100644 index 4c1fd2447350..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/LazyGroupUpdateTestTask.java +++ /dev/null @@ -1,172 +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.bytecode.enhancement.lazy.group; - -import org.hibernate.Session; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.proxy.HibernateProxy; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNull; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; - -/** - * @author Steve Ebersole - * @author Gail Badner - */ -public class LazyGroupUpdateTestTask extends AbstractEnhancerTestTask { - - @Override - public Class[] getAnnotatedClasses() { - return new Class[] { Child.class, Parent.class }; - } - - @Override - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - Child c1 = new Child( "steve", "hibernater" ); - Child c2 = new Child( "sally", "Joe Mama" ); - - Parent p1 = new Parent( "Hibernate" ); - Parent p2 = new Parent( "Swimming" ); - - c1.setParent( p1 ); - p1.getChildren().add( c1 ); - - c1.setAlternateParent( p2 ); - p2.getAlternateChildren().add( c1 ); - - c2.setAlternateParent( p1 ); - p1.getAlternateChildren().add( c2 ); - - c2.setParent( p2 ); - p2.getChildren().add( c2 ); - - s.save( p1 ); - s.save( p2 ); - - s.getTransaction().commit(); - s.close(); - } - - @Override - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - Child c1 = (Child) s.createQuery( "from Child c where c.name = :name" ).setString( "name", "steve" ).uniqueResult(); - - // verify the expected initial loaded state - assertLoaded( c1, "name" ); - assertNotLoaded( c1, "nickName" ); - assertNotLoaded( c1, "parent" ); - assertNotLoaded( c1, "alternateParent" ); - - // Now lets update nickName which ought to initialize nickName and parent, but not alternateParent - c1.setNickName( "new nickName" ); - assertLoaded( c1, "nickName" ); - assertNotLoaded( c1, "parent" ); - assertNotLoaded( c1, "alternateParent" ); - assertEquals( "Hibernate", c1.getParent().getNombre() ); - assertFalse( c1.getParent() instanceof HibernateProxy ); - - // Now update c1.parent - c1.getParent().getChildren().remove( c1 ); - Parent p1New = new Parent(); - p1New.setNombre( "p1New" ); - c1.setParent( p1New ); - p1New.getChildren().add( c1 ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.getTransaction().begin(); - - c1 = (Child) s.createQuery( "from Child c where c.name = :name" ).setString( "name", "steve" ).uniqueResult(); - - // verify updates - assertEquals( "new nickName", c1.getNickName() ); - assertEquals( "p1New", c1.getParent().getNombre() ); - assertFalse( c1.getParent() instanceof HibernateProxy ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.getTransaction().begin(); - - Child c2 = (Child) s.createQuery( "from Child c where c.name = :name" ).setString( "name", "sally" ).uniqueResult(); - - // verify the expected initial loaded state - assertLoaded( c2, "name" ); - assertNotLoaded( c2, "nickName" ); - assertNotLoaded( c2, "parent" ); - assertNotLoaded( c2, "alternateParent" ); - - // Now lets access and update alternateParent which ought to initialize alternateParent and nothing else - Parent p1 = c2.getAlternateParent(); - c2.setAlternateParent( p1New ); - assertNotLoaded( c2, "nickName" ); - assertNotLoaded( c2, "parent" ); - assertLoaded( c2, "alternateParent" ); - assertEquals( "p1New", c2.getAlternateParent().getNombre() ); - assertFalse( c2.getAlternateParent() instanceof HibernateProxy ); - - p1.getAlternateChildren().remove( c2 ); - p1New.getAlternateChildren().add( c2 ); - - s.getTransaction().commit(); - s.close(); - - s = getFactory().openSession(); - s.getTransaction().begin(); - - c2 = (Child) s.createQuery( "from Child c where c.name = :name" ).setString( "name", "sally" ).uniqueResult(); - - // verify update - assertEquals( "p1New", c2.getAlternateParent().getNombre() ); - - s.getTransaction().commit(); - s.close(); - } - - private void assertLoaded(Object owner, String name) { - // NOTE we assume null == not-loaded - Object fieldByReflection = EnhancerTestUtils.getFieldByReflection( owner, name ); - assertNotNull( "Expecting field '" + name + "' to be loaded, but it was not", fieldByReflection ); - } - - private void assertNotLoaded(Object owner, String name) { - // NOTE we assume null == not-loaded - Object fieldByReflection = EnhancerTestUtils.getFieldByReflection( owner, name ); - assertNull( "Expecting field '" + name + "' to be not loaded, but it was", fieldByReflection ); - } - - @Override - protected void cleanup() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - s.createQuery( "delete Child" ).executeUpdate(); - s.createQuery( "delete Parent" ).executeUpdate(); - - s.getTransaction().commit(); - s.close(); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/Parent.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/Parent.java deleted file mode 100644 index 2e5c1292a443..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/Parent.java +++ /dev/null @@ -1,71 +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.bytecode.enhancement.lazy.group; - -import java.util.ArrayList; -import java.util.List; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -/** - * @author Steve Ebersole - */ -@Entity -public class Parent { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - Long id; - String nombre; - @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - List children = new ArrayList(); - @OneToMany(mappedBy = "alternateParent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - List alternateChildren = new ArrayList(); - - public Parent() { - } - - public Parent(String nombre) { - this.nombre = nombre; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getNombre() { - return nombre; - } - - public void setNombre(String nombre) { - this.nombre = nombre; - } - - public List getChildren() { - return children; - } - - public void setChildren(List children) { - this.children = children; - } - - public List getAlternateChildren() { - return alternateChildren; - } - - public void setAlternateChildren(List alternateChildren) { - this.alternateChildren = alternateChildren; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java new file mode 100644 index 000000000000..93a66e308c65 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.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.test.bytecode.enhancement.lazy.group; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +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.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +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.Id; +import javax.persistence.Table; + +import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.getFieldByReflection; +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; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-11155, HHH-11506" ) +@RunWith( BytecodeEnhancerRunner.class ) +@CustomEnhancementContext( {EnhancerTestContext.class, SimpleLazyGroupUpdateTest.NoDirtyCheckingContext.class} ) +public class SimpleLazyGroupUpdateTest extends BaseCoreFunctionalTestCase { + + public static final String REALLY_BIG_STRING = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{TestEntity.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 -> { + s.save( new TestEntity( 1L, "entity 1", "blah", REALLY_BIG_STRING ) ); + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + TestEntity entity = s.get( TestEntity.class, 1L ); + assertLoaded( entity, "name" ); + assertNotLoaded( entity, "lifeStory" ); + assertNotLoaded( entity, "reallyBigString" ); + + entity.lifeStory = "blah blah blah"; + assertLoaded( entity, "name" ); + assertLoaded( entity, "lifeStory" ); + assertNotLoaded( entity, "reallyBigString" ); + } ); + + doInHibernate( this::sessionFactory, s -> { + TestEntity entity = s.get( TestEntity.class, 1L ); + + assertLoaded( entity, "name" ); + assertNotLoaded( entity, "lifeStory" ); + assertNotLoaded( entity, "reallyBigString" ); + assertEquals( "blah blah blah", entity.lifeStory ); + assertEquals( REALLY_BIG_STRING, entity.reallyBigString ); + } ); + } + + private void assertLoaded(Object owner, String name) { + // NOTE we assume null == not-loaded + Object fieldByReflection = getFieldByReflection( owner, name ); + assertNotNull( "Expecting field '" + name + "' to be loaded, but it was not", fieldByReflection ); + } + + private void assertNotLoaded(Object owner, String name) { + // NOTE we assume null == not-loaded + Object fieldByReflection = getFieldByReflection( owner, name ); + assertNull( "Expecting field '" + name + "' to be not loaded, but it was", fieldByReflection ); + } + + @After + public void cleanup() { + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete TestEntity" ).executeUpdate(); + } ); + } + + // --- // + + @Entity( name = "TestEntity" ) + @Table( name = "TEST_ENTITY" ) + private static class TestEntity { + + @Id + Long id; + + String name; + + @Basic( fetch = FetchType.LAZY ) + @LazyGroup( "grp1" ) + String lifeStory; + + @Basic( fetch = FetchType.LAZY ) + @LazyGroup( "grp2" ) + String reallyBigString; + + TestEntity() { + } + + TestEntity(Long id, String name, String lifeStory, String reallyBigString) { + this.id = id; + this.name = name; + this.lifeStory = lifeStory; + this.reallyBigString = reallyBigString; + } + } + + // --- // + + public static class NoDirtyCheckingContext extends EnhancerTestContext { + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTestTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTestTask.java deleted file mode 100644 index 576d1d229cb3..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTestTask.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.test.bytecode.enhancement.lazy.group; - -import javax.persistence.Basic; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - -import org.hibernate.Session; -import org.hibernate.annotations.DynamicUpdate; -import org.hibernate.annotations.LazyGroup; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; - -import org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils; -import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; - -import static junit.framework.Assert.assertNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -/** - * @author Steve Ebersole - */ -public class SimpleLazyGroupUpdateTestTask extends AbstractEnhancerTestTask { - public static final String REALLY_BIG_STRING = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - - @Override - public Class[] getAnnotatedClasses() { - return new Class[] { TestEntity.class }; - } - - @Override - public void prepare() { - Configuration cfg = new Configuration(); - cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); - cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); - super.prepare( cfg ); - - Session s = getFactory().openSession(); - s.beginTransaction(); - - s.save( new TestEntity( 1, "entity 1", "blah", REALLY_BIG_STRING ) ); - - s.getTransaction().commit(); - s.close(); - } - - @Override - public void execute() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - TestEntity entity = s.load( TestEntity.class, 1 ); - assertLoaded( entity, "name" ); - assertNotLoaded( entity, "lifeStory" ); - assertNotLoaded( entity, "reallyBigString" ); - - entity.setLifeStory( "blah blah blah" ); - assertLoaded( entity, "name" ); - assertLoaded( entity, "lifeStory" ); - assertNotLoaded( entity, "reallyBigString" ); - - s.getTransaction().commit(); - s.close(); - - - s = getFactory().openSession(); - s.beginTransaction(); - - entity = s.load( TestEntity.class, 1 ); - assertLoaded( entity, "name" ); - assertNotLoaded( entity, "lifeStory" ); - assertNotLoaded( entity, "reallyBigString" ); - assertEquals( "blah blah blah", entity.getLifeStory() ); - assertEquals( REALLY_BIG_STRING, entity.getReallyBigString() ); - - s.getTransaction().commit(); - s.close(); - } - - private void assertLoaded(Object owner, String name) { - // NOTE we assume null == not-loaded - Object fieldByReflection = EnhancerTestUtils.getFieldByReflection( owner, name ); - assertNotNull( "Expecting field '" + name + "' to be loaded, but it was not", fieldByReflection ); - } - - private void assertNotLoaded(Object owner, String name) { - // NOTE we assume null == not-loaded - Object fieldByReflection = EnhancerTestUtils.getFieldByReflection( owner, name ); - assertNull( "Expecting field '" + name + "' to be not loaded, but it was", fieldByReflection ); - } - - @Override - protected void cleanup() { - Session s = getFactory().openSession(); - s.beginTransaction(); - - s.createQuery( "delete TestEntity" ).executeUpdate(); - - s.getTransaction().commit(); - s.close(); - } - - @Entity( name = "TestEntity" ) - public static class TestEntity { - @Id - Integer id; - String name; - @Basic(fetch = FetchType.LAZY) - @LazyGroup( "grp1" ) - String lifeStory; - @Basic(fetch = FetchType.LAZY) - @LazyGroup( "grp2" ) - String reallyBigString; - - public TestEntity() { - } - - public TestEntity(Integer id, String name, String lifeStory, String reallyBigString) { - this.id = id; - this.name = name; - this.lifeStory = lifeStory; - this.reallyBigString = reallyBigString; - } - - public Integer getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getLifeStory() { - return lifeStory; - } - - public void setLifeStory(String lifeStory) { - this.lifeStory = lifeStory; - } - - public String getReallyBigString() { - return reallyBigString; - } - - public void setReallyBigString(String reallyBigString) { - this.reallyBigString = reallyBigString; - } - } -} 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 new file mode 100644 index 000000000000..2db7e7102438 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.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.bytecode.enhancement.lazy.notfound; + +/** + * @author Gail Badner + */ + +import javax.persistence.CascadeType; +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.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-12226") +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyNotFoundManyToOneNonUpdatableNonInsertableTest extends BaseCoreFunctionalTestCase { + private static int ID = 1; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + User.class, + Lazy.class + }; + } + + @Test + public void test() { + doInHibernate( + this::sessionFactory, session -> { + Lazy p = new Lazy(); + p.id = ID; + User u = new User(); + u.id = ID; + u.setLazy( p ); + session.persist( u ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( session.get( Lazy.class, ID ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + User user = session.find( User.class, ID ); + assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); + assertNull( user.getLazy() ); + } + ); + } + + @Entity(name="User") + @Table(name = "USER_TABLE") + public static class User { + + @Id + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = true) + @LazyToOne(value = LazyToOneOption.NO_PROXY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn( + foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), + name = "id", + referencedColumnName = "id", + insertable = false, + updatable = false + ) + private Lazy lazy; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Lazy getLazy() { + return lazy; + } + + public void setLazy(Lazy lazy) { + this.lazy = lazy; + } + + } + + @Entity(name = "Lazy") + @Table(name = "LAZY") + public static class Lazy { + @Id + private Integer id; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } + +} 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 new file mode 100644 index 000000000000..e71c0e483449 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java @@ -0,0 +1,134 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.notfound; + +/** + * @author Gail Badner + */ + +import javax.persistence.CascadeType; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@TestForIssue( jiraKey = "HHH-12226") +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyNotFoundOneToOneNonUpdatableNonInsertableTest extends BaseCoreFunctionalTestCase { + private static int ID = 1; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + User.class, + Lazy.class + }; + } + + @Test + public void test() { + doInHibernate( + this::sessionFactory, session -> { + Lazy p = new Lazy(); + p.id = ID; + User u = new User(); + u.id = ID; + u.setLazy( p ); + session.persist( u ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( session.get( Lazy.class, ID ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + User user = session.find( User.class, ID ); + assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); + assertNull( user.getLazy() ); + assertTrue( Hibernate.isPropertyInitialized( user, "lazy" ) ); + } + ); + } + + @Entity(name="User") + @Table(name = "USER_TABLE") + public static class User { + + @Id + private Integer id; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = true) + @LazyToOne(value = LazyToOneOption.NO_PROXY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn( + foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), + name = "id", + referencedColumnName = "id", + insertable = false, + updatable = false + ) + private Lazy lazy; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Lazy getLazy() { + return lazy; + } + + public void setLazy(Lazy lazy) { + this.lazy = lazy; + } + + } + + @Entity(name = "Lazy") + @Table(name = "LAZY") + public static class Lazy { + @Id + private Integer id; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } + +} 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 new file mode 100644 index 000000000000..8908f2caf423 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java @@ -0,0 +1,128 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.notfound; + +/** + * @author Gail Badner + */ + +import javax.persistence.CascadeType; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-12226") +@RunWith( BytecodeEnhancerRunner.class ) +public class LazyNotFoundOneToOneTest extends BaseCoreFunctionalTestCase { + private static int ID = 1; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + User.class, + Lazy.class + }; + } + + @Test + public void test() { + doInHibernate( + this::sessionFactory, session -> { + Lazy p = new Lazy(); + p.id = ID; + User u = new User(); + u.id = ID; + u.setLazy( p ); + session.persist( u ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( session.get( Lazy.class, ID ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + User user = session.find( User.class, ID ); + assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); + assertNull( user.getLazy() ); + } + ); + } + + @Entity(name="User") + @Table(name = "USER_TABLE") + public static class User { + + @Id + private Integer id; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(value = LazyToOneOption.NO_PROXY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + private Lazy lazy; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Lazy getLazy() { + return lazy; + } + + public void setLazy(Lazy lazy) { + this.lazy = lazy; + } + + } + + @Entity(name = "Lazy") + @Table(name = "LAZY") + public static class Lazy { + @Id + private Integer id; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java new file mode 100644 index 000000000000..ca1ccf7066c3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java @@ -0,0 +1,130 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +@Entity +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name = "PP_DCKey") +public abstract class AbstractKey extends ModelEntity + implements Serializable { + + @Column(name = "Name") + String name; + + @OneToMany(targetEntity = RoleEntity.class, mappedBy = "key", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("R") + protected Set roles = new LinkedHashSet<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PR") + @JoinColumn + protected AbstractKey register; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "register", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("RK") + protected Set keys = new LinkedHashSet(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PP") + @JoinColumn + protected AbstractKey parent; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "parent", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PK") + protected Set otherKeys = new LinkedHashSet(); + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set role) { + this.roles = role; + } + + public void addRole(RoleEntity role) { + this.roles.add( role ); + } + + public AbstractKey getRegister() { + return register; + } + + public void setRegister(AbstractKey register) { + this.register = register; + } + + public Set getKeys() { + return keys; + } + + public void setKeys(Set keys) { + this.keys = keys; + } + + public void addRegisterKey(AbstractKey registerKey) { + keys.add( registerKey ); + } + + public AbstractKey getParent() { + return parent; + } + + public void setParent(AbstractKey parent) { + this.parent = parent; + } + + public Set getOtherKeys() { + return otherKeys; + } + + public void setOtherKeys(Set otherKeys) { + this.otherKeys = otherKeys; + } + + public void addPanelKey(AbstractKey panelKey) { + this.otherKeys.add( panelKey ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java new file mode 100644 index 000000000000..7b8b45a64056 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java @@ -0,0 +1,79 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Activity") +@Table(name = "activity") +@SuppressWarnings("WeakerAccess") +public class Activity extends BaseEntity { + private String description; + private Instruction instruction; + + protected WebApplication webApplication = null; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Activity() { + super(); + } + + public Activity(Integer id, String description, Instruction instruction) { + super( id ); + this.description = description; + this.instruction = instruction; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("Instruction") + @JoinColumn(name = "Instruction_Id") + public Instruction getInstruction() { + return instruction; + } + + @SuppressWarnings("unused") + public void setInstruction(Instruction instruction) { + this.instruction = instruction; + } + + @SuppressWarnings("unused") + @ManyToOne(fetch=FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("webApplication") + @JoinColumn(name="web_app_oid") + public WebApplication getWebApplication() { + return webApplication; + } + + public void setWebApplication(WebApplication webApplication) { + this.webApplication = webApplication; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java new file mode 100644 index 000000000000..360a042081c2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java @@ -0,0 +1,49 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Address") +@Table(name = "address") +public class Address { + private Integer id; + + private String text; + + public Address() { + } + + public Address(Integer id, String text) { + this.id = id; + this.text = text; + } + + @Id + @Column(name = "oid") + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java new file mode 100644 index 000000000000..463d1bb7d6c8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Steve Ebersole + */ +@MappedSuperclass +public class BaseEntity { + protected Integer id; + protected String nbr; + + public BaseEntity() { + } + + public BaseEntity(Integer id) { + this.id = id; + } + + @Id + @Column( name = "oid" ) + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getNbr() { + return nbr; + } + + public void setNbr(String nbr) { + this.nbr = nbr; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BatchFetchProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BatchFetchProxyTest.java new file mode 100644 index 000000000000..9cefe19f3b25 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BatchFetchProxyTest.java @@ -0,0 +1,261 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class BatchFetchProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + private static int NUMBER_OF_ENTITIES = 20; + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchAssociationFetch() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + List employees = session.createQuery( "from Employee", Employee.class ).getResultList(); + + assertEquals( 1, statistics.getPrepareStatementCount() ); + assertEquals( NUMBER_OF_ENTITIES, employees.size() ); + + for ( int i = 0; i < employees.size(); i++ ) { + final Employer employer = employees.get( i ).employer; + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + } + + // assert that all 20 Employee and all 20 Employers have been loaded + assertThat( statistics.getEntityLoadCount(), is( 40L ) ); + // but assert that it only took 3 queries (the initial plus the 2 batch fetches) + assertThat( statistics.getPrepareStatementCount(), is( 3L ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchAssociation() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + List employees = session.createQuery( "from Employee", Employee.class ).getResultList(); + + assertEquals( 1, statistics.getPrepareStatementCount() ); + assertEquals( NUMBER_OF_ENTITIES, employees.size() ); + + for ( int i = 0 ; i < employees.size() ; i++ ) { + final Employer employer = employees.get( i ).employer; + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + } + + assertEquals( 3, statistics.getPrepareStatementCount() ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchEntityLoad() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + + List employers = new ArrayList<>(); + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + employers.add( session.load( Employer.class, i + 1) ); + } + + assertEquals( 0, statistics.getPrepareStatementCount() ); + + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + final Employer employer = employers.get( i ); + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + } + + assertEquals( 2, statistics.getPrepareStatementCount() ); + } + ); + } + + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchEntityLoadThenModify() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + + List employers = new ArrayList<>(); + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + employers.add( session.load( Employer.class, i + 1) ); + } + + assertEquals( 0, statistics.getPrepareStatementCount() ); + + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + final Employer employer = employers.get( i ); + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + employer.name = employer.name + " new"; + } + + assertEquals( 2, statistics.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) { + final Employer employer = session.get( Employer.class, i + 1 ); + assertEquals( "Employer #" + employer.id + " new", employer.name ); + } + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + Employer.class + }; + } + + @Before + public void setUpData() { + inTransaction( + session -> { + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + final Employee employee = new Employee(); + employee.id = i + 1; + employee.name = "Employee #" + employee.id; + final Employer employer = new Employer(); + employer.id = i + 1; + employer.name = "Employer #" + employer.id; + employee.employer = employer; + session.persist( employee ); + } + } + ); + } + + @After + public void cleanupDate() { + inTransaction( + session -> { + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from Employer" ).executeUpdate(); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + ssrb.applySetting( AvailableSettings.SHOW_SQL, true ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Entity(name = "Employee") + public static class Employee { + @Id + private int id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Employer employer; + } + + @Entity(name = "Employer") + @BatchSize(size = 10) + public static class Employer { + @Id + private int id; + + private String name; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java new file mode 100644 index 000000000000..fd91eb9cf102 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java @@ -0,0 +1,364 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class BidirectionalProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testIt() { + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getStringField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getEntries(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setStringField( "this is a string" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + AMappedSuperclass mappedSuperclass = a; + mappedSuperclass.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( "this is a string", a.getStringField() ); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AChildEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AChildEntity a = new AChildEntity("a"); + BEntity b = new BEntity("b"); + b.setA(a); + session.persist(a); + session.persist(b); + + AChildEntity a1 = new AChildEntity("a1"); + CEntity c = new CEntity( "c" ); + c.setA( a1 ); + session.persist( a1 ); + session.persist( c ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from BEntity" ).executeUpdate(); + session.createQuery( "delete from CEntity" ).executeUpdate(); + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @Entity(name="CEntity") + @Table(name="C") + public static class CEntity implements Serializable { + @Id + private String id; + + public CEntity(String id) { + this(); + setId(id); + } + + protected CEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AEntity a) { + aChildEntity = a; + a.getcEntries().add(this); + } + + public AEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AEntity aChildEntity = null; + } + + @Entity(name="BEntity") + @Table(name="B") + public static class BEntity implements Serializable { + @Id + private String id; + + public BEntity(String id) { + this(); + setId(id); + } + + protected BEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AChildEntity a) { + aChildEntity = a; + a.getEntries().add(this); + } + + public AChildEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AChildEntity aChildEntity = null; + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + @Basic + private short version; + + @Column(name = "INTEGER_FIELD") + private Integer integerField; + + public AMappedSuperclass(String id) { + setId(id); + } + + protected AMappedSuperclass() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(Integer integerField) { + this.integerField = integerField; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @Table(name="A") + public static class AEntity extends AMappedSuperclass { + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + @OneToMany(targetEntity=CEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set cEntries = new LinkedHashSet(); + + public Set getcEntries() { + return cEntries; + } + + public void setcEntries(Set cEntries) { + this.cEntries = cEntries; + } + } + + @Entity(name="AChildEntity") + @Table(name="ACChild") + public static class AChildEntity extends AEntity { + + private String stringField; + + @OneToMany(targetEntity=BEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set entries = new LinkedHashSet(); + + public AChildEntity(String id) { + super(id); + } + + protected AChildEntity() { + } + + public Set getEntries() { + return entries; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } + + @Override + public Integer getIntegerField() { + return super.getIntegerField(); + } + + @Override + public void setIntegerField(Integer integerField) { + super.setIntegerField( integerField ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java new file mode 100644 index 000000000000..f0529f83e8ba --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "CreditCardPayment") +@Table(name = "credit_payment") +public class CreditCardPayment extends Payment { + private String transactionId; + + public CreditCardPayment() { + } + + public CreditCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java new file mode 100644 index 000000000000..a709fe1fea2a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java @@ -0,0 +1,106 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Customer") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class Customer { + private Integer oid; + private String name; + private Set orders = new HashSet<>(); + + private Address address; + + private Customer parentCustomer; + private Set childCustomers = new HashSet<>(); + + public Customer() { + } + + public Customer(Integer oid, String name, Address address, Customer parentCustomer) { + this.oid = oid; + this.name = name; + this.address = address; + this.parentCustomer = parentCustomer; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "customer") + public Set getOrders() { + return orders; + } + + public void setOrders(Set orders) { + this.orders = orders; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Customer getParentCustomer() { + return parentCustomer; + } + + public void setParentCustomer(Customer parentCustomer) { + this.parentCustomer = parentCustomer; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCustomer") + public Set getChildCustomers() { + return childCustomers; + } + + public void setChildCustomers(Set childCustomers) { + this.childCustomers = childCustomers; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java new file mode 100644 index 000000000000..9eb37212be10 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DebitCardPayment") +@Table(name = "debit_payment") +public class DebitCardPayment extends Payment { + private String transactionId; + + public DebitCardPayment() { + } + + public DebitCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java new file mode 100644 index 000000000000..6b522ce761cc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java @@ -0,0 +1,629 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity ) ); + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends AMappedSuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends AEntity { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends AAEntity { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java new file mode 100644 index 000000000000..5b1e466e3b10 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java @@ -0,0 +1,1514 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceWithNonEntitiesProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 5 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 5, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 10 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 10, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 4 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 99 ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 99, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( true, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + public static class NonEntityAMappedSuperclassSuperclass { + private int fieldInNonEntityAMappedSuperclassSuperclass; + + public int getFieldInNonEntityAMappedSuperclassSuperclass() { + return fieldInNonEntityAMappedSuperclassSuperclass; + } + + public void setFieldInNonEntityAMappedSuperclassSuperclass(int fieldInNonEntityAMappedSuperclassSuperclass) { + this.fieldInNonEntityAMappedSuperclassSuperclass = fieldInNonEntityAMappedSuperclassSuperclass; + } + } + + @MappedSuperclass + public static class AMappedSuperclass extends NonEntityAMappedSuperclassSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + public static class NonEntityAEntitySuperclass extends AMappedSuperclass { + + private Long fieldInNonEntityAEntitySuperclass; + + public NonEntityAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAEntitySuperclass() { + } + + public Long getFieldInNonEntityAEntitySuperclass() { + return fieldInNonEntityAEntitySuperclass; + } + + public void setFieldInNonEntityAEntitySuperclass(Long fieldInNonEntityAEntitySuperclass) { + this.fieldInNonEntityAEntitySuperclass = fieldInNonEntityAEntitySuperclass; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends NonEntityAEntitySuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + public static class NonEntityAAEntitySuperclass extends AEntity { + + private String fieldInNonEntityAAEntitySuperclass; + + public NonEntityAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAEntitySuperclass() { + } + + public String getFieldInNonEntityAAEntitySuperclass() { + return fieldInNonEntityAAEntitySuperclass; + } + + public void setFieldInNonEntityAAEntitySuperclass(String fieldInNonEntityAAEntitySuperclass) { + this.fieldInNonEntityAAEntitySuperclass = fieldInNonEntityAAEntitySuperclass; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends NonEntityAAEntitySuperclass { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + public static class NonEntityAAAEntitySuperclass extends AAEntity { + + private Boolean fieldInNonEntityAAAEntitySuperclass; + + public NonEntityAAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAAEntitySuperclass() { + } + + public Boolean getFieldInNonEntityAAAEntitySuperclass() { + return fieldInNonEntityAAAEntitySuperclass; + } + + public void setFieldInNonEntityAAAEntitySuperclass(Boolean fieldInNonEntityAAAEntitySuperclass) { + this.fieldInNonEntityAAAEntitySuperclass = fieldInNonEntityAAAEntitySuperclass; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends NonEntityAAAEntitySuperclass { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java new file mode 100644 index 000000000000..8647b578e44a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DomesticCustomer") +@Table(name = "domestic_cust") +public class DomesticCustomer extends Customer { + private String taxId; + + public DomesticCustomer() { + } + + public DomesticCustomer( + Integer oid, + String name, + String taxId, + Address address, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.taxId = taxId; + } + + public String getTaxId() { + return taxId; + } + + public void setTaxId(String taxId) { + this.taxId = taxId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java new file mode 100644 index 000000000000..1794cb01fd94 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java @@ -0,0 +1,252 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class EntitySharedInCollectionAndToOneTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void testIt() { + inTransaction( + session -> { + int passes = 0; + for ( CodeTable codeTable : session.createQuery( "from CodeTable ct where ct.id = 2", CodeTable.class ).list() ) { + assert 0 == passes; + passes++; + Hibernate.initialize( codeTable.getCodeTableItems() ); + } + + assertThat( session.getPersistenceContext().getNumberOfManagedEntities(), is( 2 ) ); + } + ); + } + + @Before + public void createTestData() { + inTransaction( + session -> { + final CodeTable codeTable1 = new CodeTable( 1, 1 ); + final CodeTableItem item1 = new CodeTableItem( 1, 1, "first" ); + final CodeTableItem item2 = new CodeTableItem( 2, 1, "second" ); + final CodeTableItem item3 = new CodeTableItem( 3, 1, "third" ); + + session.save( codeTable1 ); + session.save( item1 ); + session.save( item2 ); + session.save( item3 ); + + codeTable1.getCodeTableItems().add( item1 ); + item1.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item2 ); + item2.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item3 ); + item3.setCodeTable( codeTable1 ); + + codeTable1.setDefaultItem( item1 ); + item1.setDefaultItemInverse( codeTable1 ); + + final CodeTable codeTable2 = new CodeTable( 2, 1 ); + final CodeTableItem item4 = new CodeTableItem( 4, 1, "fourth" ); + + session.save( codeTable2 ); + session.save( item4 ); + + codeTable2.getCodeTableItems().add( item4 ); + item4.setCodeTable( codeTable2 ); + + codeTable2.setDefaultItem( item4 ); + item4.setDefaultItemInverse( codeTable2 ); + } + ); + } + +// @After +// public void deleteTestData() { +// inTransaction( +// session -> { +// for ( CodeTable codeTable : session.createQuery( "from CodeTable", CodeTable.class ).list() ) { +// session.delete( codeTable ); +// } +// } +// ); +// } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( CodeTableItem.class ); + sources.addAnnotatedClass( CodeTable.class ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private Integer oid; + private int version; + + public BaseEntity() { + } + + public BaseEntity(Integer oid, int version) { + this.oid = oid; + this.version = version; + } + + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + } + + @Entity( name = "CodeTable" ) + @Table( name = "code_table" ) + public static class CodeTable extends BaseEntity { + @OneToOne( fetch = FetchType.LAZY ) + @LazyGroup( "defaultCodeTableItem" ) + @JoinColumn( name = "default_code_id" ) + private CodeTableItem defaultItem; + + @OneToMany( mappedBy = "codeTable" ) + private Set codeTableItems = new HashSet<>(); + + public CodeTable() { + } + + public CodeTable(Integer oid, int version) { + super( oid, version ); + } + + public CodeTableItem getDefaultItem() { + return defaultItem; + } + + public void setDefaultItem(CodeTableItem defaultItem) { + this.defaultItem = defaultItem; + } + + public Set getCodeTableItems() { + return codeTableItems; + } + + public void setCodeTableItems(Set codeTableItems) { + this.codeTableItems = codeTableItems; + } + } + + @Entity( name = "CodeTableItem" ) + @Table( name = "code_table_item" ) + public static class CodeTableItem extends BaseEntity { + private String name; + + @ManyToOne( fetch = FetchType.LAZY ) + @LazyGroup( "codeTable" ) + @JoinColumn( name = "code_table_oid" ) + private CodeTable codeTable; + + @OneToOne( mappedBy = "defaultItem", fetch=FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + @LazyGroup( "defaultItemInverse" ) + protected CodeTable defaultItemInverse; + + + public CodeTableItem() { + } + + public CodeTableItem(Integer oid, int version, String name) { + super( oid, version ); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public CodeTable getCodeTable() { + return codeTable; + } + + public void setCodeTable(CodeTable codeTable) { + this.codeTable = codeTable; + } + + public CodeTable getDefaultItemInverse() { + return defaultItemInverse; + } + + public void setDefaultItemInverse(CodeTable defaultItemInverse) { + this.defaultItemInverse = defaultItemInverse; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java new file mode 100644 index 000000000000..10ec469f9ef9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java @@ -0,0 +1,928 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Blob; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollableResults; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class FetchGraphTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Test + public void testLoadNonOwningOneToOne() { + // Test loading D and accessing E + // E is the owner of the FK, not D. When `D#e` is accessed we + // need to actually load E because its table has the FK value, not + // D's table + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final DEntity entityD = session.load( DEntity.class, 1L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isInitialized( entityD.getA() ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isInitialized( entityD.getC() ); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + assert Hibernate.isInitialized( entityD.getE() ); + } + ); + } + + @Test + public void testLoadOwningOneToOne() { + // switch it around + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final EEntity entityE = session.load( EEntity.class, 17L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityE, "d" ); + + final DEntity entityD = entityE.getD(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + } + ); + } + + @Test + public void testFetchingScroll() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + + inStatelessSession( + session -> { + final String qry = "select e from E e join fetch e.d"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select d from D d " + + "join fetch d.a " + + "join fetch d.bs " + + "join fetch d.c " + + "join fetch d.e " + + "join fetch d.g"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select g from G g join fetch g.dEntities"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + } + + + @Test + public void testLazyAssociationSameAsNonLazyInPC() { + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final AEntity entityA = session.get( AEntity.class, 1L ); + + final DEntity entityD = session.load( DEntity.class, 1L ); + assert !Hibernate.isInitialized( entityD ); + Hibernate.initialize( entityD ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert entityA.getOid() == entityD.getA().getOid(); + assert session.getPersistenceContext().getEntry( entityA ) == + session.getPersistenceContext().getEntry( entityD.getA() ); + assert entityA == entityD.getA(); + } + ); + } + + @Test + public void testRandomAccess() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + final EntityPersister persister = sessionFactory().getMetamodel().entityPersister( DEntity.class ); + assert persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + + inSession( + session -> { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Load a D + + final DEntity entityD = session.load( DEntity.class, 1L ); + + assertThat( entityD instanceof HibernateProxy, is(false) ); + assertThat( entityD instanceof PersistentAttributeInterceptable, is(true) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + // access the id. + // -since entityD is a "enhanced proxy", this should not trigger loading + assertThat( entityD.getOid(), is(1L) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + assert !Hibernate.isPropertyInitialized( entityD, "g" ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C + + final CEntity c = entityD.getC(); + + // make sure interception happened + assertThat( c, notNullValue() ); + + // See `#testLoadNonOwningOneToOne` + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + // The fields themselves are initialized - set to the + // enhanced entity "proxy" instance + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + assert !Hibernate.isInitialized( entityD.getA() ); + assert !Hibernate.isInitialized( entityD.getC() ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C again + + entityD.getC(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E + + final EEntity e1 = entityD.getE(); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + + assert Hibernate.isInitialized( entityD.getE() ); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isInitialized( e1 ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E again + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + assertThat( entityD.getE().getOid(), is(17L) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // now lets access the attribute "proxies" + + // this will load the table C data + entityD.getC().getC1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assert Hibernate.isInitialized( entityD.getC() ); + + // this should not - it was already loaded above + entityD.getE().getE1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + + Set bs = entityD.getBs(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assertThat( bs.size(), is( 2 ) ); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + + entityD.getG().getOid(); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + } + ); + } + + @Test + public void testNullManyToOneHql() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from Activity e"; + final List activities = session.createQuery( qry, Activity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + long expectedCount = 1L; + + for ( Activity activity : activities ) { + if ( activity.getInstruction() != null ) { + // do something special + // - here we just access an attribute to trigger + // the initialization of the association + + activity.getInstruction().getSummary(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + + if ( activity.getWebApplication() != null ) { + // trigger base group initialization + activity.getWebApplication().getName(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + // trigger initialization + activity.getWebApplication().getSiteUrl(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + } + } + ); + } + + @Test + public void testAbstractClassAssociation() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( RoleEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + for ( RoleEntity keyRoleEntity : keyRoleEntities ) { + Object key = Hibernate.unproxy( keyRoleEntity.getKey() ); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Set specializedEntities = ( (SpecializedKey) key ) + .getSpecializedEntities(); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Iterator iterator = specializedEntities.iterator(); + while ( iterator.hasNext() ) { + SpecializedEntity specializedEntity = iterator.next(); + assertThat( specializedEntity.getId(), notNullValue() ); + specializedEntity.getValue(); + } + + // but regardless there should not be an additional query + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + } + } + ); + } + + @Test + public void testNonAbstractAssociationWithSubclassValue() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + assertThat( keyRoleEntities.size(), is( 1 ) ); + + RoleEntity roleEntity = keyRoleEntities.get( 0 ); + assertThat( + Hibernate.unproxy( roleEntity.getKey() ).getClass().getName(), + is( SpecializedKey.class.getName() ) + ); + + assertThat( + Hibernate.unproxy( roleEntity.getSpecializedKey() ).getClass().getName(), + is( MoreSpecializedKey.class.getName() ) + ); + } + ); + } + + @Test + public void testQueryAndDeleteDEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select d from D d ", + DEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + + } ); + } + ); + } + + @Test + public void testLoadAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.load( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.get( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.get( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testLoadAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.load( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testQueryAndDeleteEEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select e from E e", + EEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getD() ); + } ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( DEntity.class ); + sources.addAnnotatedClass( EEntity.class ); + sources.addAnnotatedClass( GEntity.class ); + + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + + sources.addAnnotatedClass( SpecializedKey.class ); + sources.addAnnotatedClass( MoreSpecializedKey.class ); + sources.addAnnotatedClass( RoleEntity.class ); + sources.addAnnotatedClass( AbstractKey.class ); + sources.addAnnotatedClass( GenericKey.class ); + sources.addAnnotatedClass( SpecializedEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + DEntity d = new DEntity(); + d.setD( "bla" ); + d.setOid( 1 ); + + byte[] lBytes = "agdfagdfagfgafgsfdgasfdgfgasdfgadsfgasfdgasfdgasdasfdg".getBytes(); + Blob lBlob = Hibernate.getLobCreator( session ).createBlob( lBytes ); + d.setBlob( lBlob ); + + BEntity b1 = new BEntity(); + b1.setOid( 1 ); + b1.setB1( 34 ); + b1.setB2( "huhu" ); + + BEntity b2 = new BEntity(); + b2.setOid( 2 ); + b2.setB1( 37 ); + b2.setB2( "haha" ); + + Set lBs = new HashSet<>(); + lBs.add( b1 ); + lBs.add( b2 ); + d.setBs( lBs ); + + AEntity a = new AEntity(); + a.setOid( 1 ); + a.setA( "hihi" ); + d.setA( a ); + + EEntity e = new EEntity(); + e.setOid( 17 ); + e.setE1( "Balu" ); + e.setE2( "Bär" ); + + e.setD( d ); + d.setE( e ); + + CEntity c = new CEntity(); + c.setOid( 1 ); + c.setC1( "ast" ); + c.setC2( "qwert" ); + c.setC3( "yxcv" ); + d.setC( c ); + + GEntity g = new GEntity(); + g.setOid( 1 ); + g.getdEntities().add( d ); + d.setG( g ); + + + session.save( b1 ); + session.save( b2 ); + session.save( a ); + session.save( c ); + session.save( g ); + session.save( d ); + session.save( e ); + + + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + webApplication.setName( "name #" + i ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setOid( 1L ); + + SpecializedKey specializedKey = new SpecializedKey(); + specializedKey.setOid(1L); + + MoreSpecializedKey moreSpecializedKey = new MoreSpecializedKey(); + moreSpecializedKey.setOid( 3L ); + + SpecializedEntity specializedEntity = new SpecializedEntity(); + specializedEntity.setId( 2L ); + specializedKey.addSpecializedEntity( specializedEntity ); + specializedEntity.setSpecializedKey( specializedKey); + + specializedKey.addRole( roleEntity ); + roleEntity.setKey( specializedKey ); + roleEntity.setSpecializedKey( moreSpecializedKey ); + moreSpecializedKey.addRole( roleEntity ); + session.save( specializedEntity ); + session.save( roleEntity ); + session.save( specializedKey ); + session.save( moreSpecializedKey ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from E" ).executeUpdate(); + session.createQuery( "delete from D" ).executeUpdate(); + session.createQuery( "delete from C" ).executeUpdate(); + session.createQuery( "delete from B" ).executeUpdate(); + session.createQuery( "delete from A" ).executeUpdate(); + session.createQuery( "delete from G" ).executeUpdate(); + + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + + session.createQuery( "delete from SpecializedEntity" ).executeUpdate(); + session.createQuery( "delete from RoleEntity" ).executeUpdate(); + session.createQuery( "delete from MoreSpecializedKey" ).executeUpdate(); + session.createQuery( "delete from SpecializedKey" ).executeUpdate(); + session.createQuery( "delete from GenericKey" ).executeUpdate(); + session.createQuery( "delete from AbstractKey" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private long oid; + private short version; + + public long getOid() { + return oid; + } + + public void setOid(long oid) { + this.oid = oid; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + } + + @Entity(name = "A") + @Table(name = "A") + public static class AEntity extends BaseEntity { + @Column(name = "A") + private String a; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + } + + + @Entity(name = "B") + @Table(name = "B") + public static class BEntity extends BaseEntity { + private Integer b1; + private String b2; + + public Integer getB1() { + return b1; + } + + public void setB1(Integer b1) { + this.b1 = b1; + } + + public String getB2() { + return b2; + } + + public void setB2(String b2) { + this.b2 = b2; + } + } + + + @Entity(name = "C") + @Table(name = "C") + public static class CEntity extends BaseEntity { + private String c1; + private String c2; + private String c3; + private Long c4; + + public String getC1() { + return c1; + } + + public void setC1(String c1) { + this.c1 = c1; + } + + public String getC2() { + return c2; + } + + @Basic(fetch = FetchType.LAZY) + public void setC2(String c2) { + this.c2 = c2; + } + + public String getC3() { + return c3; + } + + public void setC3(String c3) { + this.c3 = c3; + } + + public Long getC4() { + return c4; + } + + public void setC4(Long c4) { + this.c4 = c4; + } + } + + @Entity(name = "D") + @Table(name = "D") + public static class DEntity extends BaseEntity { + private String d; + + // ****** Relations ***************** + @OneToOne(fetch = FetchType.LAZY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("a") + public AEntity a; + + @OneToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("c") + public CEntity c; + + @OneToMany(targetEntity = BEntity.class) + public Set bs; + + @OneToOne(mappedBy = "d", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("e") + private EEntity e; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn() + @LazyGroup("g") + public GEntity g; + + @Lob + @Basic(fetch = FetchType.LAZY) + @LazyGroup("blob") + @Column(name = "blob_field") + private Blob blob; + + public String getD() { + return d; + } + + public void setD(String d) { + this.d = d; + } + + + public AEntity getA() { + return a; + } + + public void setA(AEntity a) { + this.a = a; + } + + public Set getBs() { + return bs; + } + + public void setBs(Set bs) { + this.bs = bs; + } + + public CEntity getC() { + return c; + } + + public void setC(CEntity c) { + this.c = c; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public EEntity getE() { + return e; + } + + public void setE(EEntity e) { + this.e = e; + } + + public GEntity getG() { + return g; + } + + public void setG(GEntity g) { + this.g = g; + } + } + + @Entity(name = "E") + @Table(name = "E") + public static class EEntity extends BaseEntity { + private String e1; + private String e2; + + @OneToOne(fetch = FetchType.LAZY) + private DEntity d; + + public String getE1() { + return e1; + } + + public void setE1(String e1) { + this.e1 = e1; + } + + public String getE2() { + return e2; + } + + public void setE2(String e2) { + this.e2 = e2; + } + + public DEntity getD() { + return d; + } + + public void setD(DEntity d) { + this.d = d; + } + } + + @Entity(name = "G") + @Table(name = "G") + public static class GEntity extends BaseEntity { + + @OneToMany(mappedBy = "g") + public Set dEntities = new HashSet<>(); + + public Set getdEntities() { + return dEntities; + } + + public void setdEntities(Set dEntities) { + this.dEntities = dEntities; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java new file mode 100644 index 000000000000..a9b0c6acc3de --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "ForeignCustomer") +@Table(name = "foreign_cust") +public class ForeignCustomer extends Customer { + private String vat; + + public ForeignCustomer() { + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.vat = vat; + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat) { + this( oid, name, address, vat, null ); + } + + public String getVat() { + return vat; + } + + public void setVat(String vat) { + this.vat = vat; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java new file mode 100644 index 000000000000..6f52802abf1e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java @@ -0,0 +1,27 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity(name="GenericKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_GenericDCKey") +public abstract class GenericKey extends AbstractKey implements Serializable { + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java new file mode 100644 index 000000000000..56d594b368da --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Instruction") +@Table(name = "instruction") +public class Instruction extends BaseEntity { + private String summary; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Instruction() { + super(); + } + + @SuppressWarnings("WeakerAccess") + public Instruction(Integer id, String summary) { + super( id ); + this.summary = summary; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java new file mode 100644 index 000000000000..e3cbe4779e0a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java @@ -0,0 +1,287 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Query; +import javax.persistence.Table; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true) +public class LazyCollectionDeletedAllowProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + private Long postId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Post.class, Tag.class, AdditionalDetails.class, Label.class }; + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + } + + @Test + public void updatingAnAttributeDoesNotDeleteLazyCollectionsTest() { + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from AdditionalDetails where id = :id" ); + query.setParameter( "id", postId ); + AdditionalDetails additionalDetails = (AdditionalDetails) query.getSingleResult(); + additionalDetails.setDetails( "New data" ); + s.persist( additionalDetails ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post where id = :id" ); + query.setParameter( "id", postId ); + Post retrievedPost = (Post) query.getSingleResult(); + + assertFalse( "No tags found", retrievedPost.getTags().isEmpty() ); + retrievedPost.getTags().forEach( tag -> assertNotNull( tag ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from AdditionalDetails where id = :id" ); + query.setParameter( "id", postId ); + AdditionalDetails additionalDetails = (AdditionalDetails) query.getSingleResult(); + + Post post = additionalDetails.getPost(); + assertIsEnhancedProxy( post ); + post.setMessage( "new message" ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post where id = :id" ); + query.setParameter( "id", postId ); + Post retrievedPost = (Post) query.getSingleResult(); + + assertEquals( "new message", retrievedPost.getMessage() ); + assertFalse( "No tags found", retrievedPost.getTags().isEmpty() ); + retrievedPost.getTags().forEach( tag -> { + assertNotNull( tag ); + assertFalse( "No Labels found", tag.getLabels().isEmpty() ); + } ); + + } ); + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Post post = new Post(); + + Tag tag1 = new Tag( "tag1" ); + Tag tag2 = new Tag( "tag2" ); + + Label label1 = new Label( "label1" ); + Label label2 = new Label( "label2" ); + + tag1.addLabel( label1 ); + tag2.addLabel( label2 ); + + Set tagSet = new HashSet<>(); + tagSet.add( tag1 ); + tagSet.add( tag2 ); + post.setTags( tagSet ); + + AdditionalDetails details = new AdditionalDetails(); + details.setPost( post ); + post.setAdditionalDetails( details ); + details.setDetails( "Some data" ); + + postId = (Long) s.save( post ); + } ); + } + + @After + public void cleanData() { + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post" ); + List posts = query.getResultList(); + posts.forEach( post -> { + s.delete( post ); + } ); + } ); + } + + + private void assertIsEnhancedProxy(Object entity) { + assertThat( entity, instanceOf( PersistentAttributeInterceptable.class ) ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + + // --- // + + @Entity(name = "Tag") + @Table(name = "TAG") + private static class Tag { + + @Id + @GeneratedValue + Long id; + + String name; + + @ManyToMany(cascade = CascadeType.ALL) + Set