JPA @EntityListener does not work as expected
Asked Answered
H

3

2

I am integrating Spring4 and Hibernate5, but there is a problem that I can't resolve. I use @EntityListener annotation on the BaseEntity class that is a super class for other business model. Also I use @MappedSuperclass on the BaseEntity. But it don't work!

Use Spring base annotation and run application successfully. Also I inserted a record to db. So I think my configuration of project is current.

Any body let me know why? Thanks very much.

This is BaseEntity class.

@MappedSuperclass
@EntityListeners(EntityListener.class)
public class BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false, updatable = false)
    private Date createDate;

    @Column(nullable = false)
    private Date modifyDate;

    public Long getId() {
        return id;
    }

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

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getModifyDate() {
        return modifyDate;
    }

    public void setModifyDate(Date modifyDate) {
        this.modifyDate = modifyDate;
    }
}

This is EntityListener class.

public class EntityListener {

  @PrePersist
  public void prePersist(BaseEntity entity) {
    entity.setCreateDate(new Date());
    entity.setModifyDate(new Date());
  }

  @PreUpdate
  public void preUpdate(BaseEntity entity) {
    entity.setModifyDate(new Date());
  }

}

The following is my project configuration base on Spring annotation.

@Configuration
@EnableWebMvc
//@ImportResource({ "classpath:xxxxx.xml" })
@PropertySources({
    @PropertySource("classpath:application.properties")
})
@ComponentScan({"com.yeager.admin.persistence","com.yeager.admin.web","com.yeager.admin.service","com.yeager.admin.common"})
@EnableAspectJAutoProxy
//@EnableRetry
public class AppConfig {

    @Bean(name = "multipartResolver")
    public CommonsMultipartResolver getResolver() throws IOException {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        return resolver;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public static SpringContext springContext() {
        return new SpringContext();
    }

}

The main configuration about DAL like this,

@Configuration
@EnableTransactionManagement
@PropertySource({"classpath:persistence-mysql.properties"})
public class PersistenceConfig {

    @Autowired
    private Environment env;

    public PersistenceConfig() {
        super();
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory() {

        final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan("com.yeager.admin.persistence.entity");
        sessionFactory.setHibernateProperties(hibernateProperties());


        return sessionFactory;
    }

    @Bean
    public DataSource dataSource() {
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        try {
            comboPooledDataSource.setDriverClass(env.getProperty("jdbc.driver"));
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        comboPooledDataSource.setJdbcUrl(env.getProperty("jdbc.url"));
        comboPooledDataSource.setUser(env.getProperty("jdbc.username"));
        comboPooledDataSource.setPassword(env.getProperty("jdbc.password"));
        comboPooledDataSource.setInitialPoolSize(Integer.valueOf(env.getProperty("datasource.pool.initialPoolSize")));

        return comboPooledDataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        final HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(sessionFactory().getObject());
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    private final Properties hibernateProperties() {

        final Properties hibernateProperties = new Properties();

        hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
        hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
        hibernateProperties.setProperty("hibernate.generate_statistics",env.getProperty("hibernate.generate_statistics"));
        hibernateProperties.setProperty("hibernate.jdbc.fetch_size", env.getProperty("hibernate.jdbc.fetch_size"));
        hibernateProperties.setProperty("hibernate.jdbc.batch_size", env.getProperty("hibernate.jdbc.batch_size"));
        hibernateProperties.setProperty("hibernate.max_fetch_depth", env.getProperty("hibernate.max_fetch_depth"));
        hibernateProperties.setProperty("hibernate.cache.use_second_level_cache",env.getProperty("hibernate.cache.use_second_level_cache"));
        hibernateProperties.setProperty("hibernate.cache.use_query_cache",env.getProperty("hibernate.cache.use_query_cache"));
//      hibernateProperties.setProperty("hibernate.cache.provider_class",env.getProperty("hibernate.cache.provider_class"));
        hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update");

        return hibernateProperties;
    }

}

I use LocalSessionFactoryBean class of Hibernate rather than EntityManager class of JPA. I wonder if this cause ?

--------------- 6.19 --------------

I am wrong. I don't should use @EntityListener annotation base on Spring LocalSessionFactoryBean class. For hibernate5, there is a special configuration way. http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners Now, I modify my code as following,

@Component
public class EntityEventListener {

    @Autowired
    private SessionFactory sessionFactory;

    @PostConstruct
    public void registerListeners(){
        EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
        eventListenerRegistry.prependListeners(EventType.PRE_INSERT, PreInsertEntityListener.class);
    }

}

PreInsertEntityListener

public class PreInsertEntityListener implements PreInsertEventListener {
    @Override
    public boolean onPreInsert(PreInsertEvent event) {
//        if (event.getEntity() instanceof AdminUser){
//            ((AdminUser) event.getEntity()).setCreateDate(new Date());
//            ((AdminUser) event.getEntity()).setModifyDate(new Date());
//        }
        BaseEntity baseEntity = (BaseEntity) event.getEntity();
        baseEntity.setCreateDate(new Date());
        baseEntity.setModifyDate(new Date());
        return false;
    }
}

But, I have a other problem. I read hibernate doc and search many information about this. My code don't work already when I insert entity data.

Please help me, thanks!

Hydrolyze answered 17/6, 2017 at 8:11 Comment(2)
Can you paste the relevant code here? Also what do you mean by "doesn't work"? Are your listener callbacks not invoked?Achorn
OK. I will paste BaseEntity class and EntityListener class.Hydrolyze
Y
2

Although you did neither post the concrete / derived entity nor the business code to persist it, the code you posted seems correct.

For giving it a small test I added a generated UID to the super class and created a concrete entity:

import javax.persistence.Entity;

@Entity
public class DerivedEntity extends BaseEntity {

    private static final long serialVersionUID = -6441043639437893962L;

}

And since you mentioned Spring, here is a Spring Data JPA repository to save it:

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DerivedEntityRepository extends CrudRepository<DerivedEntity, Long> {

}

This small test should show that the (@PrePersist) listener works:

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@Transactional
@SpringBootTest
public class DerivedEntityRepositoryTests {

    @Autowired
    private DerivedEntityRepository derivedEntityRepository;

    @Test
    public void insertDerivedEntity() {
        DerivedEntity entity = new DerivedEntity();
        entity = derivedEntityRepository.save(entity);
        assertThat(entity.getCreateDate()).isNotNull();
    }

}

And just to mention it, if you don't want to enhance your custom listener in future, the existing Spring Data JPA AuditingEntityListener does exactly what you are doing at the moment (and even more). In this case you could just enhance a @Configuration class with @EnableJpaAuditing and modify your BaseEntity as following:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {

    // ...

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private Date createDate;

    @LastModifiedDate
    @Column(nullable = false)
    private Date modifyDate;

    // ...
}

That would make your custom EntityListener dispensable.

Just take a look Spring JPA Auditing for more information. If you want to enhance auditing with Hibernate, try Hibernate Envers.

Yean answered 17/6, 2017 at 17:50 Comment(4)
Hi Kevin, thanks for your answer about Spring JPA Auditing and Hibernate Envers. But If I want to use JPA @EntityListeners annotation base Hiberante, what should I do? Maybe I don't want to integrate Spring Data JPA.Hydrolyze
Hi Kevin, I read Spring JPA Auditing and Hibernate Envers 1 minute ago. I think it is not my point that make a listener when entity creation, remove and update. I sure I should use @EntityListeners annotation to implement. Could you help me? Thanks very much.Hydrolyze
I add more information about Spring configuration. It's helpful for you.Hydrolyze
Seems you solved your problem(s) on your own. That's maybe the best way. But I'm glad I could help a little. :)Yean
H
0

Thanks very much for everyone. I have resolved this problem. I will share my solution, hope it's helpful for you if you are doing same things.

First, my starting point is wrong. Because I use JPA before, so I use acquiescently @EntityListener annotation when I integrate Spring4 and Hibernate5. Then, I read Hibernate doc and many relevant article and found there is a new way to implement entity listener. See hibernate doc

Finally, my solution is following.

This is my BaseEntity class.

@MappedSuperclass
public class BaseEntity implements Serializable {

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

