Spring Boot JPA中,带有@EmbeddedId注解的实体 – findById方法不起作用。

huangapple go评论49阅读模式
英文:

Spring boot JPA, entity with @EmbeddedId - findyById method is not working

问题

我正在尝试使用CrudRepository的findById方法通过其ID从数据库中获取对象。主实体的ID是一个复合键,由一个包含所有属性的@Embeddable类使用。

以下是我当前实现的详细信息:

这是主实体类(InitialMoneyCount):

@Entity
@Getter
@Setter
@EqualsAndHashCode
public class InitialMoneyCount {
    @EmbeddedId
    @NonNull
    private InitialMoneyCountId initialMoneyCountId;
    @NonNull
    private BigDecimal amount;
    @NonNull
    private BigDecimal amountSystemCurrency;
    @NonNull
    private String currency;

    // 默认构造函数
    public InitialMoneyCount() {
    }

    // 带有所有属性的构造函数
    public InitialMoneyCount(InitialMoneyCountId initialMoneyCountId, BigDecimal amount,
                             BigDecimal amountSystemCurrency, String currency) {
        this.initialMoneyCountId = initialMoneyCountId;
        this.amount = amount;
        this.amountSystemCurrency = amountSystemCurrency;
        this.currency = currency;
    }

    // 构建器
    public static InitialMoneyCountBuilder builder() {
        return new InitialMoneyCountBuilder();
    }

    public static class InitialMoneyCountBuilder {
        private InitialMoneyCountId initialMoneyCountId;
        private BigDecimal amount;
        private BigDecimal amountSystemCurrency;
        private String currency;

        public InitialMoneyCountBuilder initialMoneyCountId(InitialMoneyCountId initialMoneyCountId) {
            this.initialMoneyCountId = initialMoneyCountId;
            return this;
        }

        public InitialMoneyCountBuilder amount(BigDecimal amount) {
            this.amount = amount;
            return this;
        }

        public InitialMoneyCountBuilder amountSystemCurrency(BigDecimal amountSystemCurrency) {
            this.amountSystemCurrency = amountSystemCurrency;
            return this;
        }

        public InitialMoneyCountBuilder currency(String currency) {
            this.currency = currency;
            return this;
        }

        public InitialMoneyCount build() {
            return new InitialMoneyCount(initialMoneyCountId, amount, amountSystemCurrency, currency);
        }
    }
}

这是主键@Embeddable类:

@EqualsAndHashCode
@Embeddable
@Getter
public class InitialMoneyCountId implements Serializable {
    @NonNull
    private UUID posTerminalId;
    @NonNull
    private UUID cashierId;
    @NonNull
    private UUID cashDrawerId;
    @NonNull
    private LocalDate bookingDate;
    @NonNull
    private Integer bookingPeriod;
    @NonNull
    private UUID paymentMethodId;

    // 默认构造函数
    public InitialMoneyCountId() {
    }

    // 带有所有属性的构造函数
    public InitialMoneyCountId(UUID posTerminalId, UUID cashierId, UUID cashDrawerId,
                               LocalDate bookingDate, Integer bookingPeriod, UUID paymentMethodId) {
        this.posTerminalId = posTerminalId;
        this.cashierId = cashierId;
        this.cashDrawerId = cashDrawerId;
        this.bookingDate = bookingDate;
        this.bookingPeriod = bookingPeriod;
        this.paymentMethodId = paymentMethodId;
    }

    // 构建器
    public static InitialMoneyCountIdBuilder builder() {
        return new InitialMoneyCountIdBuilder();
    }

    public static class InitialMoneyCountIdBuilder {
        private UUID posTerminalId;
        private UUID cashierId;
        private UUID cashDrawerId;
        private LocalDate bookingDate;
        private Integer bookingPeriod;
        private UUID paymentMethodId;

        public InitialMoneyCountIdBuilder posTerminalId(UUID posTerminalId) {
            this.posTerminalId = posTerminalId;
            return this;
        }

        public InitialMoneyCountIdBuilder cashierId(UUID cashierId) {
            this.cashierId = cashierId;
            return this;
        }

        public InitialMoneyCountIdBuilder cashDrawerId(UUID cashDrawerId) {
            this.cashDrawerId = cashDrawerId;
            return this;
        }

        public InitialMoneyCountIdBuilder bookingDate(LocalDate bookingDate) {
            this.bookingDate = bookingDate;
            return this;
        }

        public InitialMoneyCountIdBuilder bookingPeriod(Integer bookingPeriod) {
            this.bookingPeriod = bookingPeriod;
            return this;
        }

        public InitialMoneyCountIdBuilder paymentMethodId(UUID paymentMethodId) {
            this.paymentMethodId = paymentMethodId;
            return this;
        }

        public InitialMoneyCountId build() {
            return new InitialMoneyCountId(posTerminalId, cashierId, cashDrawerId,
                    bookingDate, bookingPeriod, paymentMethodId);
        }
    }
}

这是JpaRepository接口的定义:

