diff --git a/changelog.txt b/changelog.txt index 6fe28b217b..15d72ff206 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,76 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +hanges in 5.4.12.Final (February 13, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31827/tab/release-report-done + +** Bug + * [HHH-13858] - Fix Oracle failing tests + * [HHH-13859] - NPE on scanning for entities in a project having module-info.class resources + +** New Feature + * [HHH-13861] - Expose the doWork() and doReturningWork() APIs on StatelessSession as well + * [HHH-13863] - Introduce a module to distribute some helpers useful to compile Hibernate ORM to GraalVM native images + +** Improvement + * [HHH-13864] - Cosmetic change of format when reporting version number + + +Changes in 5.4.11.Final (February 07, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31818/tab/release-report-done + +** Bug + * [HHH-6615] - int type in Revision number + * [HHH-6686] - JPQL operator "is empty" failes for @ElementCollection + * [HHH-10844] - Resolve columnDefinition to appropriate sql-type for audit mappings + * [HHH-13373] - Hibernate report query hibernate_sequence table error in spring-boot application starting on a multi-database mariadb server + * [HHH-13456] - ForeignGenerator Throws ClassCastException When Using StatelessSession + * [HHH-13472] - Error creating hibernate_sequence in MariaDB 10.3 + * [HHH-13644] - NullPointerException when calling StoredProcedureQuery.getResultStream() instead of StoredProcedureQuery.getResultList() + * [HHH-13677] - org.hibernate.flushMode property not applied + * [HHH-13704] - Make sure javassist is really an optional dependency + * [HHH-13752] - Delete doesn't work when many-to-many uses non-primary key for join table + * [HHH-13759] - Bytecode enhancement fails for an embedded field in a MappedSuperclass + * [HHH-13760] - Envers tries to use relationship's entity as value for column instead of numeric identifier (cast class exception happens) for LAZY @ManyToOne + * [HHH-13770] - Envers - modified flag column value set to null from 5.4.7 onwards + * [HHH-13780] - Allow NamedQuery to set hint QueryHints.PASS_DISTINCT_THROUGH + * [HHH-13783] - org.hibernate.MappingException: The increment size of the sequence is set to [10] in the entity mapping while … size is [1] + * [HHH-13792] - L2 entity cache is evicted prior to committing transaction for HQL/native updates + * [HHH-13796] - Missing from clause in query from BinaryLogicOperatorNode row value constructor translation + * [HHH-13804] - HibernateProxy might need to be instantiated even with build-time enhancement + * [HHH-13806] - CoreMessageLogger#unableToLoadCommand is not printing the cause of the error + * [HHH-13808] - Incorrect String format in log + * [HHH-13831] - Replaced listener is not called when EventListenerGroup#fireEventOnEachListener is called + +** Task + * [HHH-13726] - Extract org.hibernate.internal.SessionFactoryImpl#prepareEventListeners from SessionFactoryImpl + * [HHH-13767] - Remove mention of Oracle and DB2 not being in MC + * [HHH-13821] - Update to Byte Buddy 1.10.7 + * [HHH-13822] - OSGi integration tests need to be able to download dependencies from Maven Central using HTTPS + * [HHH-13823] - Various visibility changes to help prototyping of Hibernate RX + * [HHH-13833] - Byte Buddy enhancer should use ASM7 opcodes to improve compatibility with code compiled for Java 11 + * [HHH-13837] - Initialize the Hibernate VERSION as a real constant + * [HHH-13838] - Allow extension of PersistenceXmlParser + * [HHH-13849] - Convert ProxyFactoryFactory and BytecodeProvider into a Service + +** Improvement + * [HHH-8776] - Ability for JPA entity-graphs to handle non-lazy attributes as lazy + * [HHH-11958] - Apply QueryHints.HINT_READONLY to load operations + * [HHH-12856] - Upgrade DB2400 dialect to use the DB2 for i improvements + * [HHH-13390] - Upgrade JPA MetaModel Generator (jpamodelgen) to support Gradle Incremental Compile + * [HHH-13800] - Correct some typos in the javadocs of hibernate-core module + * [HHH-13802] - fix javadoc warnings in 'hibernate-core' + * [HHH-13809] - Various improvements in the user guides + * [HHH-13830] - Fixing typo on the build task description + * [HHH-13832] - Optimise setting of default Flush Mode on a newly created Session + * [HHH-13850] - Clear the BytecodeProvider caches both after SessionFactory creation and stop + * [HHH-13851] - Rework initialization of ProxyFactoryFactory to move responsibility out of PojoEntityTuplizer + * [HHH-13854] - Allow extensions of StandardServiceRegistryBuilder to ignore Environment variables + Changes in 6.0.0.Alpha4 (December 20, 2019) ------------------------------------------------------------------------------------------------------------------------ @@ -22,8 +92,6 @@ https://hibernate.atlassian.net/projects/HHH/versions/31817 * [HHH-13769] - Avoid unnecessary joins - - Changes in 5.4.10.Final (December 05, 2019) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 7baeb8374c..65b9572d78 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -48,6 +48,9 @@ ext { jaxbApiVersionOsgiRange = "[2.2,3)" jaxbRuntimeVersion = '2.3.1' + //GraalVM + graalvmVersion = '19.3.1' + libraries = [ // Ant ant: 'org.apache.ant:ant:1.8.2', @@ -143,7 +146,7 @@ ext { // EL required by Hibernate Validator at test runtime expression_language: "org.glassfish:javax.el:${elVersion}", - c3p0: "com.mchange:c3p0:0.9.5.2", + c3p0: "com.mchange:c3p0:0.9.5.3", ehcache: "net.sf.ehcache:ehcache:2.10.6", ehcache3: "org.ehcache:ehcache:3.6.1", jcache: "javax.cache:cache-api:1.0.0", @@ -179,10 +182,11 @@ ext { 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', - asciidoclet : 'org.asciidoctor:asciidoclet:1.+' + asciidoclet : 'org.asciidoctor:asciidoclet:1.+', //asciidoclet : 'org.asciidoctor:asciidoclet:1.5.7-SNAPSHOT' - ] + graalvm_nativeimage : "org.graalvm.nativeimage:svm:${graalvmVersion}" + ] } configurations.all { diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index 3c76125e01..2201494b93 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -18,8 +18,6 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaUpdate; import org.hibernate.graph.RootGraph; -import org.hibernate.jdbc.ReturningWork; -import org.hibernate.jdbc.Work; import org.hibernate.stat.SessionStatistics; /** @@ -940,27 +938,6 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose */ void setReadOnly(Object entityOrProxy, boolean readOnly); - /** - * Controller for allowing users to perform JDBC related work using the Connection managed by this Session. - * - * @param work The work to be performed. - * @throws HibernateException Generally indicates wrapped {@link java.sql.SQLException} - */ - void doWork(Work work) throws HibernateException; - - /** - * Controller for allowing users to perform JDBC related work using the Connection managed by this Session. After - * execution returns the result of the {@link ReturningWork#execute} call. - * - * @param work The work to be performed. - * @param The type of the result returned from the work - * - * @return the result from calling {@link ReturningWork#execute}. - * - * @throws HibernateException Generally indicates wrapped {@link java.sql.SQLException} - */ - T doReturningWork(ReturningWork work) throws HibernateException; - @Override RootGraph createEntityGraph(Class rootType); diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index 0d96710d1a..cca1da3c67 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -13,6 +13,8 @@ import javax.persistence.criteria.CriteriaDelete; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaUpdate; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.Work; import org.hibernate.procedure.ProcedureCall; import org.hibernate.query.QueryProducer; @@ -163,4 +165,30 @@ public interface SharedSessionContract extends QueryProducer, Serializable { org.hibernate.query.Query createQuery(CriteriaDelete deleteQuery); org.hibernate.query.Query createNamedQuery(String name, Class resultType); + + /** + * Controller for allowing users to perform JDBC related work using the Connection managed by this Session. + * + * @param work The work to be performed. + * @throws HibernateException Generally indicates wrapped {@link java.sql.SQLException} + */ + default void doWork(Work work) throws HibernateException { + throw new UnsupportedOperationException( "The doWork method has not been implemented in this implementation of org.hibernate.engine.spi.SharedSessionContractImplemento" ); + } + + /** + * Controller for allowing users to perform JDBC related work using the Connection managed by this Session. After + * execution returns the result of the {@link ReturningWork#execute} call. + * + * @param work The work to be performed. + * @param The type of the result returned from the work + * + * @return the result from calling {@link ReturningWork#execute}. + * + * @throws HibernateException Generally indicates wrapped {@link java.sql.SQLException} + */ + default T doReturningWork(ReturningWork work) throws HibernateException { + throw new UnsupportedOperationException( "The doReturningWork method has not been implemented in this implementation of org.hibernate.engine.spi.SharedSessionContractImplemento" ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/Version.java b/hibernate-core/src/main/java/org/hibernate/Version.java index 0a0a2d1cca..769eb9d4a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/Version.java +++ b/hibernate-core/src/main/java/org/hibernate/Version.java @@ -52,6 +52,6 @@ public final class Version { */ @AllowSysOut public static void main(String[] args) { - System.out.println( "Hibernate Core {" + getVersionString() + "}" ); + System.out.println( "Hibernate ORM core version " + getVersionString() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/internal/NoopEntryHandler.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/internal/NoopEntryHandler.java new file mode 100644 index 0000000000..a157c92650 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/internal/NoopEntryHandler.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.boot.archive.scan.internal; + +import org.hibernate.boot.archive.spi.ArchiveContext; +import org.hibernate.boot.archive.spi.ArchiveEntry; +import org.hibernate.boot.archive.spi.ArchiveEntryHandler; + +public final class NoopEntryHandler implements ArchiveEntryHandler { + + public static final ArchiveEntryHandler NOOP_INSTANCE = new NoopEntryHandler(); + + private NoopEntryHandler() { + //Use the singleton. + } + + @Override + public void handleEntry(ArchiveEntry entry, ArchiveContext context) { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/AbstractScannerImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/AbstractScannerImpl.java index 8d7423c180..ecfd24956d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/AbstractScannerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/AbstractScannerImpl.java @@ -10,6 +10,7 @@ import java.net.URL; import java.util.HashMap; import java.util.Map; +import org.hibernate.boot.archive.scan.internal.NoopEntryHandler; import org.hibernate.boot.archive.scan.internal.ScanResultCollector; import org.hibernate.boot.archive.spi.ArchiveContext; import org.hibernate.boot.archive.spi.ArchiveDescriptor; @@ -132,6 +133,12 @@ public abstract class AbstractScannerImpl implements Scanner { if ( nameWithinArchive.endsWith( "package-info.class" ) ) { return packageEntryHandler; } + else if ( nameWithinArchive.endsWith( "module-info.class" ) ) { + //There's two reasons to skip this: the most important one is that Jandex + //is unable to analyze them, so we need to dodge it. + //Secondarily, we have no use for these so let's save the effort. + return NoopEntryHandler.NOOP_INSTANCE; + } else if ( nameWithinArchive.endsWith( ".class" ) ) { return classEntryHandler; } 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 7647c0a1cf..8679accff6 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 @@ -6,7 +6,6 @@ */ package org.hibernate.boot.archive.scan.spi; -import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import javax.persistence.Converter; 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 1896154786..6944a17a2d 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 @@ -20,10 +20,13 @@ import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.TempTableDdlTransactionHandling; +import org.hibernate.boot.registry.StandardServiceRegistry; 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.bytecode.internal.SessionFactoryObserverForBytecodeEnhancer; +import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.internal.SessionFactoryImpl; @@ -450,6 +453,9 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement @Override public SessionFactory build() { metadata.validate(); + final StandardServiceRegistry serviceRegistry = metadata.getMetadataBuildingOptions().getServiceRegistry(); + BytecodeProvider bytecodeProvider = serviceRegistry.getService( BytecodeProvider.class ); + addSessionFactoryObservers( new SessionFactoryObserverForBytecodeEnhancer( bytecodeProvider ) ); return new SessionFactoryImpl( metadata, buildSessionFactoryOptions() ); } 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 a2c9e22d8e..4c3b20a2b2 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 @@ -97,11 +97,12 @@ public class StandardServiceRegistryBuilder { } /** - * Intended for use exclusively from JPA boot-strapping. + * Intended for use exclusively from JPA boot-strapping, or extensions of + * this class. Consider this an SPI. * * @see #forJpa */ - private StandardServiceRegistryBuilder( + protected StandardServiceRegistryBuilder( BootstrapServiceRegistry bootstrapServiceRegistry, Map settings, LoadedConfig loadedConfig) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeProviderInitiator.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeProviderInitiator.java new file mode 100644 index 0000000000..d694a325af --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeProviderInitiator.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.bytecode.internal; + +import java.util.Map; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.bytecode.spi.BytecodeProvider; +import org.hibernate.cfg.Environment; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +public final class BytecodeProviderInitiator implements StandardServiceInitiator { + + /** + * Singleton access + */ + public static final StandardServiceInitiator INSTANCE = new BytecodeProviderInitiator(); + + @Override + public BytecodeProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + // TODO in 6 this will no longer use Environment, which is configured via global environment variables, + // but move to a component which can be reconfigured differently in each registry. + return Environment.getBytecodeProvider(); + } + + @Override + public Class getServiceInitiated() { + return BytecodeProvider.class; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/ProxyFactoryFactoryInitiator.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/ProxyFactoryFactoryInitiator.java new file mode 100644 index 0000000000..f8e1d896a2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/ProxyFactoryFactoryInitiator.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.bytecode.internal; + +import java.util.Map; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.bytecode.spi.BytecodeProvider; +import org.hibernate.bytecode.spi.ProxyFactoryFactory; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * Most commonly the {@link ProxyFactoryFactory} will depend directly on the chosen {@link BytecodeProvider}, + * however by registering them as two separate services we can allow to override either one + * or both of them. + * @author Sanne Grinovero + */ +public final class ProxyFactoryFactoryInitiator implements StandardServiceInitiator { + + /** + * Singleton access + */ + public static final StandardServiceInitiator INSTANCE = new ProxyFactoryFactoryInitiator(); + + @Override + public ProxyFactoryFactory initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + final BytecodeProvider bytecodeProvider = registry.getService( BytecodeProvider.class ); + return bytecodeProvider.getProxyFactoryFactory(); + } + + @Override + public Class getServiceInitiated() { + return ProxyFactoryFactory.class; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/SessionFactoryObserverForBytecodeEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/SessionFactoryObserverForBytecodeEnhancer.java new file mode 100644 index 0000000000..7a84b3b038 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/SessionFactoryObserverForBytecodeEnhancer.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.bytecode.internal; + +import org.hibernate.SessionFactory; +import org.hibernate.SessionFactoryObserver; +import org.hibernate.bytecode.spi.BytecodeProvider; + +public final class SessionFactoryObserverForBytecodeEnhancer implements SessionFactoryObserver { + + private final BytecodeProvider bytecodeProvider; + + public SessionFactoryObserverForBytecodeEnhancer(BytecodeProvider bytecodeProvider) { + this.bytecodeProvider = bytecodeProvider; + } + + @Override + public void sessionFactoryCreated(final SessionFactory factory) { + this.bytecodeProvider.resetCaches(); + } + + @Override + public void sessionFactoryClosing(final SessionFactory factory) { + this.bytecodeProvider.resetCaches(); + } + + @Override + public void sessionFactoryClosed(final SessionFactory factory) { + this.bytecodeProvider.resetCaches(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index f17e48b0b5..e0f7122917 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -159,7 +159,7 @@ public final class ByteBuddyState { /** * 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 + * to re-create some helpers if used at runtime, especially as this state might be 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 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 f0c617247b..d56ec0f2dc 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 @@ -8,6 +8,7 @@ package org.hibernate.bytecode.spi; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.service.Service; /** * Contract for providers of bytecode services to Hibernate. @@ -19,7 +20,7 @@ import org.hibernate.bytecode.enhance.spi.Enhancer; * * @author Steve Ebersole */ -public interface BytecodeProvider { +public interface BytecodeProvider extends Service { /** * Retrieve the specific factory for this provider capable of * generating run-time proxies for lazy-loading purposes. 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 7e624c0145..52429d3f05 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 @@ -6,11 +6,9 @@ */ package org.hibernate.bytecode.spi; -import org.hibernate.SessionFactory; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.proxy.ProxyFactory; -import org.hibernate.service.ServiceRegistry; +import org.hibernate.service.Service; /** * An interface for factories of {@link ProxyFactory proxy factory} instances. @@ -20,7 +18,7 @@ import org.hibernate.service.ServiceRegistry; * * @author Steve Ebersole */ -public interface ProxyFactoryFactory { +public interface ProxyFactoryFactory extends Service { /** * Build a proxy factory specifically for handling runtime * lazy loading. 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 index 874a254182..ed39893b3f 100644 --- 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 @@ -198,13 +198,18 @@ public abstract class AbstractReadWriteAccess extends AbstractCachedDomainDataAc 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 + // don't do anything... we want the SoftLock to remain in place } else { super.remove( session, key ); } } + @Override + public void removeAll(SharedSessionContractImplementor session) { + // A no-op + } + /** * Interface type implemented by all wrapper objects in the cache. */ 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 index 0b6764d665..093426a416 100644 --- 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 @@ -151,9 +151,4 @@ public class EntityReadWriteAccess extends AbstractReadWriteAccess implements En public SoftLock lockRegion() { return null; } - - @Override - public void unlockRegion(SoftLock lock) { - - } } 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 30d97d5bcb..824f556d1f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java @@ -351,8 +351,15 @@ public final class Environment implements AvailableSettings { LOG.bytecodeProvider( providerName ); - // todo : allow a custom class name - just check if the config is a FQN - // currently we assume it is only ever the Strings "javassist" or "bytebuddy"... + // there is no need to support plugging in a custom BytecodeProvider via FQCN: + // - the static helper methods on this class are deprecated + // - it's possible to plug a custom BytecodeProvider directly into the ServiceRegistry + // + // This also allows integrators to inject a BytecodeProvider instance which has some + // state; particularly useful to inject proxy definitions which have been prepared in + // advance. + // See also https://hibernate.atlassian.net/browse/HHH-13804 and how this was solved in + // Quarkus. LOG.unknownBytecodeProvider( providerName, BYTECODE_PROVIDER_NAME_DEFAULT ); return new org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl(); 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 2c6175cacd..3579a94c36 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -51,6 +51,9 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.transaction.internal.TransactionImpl; import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.id.uuid.StandardRandomStrategy; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.Work; +import org.hibernate.jdbc.WorkExecutorVisitable; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.persister.entity.EntityPersister; @@ -728,6 +731,28 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont return buildNamedQuery( name, resultClass ); } + @Override + public void doWork(final Work work) throws HibernateException { + WorkExecutorVisitable realWork = (workExecutor, connection) -> { + workExecutor.executeWork( work, connection ); + return null; + }; + doWork( realWork ); + } + + @Override + public T doReturningWork(final ReturningWork work) throws HibernateException { + WorkExecutorVisitable realWork = (workExecutor, connection) -> workExecutor.executeReturningWork( + work, + connection + ); + return doWork( realWork ); + } + + private T doWork(WorkExecutorVisitable work) throws HibernateException { + return getJdbcCoordinator().coordinateWork( work ); + } + protected QueryImplementor buildNamedQuery(String queryName, Class resultType) { checkOpen(); pulseTransactionCoordinator(); @@ -794,7 +819,6 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont throw getExceptionConverter().convert( new IllegalArgumentException( "No query defined for that name [" + queryName + "]" ) ); } - @Override @SuppressWarnings("UnnecessaryLocalVariable") public ProcedureCall getNamedProcedureCall(String name) { 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 471a8754f5..ab25769570 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1439,7 +1439,7 @@ public interface CoreMessageLogger extends BasicLogger { void validatorNotFound(); @LogMessage(level = INFO) - @Message(value = "Hibernate Core {%s}", id = 412) + @Message(value = "Hibernate ORM core version %s", id = 412) void version(String versionString); @LogMessage(level = WARN) 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 feebf2ca86..95debda804 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -770,8 +770,6 @@ public class SessionFactoryImpl implements SessionFactoryImplementor { * @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(); synchronized (this) { if ( isClosed ) { if ( getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled() ) { 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 9020236e46..2f504e041a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -115,10 +115,6 @@ import org.hibernate.graph.RootGraph; import org.hibernate.graph.internal.RootGraphImpl; import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.jdbc.ReturningWork; -import org.hibernate.jdbc.Work; -import org.hibernate.jdbc.WorkExecutor; -import org.hibernate.jdbc.WorkExecutorVisitable; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.internal.util.CacheModeHelper; @@ -1745,33 +1741,6 @@ public class SessionImpl persistenceContext.setReadOnly( entity, readOnly ); } - @Override - public void doWork(final Work work) throws HibernateException { - WorkExecutorVisitable realWork = new WorkExecutorVisitable() { - @Override - public Void accept(WorkExecutor workExecutor, Connection connection) throws SQLException { - workExecutor.executeWork( work, connection ); - return null; - } - }; - doWork( realWork ); - } - - @Override - public T doReturningWork(final ReturningWork work) throws HibernateException { - WorkExecutorVisitable realWork = new WorkExecutorVisitable() { - @Override - public T accept(WorkExecutor workExecutor, Connection connection) throws SQLException { - return workExecutor.executeReturningWork( work, connection ); - } - }; - return doWork( realWork ); - } - - private T doWork(WorkExecutorVisitable work) throws HibernateException { - return getJdbcCoordinator().coordinateWork( work ); - } - @Override public void afterScrollOperation() { // nothing to do in a stateful session 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 b8441d153d..1952d911c3 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 @@ -3921,7 +3921,7 @@ public abstract class AbstractEntityPersister final String[] deleteStrings; if ( isImpliedOptimisticLocking && loadedState != null ) { // we need to utilize dynamic delete statements - deleteStrings = generateSQLDeletStrings( loadedState ); + deleteStrings = generateSQLDeleteStrings( loadedState ); } else { // otherwise, utilize the static delete statements @@ -3939,7 +3939,7 @@ public abstract class AbstractEntityPersister || entityMetamodel.getOptimisticLockStyle() == OptimisticLockStyle.ALL; } - private String[] generateSQLDeletStrings(Object[] loadedState) { + private String[] generateSQLDeleteStrings(Object[] loadedState) { int span = getTableSpan(); String[] deleteStrings = new String[span]; for ( int j = span - 1; j >= 0; j-- ) { 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 0000000000..af9a3d7e3b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/ProxyFactoryHelper.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.proxy.pojo; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Iterator; +import java.util.Set; + +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(); + 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() ); + } + } + } + + 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/service/StandardServiceInitiators.java b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java index df7a891f3a..e073696a47 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java +++ b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java @@ -12,6 +12,8 @@ import java.util.List; import org.hibernate.boot.cfgxml.internal.CfgXmlAccessServiceInitiator; import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.bytecode.internal.BytecodeProviderInitiator; +import org.hibernate.bytecode.internal.ProxyFactoryFactoryInitiator; import org.hibernate.cache.internal.RegionFactoryInitiator; import org.hibernate.engine.config.internal.ConfigurationServiceInitiator; import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; @@ -51,6 +53,9 @@ public final class StandardServiceInitiators { private static List buildStandardServiceInitiatorList() { final ArrayList serviceInitiators = new ArrayList(); + serviceInitiators.add( BytecodeProviderInitiator.INSTANCE ); + serviceInitiators.add( ProxyFactoryFactoryInitiator.INSTANCE ); + serviceInitiators.add( CfgXmlAccessServiceInitiator.INSTANCE ); serviceInitiators.add( ConfigurationServiceInitiator.INSTANCE ); serviceInitiators.add( PropertyAccessStrategyResolverInitiator.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/PojoComponentTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/PojoComponentTuplizer.java index 6c21250af3..d8b771aa4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/PojoComponentTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/PojoComponentTuplizer.java @@ -5,12 +5,15 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.tuple.component; + import java.io.Serializable; import java.lang.reflect.Method; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.bytecode.spi.BasicProxyFactory; +import org.hibernate.bytecode.spi.BytecodeProvider; +import org.hibernate.bytecode.spi.ProxyFactoryFactory; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -67,9 +70,8 @@ public class PojoComponentTuplizer extends AbstractComponentTuplizer { optimizer = null; } else { - // TODO: here is why we need to make bytecode provider global :( - // TODO : again, fix this after HHH-1907 is complete - optimizer = Environment.getBytecodeProvider().getReflectionOptimizer( + final BytecodeProvider bytecodeProvider = component.getServiceRegistry().getService( BytecodeProvider.class ); + optimizer = bytecodeProvider.getReflectionOptimizer( componentClass, getterNames, setterNames, propTypes ); } @@ -124,7 +126,8 @@ public class PojoComponentTuplizer extends AbstractComponentTuplizer { protected Instantiator buildInstantiator(Component component) { if ( component.isEmbedded() && ReflectHelper.isAbstractClass( this.componentClass ) ) { - return new ProxiedInstantiator( this.componentClass ); + ProxyFactoryFactory proxyFactoryFactory = component.getServiceRegistry().getService( ProxyFactoryFactory.class ); + return new ProxiedInstantiator( this.componentClass, proxyFactoryFactory ); } if ( optimizer == null ) { return new PojoInstantiator( this.componentClass, null ); @@ -151,16 +154,14 @@ public class PojoComponentTuplizer extends AbstractComponentTuplizer { private final Class proxiedClass; private final BasicProxyFactory factory; - public ProxiedInstantiator(Class componentClass) { + public ProxiedInstantiator(Class componentClass, ProxyFactoryFactory proxyFactoryFactory) { proxiedClass = componentClass; if ( proxiedClass.isInterface() ) { - factory = Environment.getBytecodeProvider() - .getProxyFactoryFactory() + factory = proxyFactoryFactory .buildBasicProxyFactory( null, new Class[] { proxiedClass } ); } else { - factory = Environment.getBytecodeProvider() - .getProxyFactoryFactory() + factory = proxyFactoryFactory .buildBasicProxyFactory( proxiedClass, null ); } } 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 6d7b5d0cb7..8125c2174c 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,17 +7,16 @@ 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.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.spi.BytecodeProvider; +import org.hibernate.bytecode.spi.ProxyFactoryFactory; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; import org.hibernate.classic.Lifecycle; @@ -27,14 +26,12 @@ 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.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; @@ -52,15 +49,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]; @@ -75,16 +68,13 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer { optimizer = null; } else { - // todo : YUCK!!! - optimizer = Environment.getBytecodeProvider().getReflectionOptimizer( + final BytecodeProvider bytecodeProvider = entityMetamodel.getSessionFactory().getServiceRegistry().getService( BytecodeProvider.class ); + optimizer = bytecodeProvider.getReflectionOptimizer( mappedClass, getterNames, setterNames, propTypes ); -// optimizer = getFactory().getSettings().getBytecodeProvider().getReflectionOptimizer( -// mappedClass, getterNames, setterNames, propTypes -// ); } } @@ -92,76 +82,21 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer { 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 + final String entityName = getEntityName(); + final Class mappedClass = persistentClass.getMappedClass(); + final Class proxyInterface = persistentClass.getProxyInterface(); - /* - * 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(); + final Set proxyInterfaces = ProxyFactoryHelper.extractProxyInterfaces( persistentClass, entityName ); - Class mappedClass = persistentClass.getMappedClass(); - Class proxyInterface = persistentClass.getProxyInterface(); + ProxyFactoryHelper.validateProxyability( persistentClass ); - 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 ); - } - - 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 ); - - 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() ); - } - } - - Method idGetterMethod = idGetter == null ? null : idGetter.getMethod(); - Method idSetterMethod = idSetter == null ? null : idSetter.getMethod(); - - Method proxyGetIdentifierMethod = idGetterMethod == null || proxyInterface == null ? - null : - ReflectHelper.getMethod( proxyInterface, idGetterMethod ); - Method proxySetIdentifierMethod = idSetterMethod == null || proxyInterface == null ? - null : - ReflectHelper.getMethod( proxyInterface, idSetterMethod ); + Method proxyGetIdentifierMethod = ProxyFactoryHelper.extractProxyGetIdentifierMethod( idGetter, proxyInterface ); + Method proxySetIdentifierMethod = ProxyFactoryHelper.extractProxySetIdentifierMethod( idSetter, proxyInterface ); ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); try { pf.postInstantiate( - getEntityName(), + entityName, mappedClass, proxyInterfaces, proxyGetIdentifierMethod, @@ -172,7 +107,7 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer { ); } catch (HibernateException he) { - LOG.unableToCreateProxyFactory( getEntityName(), he ); + LOG.unableToCreateProxyFactory( entityName, he ); pf = null; } return pf; @@ -182,9 +117,8 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer { PersistentClass persistentClass, Getter idGetter, Setter idSetter) { - // TODO : YUCK!!! fix after HHH-1907 is complete - return Environment.getBytecodeProvider().getProxyFactoryFactory().buildProxyFactory( getFactory() ); -// return getFactory().getSettings().getBytecodeProvider().getProxyFactoryFactory().buildProxyFactory(); + ProxyFactoryFactory proxyFactory = getFactory().getServiceRegistry().getService( ProxyFactoryFactory.class ); + return proxyFactory.buildProxyFactory( getFactory() ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java b/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java new file mode 100644 index 0000000000..b81aefd393 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java @@ -0,0 +1,310 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: 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.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import javax.persistence.Cacheable; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.EmptyInterceptor; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cfg.Configuration; + +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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + +/** + * @author Frank Doherty + */ +public class ReadWriteCacheTest extends BaseCoreFunctionalTestCase { + + private static final String ORIGINAL_TITLE = "Original Title"; + private static final String UPDATED_TITLE = "Updated Title"; + + private long bookId; + private CountDownLatch endLatch; + private AtomicBoolean interceptTransaction; + + @Override + public void buildSessionFactory() { + buildSessionFactory( getCacheConfig() ); + } + + @Before + public void init() { + endLatch = new CountDownLatch( 1 ); + interceptTransaction = new AtomicBoolean(); + } + + @Override + public void rebuildSessionFactory() { + rebuildSessionFactory( getCacheConfig() ); + } + + @Test + public void testDelete() throws InterruptedException { + bookId = 1L; + + doInHibernate( this::sessionFactory, session -> { + createBook( bookId, session ); + } ); + + doInHibernate( this::sessionFactory, session -> { + log.info( "Delete Book" ); + Book book = session.get( Book.class, bookId ); + session.delete( book ); + interceptTransaction.set( true ); + } ); + + endLatch.await(); + interceptTransaction.set( false ); + + doInHibernate( this::sessionFactory, session -> { + assertBookNotFound( bookId, session ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13792") + public void testDeleteHQL() throws InterruptedException { + bookId = 2L; + + doInHibernate( this::sessionFactory, session -> { + createBook( bookId, session ); + } ); + + doInHibernate( this::sessionFactory, session -> { + log.info( "Delete Book using HQL" ); + int numRows = session.createQuery( "delete from Book where id = :id" ) + .setParameter( "id", bookId ) + .executeUpdate(); + assertEquals( 1, numRows ); + interceptTransaction.set( true ); + } ); + + endLatch.await(); + interceptTransaction.set( false ); + + doInHibernate( this::sessionFactory, session -> { + assertBookNotFound( bookId, session ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13792") + public void testDeleteNativeQuery() throws InterruptedException { + bookId = 3L; + + doInHibernate( this::sessionFactory, session -> { + createBook( bookId, session ); + } ); + + doInHibernate( this::sessionFactory, session -> { + log.info( "Delete Book using NativeQuery" ); + int numRows = session.createNativeQuery( "delete from Book where id = :id" ) + .setParameter( "id", bookId ) + .addSynchronizedEntityClass( Book.class ) + .executeUpdate(); + assertEquals( 1, numRows ); + interceptTransaction.set( true ); + } ); + + endLatch.await(); + interceptTransaction.set( false ); + + doInHibernate( this::sessionFactory, session -> { + assertBookNotFound( bookId, session ); + } ); + } + + @Test + public void testUpdate() throws InterruptedException { + bookId = 4L; + + doInHibernate( this::sessionFactory, session -> { + createBook( bookId, session ); + } ); + + doInHibernate( this::sessionFactory, session -> { + log.info( "Update Book" ); + Book book = session.get( Book.class, bookId ); + book.setTitle( UPDATED_TITLE ); + session.save( book ); + interceptTransaction.set( true ); + } ); + + endLatch.await(); + interceptTransaction.set( false ); + + doInHibernate( this::sessionFactory, session -> { + loadBook( bookId, session ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13792") + public void testUpdateHQL() throws InterruptedException { + bookId = 5L; + + doInHibernate( this::sessionFactory, session -> { + createBook( bookId, session ); + } ); + + doInHibernate( this::sessionFactory, session -> { + log.info( "Update Book using HQL" ); + int numRows = session.createQuery( "update Book set title = :title where id = :id" ) + .setParameter( "title", UPDATED_TITLE ) + .setParameter( "id", bookId ) + .executeUpdate(); + assertEquals( 1, numRows ); + interceptTransaction.set( true ); + } ); + + endLatch.await(); + interceptTransaction.set( false ); + + doInHibernate( this::sessionFactory, session -> { + loadBook( bookId, session ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13792") + public void testUpdateNativeQuery() throws InterruptedException { + bookId = 6L; + + doInHibernate( this::sessionFactory, session -> { + createBook( bookId, session ); + } ); + + doInHibernate( this::sessionFactory, session -> { + log.info( "Update Book using NativeQuery" ); + int numRows = session.createNativeQuery( "update Book set title = :title where id = :id" ) + .setParameter( "title", UPDATED_TITLE ) + .setParameter( "id", bookId ) + .addSynchronizedEntityClass( Book.class ) + .executeUpdate(); + assertEquals( 1, numRows ); + interceptTransaction.set( true ); + } ); + + endLatch.await(); + interceptTransaction.set( false ); + + doInHibernate( this::sessionFactory, session -> { + loadBook( bookId, session ); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class, + }; + } + + @Override + protected String getCacheConcurrencyStrategy() { + return "read-write"; + } + + private void assertBookNotFound(long bookId, Session session) { + log.info( "Load Book" ); + Book book = session.get( Book.class, bookId ); + assertNull( book ); + } + + private void createBook(long bookId, Session session) { + log.info( "Create Book" ); + Book book = new Book(); + book.setId( bookId ); + book.setTitle( ORIGINAL_TITLE ); + session.save( book ); + } + + private Consumer getCacheConfig() { + return configuration -> configuration.setInterceptor( new TransactionInterceptor() ); + } + + private void loadBook(long bookId, Session session) { + log.info( "Load Book" ); + Book book = session.get( Book.class, bookId ); + assertNotNull( book ); + assertEquals( "Found old value", UPDATED_TITLE, book.getTitle() ); + } + + @Entity(name = "Book") + @Cacheable + @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + private static final class Book { + + @Id + 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; + } + + public String toString() { + return "Book[id=" + id + ",title=" + title + "]"; + } + } + + private final class TransactionInterceptor extends EmptyInterceptor { + @Override + public void beforeTransactionCompletion(Transaction tx) { + if ( interceptTransaction.get() ) { + try { + log.info( "Fetch Book" ); + + executeSync( () -> { + Session session = sessionFactory() + .openSession(); + Book book = session.get( Book.class, bookId ); + assertNotNull( book ); + log.infof( "Fetched %s", book ); + session.close(); + } ); + + assertTrue( sessionFactory().getCache() + .containsEntity( Book.class, bookId ) ); + } + finally { + endLatch.countDown(); + } + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/CompanyWithFetchProfile.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/CompanyFetchProfile.java similarity index 90% rename from hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/CompanyWithFetchProfile.java rename to hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/CompanyFetchProfile.java index b5394e7dce..8b13e2e53a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/CompanyWithFetchProfile.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/CompanyFetchProfile.java @@ -22,13 +22,13 @@ import java.util.Set; name = "company.location", fetchOverrides = { @FetchProfile.FetchOverride( - entity = CompanyWithFetchProfile.class, + entity = CompanyFetchProfile.class, association = "location", mode = FetchMode.JOIN ) } ) -public class CompanyWithFetchProfile { +public class CompanyFetchProfile { @Id @GeneratedValue public long id; @@ -42,6 +42,7 @@ public class CompanyWithFetchProfile { public Set markets = new HashSet(); @ElementCollection(fetch = FetchType.EAGER) + @JoinTable(name= "companyfp_phonenos") public Set phoneNumbers = new HashSet(); public Location getLocation() { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mapped_by_id/FetchGraphFindByIdTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mappedbyid/FetchGraphFindByIdTest.java similarity index 85% rename from hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mapped_by_id/FetchGraphFindByIdTest.java rename to hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mappedbyid/FetchGraphFindByIdTest.java index 88818d7663..31f7394901 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mapped_by_id/FetchGraphFindByIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mappedbyid/FetchGraphFindByIdTest.java @@ -1,4 +1,4 @@ -package org.hibernate.jpa.test.graphs.mapped_by_id; +package org.hibernate.jpa.test.graphs.mappedbyid; import org.hibernate.Hibernate; import org.hibernate.Session; @@ -99,12 +99,12 @@ public class FetchGraphFindByIdTest extends BaseEntityManagerFunctionalTestCase entityManager.unwrap( Session.class ).enableFetchProfile("company.location"); - EntityGraph entityGraph = entityManager.createEntityGraph( CompanyWithFetchProfile.class ); + EntityGraph entityGraph = entityManager.createEntityGraph( CompanyFetchProfile.class ); entityGraph.addAttributeNodes( "markets" ); Map properties = Collections.singletonMap( "javax.persistence.fetchgraph", entityGraph ); - CompanyWithFetchProfile company = entityManager.find( CompanyWithFetchProfile.class, companyWithFetchProfileId, properties ); + CompanyFetchProfile company = entityManager.find( CompanyFetchProfile.class, companyWithFetchProfileId, properties ); entityManager.getTransaction().commit(); entityManager.close(); @@ -126,7 +126,7 @@ public class FetchGraphFindByIdTest extends BaseEntityManagerFunctionalTestCase subSubgraph.addAttributeNodes( "managers" ); subSubgraph.addAttributeNodes( "friends" ); - company = entityManager.find( CompanyWithFetchProfile.class, companyWithFetchProfileId, properties ); + company = entityManager.find( CompanyFetchProfile.class, companyWithFetchProfileId, properties ); entityManager.getTransaction().commit(); entityManager.close(); @@ -189,17 +189,17 @@ public class FetchGraphFindByIdTest extends BaseEntityManagerFunctionalTestCase entityManager.persist( company ); companyId = company.id; - CompanyWithFetchProfile companyWithFetchProfile = new CompanyWithFetchProfile(); - companyWithFetchProfile.employees.add( employee ); - companyWithFetchProfile.employees.add( manager1 ); - companyWithFetchProfile.employees.add( manager2 ); - companyWithFetchProfile.location = location; - companyWithFetchProfile.markets.add( Market.SERVICES ); - companyWithFetchProfile.markets.add( Market.TECHNOLOGY ); - companyWithFetchProfile.phoneNumbers.add( "012-345-6789" ); - companyWithFetchProfile.phoneNumbers.add( "987-654-3210" ); - entityManager.persist( companyWithFetchProfile ); - companyWithFetchProfileId = companyWithFetchProfile.id; + CompanyFetchProfile companyFetchProfile = new CompanyFetchProfile(); + companyFetchProfile.employees.add( employee ); + companyFetchProfile.employees.add( manager1 ); + companyFetchProfile.employees.add( manager2 ); + companyFetchProfile.location = location; + companyFetchProfile.markets.add( Market.SERVICES ); + companyFetchProfile.markets.add( Market.TECHNOLOGY ); + companyFetchProfile.phoneNumbers.add( "012-345-6789" ); + companyFetchProfile.phoneNumbers.add( "987-654-3210" ); + entityManager.persist( companyFetchProfile ); + companyWithFetchProfileId = companyFetchProfile.id; entityManager.getTransaction().commit(); entityManager.close(); @@ -207,7 +207,7 @@ public class FetchGraphFindByIdTest extends BaseEntityManagerFunctionalTestCase @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Company.class, CompanyWithFetchProfile.class, Employee.class, Manager.class, Location.class, Course.class, Student.class }; + return new Class[] { Company.class, CompanyFetchProfile.class, Employee.class, Manager.class, Location.class, Course.class, Student.class }; } } 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/mappedbyid/LoadGraphFindByIdTest.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mapped_by_id/LoadGraphFindByIdTest.java rename to hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/mappedbyid/LoadGraphFindByIdTest.java index 2f160233d7..cec8e85db3 100644 --- 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/mappedbyid/LoadGraphFindByIdTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.jpa.test.graphs.mapped_by_id; +package org.hibernate.jpa.test.graphs.mappedbyid; import java.util.HashMap; import java.util.Map; 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 e5e1b45a1d..bfb7f3c66f 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 @@ -175,11 +175,11 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas entityManager.unwrap( Session.class ).enableFetchProfile( "company.location" ); - EntityGraph entityGraph = entityManager.createEntityGraph( CompanyWithFetchProfile.class ); + EntityGraph entityGraph = entityManager.createEntityGraph( CompanyFetchProfile.class ); entityGraph.addAttributeNodes( "markets" ); - Query query = entityManager.createQuery( "from " + CompanyWithFetchProfile.class.getName() ); + Query query = entityManager.createQuery( "from " + CompanyFetchProfile.class.getName() ); query.setHint( QueryHints.HINT_FETCHGRAPH, entityGraph ); - CompanyWithFetchProfile company = (CompanyWithFetchProfile) query.getSingleResult(); + CompanyFetchProfile company = (CompanyFetchProfile) query.getSingleResult(); entityManager.getTransaction().commit(); entityManager.close(); @@ -201,9 +201,9 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas subSubgraph.addAttributeNodes( "managers" ); subSubgraph.addAttributeNodes( "friends" ); - query = entityManager.createQuery( "from " + CompanyWithFetchProfile.class.getName() ); + query = entityManager.createQuery( "from " + CompanyFetchProfile.class.getName() ); query.setHint( QueryHints.HINT_FETCHGRAPH, entityGraph ); - company = (CompanyWithFetchProfile) query.getSingleResult(); + company = (CompanyFetchProfile) query.getSingleResult(); entityManager.getTransaction().commit(); entityManager.close(); @@ -508,16 +508,16 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas company.phoneNumbers.add( "987-654-3210" ); entityManager.persist( company ); - CompanyWithFetchProfile companyWithFetchProfile = new CompanyWithFetchProfile(); - companyWithFetchProfile.employees.add( employee ); - companyWithFetchProfile.employees.add( manager1 ); - companyWithFetchProfile.employees.add( manager2 ); - companyWithFetchProfile.location = location; - companyWithFetchProfile.markets.add( Market.SERVICES ); - companyWithFetchProfile.markets.add( Market.TECHNOLOGY ); - companyWithFetchProfile.phoneNumbers.add( "012-345-6789" ); - companyWithFetchProfile.phoneNumbers.add( "987-654-3210" ); - entityManager.persist( companyWithFetchProfile ); + CompanyFetchProfile companyFetchProfile = new CompanyFetchProfile(); + companyFetchProfile.employees.add( employee ); + companyFetchProfile.employees.add( manager1 ); + companyFetchProfile.employees.add( manager2 ); + companyFetchProfile.location = location; + companyFetchProfile.markets.add( Market.SERVICES ); + companyFetchProfile.markets.add( Market.TECHNOLOGY ); + companyFetchProfile.phoneNumbers.add( "012-345-6789" ); + companyFetchProfile.phoneNumbers.add( "987-654-3210" ); + entityManager.persist( companyFetchProfile ); entityManager.getTransaction().commit(); entityManager.close(); @@ -525,6 +525,6 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Company.class, CompanyWithFetchProfile.class, Employee.class, Manager.class, Location.class, Course.class, Student.class }; + return new Class[] { Company.class, CompanyFetchProfile.class, Employee.class, Manager.class, Location.class, Course.class, Student.class }; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/StatelessDoWorkTest.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/StatelessDoWorkTest.java new file mode 100644 index 0000000000..86693b6525 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/StatelessDoWorkTest.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 . + */ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.stateless; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.StatelessSession; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@RequiresDialect(H2Dialect.class) +public class StatelessDoWorkTest extends BaseCoreFunctionalTestCase { + public static final String EXPECTED_ENTITY_NAME = "test"; + public static final Integer PERSISTED_TEST_ENTITY_ID = 1; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + TestEntity entity = new TestEntity( PERSISTED_TEST_ENTITY_ID, EXPECTED_ENTITY_NAME ); + session.save( entity ); + } + ); + } + + @After + public void tearDown() { + inTransaction( + session -> { + session.createQuery( "delete from TestEntity" ).executeUpdate(); + } + ); + } + + @Test + public void testDoReturningWork() { + String retrievedEntityName; + try (StatelessSession statelessSession = sessionFactory().openStatelessSession()) { + retrievedEntityName = statelessSession.doReturningWork( + (connection) -> { + try (PreparedStatement preparedStatement = connection.prepareStatement( + "SELECT NAME FROM TEST_ENTITY WHERE ID = ?" )) { + preparedStatement.setInt( 1, PERSISTED_TEST_ENTITY_ID ); + ResultSet resultSet = preparedStatement.executeQuery(); + String name = null; + if ( resultSet.next() ) { + name = resultSet.getString( 1 ); + } + return name; + } + } + ); + } + + assertThat( retrievedEntityName, is( EXPECTED_ENTITY_NAME ) ); + } + + @Test + public void testDoWork() { + try (StatelessSession statelessSession = sessionFactory().openStatelessSession()) { + statelessSession.doWork( + (connection) -> { + try (PreparedStatement preparedStatement = connection.prepareStatement( + "DELETE FROM TEST_ENTITY " )) { + preparedStatement.execute(); + } + } + ); + } + + assertThatAllTestEntitiesHaveBeenDeleted(); + } + + private void assertThatAllTestEntitiesHaveBeenDeleted() { + inTransaction( session -> { + List results = session.createQuery( "from TestEntity" ).list(); + assertThat( results.size(), is( 0 ) ); + } ); + } + + @Entity(name = "TestEntity") + @Table(name = "TEST_ENTITY") + public static class TestEntity { + @Id + @Column(name = "ID") + private Integer id; + + @Column(name = "NAME") + private String name; + + public TestEntity() { + } + + public TestEntity(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-graalvm/hibernate-graalvm.gradle b/hibernate-graalvm/hibernate-graalvm.gradle new file mode 100644 index 0000000000..4ba6b2e39c --- /dev/null +++ b/hibernate-graalvm/hibernate-graalvm.gradle @@ -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 . + */ + +apply from: rootProject.file( 'gradle/published-java-module.gradle' ) + +description = "Experimental extension to make it easier to compile applications into a GraalVM native image" + +dependencies { + //No need for transitive dependencies: this is all just metadata to be used as companion jar. + compileOnly project( ':hibernate-core' ) + compileOnly( libraries.graalvm_nativeimage ) + testCompile( project( ':hibernate-core' ) ) +} diff --git a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticAutofeature.java b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticAutofeature.java new file mode 100644 index 0000000000..34fb70f6ed --- /dev/null +++ b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticAutofeature.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.graalvm.internal; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.util.ArrayList; + +import org.hibernate.internal.util.ReflectHelper; + +import com.oracle.svm.core.annotate.AutomaticFeature; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeReflection; + +/** + * This is a best effort, untested experimental GraalVM feature to help people getting Hibernate ORM + * to work with GraalVM native images. + * There are multiple reasons for this to be untested. One is that for tests to be effective they would + * need very extensive coverage of all functionality: the point of this class being a list of all things + * being initialized reflectively, it's not possible to ensure that the list is comprehensive without the + * tests being comprehensive as well. + * The other problem is that this is listing just that "static needs" of Hibernate ORM: it will very likely + * also need to access reflectively the user's domain model and the various extension points, depending on + * configurations. Such configuration - and especially the domain model - is dynamic by its very own nature, + * and therefore this list is merely provided as a useful starting point, but it needs to be extended; + * such extensions could be automated, or will need to be explicitly passed to the native-image arguments. + *

+ * In conclusion, it's not possible to provide a fully comprehensive list: take this as a hopefully + * useful building block. + *

+ * @author Sanne Grinovero + */ +@AutomaticFeature +public class GraalVMStaticAutofeature implements Feature { + + public void beforeAnalysis(Feature.BeforeAnalysisAccess before) { + final Class[] needsHavingSimpleConstructors = StaticClassLists.typesNeedingDefaultConstructorAccessible(); + final Class[] neddingAllConstructorsAccessible = StaticClassLists.typesNeedingAllConstructorsAccessible(); + //Size formula is just a reasonable guess: + ArrayList executables = new ArrayList<>( needsHavingSimpleConstructors.length + neddingAllConstructorsAccessible.length * 3 ); + for ( Class c : needsHavingSimpleConstructors ) { + executables.add( ReflectHelper.getDefaultConstructor( c ) ); + } + for ( Class c : neddingAllConstructorsAccessible ) { + for ( Constructor declaredConstructor : c.getDeclaredConstructors() ) { + executables.add( declaredConstructor ); + } + } + RuntimeReflection.register( needsHavingSimpleConstructors ); + RuntimeReflection.register( neddingAllConstructorsAccessible ); + RuntimeReflection.register( StaticClassLists.typesNeedingArrayCopy() ); + RuntimeReflection.register( executables.toArray(new Executable[0]) ); + } + +} diff --git a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java new file mode 100644 index 0000000000..b53818e657 --- /dev/null +++ b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.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.graalvm.internal; + +import org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor; +import org.hibernate.type.EnumType; + +/** + * The place to list all "static" types we know of that need to be possible to + * construct at runtime via reflection. + * This is useful for GraalVM native images - but is not intenteded to be an + * exhaustive list: take these as an helpful starting point. + */ +final class StaticClassLists { + + public static Class[] typesNeedingAllConstructorsAccessible() { + return new Class[] { + //The CoreMessageLogger is sometimes looked up without it necessarily being a field, so we're + //not processing it the same way as other Logger lookups. + org.hibernate.internal.CoreMessageLogger_$logger.class, + org.hibernate.tuple.component.PojoComponentTuplizer.class, + org.hibernate.tuple.component.DynamicMapComponentTuplizer.class, + org.hibernate.tuple.entity.DynamicMapEntityTuplizer.class, + org.hibernate.persister.collection.OneToManyPersister.class, + org.hibernate.persister.collection.BasicCollectionPersister.class, + org.hibernate.persister.entity.JoinedSubclassEntityPersister.class, + org.hibernate.persister.entity.UnionSubclassEntityPersister.class, + org.hibernate.persister.entity.SingleTableEntityPersister.class, + org.hibernate.tuple.entity.PojoEntityTuplizer.class, + //ANTLR special ones: + org.hibernate.hql.internal.ast.tree.EntityJoinFromElement.class, + org.hibernate.hql.internal.ast.tree.MapKeyEntityFromElement.class, + org.hibernate.hql.internal.ast.tree.ComponentJoin.class, + }; + } + + public static Class[] typesNeedingDefaultConstructorAccessible() { + return new Class[] { + //Support for @OrderBy + org.hibernate.sql.ordering.antlr.NodeSupport.class, + org.hibernate.sql.ordering.antlr.OrderByFragment.class, + org.hibernate.sql.ordering.antlr.SortSpecification.class, + org.hibernate.sql.ordering.antlr.OrderingSpecification.class, + org.hibernate.sql.ordering.antlr.CollationSpecification.class, + org.hibernate.sql.ordering.antlr.SortKey.class, + + //ANTLR tokens: + org.hibernate.hql.internal.ast.tree.SelectClause.class, + org.hibernate.hql.internal.ast.tree.HqlSqlWalkerNode.class, + org.hibernate.hql.internal.ast.tree.MethodNode.class, + org.hibernate.hql.internal.ast.tree.UnaryLogicOperatorNode.class, + org.hibernate.hql.internal.ast.tree.NullNode.class, + org.hibernate.hql.internal.ast.tree.IntoClause.class, + org.hibernate.hql.internal.ast.tree.UpdateStatement.class, + org.hibernate.hql.internal.ast.tree.SelectExpressionImpl.class, + org.hibernate.hql.internal.ast.tree.CastFunctionNode.class, + org.hibernate.hql.internal.ast.tree.DeleteStatement.class, + org.hibernate.hql.internal.ast.tree.SqlNode.class, + org.hibernate.hql.internal.ast.tree.SearchedCaseNode.class, + org.hibernate.hql.internal.ast.tree.FromElement.class, + org.hibernate.hql.internal.ast.tree.JavaConstantNode.class, + org.hibernate.hql.internal.ast.tree.SqlFragment.class, + org.hibernate.hql.internal.ast.tree.MapKeyNode.class, + org.hibernate.hql.internal.ast.tree.ImpliedFromElement.class, + org.hibernate.hql.internal.ast.tree.IsNotNullLogicOperatorNode.class, + org.hibernate.hql.internal.ast.tree.InsertStatement.class, + org.hibernate.hql.internal.ast.tree.UnaryArithmeticNode.class, + org.hibernate.hql.internal.ast.tree.CollectionFunction.class, + org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode.class, + org.hibernate.hql.internal.ast.tree.CountNode.class, + org.hibernate.hql.internal.ast.tree.IsNullLogicOperatorNode.class, + org.hibernate.hql.internal.ast.tree.IdentNode.class, + org.hibernate.hql.internal.ast.tree.ParameterNode.class, + org.hibernate.hql.internal.ast.tree.MapEntryNode.class, + org.hibernate.hql.internal.ast.tree.MapValueNode.class, + org.hibernate.hql.internal.ast.tree.InLogicOperatorNode.class, + org.hibernate.hql.internal.ast.tree.IndexNode.class, + org.hibernate.hql.internal.ast.tree.DotNode.class, + org.hibernate.hql.internal.ast.tree.ResultVariableRefNode.class, + org.hibernate.hql.internal.ast.tree.BetweenOperatorNode.class, + org.hibernate.hql.internal.ast.tree.AggregateNode.class, + org.hibernate.hql.internal.ast.tree.QueryNode.class, + org.hibernate.hql.internal.ast.tree.BooleanLiteralNode.class, + org.hibernate.hql.internal.ast.tree.SimpleCaseNode.class, + org.hibernate.hql.internal.ast.tree.OrderByClause.class, + org.hibernate.hql.internal.ast.tree.FromClause.class, + org.hibernate.hql.internal.ast.tree.ConstructorNode.class, + org.hibernate.hql.internal.ast.tree.LiteralNode.class, + org.hibernate.hql.internal.ast.tree.BinaryArithmeticOperatorNode.class, + + //Various well known needs: + + org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, + org.hibernate.id.enhanced.SequenceStyleGenerator.class, + org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl.class, + org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl.class, + EnumType.class, + MultipleLinesSqlCommandExtractor.class, + org.hibernate.hql.internal.ast.HqlToken.class, + org.hibernate.hql.internal.ast.tree.Node.class, + + }; + } + + public static Class[] typesNeedingArrayCopy() { + return new Class[] { + //Eventlisteners need to be registered for reflection to allow creation via Array#newInstance ; + // types need to be in synch with those declared in org.hibernate.event.spi.EventType + org.hibernate.event.spi.LoadEventListener[].class, + org.hibernate.event.spi.ResolveNaturalIdEventListener[].class, + org.hibernate.event.spi.InitializeCollectionEventListener[].class, + org.hibernate.event.spi.SaveOrUpdateEventListener[].class, + org.hibernate.event.spi.PersistEventListener[].class, + org.hibernate.event.spi.MergeEventListener[].class, + org.hibernate.event.spi.DeleteEventListener[].class, + org.hibernate.event.spi.ReplicateEventListener[].class, + org.hibernate.event.spi.FlushEventListener[].class, + org.hibernate.event.spi.AutoFlushEventListener[].class, + org.hibernate.event.spi.DirtyCheckEventListener[].class, + org.hibernate.event.spi.FlushEntityEventListener[].class, + org.hibernate.event.spi.ClearEventListener[].class, + org.hibernate.event.spi.EvictEventListener[].class, + org.hibernate.event.spi.LockEventListener[].class, + org.hibernate.event.spi.RefreshEventListener[].class, + org.hibernate.event.spi.PreLoadEventListener[].class, + org.hibernate.event.spi.PreDeleteEventListener[].class, + org.hibernate.event.spi.PreUpdateEventListener[].class, + org.hibernate.event.spi.PreInsertEventListener[].class, + org.hibernate.event.spi.PostLoadEventListener[].class, + org.hibernate.event.spi.PostDeleteEventListener[].class, + org.hibernate.event.spi.PostUpdateEventListener[].class, + org.hibernate.event.spi.PostInsertEventListener[].class, + org.hibernate.event.spi.PreCollectionRecreateEventListener[].class, + org.hibernate.event.spi.PreCollectionRemoveEventListener[].class, + org.hibernate.event.spi.PreCollectionUpdateEventListener[].class, + org.hibernate.event.spi.PostCollectionRecreateEventListener[].class, + org.hibernate.event.spi.PostCollectionRemoveEventListener[].class, + org.hibernate.event.spi.PostCollectionUpdateEventListener[].class + }; + } + +} diff --git a/hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/BasicConstructorsAvailableTest.java b/hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/BasicConstructorsAvailableTest.java new file mode 100644 index 0000000000..dd60ab9762 --- /dev/null +++ b/hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/BasicConstructorsAvailableTest.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.graalvm.internal; + +import java.lang.reflect.Constructor; + +import org.hibernate.internal.util.ReflectHelper; + +import org.junit.Assert; +import org.junit.Test; + +public class BasicConstructorsAvailableTest { + + @Test + public void checkNonDefaultConstructorsCanBeLoaded() { + Class[] classes = StaticClassLists.typesNeedingAllConstructorsAccessible(); + for ( Class c : classes ) { + Constructor[] declaredConstructors = c.getDeclaredConstructors(); + Assert.assertTrue( declaredConstructors.length > 0 ); + if ( declaredConstructors.length == 1 ) { + //If there's only one, let's check that this class wasn't placed in the wrong cathegory: + Assert.assertTrue( declaredConstructors[0].getParameterCount() > 0 ); + } + } + } + + @Test + public void checkDefaultConstructorsAreAvailable() { + Class[] classes = StaticClassLists.typesNeedingDefaultConstructorAccessible(); + for ( Class c : classes ) { + Constructor constructor = ReflectHelper.getDefaultConstructor( c ); + Assert.assertNotNull( "Failed for class: " + c.getName(), constructor ); + } + } + + @Test + public void checkArraysAreArrays() { + Class[] classes = StaticClassLists.typesNeedingArrayCopy(); + for ( Class c : classes ) { + Assert.assertTrue( "Wrong category for type: " + c.getName(), c.isArray() ); + Constructor[] constructors = c.getConstructors(); + Assert.assertEquals( 0, constructors.length ); + } + } + +} diff --git a/hibernate-integrationtest-java-modules/src/test/java/org/hibernate/orm/integrationtest/java/module/test/ScannerTest.java b/hibernate-integrationtest-java-modules/src/test/java/org/hibernate/orm/integrationtest/java/module/test/ScannerTest.java new file mode 100644 index 0000000000..7e00d53bd9 --- /dev/null +++ b/hibernate-integrationtest-java-modules/src/test/java/org/hibernate/orm/integrationtest/java/module/test/ScannerTest.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.orm.integrationtest.java.module.test; + +import java.net.URL; +import java.util.Set; + +import org.hibernate.boot.archive.internal.StandardArchiveDescriptorFactory; +import org.hibernate.boot.archive.scan.internal.StandardScanOptions; +import org.hibernate.boot.archive.scan.internal.StandardScanParameters; +import org.hibernate.boot.archive.scan.internal.StandardScanner; +import org.hibernate.boot.archive.scan.spi.ClassDescriptor; +import org.hibernate.boot.archive.scan.spi.ScanResult; +import org.hibernate.orm.integrationtest.java.module.entity.Author; + +import org.junit.Assert; +import org.junit.Test; + +/** + * We need to test that the scanner works, including when there is a module-info.class + * resource in the project. See also HHH-13859. + */ +public class ScannerTest { + + @Test + public void verifyModuleInfoScanner() { + URL urlToThis = Author.class.getProtectionDomain().getCodeSource().getLocation(); + StandardScanner standardScanner = new StandardScanner( StandardArchiveDescriptorFactory.INSTANCE ); + ScanResult scan = standardScanner.scan( + new TestScanEnvironment( urlToThis ), + new StandardScanOptions(), + StandardScanParameters.INSTANCE + ); + Set locatedClasses = scan.getLocatedClasses(); + Assert.assertEquals( 1, locatedClasses.size() ); + ClassDescriptor classDescriptor = locatedClasses.iterator().next(); + Assert.assertNotNull( classDescriptor ); + Assert.assertEquals( Author.class.getName(), classDescriptor.getName() ); + Assert.assertEquals( ClassDescriptor.Categorization.MODEL, classDescriptor.getCategorization() ); + } + +} diff --git a/hibernate-integrationtest-java-modules/src/test/java/org/hibernate/orm/integrationtest/java/module/test/TestScanEnvironment.java b/hibernate-integrationtest-java-modules/src/test/java/org/hibernate/orm/integrationtest/java/module/test/TestScanEnvironment.java new file mode 100644 index 0000000000..709bf0c5cb --- /dev/null +++ b/hibernate-integrationtest-java-modules/src/test/java/org/hibernate/orm/integrationtest/java/module/test/TestScanEnvironment.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.orm.integrationtest.java.module.test; + +import java.net.URL; +import java.util.Collections; +import java.util.List; + +import org.hibernate.boot.archive.scan.spi.ScanEnvironment; + +final class TestScanEnvironment implements ScanEnvironment { + + private final URL root; + + TestScanEnvironment(URL root) { + this.root = root; + } + + @Override + public URL getRootUrl() { + return root; + } + + @Override + public List getNonRootUrls() { + return Collections.emptyList(); + } + + @Override + public List getExplicitlyListedClassNames() { + return Collections.emptyList(); + } + + @Override + public List getExplicitlyListedMappingFiles() { + return Collections.emptyList(); + } +} diff --git a/settings.gradle b/settings.gradle index 427cefa506..ab97bf799d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,6 +31,7 @@ include 'hibernate-infinispan' include 'hibernate-jipijapa' include 'hibernate-orm-modules' +include 'hibernate-graalvm' if ( JavaVersion.current().isJava11Compatible() ) { include 'hibernate-integrationtest-java-modules'