Есть ли способ сохранить @Entity с предопределенным значением @EmbeddedId, используя метод CrudRepository # save () Spring Data? - PullRequest
0 голосов
/ 18 апреля 2019

Я создаю новый сервис с целью идемпотентного потребления событий Kafka и сохранения данных в новой базе данных PostgreSQL.

Событие предоставит данные, которые будут использованы в составном ключе:

@Embeddable
public class MyCompositeKey implements Serializable {

    @Column(name="field1", nullable = false)
    private UUID field1;

    @Column(name="field2", nullable = false)
    private UUID field2;

    @Column(name="field3", nullable = false)
    private UUID field3;

... boilerplate Constructors/getters ...

И сущность будет ссылаться на нее через @EmbeddedId:

@Entity
@Table
public class MyEntity implements Serializable {

  @EmbeddedId private MyCompositeKey myCompositeKey;

... Columns/Constructors/getters ...

Когда событие используется, я хочу, чтобы spring-data-jpa был достаточно умен, чтобы знать, заменяем ли мы данные из существующего MyEntity или создаем новую строку.

Логика считалась достаточно безопасной, чтобы использовать метод CrudRepository#save, прежде чем исследовать ожидание логики в этом методе:

    @Transactional
    public <S extends T> S save(S entity) {
        if (this.entityInformation.isNew(entity)) {
            this.em.persist(entity);
            return entity;
        } else {
            return this.em.merge(entity);
        }
    }

Я дошел до того, что транзакции кажутся завершенными, но в таблице не сохраняются никакие записи.

Я подтвердил с помощью отладки, что вызов #save ответвляется к логике return this.em.merge(entity), упомянутой выше.

Я нашел только один, возможно, полезный пост в блоге [1] для подобного сценария, и я заблудился о том, куда идти дальше после того, как он, похоже, не решил проблему.

Единственный другой вариант, который я могу предвидеть, - вручную выполнить потенциальное выполнение трех запросов:

  1. findById
  2. , если существует, delete
  3. save

Компоненты

  • spring-boot-starter 2.0.6
  • spring-boot-starter-data-jpa 2.0.6
  • hibernate 5.2.x

Ссылки

[1] https://jivimberg.io/blog/2018/11/05/using-uuid-on-spring-data-jpa-entities/

1 Ответ

0 голосов
/ 18 апреля 2019

Хорошо, я нашел проблему. Весь этот дизайн работал нормально, это была конфигурация, которая отсутствовала.

Для некоторого контекста - Spring Boot, кажется, настраивает значения по умолчанию javax.sql.DataSource, значения по умолчанию javax.persistence.EntityManagerFactory и значения по умолчанию org.springframework.transaction.PlatformTransactionManager bean.

Мой контекст был настроен с компонентом javax.sql.DataSource, чтобы указать различие префикса конфигурации с использованием org.springframework.boot.context.properties.ConfigurationProperties.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {

  @Bean
  @ConfigurationProperties(prefix = "myservice.datasource")
  public DataSource dataSource() {
    return DataSourceBuilder.create().build();
  }
}

Мой контекст не добавил замены для зависимых компонентов javax.persistence.EntityManagerFactory и org.springframework.transaction.PlatformTransactionManager.

Исправление было добавить во все конфигурации. Из документов :

Вы должны создавать LocalContainerEntityManagerFactoryBean, а не EntityManagerFactory напрямую, поскольку первый также участвует в механизмах преобразования исключений в дополнение к созданию EntityManagerFactory.

Полученная конфигурация:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {

  @Bean
  @ConfigurationProperties(prefix = "myservice.datasource")
  public DataSource dataSource() {
    return DataSourceBuilder.create().build();
  }

  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setPackagesToScan("com.myservice");
    factory.setDataSource(dataSource());
    return factory;
  }

  @Bean
  public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager txManager = new JpaTransactionManager();
    txManager.setEntityManagerFactory(entityManagerFactory);
    return txManager;
  }
}
...