16

I'm trying to get Spring Data Auditing to work in my Spring 3.2.8 / Spring Data 1.5 / Hibernate 4 project.

As per the Spring Data Auditing docs, I've added the @CreatedBy, etc annotations to my entities, created by AuditorAware implementation, and instantiated it from within my JavaConfig. However, it never seems to fire.

I find the docs a little confusing. It appears that the JavaConfig entry replaces the xml entry, but I am not sure.

I don't currently have any orm.xml file in my application. To be entirely honest, I'm not even sure where/how to configure it, or why I need it. All my entities are using annotations. I have tried adding @EntityListeners(AuditingEntityListener.class) to the entity, but that has not helped.

My current entity manager is defined without a persistence.xml file:

    <!--  entity manager -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
        <property name="packagesToScan" value="com.ia.domain"/>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
                <prop key="hibernate.query.substitutions">true '1', false '0'</prop>
                <prop key="hibernate.generate_statistics">true</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <prop key="hibernate.connection.charSet">UTF-8</prop>
            </props>
        </property>
    </bean>

JavaConfig:

@Configuration
@EnableJpaAuditing
public class AuditConfig {
    @Bean
    public AuditorAware<User> auditorProvider(){
        return new SpringSecurityAuditorAware();
    }
}

Entity:

@EntityListeners({AuditingEntityListener.class})
@Entity
public class User
{

  @TableGenerator(name="UUIDGenerator", pkColumnValue="user_id", table="uuid_generator", allocationSize=1)
  @Id
  @GeneratedValue(strategy=GenerationType.TABLE, generator="UUIDGenerator")
  @Column(name="id")
  private Long id;

  @NotNull
  private String username;

  @CreatedDate
  @NotNull
  @Temporal(TemporalType.TIMESTAMP)
  @Column(name="created_date", nullable=false)
  private Date createdDate;

  @LastModifiedDate
  @NotNull
  @Temporal(TemporalType.TIMESTAMP)
  @Column(name="last_modified_date", nullable=false)
  private Date lastModifiedDate;

  @CreatedBy
  @ManyToOne(fetch=FetchType.LAZY)
  @JoinColumn(name="created_by")
  private User createdBy;

  @LastModifiedBy
  @ManyToOne(fetch=FetchType.LAZY)
  @JoinColumn(name="last_modified_by")
  private User lastModifiedBy;
  private String password;
  private Boolean enabled;


...
}

I've put a breakpoint in my SpringSecurityAuditorAware class but it is never being hit.

Do I still need an orm.xml file? How/where is this referenced from the EntityManager?

