HHH-2643 - Improve Session.refresh() Javdoc to clarify contract

Instead of writing it in the JavaDocs, it's much more appropriate to document this behavior in the User Guide
This commit is contained in:
Vlad Mihalcea 2016-10-31 13:54:30 +02:00
parent d126faa7d9
commit 5629457aa8
2 changed files with 55 additions and 1 deletions

View File

@ -300,6 +300,29 @@ For more about the `hibernate.allow_refresh_detached_entity` configuration prope
check out the <<appendices/Configurations.adoc#misc,Configurations>> section as well.
====
[[pc-refresh-gotchas]]
==== Refresh gotchas
The `refresh` entity state transition is meant to overwrite the entity attributes according to the info currently contained in the associated database record.
However, you have to be very careful when cascading the refresh action to any transient entity.
For instance, consider the following example:
[[pc-refresh-child-entity-jpa-example]]
.Refreshing entity state gotcha
====
[source, JAVA, indent=0]
----
include::{sourcedir}/PersistenceContextTest.java[tags=pc-refresh-child-entity-jpa-example]
----
====
In the aforementioned example, an `EntityNotFoundException` is thrown because the `Book` entity is still in a transient state.
When the refresh action is cascaded from the `Person` entity, Hibernate will not be able to locate the `Book` entity in the database.
For this reason, you should be very careful when mixing the refresh action with transient child entity objects.
[[pc-detach]]
=== Working with detached data

View File

@ -10,7 +10,9 @@ import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityNotFoundException;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@ -28,6 +30,8 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -38,6 +42,8 @@ import static org.junit.Assert.assertTrue;
*/
public class PersistenceContextTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( PersistenceContextTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
@ -187,6 +193,31 @@ public class PersistenceContextTest extends BaseEntityManagerFunctionalTestCase
//end::pc-refresh-jpa-example[]
} );
try {
doInJPA( this::entityManagerFactory, entityManager -> {
Long personId = _personId;
//tag::pc-refresh-child-entity-jpa-example[]
try {
Person person = entityManager.find( Person.class, personId );
Book book = new Book();
book.setId( 100L );
book.setTitle( "Hibernate User Guide" );
book.setAuthor( person );
person.getBooks().add( book );
entityManager.refresh( person );
}
catch ( EntityNotFoundException expected ) {
log.info( "Beware when cascading the refresh associations to transient entities!" );
}
//end::pc-refresh-child-entity-jpa-example[]
} );
}
catch ( Exception expected ) {
}
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
Long personId = _personId;
@ -350,7 +381,7 @@ public class PersistenceContextTest extends BaseEntityManagerFunctionalTestCase
private String name;
@OneToMany(mappedBy = "author")
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Book> books = new ArrayList<>( );
public Long getId() {