nguyenngoc101
nguyenngoc101

Reputation: 1211

JPA EntityManager can not flush in PostInsertEventListener

I'm trying to write audit log using Hibernate event listeners such as: PostInsertEventListener, PostUpdateEventListener and PostDeleteEventListener. But I have a problem as using entityManager to flush data from persistence context to Database in these listeners:

Here is the source code:

EntityA.class

@Entity
@Table(name = "ENTITY_A")
public class EntityA implements Serializable {
  private static final long serialVersionUID = -8674903027075338289L;

  @Id
  @Column(name = "ENTITY_A_ID")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @SortableField
  private Long buStepId;


  @Column(name = "ENTITY_A_CODE", unique = true)
  private String buStepCode;


  @Column(name = "ENTITY_A_NAME")
  private String buStepName;

  @OneToMany(mappedBy = "buStep", fetch = FetchType.LAZY)
  private List<BuEvent> BuEvents;

  @OneToMany(mappedBy = "buStep", fetch = FetchType.LAZY)
  private Set<BuEventFlow> BuEventFlows;

  @ManyToOne
  @JoinColumn(name = "GS_STATUS_ID", referencedColumnName = "STATUS_ID")
  private RefStatus refGsStatus;

  @ManyToOne
  @JoinColumn(name = "GT_STATUS_ID", referencedColumnName = "STATUS_ID")
  private RefStatus refGtStatus;

  @ManyToOne
  @JoinColumn(name = "GS_VERSION_ID", referencedColumnName = "VERSION_ID")
  private RefVersion refGsVersion;

  @ManyToOne
  @JoinColumn(name = "GT_VERSION_ID", referencedColumnName = "VERSION_ID")
  @AuditableField(name = "GT Version")
  private RefVersion refGtVersion;

  @ManyToOne
  @JoinColumn(name = "EVO_TYPE_ID")
  private RefEvoType refEvoType;

  @Column(name = "TURN")
  private Long turn;

  @ManyToOne
  @JoinColumn(name = "step_family_id", nullable = true)
  private RefStepFamily buStepFamily;

  @OneToMany(mappedBy = "buStep", fetch = FetchType.LAZY)
  private List<BuStepFlow> buStepFlows;

  @Transient
  private Long stepFamilyId;
}

BusinessService.java

@Service
public class BusinessService {
  @PersistenceContext
  protected EntityManager entityManager;

  public void createEntityA() {
    EntityA entityA = createPojoEntityA();
    entityManager.persist(entityA);
  }
}

EntityEventListenerRegistry.java:

@Component
public class EntityEventListenerRegistry implements 
PostInsertEventListener, PostUpdateEventListener, 
PostDeleteEventListener {

  @PersistenceContext
  EntityManager entityManager;

  @PostConstruct
  protected void init() {
    HibernateEntityManagerFactory hibernateEntityManagerFactory = (HibernateEntityManagerFactory) this.emf;
    SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl) hibernateEntityManagerFactory.getSessionFactory();
    EventListenerRegistry registry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);
    registry.appendListeners(EventType.POST_INSERT, this);
    registry.appendListeners(EventType.POST_UPDATE, this);
    registry.appendListeners(EventType.POST_DELETE, this);
  }

  @Override
  public void onPostInsert(PostInsertEvent event) {
    // Do something with entityA before
    entityManager.flush();
    // Do something with entityA after
  }
...
}

Then an exception happens in method onPostInsert:

org.hibernate.AssertionFailure: null id in EntityA entry (don't flush the Session after an exception occurs)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:60)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:175)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:135)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1300)

It took me one day to debug, but I still don't know the reason why. Could you please help me explain it?

Upvotes: 0

Views: 1567

Answers (1)

Aleksandr Semyannikov
Aleksandr Semyannikov

Reputation: 1452

I see that some people already told you about limitations in Hibernate. Documentation (Hibernate User Guide) says:

A callback method must not invoke EntityManager or Query methods!

Anyway, for solving auditing task you can use Hibernate Envers: http://hibernate.org/orm/envers/

Also you can read about auditing there: https://www.baeldung.com/database-auditing-jpa

  • The pure JPA approach is the most basic and consists of using lifecycle callbacks. However, you are only allowed to modify the non-relationship state of an entity. This makes the @PreRemove callback useless for our purposes, as any settings you’ve made in the method will be deleted then along with the entity.
  • Envers is a mature auditing module provided by Hibernate. It is highly configurable and lacks the flaws of the pure JPA implementation. Thus, it allows us to audit the delete operation, as it logs into tables other than the entity’s table.

  • The Spring Data JPA approach abstracts working with JPA callbacks and provides handy annotations for auditing properties. It’s also ready for integration with Spring Security. The disadvantage is that it inherits the same flaws of the JPA approach, so the delete operation cannot be audited.

Upvotes: 1

Related Questions