@Repository
public interface InitialMoneyCountRepository extends JpaRepository<InitialMoneyCount, InitialMoneyCountId> {}

这是findById(InitialMoneyCountId id)的测试:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = "spring.h2.console.enabled=true")
public class InitialMoneyCountRepositoryTest {
    @Autowired
    SampleDataGenerator sampleDataGenerator;
    @Autowired
    InitialMoneyCountRepository initialMoneyCountRepository;

    @AfterEach
    public void resetDatabase_AfterEach() {
        initialMoneyCountRepository.deleteAll();
    }

    @After
    public void resetDatabase_After() {
        initialMoneyCountRepository.deleteAll();
    }

    @Before
    public void resetDatabase_Before() {
        initialMoneyCountRepository.deleteAll();
    }

    @Test
    public void findById_success() {
        InitialMoneyCountId initialMoneyCountId = InitialMoneyCountId.builder()
                .posTerminalId(UUID.randomUUID())
                .cashDrawerId(UUID.randomUUID())
                .cashierId(UUID.randomUUID())
                .bookingDate(LocalDate.now())
                .bookingPeriod(5)
                .paymentMethodId(UUID.randomUUID())
                .build();
        InitialMoneyCount initialMoneyCount = InitialMoneyCount.builder()
                .initialMoneyCountId(initialMoneyCountId)
                .amount(BigDecimal.valueOf(555.55))
                .amountSystemCurrency(BigDecimal.valueOf(55.555))
                .currency(Currency.EUR.value())
                .build();
        InitialMoneyCount savedInitialMoneyCount = initialMoneyCountRepository.save(initialMoneyCount);
        Optional<InitialMoneyCount> imc = initialMoneyCountRepository.findById(initialMoneyCountId);
        Assertions.assertTrue(imc.isPresent());
    }
}

然而,我无法通过其ID找到对象。这是我得到的异常:

expected: <true> but was: <false>
Comparison Failure:
Expected :true
Actual   :false

任何关于我在这里遗漏了什么的想法吗?

我认为CrudRepository的findById方法对于@Embeddable的复合ID是有效的,正确吗?

英文:

I am trying to use the CrudRepository's findById method to get an object from the database by it's id. The id of the main entity is a composite key, utilized by an @Embeadable class including all the properties.

Here are the details of my current implementation:

Here is the main entity class (InitialMoneyCount):

@Entity
@Getter
@Setter
@EqualsAndHashCode
public class InitialMoneyCount {
@EmbeddedId
@NonNull
private InitialMoneyCountId initialMoneyCountId;
@NonNull
private BigDecimal amount;
@NonNull
private BigDecimal amountSystemCurrency;
@NonNull
private String currency;
// Default constructor
public InitialMoneyCount() {
}
// Constructor with all properties
public InitialMoneyCount(InitialMoneyCountId initialMoneyCountId, BigDecimal amount,
BigDecimal amountSystemCurrency, String currency) {
this.initialMoneyCountId = initialMoneyCountId;
this.amount = amount;
this.amountSystemCurrency = amountSystemCurrency;
this.currency = currency;
}
// Builder
public static InitialMoneyCountBuilder builder() {
return new InitialMoneyCountBuilder();
}
public static class InitialMoneyCountBuilder {
private InitialMoneyCountId initialMoneyCountId;
private BigDecimal amount;
private BigDecimal amountSystemCurrency;
private String currency;
public InitialMoneyCountBuilder initialMoneyCountId(InitialMoneyCountId initialMoneyCountId) {
this.initialMoneyCountId = initialMoneyCountId;
return this;
}
public InitialMoneyCountBuilder amount(BigDecimal amount) {
this.amount = amount;
return this;
}
public InitialMoneyCountBuilder amountSystemCurrency(BigDecimal amountSystemCurrency) {
this.amountSystemCurrency = amountSystemCurrency;
return this;
}
public InitialMoneyCountBuilder currency(String currency) {
this.currency = currency;
return this;
}
public InitialMoneyCount build() {
return new InitialMoneyCount(initialMoneyCountId, amount, amountSystemCurrency, currency);
}
}
}

Here is the primary key @Embeddable class:

