为什么以这种模式保存OneToMany相关数据时DataJpaTest失败



我下面有三个Hibernate@Entity,它们模拟了我的生产应用程序中的故障:

@Entity
@Data
@SuperBuilder(toBuilder = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class Dog extends Animal {
String barkType;
}

Dog实体对此类Animal:使用JOINED继承

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Data
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class Animal {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Type(type = "uuid-char")
private UUID id;
@OneToMany(cascade = CascadeType.REMOVE)
@JoinColumn(name = "animalId", referencedColumnName = "id", insertable = false, updatable = false)
@Builder.Default
private List<Toy> toys = new ArrayList<>();
}

Toy实体与父类Animal相关

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Toy {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Type(type = "uuid-char")
private UUID id;

@Type(type = "uuid-char")
private UUID animalId;

private String shape;
}

这是我正在测试的实现:

@Service
@AllArgsConstructor
public class DogService {
DogRepository repository;
ToyRepository toyRepository;
@Transactional
public Dog saveDogDTO(DogDTO dogDTO) {
Dog entity = Dog.builder()
.barkType(dogDTO.getBarkType())
.build();
repository.save(entity);
toyRepository.save(Toy.builder()
.shape(dogDTO.getToyShape())
.animalId(entity.getId())
.build());
return entity;
}
}

这是我的失败测试,它在最后一行失败:

@DataJpaTest
class DogServiceTests {
private DogService dogService;
@Autowired
private DogRepository dogRepository;
@Autowired
private ToyRepository toyRepository;
@Test
void save_not_working_example() {
dogService = new DogService(dogRepository, toyRepository);
var dogDTO = DogDTO.builder()
.barkType("big bark")
.toyShape("some shape")
.build();
var savedDog = dogService.saveDogDTO(dogDTO);
assertThat(dogRepository.count()).isEqualTo(1);
assertThat(toyRepository.count()).isEqualTo(1);
var findByIdResult = dogRepository.findById(savedDog.getId());
assertThat(findByIdResult.get().getToys()).hasSize(1);
}
}

测试失败消息:

Expected size: 1 but was: 0 in:
[]
java.lang.AssertionError: 
Expected size: 1 but was: 0 in:
[]

问题似乎是@Transaction中的双JPA存储库保存冲突。有没有办法克服这个问题?我尝试将@Transactional(propagation = Propagation.NEVER)添加到测试中,但后来失败了:

failed to lazily initialize a collection of role: com.example.datajpatest.demo.models.Animal.toys, could not initialize proxy - no Session
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.datajpatest.demo.models.Animal.toys, could not initialize proxy - no Session

@DataJpaTest被注释为@Transactional,因此您的测试方法都被封装在一个事务中,因此也就被封装在单个EntityManager中。在使用findById()进行查询之前,您可以通过在savedDog上调用EntityManager.detach()来通过测试。你也可以通过在DogService中手动设置狗的玩具来修复它。这将是我的建议,因为否则你迟早会在生产代码中发现同样的不一致性错误——事务边界只需要稍微改变一点,这将很难发现。在某种程度上,@DataJpaTest通过指出问题帮了你一个忙,尽管有些间接。

最终,数据库状态与EntityManager缓存的状态不匹配,因此必须清除缓存才能获得所需的结果。启动一个新事务也会清除缓存,这可能是生产中正在发生的事情。Hibernate信任您在保存(或刷新(时使对象图与数据库状态匹配。如果它们不匹配,那么Hibernate在不查询数据库的情况下就无法知道,这将被认为是冗余和低效的。

请在此处尝试此映射:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Data
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class Animal {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Type(type = "uuid-char")
private UUID id;
@OneToMany(mappedBy = "animal", cascade = CascadeType.REMOVE)
@Builder.Default
private List<Toy> toys = new ArrayList<>();
}
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Toy {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Type(type = "uuid-char")
private UUID id;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "animalId")
private Animal animal;

private String shape;
}

最新更新