diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 7333b449b4..703b11238f 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -182,14 +182,14 @@ See the <> section for more info. [[annotations-jpa-excludesuperclasslisteners]] ==== `@ExcludeSuperclassListeners` The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the current annotated entity skips the invocation of listeners declared by its superclass. -//TODO: Add example +See the <> section for more info. [[annotations-jpa-fieldresult]] ==== `@FieldResult` diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc index 033534787e..6a94ea8b4c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc @@ -1,6 +1,7 @@ [[events]] == Interceptors and events :sourcedir: ../../../../../test/java/org/hibernate/userguide/events +:extrasdir: extras It is useful for the application to react to certain events that occur inside Hibernate. This allows for the implementation of generic functionality and the extension of Hibernate functionality. @@ -177,3 +178,105 @@ See the `javax.persistence.ExcludeSuperclassListener`s annotation. If a callback type is annotated on both an entity and one or more of its superclasses without method overriding, both would be called, the most general superclass first. An entity class is also allowed to override a callback method defined in a superclass in which case the super callback would not get invoked; the overriding method would get invoked provided it is annotated. +[[events-default-listener]] +=== Default entity listeners + +The JPA specification allows you to define a default entity listener which is going to be applied for every entity in that particular system. +Default entity listeners can only be defined in XML mapping files. + +[[events-default-listener-mapping-example]] +.Default event listner mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListener.java[tags=events-default-listener-mapping-example] +---- + +[source, XML, indent=0] +---- +include::{sourcedir}/DefaultEntityListener-orm.xml[tags=events-default-listener-mapping-example] +---- +==== + +Considering that all entities extend the `BaseEntity` class: + +[source, JAVA, indent=0] +---- +include::{sourcedir}/BaseEntity.java[tags=events-default-listener-mapping-example] +---- + +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-mapping-example] +---- + +When persisting a `Person` or `Book` entity, the `createdOn` is going to be set by the `onPersist` method of the `DefaultEntityListener`. + +[[events-default-listener-persist-example]] +.Default event listner persist event +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/events-default-listener-persist-example.sql[] +---- +==== + +When updating a `Person` or `Book` entity, the `updatedOn` is going to be set by the `onUpdate` method of the `DefaultEntityListener`. + +[[events-default-listener-update-example]] +.Default event listner update event +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-update-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/events-default-listener-update-example.sql[] +---- +==== + +[[events-exclude-default-listener]] +==== Exclude default entity listeners + +If you already registered a default entity listener, but you don't want to apply it to a particular entity, +you can use the +http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeDefaultListeners.html[`@ExcludeDefaultListeners`] and +http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] JPA annotations. + +`@ExcludeDefaultListeners` instructs the current class to ignore the default entity listeners for the current entity +while `@ExcludeSuperclassListeners` is used to ignore the default entity listeners propagated to the `BaseEntity` super-class. + +[[events-exclude-default-listener-mapping-example]] +.Exclude default event listner mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-exclude-default-listener-mapping-example] +---- +==== + +When persisting a `Publisher` entity, +the `createdOn` is not going to be set by the `onPersist` method of the `DefaultEntityListener` +because the `Publisher` entity was marked with the `@ExcludeDefaultListeners` and `@ExcludeSuperclassListeners` annotations. + +[[events-exclude-default-listener-persist-example]] +.Excluding default event listner events +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-exclude-default-listener-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/events-exclude-default-listener-persist-example.sql[] +---- +==== + diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-persist-example.sql new file mode 100644 index 0000000000..f9e724622f --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-persist-example.sql @@ -0,0 +1,24 @@ +insert +into + Person + (createdOn, updatedOn, name, id) +values + (?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224] +-- binding parameter [2] as [TIMESTAMP] - [null] +-- binding parameter [3] as [VARCHAR] - [Vlad Mihalcea] +-- binding parameter [4] as [BIGINT] - [1] + +insert +into + Book + (createdOn, updatedOn, author_id, title, id) +values + (?, ?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246] +-- binding parameter [2] as [TIMESTAMP] - [null] +-- binding parameter [3] as [BIGINT] - [1] +-- binding parameter [4] as [VARCHAR] - [High-Performance Java Persistence] +-- binding parameter [5] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-update-example.sql b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-update-example.sql new file mode 100644 index 0000000000..36a7502af1 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-default-listener-update-example.sql @@ -0,0 +1,29 @@ +update + Person +set + createdOn=?, + updatedOn=?, + name=? +where + id=? + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224] +-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.316] +-- binding parameter [3] as [VARCHAR] - [Vlad-Alexandru Mihalcea] +-- binding parameter [4] as [BIGINT] - [1] + +update + Book +set + createdOn=?, + updatedOn=?, + author_id=?, + title=? +where + id=? + +-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246] +-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.317] +-- binding parameter [3] as [BIGINT] - [1] +-- binding parameter [4] as [VARCHAR] - [High-Performance Java Persistence 2nd Edition] +-- binding parameter [5] as [BIGINT] - [1] diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-exclude-default-listener-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-exclude-default-listener-persist-example.sql new file mode 100644 index 0000000000..37837d0bf4 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/events/extras/events-exclude-default-listener-persist-example.sql @@ -0,0 +1,11 @@ +insert +into + Publisher + (createdOn, updatedOn, name, id) +values + (?, ?, ?, ?) + +-- binding parameter [1] as [TIMESTAMP] - [null] +-- binding parameter [2] as [TIMESTAMP] - [null] +-- binding parameter [3] as [VARCHAR] - [Amazon] +-- binding parameter [4] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java new file mode 100644 index 0000000000..f8e5f4d6da --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java @@ -0,0 +1,34 @@ +package org.hibernate.userguide.events; + +import java.sql.Timestamp; +import javax.persistence.MappedSuperclass; + +/** + * @author Vlad Mihalcea + */ +//tag::events-default-listener-mapping-example[] +@MappedSuperclass +public abstract class BaseEntity { + + private Timestamp createdOn; + + private Timestamp updatedOn; + + public Timestamp getCreatedOn() { + return createdOn; + } + + void setCreatedOn(Timestamp createdOn) { + this.createdOn = createdOn; + } + + public Timestamp getUpdatedOn() { + return updatedOn; + } + + void setUpdatedOn(Timestamp updatedOn) { + this.updatedOn = updatedOn; + } +} +//end::events-default-listener-mapping-example[] + diff --git a/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener-orm.xml b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener-orm.xml new file mode 100644 index 0000000000..1ca0182f29 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener-orm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener.java b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener.java new file mode 100644 index 0000000000..2a9642d2e4 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListener.java @@ -0,0 +1,33 @@ +package org.hibernate.userguide.events; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +/** + * @author Vlad Mihalcea + */ +//tag::events-default-listener-mapping-example[] +public class DefaultEntityListener { + + public void onPersist(Object entity) { + if ( entity instanceof BaseEntity ) { + BaseEntity baseEntity = (BaseEntity) entity; + baseEntity.setCreatedOn( now() ); + } + } + + public void onUpdate(Object entity) { + if ( entity instanceof BaseEntity ) { + BaseEntity baseEntity = (BaseEntity) entity; + baseEntity.setUpdatedOn( now() ); + } + } + + private Timestamp now() { + return Timestamp.from( + LocalDateTime.now().toInstant( ZoneOffset.UTC ) + ); + } +} +//end::events-default-listener-mapping-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListenerTest.java b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListenerTest.java new file mode 100644 index 0000000000..f74570f01c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/events/DefaultEntityListenerTest.java @@ -0,0 +1,192 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.events; + +import javax.persistence.Entity; +import javax.persistence.ExcludeDefaultListeners; +import javax.persistence.ExcludeSuperclassListeners; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static junit.framework.TestCase.assertNull; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class DefaultEntityListenerTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Book.class, + Publisher.class + }; + } + + @Override + protected String[] getMappings() { + return new String[] { "org/hibernate/userguide/events/DefaultEntityListener-orm.xml" }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::events-default-listener-persist-example[] + Person author = new Person(); + author.setId( 1L ); + author.setName( "Vlad Mihalcea" ); + + entityManager.persist( author ); + + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( author ); + + entityManager.persist( book ); + //end::events-default-listener-persist-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::events-default-listener-update-example[] + Person author = entityManager.find( Person.class, 1L ); + author.setName( "Vlad-Alexandru Mihalcea" ); + + Book book = entityManager.find( Book.class, 1L ); + book.setTitle( "High-Performance Java Persistence 2nd Edition" ); + //end::events-default-listener-update-example[] + } ); + } + + @Test + public void testExclude() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::events-exclude-default-listener-persist-example[] + Publisher publisher = new Publisher(); + publisher.setId( 1L ); + publisher.setName( "Amazon" ); + + entityManager.persist( publisher ); + //end::events-exclude-default-listener-persist-example[] + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Publisher publisher = entityManager.find( Publisher.class, 1L ); + assertNull(publisher.getCreatedOn()); + } ); + } + + //tag::events-default-listener-mapping-example[] + @Entity(name = "Person") + public static class Person extends BaseEntity { + + @Id + private Long id; + + private String name; + + //Getters and setters omitted for brevity + //end::events-default-listener-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + //tag::events-default-listener-mapping-example[] + } + + @Entity(name = "Book") + public static class Book extends BaseEntity { + + @Id + private Long id; + + private String title; + + @ManyToOne + private Person author; + + //Getters and setters omitted for brevity + //end::events-default-listener-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Person getAuthor() { + return author; + } + + public void setAuthor(Person author) { + this.author = author; + } + //tag::events-default-listener-mapping-example[] + } + //end::events-default-listener-mapping-example[] + + //tag::events-exclude-default-listener-mapping-example[] + @Entity(name = "Publisher") + @ExcludeDefaultListeners + @ExcludeSuperclassListeners + public static class Publisher extends BaseEntity { + + @Id + private Long id; + + private String name; + + //Getters and setters omitted for brevity + //end::events-exclude-default-listener-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + //tag::events-exclude-default-listener-mapping-example[] + } + //end::events-exclude-default-listener-mapping-example[] +}