你的原始查询:
@Query("select p from Parent p left join p.child c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth")
看起来很好,它将独立地返回所有的Parent
实体,无论它们是否有children
,正如您所需。
问题在于,之后,由于某种原因,可能是当您将信息序列化为JSON格式时,对于原始查询返回的每个parent
,都会解析与children
的OneToMany
关系。因此,执行了您提到的第二个查询,并且您得到的结果是不符合筛选条件的children
。
虽然我不清楚您的事务划分在哪里建立,但这可能是因为当实体被序列化时,它们仍然与持久性上下文相关联-相反,它会导致懒惰集合初始化的问题。
您可以尝试的一个选项是,在序列化它们之前将这些实体从持久性上下文中分离出来。
为了实现这个目标,首先在您的查询中包含fetch
术语:
@Query("select p from Parent p left join fetch p.children c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth"
很遗憾,Spring Data没有提供一个开箱即用的解决方案来分离实体,但是您可以轻松地实现必要的代码。
首先,定义一个基本的存储库接口:
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
@NoRepositoryBean
public interface CustomRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
void detach(T entity);
}
以及它的实现:
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements CustomRepository<T, ID> {
private final EntityManager entityManager;
public CustomRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
public CustomRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
public void detach(T entity) {
this.entityManager.detach(entity);
}
}
现在,为新的存储库创建一个工厂bean:
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
public class CustomRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
public CustomRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomRepositoryFactory(entityManager);
}
private static class CustomRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private final EntityManager em;
public CustomRepositoryFactory(EntityManager em) {
super(em);
this.em = em;
}
protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryMetadata metadata, EntityManager entityManager) {
SimpleJpaRepository<?, ?> repo = new CustomRepositoryImpl(metadata.getDomainType(), entityManager);
return repo;
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomRepositoryImpl.class;
}
}
}
这个工厂必须在你的@EnableJpaRepositories
注解中注册:
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class /* your other configuration */)
你的代码库需要实现新定义的内容:
public interface ParentRepository extends CustomRepository<Parent, Integer> {
@Query("select p from Parent p left join fetch p.children c where (YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth) or c.parent is null")
@Transactional(readOnly=true)
public List<Parent> getParentsForDate(@Param("dateOccurred") LocalDate dateOccurred)
}
创建一个新的服务:
public interface ParentService {
List<Parent> getParentsForDate(LocalDate dateOccurred);
}
对应的实现如下:
@Service
public class ParentServiceImpl implements ParentService {
private final ParentRepository parentRepository;
public ParentServiceImpl(ParentRepository parentRepository) {
this.parentRepository = parentRepository;
}
@Override
@Transactional
public List<Parent> getParentsForDate(LocalDate dateOccurred) {
Lis<Parent> parents = parentRepository.getParentsForDate(dateOccurred);
parents.forEach(parent -> parentRepository.detach(parent));
return parents;
}
}
在控制器中使用该服务:
@GetMapping(path = "/parents/{date}", produces = { "application/json" })
@Operation(summary = "Get all parents for date")
public @ResponseBody List<Parent> getParentsByDate(@PathVariable("date") String dateString) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate dateOccurred = LocalDate.parse(dateString, formatter);
return parentService.getParentsForDate(dateOccurred);
}
我还没有测试过这段代码,也不知道这种分离实体的方式是否可行,但我认为很可能会成功。
如果这种方法不起作用,我的建议是尝试另一种方法。
与其查询父级,不如直接过滤子级并获取符合筛选条件的子级。
@Query("select c from Child c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth)
另一方面,列出所有的
Parent
(例如使用
findAll
),并创建某种中间POJO,即DTO,以混合获取的几个结果的信息,并将该信息返回给客户端:
public class ParentDTO {
private long id;
private String name;
private Map<LocalDate, ChildDTO> children = Collections.emptyMap();
}
public class ChildDTO {
private long id;
private String name;
private LocalDate date;
}
List<Child> children =
List<Parent> parents = parentRepository.findAll();
List<ParentDTO> parentDTOs = new ArrayList(parents.size());
for (Parent parent:parents) {
ParentDTO parentDTO = new ParentDTO();
parentDTOs.add(parentDTO);
parentDTO.setId(parent.getId());
parentDTO.setName(parent.getName());
if (children == null || children.isEmpty()) {
continue;
}
Map<LocalDate, ChildDTO> childrenForThisParent = children.stream()
.filter(child -> child.parent.equals(parent))
.map(child -> {
ChildDTO childDTO = new ChildDTO();
childDTO.setId(child.getId());
childDTO.setName(child.getName());
childDTO.setDate(child.getDate());
return childDTO;
})
.collect(Collectors.toMap(c -> c.getDate(), c));
parentTO.setChildren(childrenForThisParent);
}
这段代码有几种优化方式,但我希望你能理解我的意思。
select * from Child where c.parent_id = ?
是何时生成的吗?我的意思是,您是否以某种方式处理实体?例如,将它们转换为JSON格式? - jccampanero