From 3071fa892fd6c08b37983a5869c0b7ca14e4180d Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 13 Jan 2012 15:23:02 -0600 Subject: [PATCH] HHH-2879 - create a SimpleNaturalIdLoadAccess for easier access for simple (single attribute) natural ids --- .../src/main/java/org/hibernate/Session.java | 26 ++++ .../hibernate/SimpleNaturalIdLoadAccess.java | 70 ++++++++++ .../org/hibernate/internal/SessionImpl.java | 120 ++++++++++++++++-- .../jpa/naturalid/ImmutableNaturalIdTest.java | 27 ++++ 4 files changed, 232 insertions(+), 11 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index e47cacf89e..dca4db0992 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -824,6 +824,32 @@ public interface Session extends SharedSessionContract { */ public NaturalIdLoadAccess byNaturalId(Class entityClass); + /** + * Create an {@link SimpleNaturalIdLoadAccess} instance to retrieve the specified entity by + * its natural id. + * + * @param entityName The entity name of the entity type to be retrieved + * + * @return load delegate for loading the specified entity type by natural id + * + * @throws HibernateException If the specified entityClass cannot be resolved as a mapped entity, or if the + * entity does not define a natural-id or if its natural-id is made up of multiple attributes. + */ + public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName); + + /** + * Create an {@link SimpleNaturalIdLoadAccess} instance to retrieve the specified entity by + * its simple (single attribute) natural id. + * + * @param entityClass The entity type to be retrieved + * + * @return load delegate for loading the specified entity type by natural id + * + * @throws HibernateException If the specified entityClass cannot be resolved as a mapped entity, or if the + * entity does not define a natural-id or if its natural-id is made up of multiple attributes. + */ + public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass); + /** * Enable the named filter for this current session. * diff --git a/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java new file mode 100644 index 0000000000..64d9602c3a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate; + +/** + * Loads an entity by its natural identifier + * + * @author Eric Dalquist + * @author Steve Ebersole + * + * @see org.hibernate.annotations.NaturalId + * @see NaturalIdLoadAccess + */ +public interface SimpleNaturalIdLoadAccess { + /** + * Specify the {@link org.hibernate.LockOptions} to use when retrieving the entity. + * + * @param lockOptions The lock options to use. + * + * @return {@code this}, for method chaining + */ + public SimpleNaturalIdLoadAccess with(LockOptions lockOptions); + + /** + * Return the persistent instance with the given natural id value, assuming that the instance exists. This method + * might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed. + * + * You should not use this method to determine if an instance exists; to check for existence, use {@link #load} + * instead. Use this only to retrieve an instance that you assume exists, where non-existence would be an + * actual error. + * + * @param naturalIdValue The value of the natural-id for the entity to retrieve + * + * @return the persistent instance or proxy + */ + public Object getReference(Object naturalIdValue); + + /** + * Return the persistent instance with the given natural id value, or {@code null} if there is no such persistent + * instance. If the instance is already associated with the session, return that instance, initializing it if + * needed. This method never returns an uninitialized instance. + * + * @param naturalIdValue The value of the natural-id for the entity to retrieve + * + * @return The persistent instance or {@code null} + */ + public Object load(Object naturalIdValue); + +} 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 ba2e3fdff9..8a16e18bed 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -71,6 +71,7 @@ import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionException; import org.hibernate.SharedSessionBuilder; +import org.hibernate.SimpleNaturalIdLoadAccess; import org.hibernate.Transaction; import org.hibernate.TransientObjectException; import org.hibernate.TypeHelper; @@ -946,6 +947,16 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return new NaturalIdLoadAccessImpl( entityClass ); } + @Override + public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName) { + return new SimpleNaturalIdLoadAccessImpl( entityName ); + } + + @Override + public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass) { + return new SimpleNaturalIdLoadAccessImpl( entityClass ); + } + private void fireLoad(LoadEvent event, LoadType loadType) { errorIfClosed(); checkTransactionSynchStatus(); @@ -975,7 +986,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc } public void refresh(Object object, LockMode lockMode) throws HibernateException { - fireRefresh( new RefreshEvent(object, lockMode, this) ); + fireRefresh( new RefreshEvent( object, lockMode, this ) ); } public void refresh(Object object, LockOptions lockOptions) throws HibernateException { @@ -1735,7 +1746,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc public void setReadOnly(Object entity, boolean readOnly) { errorIfClosed(); checkTransactionSynchStatus(); - persistenceContext.setReadOnly(entity, readOnly); + persistenceContext.setReadOnly( entity, readOnly ); } public void doWork(final Work work) throws HibernateException { @@ -2171,20 +2182,23 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc fireLock( object, lockOptions ); } } - + private class IdentifierLoadAccessImpl implements IdentifierLoadAccess { private final EntityPersister entityPersister; private LockOptions lockOptions; private IdentifierLoadAccessImpl(EntityPersister entityPersister) { this.entityPersister = entityPersister; + + if ( ! entityPersister.hasNaturalIdentifier() ) { + throw new HibernateException( + String.format( "Entity [%s] did not define a natural id", entityPersister.getEntityName() ) + ); + } } private IdentifierLoadAccessImpl(String entityName) { - this( factory.getEntityPersister( entityName ) ); - if ( entityPersister == null ) { - throw new HibernateException( "Unable to locate persister: " + entityName ); - } + this( locateEntityPersister( entityName ) ); } private IdentifierLoadAccessImpl(Class entityClass) { @@ -2241,6 +2255,14 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc } } + private EntityPersister locateEntityPersister(String entityName) { + final EntityPersister entityPersister = factory.getEntityPersister( entityName ); + if ( entityPersister == null ) { + throw new HibernateException( "Unable to locate persister: " + entityName ); + } + return entityPersister; + } + private class NaturalIdLoadAccessImpl implements NaturalIdLoadAccess { private final EntityPersister entityPersister; private final Map naturalIdParameters = new LinkedHashMap(); @@ -2248,13 +2270,16 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc private NaturalIdLoadAccessImpl(EntityPersister entityPersister) { this.entityPersister = entityPersister; + + if ( ! entityPersister.hasNaturalIdentifier() ) { + throw new HibernateException( + String.format( "Entity [%s] did not define a natural id", entityPersister.getEntityName() ) + ); + } } private NaturalIdLoadAccessImpl(String entityName) { - this( factory.getEntityPersister( entityName ) ); - if ( entityPersister == null ) { - throw new HibernateException( "Unable to locate persister: " + entityName ); - } + this( locateEntityPersister( entityName ) ); } private NaturalIdLoadAccessImpl(Class entityClass) { @@ -2306,4 +2331,77 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return this.getIdentifierLoadAccess().load( entityId ); } } + + private class SimpleNaturalIdLoadAccessImpl implements SimpleNaturalIdLoadAccess { + private final EntityPersister entityPersister; + private final String naturalIdAttributeName; + private LockOptions lockOptions; + + private SimpleNaturalIdLoadAccessImpl(EntityPersister entityPersister) { + this.entityPersister = entityPersister; + + if ( ! entityPersister.hasNaturalIdentifier() ) { + throw new HibernateException( + String.format( "Entity [%s] did not define a natural id", entityPersister.getEntityName() ) + ); + } + + if ( entityPersister.getNaturalIdentifierProperties().length != 1 ) { + throw new HibernateException( + String.format( "Entity [%s] did not define a simple natural id", entityPersister.getEntityName() ) + ); + } + + final int naturalIdAttributePosition = entityPersister.getNaturalIdentifierProperties()[0]; + this.naturalIdAttributeName = entityPersister.getPropertyNames()[ naturalIdAttributePosition ]; + } + + private SimpleNaturalIdLoadAccessImpl(String entityName) { + this( locateEntityPersister( entityName ) ); + } + + private SimpleNaturalIdLoadAccessImpl(Class entityClass) { + this( entityClass.getName() ); + } + + @Override + public final SimpleNaturalIdLoadAccessImpl with(LockOptions lockOptions) { + this.lockOptions = lockOptions; + return this; + } + + @Override + public Object getReference(Object naturalIdValue) { + final Serializable entityId = resolveNaturalId( naturalIdValue ); + if ( entityId == null ) { + return null; + } + return this.getIdentifierLoadAccess().getReference( entityId ); + } + + @Override + public Object load(Object naturalIdValue) { + final Serializable entityId = resolveNaturalId( naturalIdValue ); + if ( entityId == null ) { + return null; + } + return this.getIdentifierLoadAccess().load( entityId ); + } + + private Serializable resolveNaturalId(Object naturalIdValue) { + final Map naturalIdValueMap = Collections.singletonMap( naturalIdAttributeName, naturalIdValue ); + final ResolveNaturalIdEvent event = + new ResolveNaturalIdEvent( naturalIdValueMap, entityPersister, SessionImpl.this ); + fireResolveNaturalId( event ); + return event.getEntityId(); + } + + private IdentifierLoadAccess getIdentifierLoadAccess() { + final IdentifierLoadAccessImpl identifierLoadAccess = new IdentifierLoadAccessImpl( entityPersister ); + if ( this.lockOptions != null ) { + identifierLoadAccess.with( lockOptions ); + } + return identifierLoadAccess; + } + } } diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java index 8e25e10517..68b6a48470 100644 --- a/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java @@ -36,6 +36,8 @@ import org.hibernate.test.jpa.AbstractJPATest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -106,6 +108,31 @@ public class ImmutableNaturalIdTest extends AbstractJPATest { s.close(); } + @Test + public void testSimpleNaturalIdLoadAccessCache() { + Session s = openSession(); + s.beginTransaction(); + User u = new User( "steve", "superSecret" ); + s.persist( u ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + u = (User) s.bySimpleNaturalId( User.class ).load( "steve" ); + assertNotNull( u ); + User u2 = (User) s.bySimpleNaturalId( User.class ).getReference( "steve" ); + assertTrue( u == u2 ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + s.createQuery( "delete User" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + @Test public void testNaturalIdLoadAccessCache() { Session s = openSession();