读取数据时违反约束

Constraint violation when reading data

我正在用 junit-jupiter 编写集成测试,发生了一些非常奇怪的事情 -> 当我读取(不保存数据)时发生约束违反异常

storesTemplateRepository.findByCountryOrderByTemplateName(country, pageable); 引发以下异常:

could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.STORES_TEMPLATE(ID)"; SQL statement:
insert into stores_template (country, stores, template_name, id) values (?, ?, ?, ?) [23505-196]]

实体:

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "STORES_TEMPLATE")
public class StoresTemplate {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "STORES_TEMPLATE_ID_SEQ")
    @SequenceGenerator(name = "STORES_TEMPLATE_ID_SEQ", sequenceName = "STORES_TEMPLATE_ID_SEQ", allocationSize = 1)
    private long id;

    @Enumerated(EnumType.STRING)
    private CountryEnum country;

    private String templateName;

    @Lob
    private String stores;

    public void setStores(List<String> stores) {
        this.stores = String.join(",", stores);
    }

    @JsonIgnore
    public List<String> getStoresAsList() {
        return Arrays.stream(stores.split(","))
                .distinct()
                .collect(Collectors.toList());
    }

}

测试

@Slf4j
@Transactional
@SpringBootTest
public class StoresTemplateControllerTest {

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private StoresTemplateRepository storesTemplateRepository;

    private MockMvc mockMvc;

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(SecurityMockMvcConfigurers.springSecurity())
                .build();
    }

    @Test
    public void fullApiTest() {
        OAuth2AuthenticationToken dePrincipal = new TestDataBuilder()
                .createOAuth2AuthenticationToken()
                .setDefaultStoreUser()
                .setRoles(SiamRoles.DE_CREATOR_ADMIN)
                .build();

        CreateStoresTemplateDto createStoresTemplateDeDto = CreateStoresTemplateDto.builder()
                .country(CountryEnum.DE)
                .stores(List.of("de1000", "de1100"))
                .templateName("de template")
                .build();

        CreateStoresTemplateDto createStoresTemplateBgDto = CreateStoresTemplateDto.builder()
                .country(CountryEnum.BG)
                .stores(List.of("bg2000", "bg2100"))
                .templateName("bg template")
                .build();

        CreateStoresTemplateDto createStoresTemplateDefaultDto = CreateStoresTemplateDto.builder()
                .country(null)
                .stores(List.of("de3000", "de3100"))
                .templateName("default template")
                .build();

        try {

            // find all existing by predefined insertion script
            for (long id: findAll(dePrincipal, CountryEnum.DE).map(e -> e.id)) {
                storesTemplateRepository.deleteById(id);
            }


            storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateDeDto));
            storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE);

            storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateBgDto));
            storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE); // Here the exception occurs

            storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateDefaultDto));
            storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE);
            

        } catch (Exception e) {
            e.printStackTrace();
            fail(e.getMessage());
        }
    }

}
  • JPA/Hibernate 尽可能将其 session 中的操作排队,不会立即调用数据库,然后在事务完成之前,订购那些基于类型的操作并执行它们。这在休眠中称为 Transactional write-behind 。如您所见,即使您先调用删除,hibernate 也会在排队时将其排在最后。

    1. 插入,按照执行的顺序
    2. 更新
    3. 删除集合元素
    4. 集合元素的插入
    5. 按执行顺序删除
  • 因此,即使您确实先删除,正如您所见,hibernate 也会最后删除。如果你想控制订单,你需要刷新它。所以请执行以下操作。

    for (long id: findAll(dePrincipal, CountryEnum.DE).map(e -> e.id)) {
        storesTemplateRepository.deleteById(id);
    }
    storesTemplateRepository.flush();

参考