使用@Transactional和@Rollback时,JPA集成测试中实体版本更新失败

问题描述 投票:0回答:1

我正在尝试在 Spring 中为 Beer 实体的 updateById 操作实现集成测试,该实体具有用 @Version 注释的版本属性。

背景:

  • 我没有使用 MockMvc 而是直接使用 beerRepository 进行测试 啤酒控制器。
  • H2 数据库在启动时加载 3 个 Beer 实体。
  • 测试类使用@SpringBootTest注解,而具体 测试使用@Transactional和@Rollback来确保数据库状态滚动 执行后返回。

测试流程:

  • 通过 beerRepository 检索第一瓶啤酒,获取有效的 UUID。
  • 使用映射器将 Beer 对象映射到 BeerDTO 对象。
  • 将 beerDTO 的 beerName 属性设置为新值。
  • 使用 UUID 和修改后的 beerDTO 在 beerController 上调用 updateById。
  • 断言多个条件,包括版本增加 1.

问题: 版本断言测试失败。期望值为 1,但实际值为 0。

观察结果:

  • 如果我删除@Rollback和@Transactional,测试就会通过。
  • 如果我使用 saveAndFlush 而不是 save in ,测试也会通过 啤酒服务JPA。

有人可以解释为什么会发生这种情况以及我该如何解决它吗?我相信 @Transactional 和 @Rollback 在这里有意义,因为理想情况下更新应该在测试后回滚。

这是相关代码(为简洁起见,进行了简化):

实体/啤酒

@Getter
@Setter
@ToString
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Beer {

    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    @Column(length = 36, columnDefinition = "varchar", updatable = false, nullable = false)
    private UUID id;

    @Version
    private Integer version;
    private String beerName;
    private BeerStyle beerStyle;
    private String upc;
    private Integer quantityOnHand;
    private BigDecimal price;
    private LocalDateTime createdDate;
    private LocalDateTime updateDate;
}

存储库/BeerRepository

public interface BeerRepository extends JpaRepository<Beer, UUID> {
}

服务/啤酒服务

public interface BeerService {

    void updateBeerById(UUID beerId, BeerDTO beer);
}

服务/BeerServiceJPA

@Service
@Primary
@RequiredArgsConstructor
public class BeerServiceJPA implements BeerService {
    private final BeerRepository beerRepository;
    private final BeerMapper beerMapper;


    @Override
    public void updateBeerById(UUID beerId, BeerDTO beer) {
        beerRepository.findById(beerId).ifPresent(foundBeer -> {
            foundBeer.setBeerName(beer.getBeerName());
            foundBeer.setBeerStyle(beer.getBeerStyle());
            foundBeer.setUpc(beer.getUpc());
            foundBeer.setPrice(beer.getPrice());
            beerRepository.save(foundBeer);
        });
    }

}

控制器/啤酒控制器

@Slf4j
@RequiredArgsConstructor
@RestController
public class BeerController {

    public static final String BEER_PATH = "/api/v1/beer";
    public static final String BEER_PATH_ID = BEER_PATH + "/{beerId}";

    private final BeerService beerService;

    @PutMapping(BEER_PATH_ID)
    public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBody BeerDTO beer){

        beerService.updateBeerById(beerId, beer);

        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }

测试/控制器/BeerControllerIT

@SpringBootTest
class BeerControllerIT {
    @Autowired
    BeerController beerController;

    @Autowired
    BeerRepository beerRepository;

    @Autowired
    BeerMapper beerMapper;


    @Rollback
    @Transactional
    @Test
    void updateExistingBeer() {
        Beer beer = beerRepository.findAll().get(0);
        Integer version = beer.getVersion();
        BeerDTO beerDTO = beerMapper.beerToBeerDto(beer);
        beerDTO.setId(null);
        beerDTO.setVersion(null);
        final String beerName = "UPDATED";
        beerDTO.setBeerName(beerName);

        ResponseEntity responseEntity = beerController.updateById(beer.getId(), beerDTO);
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(204));

        Beer updatedBeer = beerRepository.findById(beer.getId()).get();
        System.out.println(updatedBeer);
        assertThat(updatedBeer.getBeerName()).isEqualTo(beerName);
        assertThat(updatedBeer.getVersion()).isEqualTo(version+1);
    }
}
java spring spring-boot junit spring-data-jpa
1个回答
0
投票

发生这种情况是因为仅当实体写入数据库时,

@Version
字段的值才会更新。实体写入数据库的时刻由刷新策略决定。默认情况下,使用
FlushMode.AUTO
,其中 Hibernate 尝试将实体尽可能长时间地保留在持久化上下文中,并将其卸载到数据库,仅在事务结束时,或在 JPQL、HQL 或 SQL 查询之前,或者除非使用
flush()
命令手动将持久性上下文卸载到数据库。

对于您的情况,请致电

updateById()

ResponseEntity responseEntity = beerController.updateById(beer.getId(), beerDTO);

不会立即执行 SQL UPDATE 命令,这会推迟到事务结束。

并打电话给

findById()

Beer updatedBeer = beerRepository.findById(beer.getId()).get();

不执行 SELECT 命令,因为该实体已经在持久化上下文中,您可以从中获取除

@Version
之外的所有字段的更新值。

要解决此问题,您可以在读取实体的更新版本之前手动刷新持久化上下文:

entityManager.flush();

或者使用 JPQL、HQL 或 SQL 查询阅读新版本:

TypedQuery<Beer> typedQuery = entityManager.createQuery("SELECT b FROM Beer b WHERE b.id = :id", Beer.class);
typedQuery.setParameter("id", beer.getId());
Beer entity = typedQuery.getSingleResult();
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.