    @Column(nullable = false, updatable = false)
    private Date createDate;

    @Column(nullable = false)
    private Date modifyDate;

    public Long getId() {
        return id;
    }

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

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getModifyDate() {
        return modifyDate;
    }

    public void setModifyDate(Date modifyDate) {
        this.modifyDate = modifyDate;
    }
}

First of all, you need to define EntityListener class.

public class EntityListener implements PreInsertEventListener, PreUpdateEventListener {

    private static final String CREATE_DATE_PROPERTY = "createDate";

    private static final String MODIFY_DATE_PROPERTY = "modifyDate";

    @Override
    public boolean onPreInsert(PreInsertEvent event) {

        if (event.getEntity() instanceof BaseEntity){
            //property name of entity
            String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
            //property value of entity
            Object[] state = event.getState();
            for (int i = 0; i < propertyNames.length ; i ++) {
                if (CREATE_DATE_PROPERTY.equals(propertyNames[i]) || MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
                    state[i] = new Date();
                }
            }
        }

        return false;
    }

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {

        if (event.getEntity() instanceof BaseEntity){
            //property name of entity
            String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
            //property value of entity
            Object[] state = event.getState();
            for (int i = 0; i < propertyNames.length ; i ++) {
                if (MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
                    state[i] = new Date();
                }
            }
        }

        return false;
    }
}

Last, you should register entity event listener.

@SuppressWarnings("unchecked")
@Component
public class EntityEventListenerRegistry {

    @Autowired
    private SessionFactory sessionFactory;

    /**
     * EventListenerRegistry:http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners
     */
    @PostConstruct
    public void registerListeners(){
        EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
        eventListenerRegistry.prependListeners(EventType.PRE_INSERT, EntityListener.class);
        eventListenerRegistry.prependListeners(EventType.PRE_UPDATE, EntityListener.class);
    }

}
Hydrolyze answered 19/6, 2017 at 6:51 Comment(1)
So you end saying that the annotations don't work isn't it? Does it depend on JPA or spring(boot) version?Leg
M
0

I ran into this same issue and in my case the listener defined with @EntityListeners was referring to class (not in the same classloader) in another package and it wasn't being scanned. After adding the class to my persistence context it began working as expected.

So always be sure that any classes related to the persistence are added to the persistence context.

Melamie answered 16/3, 2018 at 15:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.