代码设计:如何确保DTO对象引用同一个对象,避免Java中循环引用中的堆栈溢出



我有一个人员类,该类有一个公司类别,该类别也有个人列表

一切都很好,List对父Person有相同的引用。没有任何人的副本。(我认为hibernate实现了这一点,IDK(

现在在DTO中,我想将DAO(Person,Company(复制到(PersonDTO,CompanyDTO(

如何复制Person列表,使其具有相同的Person对象。

public class Person {
String name;
String address;
Company Company;
}
public class Company {
String name;
String phoneNumber;
List<Person> Persons;
}

您可以将外部库用于ModelMapper、Dozer或MapStruct等。

以下是映射框架的概述。

假设您正在使用Spring或Apache Commons utils进行复制:

public static CompanyDTO convertToDTO(Company dao) {
return dao != null ? convertToDTO(dao, null, null) : null;
}
public static PersonDTO convertToDTO(Person dao) {
return dao != null ? convertToDTO(dao, null) : null;
}
private static CompanyDTO convertToDTO(Company dao, Person original, PersonDTO converted) {
CompanyDTO dto = new CompanyDTO();
BeanUtils.copyProperties(dao, dto, "persons");
dto.setPersons(new ArrayList<>());
for (Person person : dao.getPersons()) {
if (person.equals(original)) {
dto.getPersons().add(converted);
continue;
}
PersonDTO personDTO = convertToDTO(person, dto);
dto.getPersons().add(personDTO);
}
return dto;
}
private static PersonDTO convertToDTO(Person dao, Company converted) {
PersonDTO dto = new PersonDTO();
BeanUtils.copyProperties(dao, dto, "company");
if (converted == null) {
converted = convertToDTO(dao.getCompany(), dao, dto);
}
dto.setCompany(converted);
return dto;
}

这是相当手动的,但它应该完成工作。还要注意,如果您创建更多使用泛型接收对象的方法,它可能会更加可重用。

附言:我没有测试这个,因为我直接把它写在SO上,所以它可能不起作用,或者可能包含一些拼写错误。

编辑

通常,创建DTO时没有任何形式的缓存,即使对于非循环依赖实体也是如此。这意味着,如果我有一个Person的列表,并且有多个出生日期相同的人,那么相同的Date(或LocalDate(将被多次创建。

你肯定可以缓存创建的DTO:

public final class DTOManager {
private final Map<Class<?>, List<SoftReference<Object>>> cache = new HashMap<>();
public static CompanyDTO convertToDTO(Company dao) {
return dao != null ? convertToDTO(dao, null, null) : null;
}

public static PersonDTO convertToDTO(Person dao) {
return dao != null ? convertToDTO(dao, null) : null;
}

private static CompanyDTO convertToDTO(Company dao, Person original, PersonDTO converted) {
List<SoftReference<Object>> c = cache.get(CompanyDTO.class);
Optional<Object> cachedDTO = (c == null) ? Optional.empty() : c.stream()
.filter(ref ->
ref.get() != null &&
ref.get().getPrimaryKey() == dao.getPrimaryKey()) // Use equal() if string key
.findAny();
if (cachedDTO.isPresent()) {
return (CompanyDTO) cachedDTO.get();
}
CompanyDTO dto = new CompanyDTO();
BeanUtils.copyProperties(dao, dto, "persons");
dto.setPersons(new ArrayList<>());

for (Person person : dao.getPersons()) {
if (person.equals(original)) {
dto.getPersons().add(converted);
continue;
}

PersonDTO personDTO = convertToDTO(person, dto);
dto.getPersons().add(personDTO);
}
if (c == null) {
c = new ArrayList<>();
cache.put(CompanyDTO.class, c);
}
c.add(new SoftReference<>(dto));
return dto;
}

private static PersonDTO convertToDTO(Person dao, Company converted) {
List<SoftReference<Object>> c = cache.get(PersonDTO.class);
Optional<Object> cachedDTO = (c == null) ? Optional.empty() : c.stream()
.filter(ref ->
ref.get() != null &&
ref.get().getPrimaryKey() == dao.getPrimaryKey()) // Use equal() if string key
.findAny();
if (cachedDTO.isPresent()) {
return (PersonDTO) cachedDTO.get();
}
PersonDTO dto = new PersonDTO();
BeanUtils.copyProperties(dao, dto, "company");

if (converted == null) {
converted = convertToDTO(dao.getCompany(), dao, dto);
}

dto.setCompany(converted);
if (c == null) {
c = new ArrayList<>();
cache.put(PersonDTO.class, c);
}
c.add(new SoftReference<>(dto));

return dto;
}
}

此方法的成本为精度-缓存中的对象可能不是更新最多的。您可以实现自己的缓存,我的只是一个简单的例子。我的例子也是线程安全的,所以您可能需要注意。

在我看来,实现缓存是不值得的,因为DTO应该是使用和转储的。只有当您期望在单个交易中检索到大量实体时,这才是合理的。否则,最好让它创建更多的对象,然后让数据传输过程进行,然后让垃圾收集器回收这些内存。

最新更新