@EqualsAndHashCode
@Embeddable
@Getter
public class InitialMoneyCountId implements Serializable {
@NonNull
private UUID posTerminalId;
@NonNull
private UUID cashierId;
@NonNull
private UUID cashDrawerId;
@NonNull
private LocalDate bookingDate;
@NonNull
private Integer bookingPeriod;
@NonNull
private UUID paymentMethodId;
// Default constructor
public InitialMoneyCountId() {
}
// Constructor with all properties
public InitialMoneyCountId(UUID posTerminalId, UUID cashierId, UUID cashDrawerId,
LocalDate bookingDate, Integer bookingPeriod, UUID paymentMethodId) {
this.posTerminalId = posTerminalId;
this.cashierId = cashierId;
this.cashDrawerId = cashDrawerId;
this.bookingDate = bookingDate;
this.bookingPeriod = bookingPeriod;
this.paymentMethodId = paymentMethodId;
}
// Builder
public static InitialMoneyCountIdBuilder builder() {
return new InitialMoneyCountIdBuilder();
}
public static class InitialMoneyCountIdBuilder {
private UUID posTerminalId;
private UUID cashierId;
private UUID cashDrawerId;
private LocalDate bookingDate;
private Integer bookingPeriod;
private UUID paymentMethodId;
public InitialMoneyCountIdBuilder posTerminalId(UUID posTerminalId) {
this.posTerminalId = posTerminalId;
return this;
}
public InitialMoneyCountIdBuilder cashierId(UUID cashierId) {
this.cashierId = cashierId;
return this;
}
public InitialMoneyCountIdBuilder cashDrawerId(UUID cashDrawerId) {
this.cashDrawerId = cashDrawerId;
return this;
}
public InitialMoneyCountIdBuilder bookingDate(LocalDate bookingDate) {
this.bookingDate = bookingDate;
return this;
}
public InitialMoneyCountIdBuilder bookingPeriod(Integer bookingPeriod) {
this.bookingPeriod = bookingPeriod;
return this;
}
public InitialMoneyCountIdBuilder paymentMethodId(UUID paymentMethodId) {
this.paymentMethodId = paymentMethodId;
return this;
}
public InitialMoneyCountId build() {
return new InitialMoneyCountId(posTerminalId, cashierId, cashDrawerId,
bookingDate, bookingPeriod, paymentMethodId);
}
}
}

Here is the JpaRepository interface definition:

@Repository
public interface InitialMoneyCountRepository extends JpaRepository&lt;InitialMoneyCount, InitialMoneyCountId&gt; {}

And here is a test for findById(InitialMoneyCountId id):

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = &quot;spring.h2.console.enabled=true&quot;)
public class InitialMoneyCountRepositoryTest {
@Autowired
SampleDataGenerator sampleDataGenerator;
@Autowired
InitialMoneyCountRepository initialMoneyCountRepository;
@AfterEach
public void resetDatabase_AfterEach() {
initialMoneyCountRepository.deleteAll();
}
@After
public void resetDatabase_After() {
initialMoneyCountRepository.deleteAll();
}
@Before
public void resetDatabase_Before() {
initialMoneyCountRepository.deleteAll();
}
@Test
public void findById_sucess() {
InitialMoneyCountId initialMoneyCountId = InitialMoneyCountId.builder()
.posTerminalId(UUID.randomUUID())
.cashDrawerId(UUID.randomUUID())
.cashierId(UUID.randomUUID())
.bookingDate(LocalDate.now())
.bookingPeriod(5)
.paymentMethodId(UUID.randomUUID())
.build();
InitialMoneyCount initialMoneyCount = InitialMoneyCount.builder()
.initialMoneyCountId(initialMoneyCountId)
.amount(BigDecimal.valueOf(555.55))
.amountSystemCurrency(BigDecimal.valueOf(55.555))
.currency(Currency.EUR.value())
.build();
InitialMoneyCount savedInitialMoneyCount = initialMoneyCountRepository.save(initialMoneyCount);
Optional&lt;InitialMoneyCount&gt; imc = initialMoneyCountRepository.findById(initialMoneyCountId);
Assertions.assertTrue(imc.isPresent());
}
}

However I am not able to find the object by it's id. This is the exception I am getting:

expected: &lt;true&gt; but was: &lt;false&gt;
Comparison Failure: 
Expected :true
Actual   :false
&lt;Click to see difference&gt;
org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: delete from initial_money_count where booking_date=? and booking_period=? and cash_drawer_id=? and cashier_id=? and payment_method_id=? and pos_terminal_id=?; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: delete from initial_money_count where booking_date=? and booking_period=? and cash_drawer_id=? and cashier_id=? and payment_method_id=? and pos_terminal_id=?

Any ideas of what I am missing here?

I suppose that CrudRepository's findById method works for @Embeadable composite Ids, correct?

答案1

得分: 2

我无法重现使用 spring-boot:3/jakarta.perstistence(,hibernate:6) 的问题,

立刻切换到 spring-boot:2/javax.persistence 就能够 触发 问题!

(快速) 解决方案:

@Test
@org.springframework.transaction.annotation.Transactional // !

...(跟踪) SQL 和参数似乎正确,所以问题必定出现在事务传播/刷新的某个地方。

原因:
在 spring-boot 2 和 3/hibernate 5 vs 6 之间的发布说明/版本差异中(非常隐蔽)... spring(-boot)-test(!)...

英文:

While I could not reproduce the issue with spring-boot:3/jakarta.perstistence(,hibernate:6),

switching to spring-boot:2/javax.persistence immediately hit!

(Quick) Solution:

@Test
@org.springframework.transaction.annotation.Transactional // !

...(tracing) sql and params seemed correct, so the problem must be somewhere in transaction propagation/flushing.

Reasons:
(Well hidden) in the release notes/version diff between spring-boot 2 and 3/ hibernate 5 vs 6... spring(-boot)-test(!)...

huangapple
  • 本文由 发表于 2023年6月8日 18:23:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76430890.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定