【问题标题】:JPA @EntityListener does not work as expectedJPA @EntityListener 无法按预期工作
【发布时间】:2017-11-20 00:02:13
【问题描述】:

我正在集成 Spring4 和 Hibernate5,但是有一个我无法解决的问题。 我在 BaseEntity 类上使用 @EntityListener 注释,该类是其他业务模型的超类。 我还在 BaseEntity 上使用 @MappedSuperclass。 但它不起作用!

使用 Spring 基础注解并成功运行应用程序。 我还向数据库插入了一条记录。 所以我认为我的项目配置是最新的。

有人告诉我为什么吗? 非常感谢。

这是 BaseEntity 类。

@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;
    }
}

这是 EntityListener 类。

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());
  }

}

以下是我基于Spring注解的项目配置。

@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();
    }

}

关于DAL的主要配置是这样的,

@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;
    }

}

我使用 Hibernate 的 LocalSessionFactoryBean 类而不是 JPA 的 EntityManager 类。不知道是不是这个原因?

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

我错了。我不应该在 Spring LocalSessionFactoryBean 类上使用 @EntityListener 注释。 对于hibernate5,有一种特殊的配置方式。 http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners 现在,我修改我的代码如下,

@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;
    }
}

但是,我还有一个问题。 我阅读了休眠文档并搜索了许多有关此的信息。当我插入实体数据时,我的代码已经不能工作了。

请帮帮我,谢谢!

【问题讨论】:

  • 能把相关代码贴在这里吗?另外,“不起作用”是什么意思?你的监听器回调没有被调用吗?
  • 好的。我将粘贴 BaseEntity 类和 EntityListener 类。

标签: spring hibernate jpa entitylisteners


【解决方案1】:

虽然您既没有发布具体/派生实体也没有发布业务代码来持久化它,但您发布的代码似乎是正确的。

为了给它一个小测试,我向超类添加了一个生成的 UID 并创建了一个具体实体:

import javax.persistence.Entity;

@Entity
public class DerivedEntity extends BaseEntity {

    private static final long serialVersionUID = -6441043639437893962L;

}

既然你提到了 Spring,这里有一个 Spring Data JPA 存储库来保存它:

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

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

}

这个小测试应该表明 (@PrePersist) 监听器工作正常:

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();
    }

}

顺便提一下,如果您将来不想增强自定义侦听器,现有的 Spring Data JPA AuditingEntityListener 完全可以满足您目前正在做的事情(甚至更多)。在这种情况下,您可以使用 @EnableJpaAuditing 增强 @Configuration 类并修改您的 BaseEntity 如下:

@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;

    // ...
}

这将使您的自定义 EntityListener 可有可无。

请查看Spring JPA Auditing 了解更多信息。如果您想使用 Hibernate 增强审计功能,请尝试 Hibernate Envers

【讨论】:

  • 嗨 Kevin,感谢您对 Spring JPA 审计和 Hibernate Envers 的回答。但是如果我想使用 JPA @EntityListeners 注释库 Hiberante,我该怎么办?也许我不想集成 Spring Data JPA。
  • 嗨 Kevin,我在一分钟前阅读了 Spring JPA Auditing 和 Hibernate Envers。我认为在实体创建、删除和更新时做一个监听器不是我的意思。我确定我应该使用@EntityListeners 注释来实现。你可以帮帮我吗?非常感谢。
  • 我添加了更多关于 Spring 配置的信息。对你有帮助。
  • 看来你自己解决了你的问题。这也许是最好的办法。但我很高兴能帮上一点忙。 :)
【解决方案2】:

非常感谢大家。我已经解决了这个问题。 我将分享我的解决方案,希望对您做同样的事情有所帮助。

首先,我的出发点是错误的。因为我之前用过JPA,所以在集成Spring4和Hibernate5的时候默认使用@EntityListener注解。 然后,我阅读了 Hibernate 文档和许多相关文章,发现有一种实现实体监听器的新方法。 See hibernate doc

最后,我的解决方案如下。

这是我的 BaseEntity 类。

@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;
    }
}

首先,你需要定义EntityListener类。

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;
    }
}

最后,你应该注册实体事件监听器。

@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);
    }

}

【讨论】:

  • 所以你最后说注释不起作用不是吗?它取决于 JPA 还是 spring(boot) 版本?
【解决方案3】:

我遇到了同样的问题,在我的情况下,使用 @EntityListeners 定义的侦听器指的是另一个包中的类(不在同一个类加载器中)并且没有被扫描。将类添加到我的持久性上下文后,它开始按预期工作。

因此,请始终确保将任何与持久性相关的类添加到持久性上下文中。

【讨论】:

    猜你喜欢
    • 2013-12-23
    • 2014-12-09
    • 2016-01-13
    • 2020-09-21
    • 2011-08-17
    • 2012-04-29
    • 2021-08-12
    • 2019-02-04
    • 2014-12-02
    相关资源
    最近更新 更多