在 Spring 启动测试中注入一个 JPA 存储库,没有会话问题
inject a JPA repository in Spring Boot test, without session issue
我正在对我的 Spring Boot 2.4 应用程序添加一些测试,该应用程序在生产中运行良好。
在我的一个 SpringBootTest
中,我调用 API(使用 mockMvc
)并将结果与数据库中的结果进行比较。
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class TicketIT {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TicketTypeRepository ticketTypeRepository;
@Test
void shouldReturnListOfTicketTypes() throws Exception {
RequestBuilder request =
MockMvcRequestBuilders.get(RESOURCE_BASE_URL + "/types").contentType(APPLICATION_JSON);
String responseAsString =
mockMvc
.perform(request)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
List<TicketTypesRepresentation> ticketTypes =
objectMapper.readValue(
responseAsString, new TypeReference<List<TicketTypesRepresentation>>() {
});
assertThat(ticketTypes).hasSameSizeAs(ticketTypeRepository.findAll());
}
}
我觉得我已经编写了数百次这种类型的测试,但是在这一次,我遇到了一个问题:我的应用程序配置正确,因为我收到了 [=44 中的项目列表=]响应。
但是,我觉得奇怪的是我从 ticketTypeRepository.findAll()
调用中得到一个异常:
failed to lazily initialize a collection of role ... could not initialize proxy - no Session
我理解这个问题,我可以通过使关系变得急切(在实体上使用 @Fetch(FetchMode.JOIN)
)或进行测试来解决它 @Transactional
但我不确定我是否喜欢任何选项..
我不记得过去在其他 Spring 启动测试中遇到过这个问题,所以我有点困惑。
我是否遗漏了一些东西来确保对 ticketTypeRepository 的所有调用都是在事务中进行的? TicketTypeRepository
是 CrudRepository
的包装器,这是它不能直接工作的原因吗?
这是实体和存储库代码:
public class JpaTicketTypeRepository implements TicketTypeRepository {
public List<TicketType> findAll() {
var allTicketTypesEntity= jpaTicketTypesEntityRepository.findAll();
return StreamSupport.stream(allTicketTypesEntity.spliterator(), false)
.map(TicketTypeEntity::toTicketTypeList)
.collect(Collectors.toList())
.stream().flatMap(List::stream)
.collect(Collectors.toList());
}
}
和实体(简体):
@Table(name = "TICKET_TYPES")
@Entity
@Slf4j
public class TicketTypeEntity {
@Id
private Long id;
@OneToMany
@JoinTable(name = "TICKET_TYPES_GROUPS",
joinColumns =
{@JoinColumn(name = "TICKET_TYPE_ID", referencedColumnName = "ID")},
inverseJoinColumns =
{@JoinColumn(name = "TICKET_GROUP_ID", referencedColumnName = "ID")})
@Nonnull
private List<TicketGroupsEntity> ticketGroupsEntity;
@Nonnull
public List<TicketType> toTicketTypeList() {
log.info("calling toTicketTypeList for id "+id);
log.info(" with size : "+ticketGroupsEntity.size());
return ticketGroupsEntity.stream().map(group -> TicketType.builder()
.id(id)
.build()).collect(Collectors.toList());
}
}
第一次对集合调用 size()
时发生异常:
failed to lazily initialize a collection of role:
my.service.database.entities.TicketTypeEntity.ticketGroupsEntity,
could not initialize proxy - no Session
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role:
my.service.database.entities.TicketTypeEntity.ticketGroupsEntity,
could not initialize proxy - no Session at
org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at
org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at
org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:162)
at
org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:371)
at
my.service.database.entities.TicketTypeEntity.toTicketTypeList(TicketTypeEntity.java:78)
at
java.base/java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:195)
at
java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
at
java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at
java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at
java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at
java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at
java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at
my.service.database.JpaTicketTypeRepository.findAll(JpaTicketTypeRepository.java:29)
我相信你 mis-interpret 堆栈跟踪。问题不在于对 findAll()
的结果调用方法 size()
,而在于方法 findAll
本身。
在 findAll
中调用 TicketTypeEntity.toTicketTypeList
,它将 DB 实体转换为 DTO。此方法涉及ticketGroupsEntity
,这是一个惰性集合。
代码在单元测试中失败,但在通过 springs 控制器访问时运行。
这是由于在视图中打开会话,默认启用。
参见:
您可以通过多种方式解决:
- @Transactional findAll(注意延迟加载问题)
- 查询中的显式提取
- 实体图
但在我看来,您的实体映射看起来很可疑,您似乎拥有在 TicketGroupsEntity 中构造 TicketType 所需的所有数据。也许您可以改为查询该实体?
我正在对我的 Spring Boot 2.4 应用程序添加一些测试,该应用程序在生产中运行良好。
在我的一个 SpringBootTest
中,我调用 API(使用 mockMvc
)并将结果与数据库中的结果进行比较。
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class TicketIT {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TicketTypeRepository ticketTypeRepository;
@Test
void shouldReturnListOfTicketTypes() throws Exception {
RequestBuilder request =
MockMvcRequestBuilders.get(RESOURCE_BASE_URL + "/types").contentType(APPLICATION_JSON);
String responseAsString =
mockMvc
.perform(request)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
List<TicketTypesRepresentation> ticketTypes =
objectMapper.readValue(
responseAsString, new TypeReference<List<TicketTypesRepresentation>>() {
});
assertThat(ticketTypes).hasSameSizeAs(ticketTypeRepository.findAll());
}
}
我觉得我已经编写了数百次这种类型的测试,但是在这一次,我遇到了一个问题:我的应用程序配置正确,因为我收到了 [=44 中的项目列表=]响应。
但是,我觉得奇怪的是我从 ticketTypeRepository.findAll()
调用中得到一个异常:
failed to lazily initialize a collection of role ... could not initialize proxy - no Session
我理解这个问题,我可以通过使关系变得急切(在实体上使用 @Fetch(FetchMode.JOIN)
)或进行测试来解决它 @Transactional
但我不确定我是否喜欢任何选项..
我不记得过去在其他 Spring 启动测试中遇到过这个问题,所以我有点困惑。
我是否遗漏了一些东西来确保对 ticketTypeRepository 的所有调用都是在事务中进行的? TicketTypeRepository
是 CrudRepository
的包装器,这是它不能直接工作的原因吗?
这是实体和存储库代码:
public class JpaTicketTypeRepository implements TicketTypeRepository {
public List<TicketType> findAll() {
var allTicketTypesEntity= jpaTicketTypesEntityRepository.findAll();
return StreamSupport.stream(allTicketTypesEntity.spliterator(), false)
.map(TicketTypeEntity::toTicketTypeList)
.collect(Collectors.toList())
.stream().flatMap(List::stream)
.collect(Collectors.toList());
}
}
和实体(简体):
@Table(name = "TICKET_TYPES")
@Entity
@Slf4j
public class TicketTypeEntity {
@Id
private Long id;
@OneToMany
@JoinTable(name = "TICKET_TYPES_GROUPS",
joinColumns =
{@JoinColumn(name = "TICKET_TYPE_ID", referencedColumnName = "ID")},
inverseJoinColumns =
{@JoinColumn(name = "TICKET_GROUP_ID", referencedColumnName = "ID")})
@Nonnull
private List<TicketGroupsEntity> ticketGroupsEntity;
@Nonnull
public List<TicketType> toTicketTypeList() {
log.info("calling toTicketTypeList for id "+id);
log.info(" with size : "+ticketGroupsEntity.size());
return ticketGroupsEntity.stream().map(group -> TicketType.builder()
.id(id)
.build()).collect(Collectors.toList());
}
}
第一次对集合调用 size()
时发生异常:
failed to lazily initialize a collection of role: my.service.database.entities.TicketTypeEntity.ticketGroupsEntity, could not initialize proxy - no Session org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: my.service.database.entities.TicketTypeEntity.ticketGroupsEntity, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606) at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218) at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:162) at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:371) at my.service.database.entities.TicketTypeEntity.toTicketTypeList(TicketTypeEntity.java:78) at java.base/java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:195) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at my.service.database.JpaTicketTypeRepository.findAll(JpaTicketTypeRepository.java:29)
我相信你 mis-interpret 堆栈跟踪。问题不在于对 findAll()
的结果调用方法 size()
,而在于方法 findAll
本身。
在 findAll
中调用 TicketTypeEntity.toTicketTypeList
,它将 DB 实体转换为 DTO。此方法涉及ticketGroupsEntity
,这是一个惰性集合。
代码在单元测试中失败,但在通过 springs 控制器访问时运行。 这是由于在视图中打开会话,默认启用。
参见:
您可以通过多种方式解决:
- @Transactional findAll(注意延迟加载问题)
- 查询中的显式提取
- 实体图
但在我看来,您的实体映射看起来很可疑,您似乎拥有在 TicketGroupsEntity 中构造 TicketType 所需的所有数据。也许您可以改为查询该实体?