Я еще учусь, так что строго не судите)
Два вопроса: 1) Ошибка сопоставления - не устанавливает объект List в зеркальные объекты (в одном направлении список равен Null, в другом - за пределами длины 0) 2) Оптимизация отображения иерархии объектов
Кода во вставках получилось очень много, но я еще не совсем понял, что показывать ошибки не важно для анализа и показал все как есть, на всякий случай. Итак, есть начальная структура, подобная этой:
@MappedSuperclass
@Data
@NoArgsConstructor
public abstract class BusinessEntity {
@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "id", updatable = false, nullable = false)
private UUID id;
@Column(updatable = false)
@CreationTimestamp
private LocalDateTime createdOn;
@UpdateTimestamp
private LocalDateTime modifiedOn;
private LocalDateTime deletedOn;
@NotNull
private boolean SystemProtectedStatus = false;
@NotNull
private boolean SuspendedStatus = false;
@NotNull
private boolean blockedStatus = false;
@NotNull
private boolean deletedStatus = false;
private LocalDateTime activeFrom;
private LocalDateTime activeUntil;
}
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SystemRights extends BusinessEntity {
@NotNull
private String name;
private String description;
public SystemRights(@NotNull String name) {
super();
this.name = name;
}
}
@EqualsAndHashCode(callSuper = true)
@Entity
@Data
@NoArgsConstructor
public class UserRole extends BusinessEntity{
@NotNull
private String name;
private String description;
@ManyToMany
@JoinTable(
name = "rights_in_roles"
, joinColumns = @JoinColumn(name = "right_id")
, inverseJoinColumns = @JoinColumn (name = "role_id")
)
public List<SystemRights> systemRights;
public UserRole(@NotNull String name) {
this.name = name;
}
public List<SystemRights> getSystemRights() {
if (systemRights == null)
systemRights = new ArrayList<>();
return systemRights;
}
public void addSystemRights(SystemRights newSystemRights) {
if (newSystemRights == null)
return;
if (this.systemRights == null)
this.systemRights = new ArrayList<>();
this.systemRights.add(newSystemRights);
}
public void removeSystemRights(SystemRights oldSystemRights) {
if (oldSystemRights == null)
return;
if (this.systemRights != null)
this.systemRights.remove(oldSystemRights);
}
public void removeAllSystemRights() {
if (systemRights != null)
systemRights.clear();
}
}
Я хочу сопоставить его с DTO следующим образом: (фактически один к одному)
@Data
public abstract class Dto {
private String id;
private String activeFrom;
private String activeUntil;
private boolean SystemProtectedStatus;
private boolean SuspendedStatus;
private boolean blockedStatus;
private boolean deletedStatus;
private String createdOn;
// private User createdBy;
private String modifiedOn;
// private User modifiedBy;
private String deletedOn;
// private User deletedBy;
}
@Data
public class SystemRightsDto extends Dto{
@NotNull
private String name;
private String description;
}
@Data
public class UserRoleDto extends Dto {
@NotNull
private String name;
private String description;
@NotNull
public List<SystemRightsDto> systemRightsDto;
}
Для этого я сложил эти картографы ниже. - И вот первый вопрос: - как в MapStruct правильно унаследовать отображение от корневого объекта BusinessEntity, который является предком для всех объектов учета в системе и не страдать от копирования-вставки в каждый дочерний объект, как я здесь пострадал во время тренировки?
@Mapper
public interface UserRoleMapper {
@Mapping(target = "id",
expression = "java(getStringFromId(entity.getId()))")
@Mapping(target = "activeFrom",
expression = "java(getStringfromLDT(entity.getActiveFrom()))")
@Mapping(target = "activeUntil",
expression = "java(getStringfromLDT(entity.getActiveUntil()))")
@Mapping(target = "createdOn",
expression = "java(getStringfromLDT(entity.getCreatedOn()))")
@Mapping(target = "modifiedOn",
expression = "java(getStringfromLDT(entity.getModifiedOn()))")
@Mapping(target = "deletedOn",
expression = "java(getStringfromLDT(entity.getDeletedOn()))")
UserRoleDto getDtoFromEntity(UserRole entity);
@Mapping(target = "id",
expression = "java(getIdFromString(dto.getId()))")
@Mapping(target = "activeFrom",
expression = "java(getLDTfromString(dto.getActiveFrom()))")
@Mapping(target = "activeUntil",
expression = "java(getLDTfromString(dto.getActiveUntil()))")
@Mapping(target = "createdOn",
expression = "java(getLDTfromString(dto.getCreatedOn()))")
@Mapping(target = "modifiedOn",
expression = "java(getLDTfromString(dto.getModifiedOn()))")
@Mapping(target = "deletedOn",
expression = "java(getLDTfromString(dto.getDeletedOn()))")
UserRole getEntityFromDto(UserRoleDto dto);
List<SystemRights> SystemRightsDtoToEnt (List<SystemRightsDto> dto);
List<SystemRightsDto> SystemRightsEntToDto (List<SystemRights> entity);
default LocalDateTime getLDTfromString(String string) {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
if (!isNull(string)) {
return LocalDateTime.parse(string, formatter);
} else {
return null;
}
}
default String getStringfromLDT(LocalDateTime ldt) {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
if (!isNull(ldt)) {
return ldt.toString();
} else {
return null;
}
}
default String getStringFromId(UUID id) {
if (!isNull(id)) {
return id.toString();
} else {
return null;
}
}
default UUID getIdFromString(String id) {
if (!isNull(id)) {
return UUID.fromString(id);
} else {
return null;
}
}
}
@Mapper
public interface SystemRightsMapper {
@Mapping(target = "id",
expression = "java(getStringFromId(entity.getId()))")
@Mapping(target = "activeFrom",
expression = "java(getStringfromLDT(entity.getActiveFrom()))")
@Mapping(target = "activeUntil",
expression = "java(getStringfromLDT(entity.getActiveUntil()))")
@Mapping(target = "createdOn",
expression = "java(getStringfromLDT(entity.getCreatedOn()))")
@Mapping(target = "modifiedOn",
expression = "java(getStringfromLDT(entity.getModifiedOn()))")
@Mapping(target = "deletedOn",
expression = "java(getStringfromLDT(entity.getDeletedOn()))")
SystemRightsDto getDtoFromEntity(SystemRights entity);
@Mapping(target = "id",
expression = "java(getIdFromString(dto.getId()))")
@Mapping(target = "activeFrom",
expression = "java(getLDTfromString(dto.getActiveFrom()))")
@Mapping(target = "activeUntil",
expression = "java(getLDTfromString(dto.getActiveUntil()))")
@Mapping(target = "createdOn",
expression = "java(getLDTfromString(dto.getCreatedOn()))")
@Mapping(target = "modifiedOn",
expression = "java(getLDTfromString(dto.getModifiedOn()))")
@Mapping(target = "deletedOn",
expression = "java(getLDTfromString(dto.getDeletedOn()))")
SystemRights getEntityFromDto(SystemRightsDto dto);
List<SystemRights> SystemRightsDtoToEnt (List<SystemRightsDto> dto);
List<SystemRightsDto> SystemRightsEntToDto (List<SystemRights> entity);
default LocalDateTime getLDTfromString(String string) {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
if (!isNull(string)) {
return LocalDateTime.parse(string, formatter);
} else {
return null;
}
}
default String getStringfromLDT(LocalDateTime ldt) {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
if (!isNull(ldt)) {
return ldt.toString();
} else {
return null;
}
}
default String getStringFromId(UUID id) {
if (!isNull(id)) {
return id.toString();
} else {
return null;
}
}
default UUID getIdFromString(String id) {
if (!isNull(id)) {
return UUID.fromString(id);
} else {
return null;
}
}
}
Тест передачи SystemRights работает в обоих направлениях, но тест UserRole не проходит. Сам тест такой:
public class UserRoleMapperTest {
private UserRoleMapper userRoleMapper = Mappers.getMapper(UserRoleMapper.class);
@Test
public void testEntityToDto(){
String testDateStr = "2021-04-05T12:53:16.173706900";
LocalDateTime testDateLDT = LocalDateTime.parse(testDateStr);
UUID uuidTest = UUID.fromString("c095ad9f-37d4-479f-b88d-17df04f2437b");
UserRole e = new UserRole();
e.setName("Admin");
e.setDescription("Super");
e.setId(uuidTest);
e.setActiveFrom(testDateLDT);
e.setActiveUntil(testDateLDT);
e.setSystemProtectedStatus(true);
e.setBlockedStatus(false);
e.setSuspendedStatus(true);
e.setDeletedStatus(false);
e.setCreatedOn(testDateLDT);
e.setDeletedOn(testDateLDT);
e.setModifiedOn(testDateLDT);
List<SystemRights> eList = new ArrayList<>();
e.setSystemRights(eList);
SystemRights sr1= new SystemRights("Create");
SystemRights sr2= new SystemRights("Read");
SystemRights sr3= new SystemRights("Delete");
eList.add(sr1);
eList.add(sr2);
eList.add(sr3);
System.out.println("Testing testEntityToDto: Entity have");
System.out.println(e);
UserRoleDto d = userRoleMapper.getDtoFromEntity(e);
assertEquals(d.getName(),e.getName());
assertEquals(d.getDescription(),e.getDescription());
assertEquals(d.getId(),"c095ad9f-37d4-479f-b88d-17df04f2437b");
assertEquals(d.getActiveFrom(),"2021-04-05T12:53:16.173706900");
assertEquals(d.getActiveUntil(), "2021-04-05T12:53:16.173706900");
assertEquals(d.isSystemProtectedStatus(),e.isSystemProtectedStatus());
assertEquals(d.isDeletedStatus(),e.isDeletedStatus());
assertEquals(d.isBlockedStatus(),e.isBlockedStatus());
assertEquals(d.isSuspendedStatus(),e.isSuspendedStatus());
assertEquals(d.getCreatedOn(),"2021-04-05T12:53:16.173706900");
assertEquals(d.getModifiedOn(),"2021-04-05T12:53:16.173706900");
assertEquals(d.getDeletedOn(),"2021-04-05T12:53:16.173706900");
assertEquals(d.getSystemRightsDto().get(0).toString(),e.getSystemRights().get(0).toString());
assertEquals(d.getSystemRightsDto().get(1).toString(),e.getSystemRights().get(1).toString());
assertEquals(d.getSystemRightsDto().get(2).toString(),e.getSystemRights().get(2).toString());
assertEquals(d.getSystemRightsDto().toString(),e.getSystemRights().toString());
System.out.println("Testing testEntityToDto: Dto have");
System.out.println(d);
}
@Test
public void testDtoToEntity(){
String testDateStr = "2021-04-05T12:53:16.173706900";
LocalDateTime testDateLDT = LocalDateTime.parse(testDateStr);
UUID uuidTest = UUID.fromString("c095ad9f-37d4-479f-b88d-17df04f2437b");
UserRoleDto d = new UserRoleDto();
d.setName("Create");
d.setDescription("Desc");
d.setId("c095ad9f-37d4-479f-b88d-17df04f2437b");
d.setActiveFrom("2021-04-05T12:53:16.173706900");
d.setActiveUntil("2021-04-05T12:53:16.173706900");
d.setSystemProtectedStatus(true);
d.setBlockedStatus(false);
d.setSuspendedStatus(true);
d.setDeletedStatus(false);
d.setCreatedOn("2021-04-05T12:53:16.173706900");
d.setDeletedOn("2021-04-05T12:53:16.173706900");
d.setModifiedOn("2021-04-05T12:53:16.173706900");
List<SystemRightsDto> eListDto = new ArrayList<>();
d.setSystemRightsDto(eListDto);
SystemRightsDto sr1= new SystemRightsDto();
sr1.setName("Create");
SystemRightsDto sr2= new SystemRightsDto();
sr2.setName("Read");
SystemRightsDto sr3= new SystemRightsDto();
sr3.setName("Delete");
eListDto.add(sr1);
eListDto.add(sr2);
eListDto.add(sr3);
System.out.println("Testing testDtoToEntity: Dto have");
System.out.println(d);
UserRole e = userRoleMapper.getEntityFromDto(d);
assertEquals(e.getName(),d.getName());
assertEquals(e.getDescription(),d.getDescription());
assertEquals(e.getId(),uuidTest);
assertEquals(e.getActiveFrom(),testDateLDT);
assertEquals(e.getActiveUntil(),testDateLDT);
assertEquals(e.isSystemProtectedStatus(),d.isSystemProtectedStatus());
assertEquals(e.isDeletedStatus(),d.isDeletedStatus());
assertEquals(e.isBlockedStatus(),d.isBlockedStatus());
assertEquals(e.isSuspendedStatus(),d.isSuspendedStatus());
assertEquals(e.getCreatedOn(),testDateLDT);
assertEquals(e.getModifiedOn(),testDateLDT);
assertEquals(e.getDeletedOn(),testDateLDT);
assertEquals(e.getSystemRights().get(0).toString(),d.getSystemRightsDto().get(0).toString());
assertEquals(e.getSystemRights().get(1).toString(),d.getSystemRightsDto().get(1).toString());
assertEquals(e.getSystemRights().get(2).toString(),d.getSystemRightsDto().get(2).toString());
assertEquals(e.getSystemRights().toString(),d.getSystemRightsDto().toString());
System.out.println("Testing testDtoToEntity: Entity have");
System.out.println(e);
}
}
Когда я запускаю тест «testEntityToDto», я получаю:
Тестирование testEntityToDto: Entity имеют UserRole (name = Admin, description = Super, systemRights = [SystemRights (name = Create, description = null), SystemRights (name = Read, description = null), SystemRights (name = Delete, description = null)])
java.lang.NullPointerException: невозможно вызвать «java.util.List.get (int)», поскольку возвращаемое значение «com.example.cparty.dto.UserRoleDto.getSystemRightsDto ()» равно null
в com.example.cparty.UserRoleMapperTest.testEntityToDto (UserRoleMapperTest.java:77)
Когда я запускаю тест "testDtoToEntity", я получаю:
Тестирование testDtoToEntity: Dto иметь UserRoleDto (name = Create, description = Desc, systemRightsDto = [SystemRightsDto (name = Create, description = null), SystemRightsDto (name = Read, description = null), SystemRightsDto (name = Delete, description = null)])
java.lang.IndexOutOfBoundsException: индекс 0 выходит за пределы для длины 0
at java.base/java.util.Objects.checkIndex(Objects.java:359)
at java.base/java.util.ArrayList.get(ArrayList.java:427)
at com.example.cparty.UserRoleMapperTest.testDtoToEntity(UserRoleMapperTest.java:149)
Сам MapStrakt сгенерировал следующий код: (в нем я не видел, как он устанавливает List для целевого объекта)
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-04-06T11:03:58+0300",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 11.0.6 (JetBrains s.r.o)"
)
public class UserRoleMapperImpl implements UserRoleMapper {
@Override
public UserRoleDto getDtoFromEntity(UserRole entity) {
if ( entity == null ) {
return null;
}
UserRoleDto userRoleDto = new UserRoleDto();
userRoleDto.setSystemProtectedStatus( entity.isSystemProtectedStatus() );
userRoleDto.setSuspendedStatus( entity.isSuspendedStatus() );
userRoleDto.setBlockedStatus( entity.isBlockedStatus() );
userRoleDto.setDeletedStatus( entity.isDeletedStatus() );
userRoleDto.setName( entity.getName() );
userRoleDto.setDescription( entity.getDescription() );
userRoleDto.setId( getStringFromId(entity.getId()) );
userRoleDto.setActiveFrom( getStringfromLDT(entity.getActiveFrom()) );
userRoleDto.setActiveUntil( getStringfromLDT(entity.getActiveUntil()) );
userRoleDto.setCreatedOn( getStringfromLDT(entity.getCreatedOn()) );
userRoleDto.setModifiedOn( getStringfromLDT(entity.getModifiedOn()) );
userRoleDto.setDeletedOn( getStringfromLDT(entity.getDeletedOn()) );
return userRoleDto;
}
@Override
public UserRole getEntityFromDto(UserRoleDto dto) {
if ( dto == null ) {
return null;
}
UserRole userRole = new UserRole();
userRole.setSystemProtectedStatus( dto.isSystemProtectedStatus() );
userRole.setSuspendedStatus( dto.isSuspendedStatus() );
userRole.setBlockedStatus( dto.isBlockedStatus() );
userRole.setDeletedStatus( dto.isDeletedStatus() );
userRole.setName( dto.getName() );
userRole.setDescription( dto.getDescription() );
userRole.setId( getIdFromString(dto.getId()) );
userRole.setActiveFrom( getLDTfromString(dto.getActiveFrom()) );
userRole.setActiveUntil( getLDTfromString(dto.getActiveUntil()) );
userRole.setCreatedOn( getLDTfromString(dto.getCreatedOn()) );
userRole.setModifiedOn( getLDTfromString(dto.getModifiedOn()) );
userRole.setDeletedOn( getLDTfromString(dto.getDeletedOn()) );
return userRole;
}
@Override
public List<SystemRights> SystemRightsDtoToEnt(List<SystemRightsDto> dto) {
if ( dto == null ) {
return null;
}
List<SystemRights> list = new ArrayList<SystemRights>( dto.size() );
for ( SystemRightsDto systemRightsDto : dto ) {
list.add( systemRightsDtoToSystemRights( systemRightsDto ) );
}
return list;
}
@Override
public List<SystemRightsDto> SystemRightsEntToDto(List<SystemRights> entity) {
if ( entity == null ) {
return null;
}
List<SystemRightsDto> list = new ArrayList<SystemRightsDto>( entity.size() );
for ( SystemRights systemRights : entity ) {
list.add( systemRightsToSystemRightsDto( systemRights ) );
}
return list;
}
protected SystemRights systemRightsDtoToSystemRights(SystemRightsDto systemRightsDto) {
if ( systemRightsDto == null ) {
return null;
}
SystemRights systemRights = new SystemRights();
systemRights.setId( getIdFromString( systemRightsDto.getId() ) );
systemRights.setCreatedOn( getLDTfromString( systemRightsDto.getCreatedOn() ) );
systemRights.setModifiedOn( getLDTfromString( systemRightsDto.getModifiedOn() ) );
systemRights.setDeletedOn( getLDTfromString( systemRightsDto.getDeletedOn() ) );
systemRights.setSystemProtectedStatus( systemRightsDto.isSystemProtectedStatus() );
systemRights.setSuspendedStatus( systemRightsDto.isSuspendedStatus() );
systemRights.setBlockedStatus( systemRightsDto.isBlockedStatus() );
systemRights.setDeletedStatus( systemRightsDto.isDeletedStatus() );
systemRights.setActiveFrom( getLDTfromString( systemRightsDto.getActiveFrom() ) );
systemRights.setActiveUntil( getLDTfromString( systemRightsDto.getActiveUntil() ) );
systemRights.setName( systemRightsDto.getName() );
systemRights.setDescription( systemRightsDto.getDescription() );
return systemRights;
}
protected SystemRightsDto systemRightsToSystemRightsDto(SystemRights systemRights) {
if ( systemRights == null ) {
return null;
}
SystemRightsDto systemRightsDto = new SystemRightsDto();
systemRightsDto.setId( getStringFromId( systemRights.getId() ) );
systemRightsDto.setActiveFrom( getStringfromLDT( systemRights.getActiveFrom() ) );
systemRightsDto.setActiveUntil( getStringfromLDT( systemRights.getActiveUntil() ) );
systemRightsDto.setSystemProtectedStatus( systemRights.isSystemProtectedStatus() );
systemRightsDto.setSuspendedStatus( systemRights.isSuspendedStatus() );
systemRightsDto.setBlockedStatus( systemRights.isBlockedStatus() );
systemRightsDto.setDeletedStatus( systemRights.isDeletedStatus() );
systemRightsDto.setCreatedOn( getStringfromLDT( systemRights.getCreatedOn() ) );
systemRightsDto.setModifiedOn( getStringfromLDT( systemRights.getModifiedOn() ) );
systemRightsDto.setDeletedOn( getStringfromLDT( systemRights.getDeletedOn() ) );
systemRightsDto.setName( systemRights.getName() );
systemRightsDto.setDescription( systemRights.getDescription() );
return systemRightsDto;
}
}
Ну может дело в моем окружении (весь pom.xml не подошел):
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
<artifactId>spring-boot-starter-actuator</artifactId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<artifactId>spring-boot-starter-data-rest</artifactId>
<artifactId>spring-boot-starter-validation</artifactId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-devtools</artifactId>
<artifactId>postgresql</artifactId>
<artifactId>spring-boot-configuration-processor</artifactId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<artifactId>lombok-mapstruct-binding</artifactId>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<artifactId>spring-boot-starter-test</artifactId>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>





При использовании Mapping#expression MapStruct скопирует это как есть, без каких-либо проверок.
В этом вопросе можно найти много expression (ов). Я бы посоветовал либо выполнить сопоставление вручную, либо оставить это на усмотрение MapStruct.
Фактически вы можете удалить все @Mapping, и все должно работать правильно.