Как клонировать объект и рекурсивно установить Id в Null? - PullRequest
1 голос
/ 26 июня 2019

Вот мой вариант использования:

У меня есть 4 класса A, B, C, D

  • класс A содержит объект (тип B) и список объектов (типC)
  • класс B содержит объект (тип D)

Я хочу клонировать класс A и установить идентификатор равным нулю рекурсивно.

Здесьпример:

public class ClassA {

    private Long id;
    private String name;
    private boolean ok;
    private ClassB classB;
    private List<ClassC> classCList;

}

public class ClassB {

    private Long id;
    private String name;
    private ClassD classD;

}

public class ClassC{

    private Long id;
    private String name;

}

public class ClassD{

    private Long id;
    private String name;

}

Я разработал две функции для реализации этого:
Первый метод:

public ClassA prepareClassA(ClassA detail) {

   Optional.ofNullable(detail).ifPresent( detail -> {
    detail.setId(null);
    Optional.ofNullable(detail).map(ClassA::getClassB)
            .ifPresent(objectB -> objectB.setId(null));

   Optional.ofNullable(detail).map(ClassA::getClassB).map(ClassB::getClassD)
            .ifPresent(objectB -> objectB.setId(null));

   Optional.ofNullable(detail).map(ClassA::getClassCList).
        .ifPresent(items -> items.stream().forEach(item -> {
            item.setId(null);
        }));

   }
}

Второй метод : (включен dozerMapper)

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>

И я использовал реализацию DozerBeanMapper

public ClassA prepareClassA(ClassA detail) {

    ClassA objectA = new ClassA();

    DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();

    BeanMappingBuilder bean = beanMappingBuilder(ClassA.class);

    dozerBeanMapper.addMapping(bean);
    Optional.ofNullable(detail).ifPresent(detail -> dozerBeanMapper.map(detail, objectA));
    return details;
}


public BeanMappingBuilder beanMappingBuilder(Class<?> source) {
    return new BeanMappingBuilder() {
        @Override
        protected void configure() {
            mapping(source, source,
                TypeMappingOptions .wildcard(true)
                //Here i have to do my work ?
                //TypeMappingOptions.mapNull(true)
            );
        }
    };
}

Я хочучтобы получить такой результат:

ClassA testA = new ClassA();
//fill all the objects in objectA with id != null

ClassA testA_convert = prepareClassA(testA);

// testA_convert.getId() must be null
// testA_convert.getClassB().getId() must be null
// testA_convert.getClassB().getClassD().getId() must be null
// testA_convert.getClassCList().forEach( element -> element.getId()  must be null

Вопросы:

  • Существует ли какая-либо существующая библиотека, которая может решить мою проблему?
  • Может ли DozerMapper сделать это?
  • Каков наилучший способ сделать это?

С уважением

Ответы [ 3 ]

1 голос
/ 26 июня 2019

, если вы уже клонировали свой объект и хотите установить нулевые идентификаторы, не имея NPE, вы можете создать вспомогательный интерфейс для этого:

interface Nullify<T> {
    void apply(T obj);

    default <G> Nullify<T> andThen(Function<T, G> function, Nullify<G> nullify) {
        return (T t) -> {
            apply(t);
            G g = function.apply(t);
            if(g != null) {
                nullify.apply(g);
            }
        };
    }
}

и использование

Nullify<ClassB> bNull = b -> b.setId(null);
bNull = bNull.andThen(ClassB::getClassD, d -> d.setId(null));

Nullify<ClassA> aNull = a -> a.setId(null);
aNull.andThen(ClassA::getClassB, bNull)
     .andThen(ClassA::getClassCList, classCList -> classCList.forEach(c -> c.setId(null)))
     .apply(classAObject);

хотя лучше настроить метод клонирования / копирования, чтобы игнорировать идентификаторы (например, как MapStruct)

0 голосов
/ 26 июня 2019

Я думаю, что нашел простой способ сделать это:

реализовать библиотеку Gson:

<dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
  <version>2.2.4</version>
</dependency>

И добавить

public class ClassA {

   private Long id;

   @Expose
   private String name;

   @Expose
   private boolean ok;

   @Expose
   private ClassB classB;

   @Expose
   private List<ClassC> classCList;

}

public class ClassB {

   private Long id;

   @Expose
   private String name;

   @Expose
   private ClassD classD;

}

Я положил @Expose на все поля! = Id

Вот реализация в моем основном классе

    import com.google.gson.annotations.Expose;

    public ClassA prepareClassA(ClassA detail) {

       Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
       ClassA object_A = gson.fromJson(gson.toJson(detail),ClassA.class);
       return  object_A;

    }

И это работает.

Но , есть ли способ поместить @Expose только один раз в определение класса для всех атрибутов (кроме Id)

Пример:

@Expose( exclude ="id" )
public class ClassB {

   private Long id;
   private String name;
   private ClassD classD;

}

Любая помощь?

0 голосов
/ 26 июня 2019

Существует ли какая-либо библиотека, которая может решить мою проблему?

Да, MapStruct имеет возможность игнорировать нужные свойства.

Например, определить интерфейс конфигурации

@Mapper(componentModel = "spring")
public interface DomainDtoMapper {

   @Mapping(source = "id", target = "id", ignore = true)
   ClassA map(ClassA cla);

   @Mapping(source = "id", target = "id", ignore = true)
   ClassB map(ClassB clb);

   //...
}

затем просто autowire DomainDtoMapper и вызов метода, он проверит все ваши правила отображения и скопирует соответственно:

@Autowire DomainDtoMapper mapper;

//...

public ClassA prepareClassA(ClassA detail) {
    return mapper.map(detail);
}
...