Spring Boot MapStructMap程序未正确Map嵌套DTO

h43kikqp  于 2023-04-06  发布在  Spring
关注(0)|答案(1)|浏览(210)

问题:User DTO的MapstructMap器未返回JSON中深度嵌套的DTO字段的空值(UserDto类具有PlaylistDto属性,而PlaylistDto类本身包含SongDto字段)。
**项目OVREVIEW:**我在使用mapstruct正确Map嵌套DTO字段时遇到了一些问题。我正在处理一个音乐流springBoot应用程序,它处理几个实体-UserPlaylistSong。用户和播放列表(用户是拥有方)之间存在一对多关联,播放列表(拥有方)和歌曲实体之间存在多对多关联。这些实体如下:

用户实体
@Entity
@Table(name = "users")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")

public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id ;

    private String username ;
    private String password ;
    private String email ;
    private String firstname ;
    private String lastname ;

    @OneToMany(
            mappedBy = "user" ,
            cascade = {CascadeType.PERSIST , CascadeType.MERGE} ,
            orphanRemoval = true
    )
    private List<Playlist> playlists = new ArrayList<>() ;

    // getters, setters and constructors ...
}
播放列表实体
@Entity
@Table(name = "playlist")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Playlist {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id ;

    private String name ;
    private String description ;
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id")
    private User user ;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "playlist_song" ,
            joinColumns = {@JoinColumn(name = "playlist_id")} ,
            inverseJoinColumns = {@JoinColumn(name = "song_id")}
    )
    private Set<Song> songs = new HashSet<>() ;

    // getters, setters and constructors ...
}
歌曲实体
@Entity
@Table(name = "song")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Song {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id ;

    private String name ;
    private String artist ;
    private String album ;
    private Integer year ;
    private String genre ;
    private Integer duration ;

    @ManyToMany(mappedBy = "songs")
    private Set<Playlist> playlists = new HashSet<>() ;

    // getters, setters and constructors ...

上述实体的DTO如下:

用户DTO
public class UserDto {
    private Long id ;
    private String username ;
    private String email ;
    private String firstname ;
    private String lastname ;

    private List<PlaylistDto> playlistsDto ;

    // constructors
    public UserDto() {
    }

    public UserDto(Long id, String username, String email, String firstname, String lastname, List<PlaylistDto> playlistsDto) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.firstname = firstname;
        this.lastname = lastname;
        this.playlistsDto = playlistsDto;
    }

    // getters and setters ...
}
播放列表DTO
public class PlaylistDto {
    private Long id ;
    private String name ;
    private String description ;
    private Set<SongDto> songsDto;

    // constructors

    public PlaylistDto() {
    }

    public PlaylistDto(Long id, String name, String description, Set<SongDto> songsDto) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.songsDto = songsDto;
    }

    // getters & setters
}
歌曲DTO
public class SongDto {
    private Long id ;
    private String name ;
    private String artist ;
    private String album ;

    // constructors

    public SongDto() {
    }

    public SongDto(Long id, String name, String artist, String album) {
        this.id = id;
        this.name = name;
        this.artist = artist;
        this.album = album;
    }

    // getters & setters
}

最后,这里是Map器:

// User Mapper
@Mapper(componentModel = "spring" , uses = {PlaylistDto.class , SongDto.class})
@Component
public interface UserMapper {

    UserMapper MAPPER = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "playlists", target = "playlistsDto")
    UserDto usertoUserDto(User user) ;

    @Mapping(source = "playlistsDto", target = "playlists")
    User userDtoToCustomer(UserDto userDto) ;
}

// Playlist Mapper
@Mapper(componentModel = "spring" , uses = {SongDto.class})
@Component
public interface PlaylistMapper {

    PlaylistMapper MAPPER = Mappers.getMapper(PlaylistMapper.class) ;
    @Mapping(source = "songs" , target = "songsDto")
    PlaylistDto playlistToDto (Playlist entity) ;

    @Mapping(source = "songsDto" , target = "songs")
    Playlist toEntity (PlaylistDto dto) ;
}

// Song Mapper 
@Mapper(componentModel = "spring")
@Component
public interface SongMapper {
    SongMapper MAPPER = Mappers.getMapper(SongMapper.class) ;

    SongDto songToDto (Song entity) ;

    Song toEntity (SongDto dto) ;
}

mapstruct生成的Mapper实现:

播放列表Map器实现
@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-04-04T11:16:24+0530",
    comments = "version: 1.5.3.Final, compiler: javac, environment: Java 19.0.2 (Oracle Corporation)"
)
@Component
public class PlaylistMapperImpl implements PlaylistMapper {

    @Override
    public PlaylistDto playlistToDto(Playlist entity) {
        if ( entity == null ) {
            return null;
        }

        PlaylistDto playlistDto = new PlaylistDto();

        playlistDto.setSongsDto( songSetToSongDtoSet( entity.getSongs() ) );
        playlistDto.setId( entity.getId() );
        playlistDto.setName( entity.getName() );
        playlistDto.setDescription( entity.getDescription() );

        return playlistDto;
    }

    @Override
    public Playlist toEntity(PlaylistDto dto) {
        if ( dto == null ) {
            return null;
        }

        Playlist playlist = new Playlist();

        playlist.setSongs( songDtoSetToSongSet( dto.getSongsDto() ) );
        playlist.setName( dto.getName() );
        playlist.setDescription( dto.getDescription() );

        return playlist;
    }