Eric B.
  • 23,425
  • 50
  • 169
  • 316
  • How do you connect the XML config to the JavavConfig? Any chance you share the project in a GitHub repo or the like? I could sucessfully alter the [auditing sample project](https://github.com/spring-projects/spring-data-examples/tree/master/jpa/spring-data-jpa-java8-auditing) to use `@EntityListeners` on `AbstractEntity` and removing the orm.xml from the project. Would you mind checking the differences between the sample project and yours? – Oliver Drotbohm Mar 13 '14 at 08:32
  • @OliverGierke My XML is part of my applicationContext file which is loaded by the Spring Listener defined in web.xml. I will try to put together a sample project and push to github if I can reproduce the problem at a smaller level. – Eric B. Mar 13 '14 at 13:40
  • @OliverGierke I spent a good part of the morning trying to reproduce the problem in a sample application. Eventually I was able to reproduce the problem, but I think I narrowed it down to being a jRebel issue. I am not yet 100% certain of this, but that's where it seems to be heading. I will keep you posted. – Eric B. Mar 13 '14 at 18:00
  • 3
    @OliverGierke Turns out it was a jRebel issue. I had been using aspectJ to compile-time-weave in the `@EntityListeners()` but when jRebel was loading the classes, it was using the classpath pointing to the original entities and not the augmented classes. I had to remove the rebel.xml file that the jRebel maven plugin generated, and everything seems to be working properly now. – Eric B. Mar 18 '14 at 16:38
  • Hey @EricB. We are using `Jrebel` and facing the same issue. Any idea to make `jrebel` play nice with `Spring Data Auditing` – oak May 13 '15 at 09:34
  • @oak Like I said, I had to remove the rebel.xml file. In reality, I wasn't using it and didn't see its value, so removing it from my build made no difference to me. – Eric B. May 13 '15 at 15:01
  • thanks for reply we are using rebel.xml to realtime update the dev server so it helps us. any idea about that ? – oak May 13 '15 at 15:38
  • @oak - unfortunately - no. This was a while ago and I've since moved on from that project so can't really help out. You should try the jRebel support; they might be able to point you in the right direction. – Eric B. May 13 '15 at 17:16

3 Answers3

10

Short version: No

As of JPA 2.0, it is not possible to define such entity listener without an XML file (orm.xml).

JPA 2.0:

Default entity listeners—entity listeners that apply to all entities in the persistence unit—can be specified by means of the XML descriptor. (p.93)

Long version: The workaround...

If all entities in your project extends an AbstractAuditable superclass then you can put @EntityListeners({AuditingEntityListener.class}) on AbstractAuditable. Listeners attached to an entity class are inherited by its subclasses.

JPA 2.0:

Multiple entity classes and mapped superclasses in an inheritance hierarchy may define listener classes and/or lifecycle callback methods directly on the class. (p.93)

Note that a subclass can exclude explicitly an inherited listener using the @ExcludeSuperclassListeners annotation.

There is one last interesting footnote from the spec I'd like to quote:

JPA 2.0:

Excluded listeners may be reintroduced on an entity class by listing them explicitly in the EntityListeners annotation or XML entity-listeners element. (Footnote [45] p.97)


Here is some code for illustrating the workaround:

AbstractAuditableEntity.java

import java.util.Date;

import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@MappedSuperclass
@EntityListeners({AuditingEntityListener.class}) // AuditingEntityListener will also audit any subclasses of AbstractAuditable...
public abstract class AbstractAuditableEntity {
    @Id
    @GeneratedValue
    private Long id;

    @CreatedDate
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;
}

MyEntity.java

@Entity
public abstract class MyEntity extends AbstractAuditableEntity {

}

I think an interface Auditable may be used (@EntityListeners can appear on an interface) instead of an AbstractAuditable class but I didn't try...


Reference: JSR-000317 Java Persistence 2.0 - Final Release

Stephan
  • 41,764
  • 65
  • 238
  • 329
7

Using Stephan's answer, https://stackoverflow.com/a/26240077/715640,

I got this working using a custom listener.

@Configurable
public class TimestampedEntityAuditListener {

    @PrePersist
    public void touchForCreate(AbstractTimestampedEntity target) {
        Date now = new Date();
        target.setCreated(now);
        target.setUpdated(now);
    }

    @PreUpdate
    public void touchForUpdate(AbstractTimestampedEntity target) {
        target.setUpdated(new Date());
    }
}

And then referencing it in my base class:

@MappedSuperclass
@EntityListeners({TimestampedEntityAuditListener.class})
public abstract class AbstractTimestampedEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private Long id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date created;

    @Temporal(TemporalType.TIMESTAMP)
    private Date updated;

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

FWIW, I'm using this in a spring-boot project, without an orm.xml file.

Community
  • 1
  • 1
Justin Smith
  • 413
  • 6
  • 12
  • Can you explicit what `@CreatedDate` and `@LastModifiedDate` annotations are in your post? – Stephan Apr 03 '15 at 05:50
  • @Stephan I'm not sure what you mean. Looking at it again, those annotations may not even be needed with this approach, since the custom audit class is not using the annotations at all, just the abstract class. – Justin Smith Apr 03 '15 at 13:36
  • If those annotations may not even be needed, why are they in the code? – Stephan Apr 03 '15 at 13:57
  • 1
    Updated, thanks for helping me get on the right track. – Justin Smith Apr 04 '15 at 02:47
4

In 1.9 of spring data you can enable JPA audits with a couple annotations.

From the docs - http://docs.spring.io/spring-data/jpa/docs/1.9.4.RELEASE/reference/html/#jpa.auditing

Using the @EntityListeners(AuditingEntityListener.class) annotation to enable class by class audits. I use it in a base class.

You'll also need @EnableJpaAuditing on a @Configuration class to enable audits in general.

denov
  • 11,180
  • 2
  • 27
  • 43