我在spring-boot上做一个api rest,并使用MapStruct在DTO和Entities之间进行转换。问题是它在ManyToMany的关系中启动了一个异常StackOverflowError。你能帮我吗?
参与者实体
@Getter @Setter
@NoArgsConstructor
@Entity()
@Table(name = "Actor")
@EqualsAndHashCode(exclude = "films")
public class Actor implements Serializable {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "name is required")
private String name;
@ManyToMany(mappedBy = "actors")
private Set<Film> films;
}
胶片实体
@Getter @Setter
@NoArgsConstructor
@Entity @Table(name = "Film")
@EqualsAndHashCode(exclude = "actors")
public class Film implements Serializable {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "Director_id")
private Director director;
@ManyToMany()
@JoinTable(
name = "Actor_has_Film",
joinColumns = @JoinColumn(name = "Film_id"),
inverseJoinColumns = @JoinColumn(name = "Actor_id"))
private Set<Actor> actors;
public void addActor(Actor actor) {
this.actors.add(actor);
}
}
ActorDTO
@Getter @Setter
@NoArgsConstructor
public class ActorDTO {
private Long id;
private String name;
private Set<FilmDTO> films;
}
FilmDTO
@Getter @Setter
@NoArgsConstructor
public class FilmDTO {
private Long id;
private DirectorDTO director;
private Set<ActorDTO> actors;
}
DataMapper
public interface DataMapper<D, E> {
E toEntity(D dto);
D toDto(E entity);
List<E> toEntity(List<D> dtoList);
List<D> toDto(List<E> entityList);
}
ActorMapper
@Mapper(componentModel = "spring", uses = { })
public interface ActorMapper extends DataMapper<ActorDTO, Actor> {
}
FilmMapper
@Mapper(componentModel = "spring", uses = { })
public interface FilmMapper extends DataMapper<FilmDTO, Film> {
}
FilmServices
@Service("filmServices")
public class FilmServices implements Services<FilmDTO> {
@Autowired @Qualifier("filmRepository")
private FilmRepository filmRepository;
@Autowired @Qualifier("actorRepository")
private ActorRepository actorRepository;
private FilmMapper filmMapper;
public FilmServices(FilmMapper filmMapper) {
this.filmMapper = filmMapper;
}
public FilmDTO addActorToFilm(Long filmId, Long actoId) {
Optional<Film> filmByIdOptional = filmRepository.findById(filmId);
Optional<Actor> actorByIdOptional = actorRepository.findById(actoId);
FilmDTO filmDtoWithNewActor = null;
if (!filmByIdOptional.isPresent())
throw new RuntimeException("The Film with id '" + filmId + "' does not exist");
if (!actorByIdOptional.isPresent())
throw new RuntimeException("The Actor with id '" + actoId + "' does not exist");
Film film = filmByIdOptional.get();
Actor actorToAdd = actorByIdOptional.get();
boolean hasActorInFilm = film.getActors().stream()
.anyMatch(actor -> actor.getName().equals(actorToAdd.getName()));
if (!hasActorInFilm) {
film.addActor(actorToAdd);
Film filmWithNewActor = filmRepository.save(film);
filmDtoWithNewActor = filmMapper.toDto(filmWithNewActor); // HERE THROW EXCEPTION
} else {
throw new RuntimeException("The Actor with id '" + actoId + "' already exist in the film");
}
return filmDtoWithNewActor;
}
}
输出日志:
Hibernate: select films0_.Actor_id as actor_id2_1_0_, films0_.Film_id as film_id1_1_0_, film1_.id as id1_3_1_, film1_.Director_id as director4_3_1_, director2_.id as id1_2_2_ from Actor_has_Film films0_ inner join Film film1_ on films0_.Film_id=film1_.id left outer join Director director2_ on film1_.Director_id=director2_.id where films0_.Actor_id=?
2020-04-07 15:27:26.296 ERROR 742 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError: null
at ar.com.ada.sb.relationship.model.mapper.FilmMapperImpl.actorSetToActorDTOSet(FilmMapperImpl.java:188) ~[classes/:na]
at ar.com.ada.sb.relationship.model.mapper.FilmMapperImpl.toDto(FilmMapperImpl.java:53) ~[classes/:na]
at ar.com.ada.sb.relationship.model.mapper.FilmMapperImpl.filmSetToFilmDTOSet(FilmMapperImpl.java:165) ~[classes/:na]
at ar.com.ada.sb.relationship.model.mapper.FilmMapperImpl.actorToActorDTO(FilmMapperImpl.java:182) ~[classes/:na]
at ar.com.ada.sb.relationship.model.mapper.FilmMapperImpl.actorSetToActorDTOSet(FilmMapperImpl.java:194) ~[classes/:na]
我非常感谢你的帮助
我偶然发现了这篇文章,并使用MapStruct通过@ManyToMany JPA关系找到了解决循环依赖关系或StackOverflowError这一相同问题的方法。java.lang.StackOverflowError中提出的解决方案@AfterMapping
(如果映射器具有循环依赖关系(目前不适合我。
在这里,我只提出了一个从实体到D的解决方案,即List<D> toDto(List<E> entityList);
,List<E> toEntity(List<D> dtoList);
暂时不需要,但需要记住的是传播到相关实体的级联操作,如PERSIST、MERGE、REMOVE、REFRESH、DETACH。
@Mapper(componentModel = "spring", uses = { })
public interface ActorMapper extends DataMapper<ActorDTO, Actor> {
@Mapping(target = "films", source = "films", qualifiedByName = "filmIdSet") // circular dependencies
ActorDTO toDto(Actor s);
@Named("filmId")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
@Mapping(target = "name", source = "name")
ActorDTO toDtoFilmId(Film film);
@Named("filmIdSet")
default Set<ActorDTO> toDtoFilmIdSet(Set<Film> films) {
return films.stream().map(this::toDtoFilmId).collect(Collectors.toSet());
}
}
@Mapper(componentModel = "spring", uses = { })
public interface FilmMapper extends DataMapper<FilmDTO, Film> {
@Mapping(target = "actors", source = "actors", qualifiedByName = "actorIdSet") // circular dependencies
FilmDTO toDto(Film s);
@Named("actorId")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
ActorDto toDtoActorId(Actor actor);
@Named("actorIdSet")
default Set<ActorDto> toDtoActorIdSet(Set<Actor> actors) {
return actors.stream().map(this::toDtoActorId).collect(Collectors.toSet());
}
}
简而言之,这个想法是默认忽略所有属性以避免循环引用,只添加非循环引用。
另一种方式是如下,但它在我们最终想要保留的属性上提供了较少的选择
@Mapper(componentModel = "spring", uses = { })
public interface ActorMapper extends DataMapper<ActorDTO, Actor> {
@Mapping(target = "films", source = "films", ignore = true) // circular dependencies
ActorDTO toDto(Actor s);
}
@Mapper(componentModel = "spring", uses = { })
public interface FilmMapper extends DataMapper<FilmDTO, Film> {
@Mapping(target = "actors", source = "actors", ignore = true) // circular dependencies
FilmDTO toDto(Film s);
}
我使用Mapstruct版本:1.5.2.最终
试试这个,实例化HashSet<>((,并且只需要一个方向映射,请保留对actor侧映射的注释。
电影
@Entity @Table(name = "Film")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Film implements Serializable {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Film_id", updatable = false, nullable = false)
private Long id;
@ManyToOne
@JoinColumn(name = "Director_id")
private Director director;
@ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.ALL
},
targetEntity= ActorModel.class
)
@JoinTable(name = "Actor_has_Film",
joinColumns = { @JoinColumn(name = "Film_id") },
inverseJoinColumns = { @JoinColumn(name = "Actor_id") })
@JsonProperty("actors")
private Set<Actor> actors = new HashSet<>();
public void addActor(Actor actor) {
this.actors.add(actor);
}
}
Actor
@Entity()
@Table(name = "Actor")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Actor implements Serializable {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Actor_id", updatable = false, nullable = false)
private Long id;
@NotBlank(message = "name is required")
private String name;
/*@ManyToMany(mappedBy = "actors")
@JsonIgnore
private Set<Film> films = new HashSet<>();*/
}