这是插入/更新的 Spring Data JDBC 问题的正确解决方案吗?

问题描述

问题:

当我尝试使用 Spring Data JDBC(在 Spring-boot 应用程序中)创建新实体“客户”时

@Data
public class Customer {
    @Id
    private String identifier;
    private String name;
}

使用 customerRepository:

@Repository
public interface CustomerRepository extends CrudRepository<Customer,String> {
}

为了测试:

@Test
public void givennewCustomer_shouldSaveCustomerInDataBase() {
    //given
    final Customer newCustomer = new Customer();
    newCustomer.setIdentifier("0002");
    newCustomer.setName("juan");
    //when
    Customer customerSaved = repository.save(newCustomer);
    //then
    then(customerSaved).isNotNull();
}

我收到此错误

Caused by: org.springframework.dao.IncorrectUpdateSemanticsDataAccessException: Failed to update entity [Customer(identifier=0002,name=juan)]. Id [0002] not found in database.
    at org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.updateWithoutVersion(JdbcAggregateChangeExecutionContext.java:370)
    at org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.executeUpdateRoot(JdbcAggregateChangeExecutionContext.java:115)
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:70)

我将 testcontainer 用于 postgresql 数据库和这个初始脚本:

create table customer(
    identifier varchar(30) primary key,name varchar(39));
insert into customer(identifier,name) values ('0001','kevin');

我希望持久性方式的行为类似于 JPA 中的 EntityManager.merge() 自动,有没有办法做到这一点?我有一个,但我不知道它是否最正确。

(完整的源代码(非常基本和直接)可用 https://github.com/FabianSR/spring_data_jdbc_example

建议的解决方案:

我已经为这个问题实现了一个可能的解决方案,但我不知道这是否是正确的方法(不使用 JPA)。

我已经让 Customer 类扩展了一个实现 Persistable 接口的类,我已经实现了它的 getId() 方法,以便它在 Customer 字段中搜索用 @Id 注释的字段并返回其值。我还添加了标志 isNew,认值为 true。

package com.example.model.core;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.domain.Persistable;
import java.util.stream.Stream;

@Data
public abstract class AbstractEntity<I> implements Persistable<I> {

    @Transient
    public boolean isNew = true;

    @Override
    public I getId() {
        return Stream.of(this.getClass().getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(Id.class))
                .map(field -> {
                    field.setAccessible(true);
                    return field;
                }).findFirst().map(field ->
                {
                    try {
                        return (I) field.get(this);
                    } catch (illegalaccessexception e) {
                        throw new RuntimeException(e);
                    }
                }).orElse(null);
    }
}

客户如下:

package com.example.model;

import com.example.model.core.AbstractEntity;
import lombok.Data;
import org.springframework.data.annotation.Id;

@Data
public class Customer extends AbstractEntity<String> {
    @Id
    private String identifier;
    private String name;
}

现在我在 CustomerRepository 接口中添加一个方法,在保存一个 Customer 之前,它首先搜索它是否存在,修改它的 isNew 属性并保存它(否则就保存它)

 package com.example.repository;

import com.example.model.Customer;
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends CrudRepository<Customer,String> {

    @Modifying
    default Customer merge(final Customer customer) {
        return this.findById(customer.getId()).map(
                c -> {
                    customer.setNew(false);
                    return this.save(customer);
                }
        ).orElse(this.save(customer));
    }
}

最后,在更改测试后,调用新的 'merge' 方法而不是 'save',它已经可以工作了:

   @Test
    public void givennewCustomer_shouldSaveCustomerInDataBase() {
        //given
        final Customer newCustomer = new Customer();
        newCustomer.setIdentifier("0002");
        newCustomer.setName("juan");
        //when
        Customer customerSaved = repository.merge(newCustomer);
        //then
        then(customerSaved).isNotNull();
    }

    @Test
    public void givenOldCustomer_shouldUpdateCustomerInDataBase() {
        //Given
        final Customer oldCustomer = repository.findById("0001").orElseThrow(AssertionError::new);
        oldCustomer.setName(oldCustomer.getName() + " hall");
        //when
        repository.merge(oldCustomer);
        //then
        then(repository.findById("0001").map(Customer::getName).map("kevin hall"::equals).orElse(false)).isTrue();
    }

(分支 https://github.com/FabianSR/spring_data_jdbc_example/tree/proposed_solution 中的代码

这是最好的解决方案还是更简单的解决方案?

解决方法

您得到的错误可能是您在保存前手动设置了 ID。尝试在没有标识符的情况下保存数据,以便稍后自动分配值,您可以使用 repository.save() 进行更新而不会出现任何问题。