    protected SongDto songToSongDto(Song song) {
        if ( song == null ) {
            return null;
        }

        SongDto songDto = new SongDto();

        songDto.setId( song.getId() );
        songDto.setName( song.getName() );
        songDto.setArtist( song.getArtist() );
        songDto.setAlbum( song.getAlbum() );

        return songDto;
    }

    protected Set<SongDto> songSetToSongDtoSet(Set<Song> set) {
        if ( set == null ) {
            return null;
        }

        Set<SongDto> set1 = new LinkedHashSet<SongDto>( Math.max( (int) ( set.size() / .75f ) + 1, 16 ) );
        for ( Song song : set ) {
            set1.add( songToSongDto( song ) );
        }

        return set1;
    }

    protected Song songDtoToSong(SongDto songDto) {
        if ( songDto == null ) {
            return null;
        }

        Song song = new Song();

        song.setName( songDto.getName() );
        song.setArtist( songDto.getArtist() );
        song.setAlbum( songDto.getAlbum() );

        return song;
    }

    protected Set<Song> songDtoSetToSongSet(Set<SongDto> set) {
        if ( set == null ) {
            return null;
        }

        Set<Song> set1 = new LinkedHashSet<Song>( Math.max( (int) ( set.size() / .75f ) + 1, 16 ) );
        for ( SongDto songDto : set ) {
            set1.add( songDtoToSong( songDto ) );
        }

        return set1;
    }
}
User MapperImpl
@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-04-04T11:16:25+0530",
    comments = "version: 1.5.3.Final, compiler: javac, environment: Java 19.0.2 (Oracle Corporation)"
)
@Component
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDto usertoUserDto(User user) {
        if ( user == null ) {
            return null;
        }

        UserDto userDto = new UserDto();

        userDto.setPlaylistsDto( playlistListToPlaylistDtoList( user.getPlaylists() ) );
        userDto.setId( user.getId() );
        userDto.setUsername( user.getUsername() );
        userDto.setEmail( user.getEmail() );
        userDto.setFirstname( user.getFirstname() );
        userDto.setLastname( user.getLastname() );

        return userDto;
    }

    @Override
    public User userDtoToCustomer(UserDto userDto) {
        if ( userDto == null ) {
            return null;
        }

        User user = new User();

        user.setPlaylists( playlistDtoListToPlaylistList( userDto.getPlaylistsDto() ) );
        user.setUsername( userDto.getUsername() );
        user.setEmail( userDto.getEmail() );
        user.setFirstname( userDto.getFirstname() );
        user.setLastname( userDto.getLastname() );

        return user;
    }

    protected PlaylistDto playlistToPlaylistDto(Playlist playlist) {
        if ( playlist == null ) {
            return null;
        }

        PlaylistDto playlistDto = new PlaylistDto();

        playlistDto.setId( playlist.getId() );
        playlistDto.setName( playlist.getName() );
        playlistDto.setDescription( playlist.getDescription() );

        return playlistDto;
    }

    protected List<PlaylistDto> playlistListToPlaylistDtoList(List<Playlist> list) {
        if ( list == null ) {
            return null;
        }

        List<PlaylistDto> list1 = new ArrayList<PlaylistDto>( list.size() );
        for ( Playlist playlist : list ) {
            list1.add( playlistToPlaylistDto( playlist ) );
        }

        return list1;
    }

    protected Playlist playlistDtoToPlaylist(PlaylistDto playlistDto) {
        if ( playlistDto == null ) {
            return null;
        }

        Playlist playlist = new Playlist();

        playlist.setName( playlistDto.getName() );
        playlist.setDescription( playlistDto.getDescription() );

        return playlist;
    }

    protected List<Playlist> playlistDtoListToPlaylistList(List<PlaylistDto> list) {
        if ( list == null ) {
            return null;
        }

        List<Playlist> list1 = new ArrayList<Playlist>( list.size() );
        for ( PlaylistDto playlistDto : list ) {
            list1.add( playlistDtoToPlaylist( playlistDto ) );
        }

        return list1;
    }
}
意外行为

我还设置了控制器,用于检索数据库中的播放列表和数据库中的用户的JSON对象。GET播放列表(DTO)的端点给出了预期的JSON对象,因为嵌套的songDto字段不会为每个播放列表对象返回null,如下图所示:

相反,对于具有GET request to fetch all users (DTOs)的端点,嵌套的playlistDto字段似乎工作正常,但内部嵌套的songsDto字段为每个userDto JSON对象返回null。此行为不应发生(因为存在与一些播放列表相关联的歌曲,这些歌曲可以在GET播放列表端点的输出中看到)。下面是GET用户请求的JSON对象的图像:

为什么只在获取所有UsersDto时出现空值问题,而在获取所有playlistsDto时不会出现空值问题,以及如何修复。

y4ekin9u

y4ekin9u1#

您在Map器配置中使用了错误的uses字段。您引用的是DTO。

@Mapper(componentModel = "spring" , uses = {PlaylistDto.class , SongDto.class})
@Component
public interface UserMapper {

根据文档,您需要引用另一个Map器,该Map器应该由已配置的Map器使用

/**
 * Other mapper types used by this mapper. May be hand-written classes or other mappers generated by MapStruct. No
 * cycle between generated mapper classes must be created.
 *
 * @return The mapper types used by this mapper.
 */
Class<?>[] uses() default { };

您的解决方案应如下所示:

@Mapper(componentModel = "spring" , uses = {PlaylistMapper.class , SongMapper .class})
@Component
public interface UserMapper {

顺便说一句,你不必在你展示的类中调用Mappers.get(。这个引用从来没有使用过。它是用uses配置引用的。

相关问题