我下面有三个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;
}