HHH-18048 - Split notions of SessionFactory name and SessionFactory JNDI name

This commit is contained in:
Steve Ebersole 2024-05-02 13:00:45 -05:00
parent d91bcecf77
commit 2284b2b142
14 changed files with 242 additions and 50 deletions

View File

@ -137,6 +137,11 @@ import static org.hibernate.internal.TransactionManagement.manageTransaction;
* @author Steve Ebersole
*/
public interface SessionFactory extends EntityManagerFactory, Referenceable, Serializable, java.io.Closeable {
/**
* The JNDI name, used to bind the SessionFactory to JNDI
*/
String getJndiName();
/**
* Obtain a {@linkplain SessionBuilder session builder} for creating
* new {@link Session}s with certain customized options.

View File

@ -84,7 +84,9 @@ public interface SessionFactoryBuilder {
*
* @return {@code this}, for method chaining
*
* @see org.hibernate.cfg.AvailableSettings#SESSION_FACTORY_NAME
* @see org.hibernate.cfg.AvailableSettings#SESSION_FACTORY_NAME_IS_JNDI
* @see org.hibernate.cfg.AvailableSettings#SESSION_FACTORY_JNDI_NAME
*/
SessionFactoryBuilder applyNameAsJndiName(boolean isJndiName);

View File

@ -23,19 +23,16 @@ import org.hibernate.internal.SessionFactoryRegistry;
* @author Gavin King
*/
class SessionFactoryObserverForRegistration implements SessionFactoryObserver {
private JndiService jndiService;
private boolean registeredInJndi;
@Override
public void sessionFactoryCreated(SessionFactory factory) {
final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) factory;
jndiService = sessionFactory.getServiceRegistry().getService( JndiService.class );
registeredInJndi = sessionFactory.getSessionFactoryOptions().isSessionFactoryNameAlsoJndiName();
SessionFactoryRegistry.INSTANCE.addSessionFactory(
sessionFactory.getUuid(),
sessionFactory.getName(),
registeredInJndi,
sessionFactory.getJndiName(),
sessionFactory,
jndiService
);
@ -47,7 +44,7 @@ class SessionFactoryObserverForRegistration implements SessionFactoryObserver {
SessionFactoryRegistry.INSTANCE.removeSessionFactory(
sessionFactory.getUuid(),
sessionFactory.getName(),
registeredInJndi,
sessionFactory.getJndiName(),
jndiService
);
}

View File

@ -171,7 +171,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
// SessionFactory behavior
private final boolean jpaBootstrap;
private String sessionFactoryName;
private boolean sessionFactoryNameAlsoJndiName;
private Boolean sessionFactoryNameAlsoJndiName;
// Session behavior
private boolean flushBeforeCompletionEnabled;
@ -931,7 +931,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
}
@Override
public boolean isSessionFactoryNameAlsoJndiName() {
public Boolean isSessionFactoryNameAlsoJndiName() {
return sessionFactoryNameAlsoJndiName;
}

View File

@ -101,7 +101,7 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
}
@Override
public boolean isSessionFactoryNameAlsoJndiName() {
public Boolean isSessionFactoryNameAlsoJndiName() {
return delegate.isSessionFactoryNameAlsoJndiName();
}

View File

@ -85,10 +85,10 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
}
/**
* The name to be used for the SessionFactory. This is used both in:<ul>
* <li>in-VM serialization</li>
* <li>JNDI binding, depending on {@link #isSessionFactoryNameAlsoJndiName}</li>
* </ul>
* The name to be used for the SessionFactory. This is used during in-VM serialization; see
* {@link org.hibernate.internal.SessionFactoryRegistry}.
* May also be used as a JNDI name depending on {@value org.hibernate.cfg.PersistenceSettings#SESSION_FACTORY_JNDI_NAME}
* and {@value org.hibernate.cfg.PersistenceSettings#SESSION_FACTORY_NAME_IS_JNDI}.
*
* @return The SessionFactory name
*/
@ -100,7 +100,7 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
*
* @return {@code true} if the SessionFactory name is also a JNDI name; {@code false} otherwise.
*/
boolean isSessionFactoryNameAlsoJndiName();
Boolean isSessionFactoryNameAlsoJndiName();
boolean isFlushBeforeCompletionEnabled();

View File

@ -7,6 +7,7 @@
package org.hibernate.cfg;
import org.hibernate.Incubating;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
import jakarta.persistence.spi.PersistenceUnitInfo;
@ -61,15 +62,26 @@ public interface PersistenceSettings {
* Naming the SessionFactory allows for it to be properly serialized across JVMs as
* long as the same name is used on each JVM.
* <p>
* If {@link #SESSION_FACTORY_NAME_IS_JNDI} is set to {@code 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.
* If {@link #SESSION_FACTORY_NAME_IS_JNDI} is set to {@code true}, this name will
* also be used as {@link #SESSION_FACTORY_JNDI_NAME}.
*
* @see #SESSION_FACTORY_JNDI_NAME
* @see org.hibernate.internal.SessionFactoryRegistry
* @see org.hibernate.boot.SessionFactoryBuilder#applyName(String)
*/
String SESSION_FACTORY_NAME = "hibernate.session_factory_name";
/**
* An optional name used to bind the SessionFactory into JNDI.
* <p>
* If {@link #SESSION_FACTORY_NAME_IS_JNDI} is set to {@code true},
* {@link #SESSION_FACTORY_NAME} will be used as the JNDI name
*
* @see #SESSION_FACTORY_NAME_IS_JNDI
* @see org.hibernate.internal.SessionFactoryRegistry
* @see org.hibernate.boot.SessionFactoryBuilder#applyName(String)
*/
String SESSION_FACTORY_NAME = "hibernate.session_factory_name";
String SESSION_FACTORY_JNDI_NAME = "hibernate.session_factory_jndi_name";
/**
* Does the value defined by {@link #SESSION_FACTORY_NAME} represent a JNDI namespace
@ -83,6 +95,10 @@ public interface PersistenceSettings {
*
* @see #SESSION_FACTORY_NAME
* @see org.hibernate.boot.SessionFactoryBuilder#applyNameAsJndiName(boolean)
*
* @settingDefault {@code true} if {@link SessionFactory#getName()} comes from
* {@value #SESSION_FACTORY_NAME}; {@code false} if there is no {@link SessionFactory#getName()}
* or if it comes from {@value #PERSISTENCE_UNIT_NAME}
*/
String SESSION_FACTORY_NAME_IS_JNDI = "hibernate.session_factory_name_is_jndi";

View File

@ -340,6 +340,11 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor,
return delegate.getName();
}
@Override
public String getJndiName() {
return delegate.getJndiName();
}
@Override
public TypeConfiguration getTypeConfiguration() {
return delegate.getTypeConfiguration();

View File

@ -60,7 +60,6 @@ import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.profile.FetchProfile;
@ -144,6 +143,8 @@ import static org.hibernate.cfg.AvailableSettings.CREATE_EMPTY_COMPOSITES_ENABLE
import static org.hibernate.cfg.AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS;
import static org.hibernate.cfg.AvailableSettings.JAKARTA_VALIDATION_FACTORY;
import static org.hibernate.cfg.AvailableSettings.JPA_VALIDATION_FACTORY;
import static org.hibernate.cfg.PersistenceSettings.PERSISTENCE_UNIT_NAME;
import static org.hibernate.cfg.PersistenceSettings.SESSION_FACTORY_JNDI_NAME;
import static org.hibernate.engine.config.spi.StandardConverters.STRING;
import static org.hibernate.internal.FetchProfileHelper.getFetchProfiles;
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
@ -176,6 +177,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( SessionFactoryImpl.class );
private final String name;
private final String jndiName;
private final String uuid;
private transient volatile Status status = Status.OPEN;
@ -238,6 +240,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
bootMetamodel.initSessionFactory( this );
name = getSessionFactoryName( options, serviceRegistry );
jndiName = determineJndiName( name, options, serviceRegistry );
uuid = options.getUuid();
jdbcServices = serviceRegistry.requireService( JdbcServices.class );
@ -346,7 +349,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
LOG.debug( "Instantiated SessionFactory" );
}
private void deprecationCheck(Map<String, Object> settings) {
private static void deprecationCheck(Map<String, Object> settings) {
for ( String s:settings.keySet() ) {
switch (s) {
case "hibernate.hql.bulk_id_strategy.global_temporary.create_tables":
@ -575,6 +578,23 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
return null;
}
private String determineJndiName(
String name,
SessionFactoryOptions options,
SessionFactoryServiceRegistry serviceRegistry) {
final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class );
assert cfgService != null;
final String explicitJndiName = cfgService.getSetting( SESSION_FACTORY_JNDI_NAME, STRING );
if ( StringHelper.isNotEmpty( explicitJndiName ) ) {
return explicitJndiName;
}
final String puName = cfgService.getSetting( PERSISTENCE_UNIT_NAME, STRING );
// do not use name for JNDI if explicitly asked not to or if name comes from JPA persistence-unit name
final boolean nameIsNotJndiName = options.isSessionFactoryNameAlsoJndiName() == Boolean.FALSE || StringHelper.isNotEmpty( puName );
return !nameIsNotJndiName ? name : null;
}
private SessionBuilderImpl createDefaultSessionOpenOptionsIfPossible() {
final CurrentTenantIdentifierResolver<Object> currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver();
if ( currentTenantIdentifierResolver == null ) {
@ -725,6 +745,11 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
return name;
}
@Override
public String getJndiName() {
return jndiName;
}
@Override
public TypeConfiguration getTypeConfiguration() {
return runtimeMetamodels.getMappingMetamodel().getTypeConfiguration();
@ -1766,7 +1791,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
return (SessionFactoryImpl) locateSessionFactoryOnDeserialization( uuid, name );
}
private void maskOutSensitiveInformation(Map<String, Object> props) {
private static void maskOutSensitiveInformation(Map<String, Object> props) {
maskOutIfSet( props, AvailableSettings.JPA_JDBC_USER );
maskOutIfSet( props, AvailableSettings.JPA_JDBC_PASSWORD );
maskOutIfSet( props, AvailableSettings.JAKARTA_JDBC_USER );
@ -1775,13 +1800,13 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
maskOutIfSet( props, AvailableSettings.PASS );
}
private void maskOutIfSet(Map<String, Object> props, String setting) {
private static void maskOutIfSet(Map<String, Object> props, String setting) {
if ( props.containsKey( setting ) ) {
props.put( setting, "****" );
}
}
private void logIfEmptyCompositesEnabled(Map<String, Object> props ) {
private static void logIfEmptyCompositesEnabled(Map<String, Object> props ) {
final boolean isEmptyCompositesEnabled = getBoolean( CREATE_EMPTY_COMPOSITES_ENABLED, props );
if ( isEmptyCompositesEnabled ) {
LOG.emptyCompositesEnabled();

View File

@ -57,14 +57,14 @@ public class SessionFactoryRegistry {
*
* @param uuid The uuid under which to register the SessionFactory
* @param name The optional name under which to register the SessionFactory
* @param isNameAlsoJndiName Is name, if provided, also a JNDI name?
* @param jndiName An optional name to use for binding the SessionFactory into JNDI
* @param instance The SessionFactory instance
* @param jndiService The JNDI service, so we can register a listener if name is a JNDI name
*/
public void addSessionFactory(
String uuid,
String name,
boolean isNameAlsoJndiName,
String jndiName,
SessionFactoryImplementor instance,
JndiService jndiService) {
if ( uuid == null ) {
@ -77,25 +77,28 @@ public class SessionFactoryRegistry {
nameUuidXref.put( name, uuid );
}
if ( name == null || !isNameAlsoJndiName ) {
if ( jndiName == null ) {
LOG.debug( "Not binding SessionFactory to JNDI, no JNDI name configured" );
return;
}
LOG.debugf( "Attempting to bind SessionFactory [%s] to JNDI", name );
bindToJndi( jndiName, instance, jndiService );
}
private void bindToJndi(String jndiName, SessionFactoryImplementor instance, JndiService jndiService) {
try {
jndiService.bind( name, instance );
LOG.factoryBoundToJndiName( name );
LOG.debugf( "Attempting to bind SessionFactory [%s] to JNDI", jndiName );
jndiService.bind( jndiName, instance );
LOG.factoryBoundToJndiName( jndiName );
try {
jndiService.addListener( name, listener );
jndiService.addListener( jndiName, listener );
}
catch (Exception e) {
LOG.couldNotBindJndiListener();
}
}
catch (JndiNameException e) {
LOG.invalidJndiName( name, e );
LOG.invalidJndiName( jndiName, e );
}
catch (JndiException e) {
LOG.unableToBindFactoryToJndi( e );
@ -107,29 +110,29 @@ public class SessionFactoryRegistry {
*
* @param uuid The uuid
* @param name The optional name
* @param isNameAlsoJndiName Is name, if provided, also a JNDI name?
* @param jndiName An optional name to use for binding the SessionFactory nto JNDI
* @param jndiService The JNDI service
*/
public void removeSessionFactory(
String uuid,
String name,
boolean isNameAlsoJndiName,
String jndiName,
JndiService jndiService) {
if ( name != null ) {
nameUuidXref.remove( name );
}
if ( isNameAlsoJndiName ) {
try {
LOG.tracef( "Unbinding SessionFactory from JNDI : %s", name );
jndiService.unbind( name );
LOG.factoryUnboundFromJndiName( name );
}
catch (JndiNameException e) {
LOG.invalidJndiName( name, e );
}
catch (JndiException e) {
LOG.unableToUnbindFactoryFromJndi( e );
}
if ( jndiName != null ) {
try {
LOG.tracef( "Unbinding SessionFactory from JNDI : %s", jndiName );
jndiService.unbind( jndiName );
LOG.factoryUnboundFromJndiName( jndiName );
}
catch (JndiNameException e) {
LOG.invalidJndiName( jndiName, e );
}
catch (JndiException e) {
LOG.unableToUnbindFactoryFromJndi( e );
}
}

View File

@ -0,0 +1,95 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.boot;
import org.hibernate.boot.model.process.internal.ScanningCoordinator;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.PersistenceSettings;
import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Logger;
import org.hibernate.testing.orm.junit.LoggingInspections;
import org.hibernate.testing.orm.junit.MessageKeyInspection;
import org.hibernate.testing.orm.junit.MessageKeyWatcher;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("JUnitMalformedDeclaration")
@MessageKeyInspection(
messageKey = "HHH000277",
logger = @Logger( loggerNameClass = SessionFactoryRegistry.class )
)
public class SessionFactoryNamingTests {
@Test
@DomainModel
@ServiceRegistry( settings = {
@Setting( name = AvailableSettings.SESSION_FACTORY_NAME_IS_JNDI, value = "true" ),
@Setting( name = AvailableSettings.SESSION_FACTORY_JNDI_NAME, value = "jndi-named" )
} )
@SessionFactory()
void testExplicitJndiName(SessionFactoryScope scope, MessageKeyWatcher logWatcher) {
scope.getSessionFactory();
assertThat( logWatcher.wasTriggered() ).isTrue();
}
@Test
@DomainModel
@ServiceRegistry( settings = @Setting( name = AvailableSettings.SESSION_FACTORY_NAME_IS_JNDI, value = "true" ) )
@SessionFactory( sessionFactoryName = "named" )
void testSessionFactoryName(SessionFactoryScope scope, MessageKeyWatcher logWatcher) {
scope.getSessionFactory();
assertThat( logWatcher.wasTriggered() ).isTrue();
}
@Test
@DomainModel
@ServiceRegistry( settings = @Setting( name = AvailableSettings.SESSION_FACTORY_NAME_IS_JNDI, value = "false" ) )
@SessionFactory( sessionFactoryName = "named" )
void testNonJndiSessionFactoryName(SessionFactoryScope scope, MessageKeyWatcher logWatcher) {
scope.getSessionFactory();
assertThat( logWatcher.wasTriggered() ).isFalse();
}
@Test
@DomainModel
@ServiceRegistry( settings = {
@Setting( name = AvailableSettings.SESSION_FACTORY_NAME_IS_JNDI, value = "true" ),
// mimics the persistence.xml persistence-unit name
@Setting( name = PersistenceSettings.PERSISTENCE_UNIT_NAME, value = "named-pu" ),
} )
@SessionFactory
void testPuName(SessionFactoryScope scope, MessageKeyWatcher logWatcher) {
scope.getSessionFactory();
assertThat( logWatcher.wasTriggered() ).isFalse();
}
@Test
@DomainModel
@ServiceRegistry( settings = {
@Setting( name = AvailableSettings.SESSION_FACTORY_NAME_IS_JNDI, value = "false" ),
// mimics the persistence.xml persistence-unit name
@Setting( name = PersistenceSettings.PERSISTENCE_UNIT_NAME, value = "named-pu" ),
} )
@SessionFactory
void testNonJndiPuName(SessionFactoryScope scope, MessageKeyWatcher logWatcher) {
scope.getSessionFactory();
assertThat( logWatcher.wasTriggered() ).isFalse();
}
}

View File

@ -45,12 +45,12 @@ public class SessionFactorySerializationTest extends BaseUnitTestCase {
// different VM
String uuid = ( (SessionFactoryImplementor) factory ).getUuid();
// deregister under this uuid...
SessionFactoryRegistry.INSTANCE.removeSessionFactory( uuid, NAME, false, null );
SessionFactoryRegistry.INSTANCE.removeSessionFactory( uuid, NAME, null, null );
// and then register under a different uuid...
SessionFactoryRegistry.INSTANCE.addSessionFactory(
"some-other-uuid",
NAME,
false,
null,
(SessionFactoryImplementor) factory,
null
);
@ -58,7 +58,7 @@ public class SessionFactorySerializationTest extends BaseUnitTestCase {
SessionFactory factory2 = (SessionFactory) SerializationHelper.clone( factory );
assertSame( factory, factory2 );
SessionFactoryRegistry.INSTANCE.removeSessionFactory( "some-other-uuid", NAME, false, null );
SessionFactoryRegistry.INSTANCE.removeSessionFactory( "some-other-uuid", NAME, null, null );
}
assertFalse( SessionFactoryRegistry.INSTANCE.hasRegistrations() );
@ -77,12 +77,12 @@ public class SessionFactorySerializationTest extends BaseUnitTestCase {
// different VM
String uuid = ( (SessionFactoryImplementor) factory ).getUuid();
// deregister under this uuid...
SessionFactoryRegistry.INSTANCE.removeSessionFactory( uuid, null, false, null );
SessionFactoryRegistry.INSTANCE.removeSessionFactory( uuid, null, null, null );
// and then register under a different uuid...
SessionFactoryRegistry.INSTANCE.addSessionFactory(
"some-other-uuid",
null,
false,
null,
(SessionFactoryImplementor) factory,
null
);
@ -94,7 +94,7 @@ public class SessionFactorySerializationTest extends BaseUnitTestCase {
catch (SerializationException expected) {
}
SessionFactoryRegistry.INSTANCE.removeSessionFactory( "some-other-uuid", null, false, null );
SessionFactoryRegistry.INSTANCE.removeSessionFactory( "some-other-uuid", null, null, null );
}
assertFalse( SessionFactoryRegistry.INSTANCE.hasRegistrations() );

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<!-- example of reference to a cfg.xml file -->
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="named-session-factory" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
</persistence-unit>
</persistence>

View File

@ -137,6 +137,33 @@ String isDefault();
The default precision for Oracle timestamps was changed to 9 i.e. nanosecond precision.
The default precision for SQL Server timestamps was changed to 7 i.e. 100 nanosecond precision.
[[sf-name]]
== SessionFactory Name (and JNDI)
Hibernate defines `SessionFactory#getName` (specified via `cfg.xml` or `hibernate.session_factory_name`) which is used to
help with (de)serializing a `SessionFactory`. It is also, unless `hibernate.session_factory_name_is_jndi` is set to `false`,
used in biding the `SessionFactory` into JNDI.
This `SessionFactory#getName` method pre-dates Jakarta Persistence (and JPA). It now implements `EntityManagerFactory#getName`
inherited from Jakarta Persistence, which states that this name should come from the persistence-unit name.
To align with Jakarta Persistence (the 3.2 TCK tests this), Hibernate now considers the persistence-unit name if no
`hibernate.session_factory_name` is specified.
However, because `hibernate.session_factory_name` is also a trigger to attempt to bind the SessionFactory into JNDI,
this change to consider persistence-unit name, means that each `SessionFactory` created through Jakarta Persistence now
have a name and Hibernate attempted to bind these to JNDI.
To work around this we have introduced a new `hibernate.session_factory_jndi_name` setting that can be used to explicitly
specify a name for JNDI binding. The new behavior is as follows (assuming `hibernate.session_factory_name_is_jndi` is not explicitly configured):
* If `hibernate.session_factory_jndi_name` is specified, the name is used to bind into JNDI
* If `hibernate.session_factory_name` is specified, the name is used to bind into JNDI
Hibernate can use the persistence-unit name for binding into JNDI as well, but `hibernate.session_factory_name_is_jndi`
must be explicitly set to true.
[[todo]]
== Todos (dev)