@OneToMany в Spring Data (канал со списком сообщений, активное получение) - PullRequest
0 голосов
/ 17 сентября 2018

Я хотел бы правильно добавить сообщения в канал.

Каналы без сообщений добавляются в базу данных должным образом:

{
    id: 1,
    name: "Off-top",
    messageDTOs: [ ],
    participantDTOs: [
    {
        id: 1,
        firstName: "Szef",
        lastName: "Ceo",
        email: "szef.ceo@company.com",
    role: "ADMIN",
    manager: true
    }
}

Но если я добавляю одно сообщение в канал, я получаю ошибку HTTP 500 для GET: /channels.

Я использовал FetchType.EAGER и соответствующие классы следующие:

Message.java:

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Message {

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

    @Column(nullable = false)
    private String content;

    @OneToOne
    private Employee author;

    @Column(nullable = false)
    private LocalDateTime creationTime;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "channel_id")
    private Channel channel;

    public Message(String content, Employee author, Channel channel) {
        this.content = content;
        this.author = author;
        this.creationTime = LocalDateTime.now();
        this.channel = channel;
    }

    public boolean checkIfDataEquals(Message message) {
        return content.equals(message.getContent()) &&
                author.checkIfDataEquals(message.getAuthor()) &&
                channel.checkIfDataEquals(message.getChannel());
    }
}

Channel.java

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Channel {

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

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    @OneToMany(mappedBy = "channel", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Message> messages;

    @Column(nullable = false)
    @ManyToMany
    private List<Employee> participants;

    public Channel(String name, List<Employee> participants) {
        this.name = name;
        this.messages = new ArrayList<>();
        this.participants = participants;
    }

    public boolean checkIfDataEquals(Channel channel) {
        return name.equals(channel.getName()) &&
                compareParticipants(channel.getParticipants());
    }

    private boolean compareParticipants(List<Employee> participantList) {
        if (participantList.isEmpty())
            return true;
        for (Employee employee : participants) {
            if (participantList.stream().noneMatch(t -> t.checkIfDataEquals(employee)))
                return false;
        }
        return true;
    }
}

Существует проблема с установкой сообщений DTO в channelDTO, потому что я получаю: java.lang.StackOverflowError: null

    at com.herokuapp.erpmesbackend.erpmesbackend.chat.ChannelDTO.<init>(ChannelDTO.java:24) ~[classes/:na]
    at com.herokuapp.erpmesbackend.erpmesbackend.chat.MessageDTO.<init>(MessageDTO.java:23) ~[classes/:na]

Для занятий:

MessageDTO.java

@Data
@AllArgsConstructor
public class MessageDTO {

    private long id;
    private String content;
    private EmployeeDTO authorDTO;
    private ChannelDTO channelDTO;
    private LocalDateTime creationTime;

    public MessageDTO(Message message) {
        this.id = message.getId();
        this.content = message.getContent();
        this.authorDTO = new EmployeeDTO(message.getAuthor());
        this.channelDTO = new ChannelDTO(message.getChannel()); // PROBLEMATIC LINE
        this.creationTime = message.getCreationTime();
    }

    public MessageDTO(String content, EmployeeDTO authorDTO, ChannelDTO channelDTO) {
        this.content = content;
        this.authorDTO = authorDTO;
        this.channelDTO = channelDTO;
    }

    public boolean checkIfDataEquals(MessageDTO messageDTO) {
        return content.equals(messageDTO.getContent()) &&
                authorDTO.checkIfDataEquals(messageDTO.getAuthorDTO()) &&
                channelDTO.checkIfDataEquals(messageDTO.getChannelDTO());
    }
}

ChannelDTO.java

@Data
@AllArgsConstructor
public class ChannelDTO {

    private long id;
    private String name;
    private List<MessageDTO> messageDTOs;
    private List<EmployeeDTO> participantDTOs;

    public ChannelDTO(Channel channel) {
        this.id = channel.getId();
        this.name = channel.getName();
        this.messageDTOs = new ArrayList<>();
        for (int i = 0; i < channel.getMessages().size(); i++)
            messageDTOs.add(new MessageDTO(channel.getMessages().get(i)));  // PROBLEMATIC LINE
        this.participantDTOs = new ArrayList<>();
        channel.getParticipants().forEach(participant -> this.participantDTOs.add(new EmployeeDTO(participant)));
    }

    public ChannelDTO(String name, List<EmployeeDTO> participantDTOs) {
        this.name = name;
        this.participantDTOs = participantDTOs;
    }

    public boolean checkIfDataEquals(ChannelDTO channelDTO) {
        return name.equals(channelDTO.getName()) &&
                compareParticipantDTOs(channelDTO.getParticipantDTOs());
    }

    private boolean compareMessageDTOs(List<MessageDTO> messageDTOList) {
        if (messageDTOList.isEmpty())
            return true;
        for (MessageDTO messageDTO: messageDTOs) {
            if (messageDTOList.stream().noneMatch(t -> t.checkIfDataEquals(messageDTO)))
                return false;
        }
        return true;
    }

    private boolean compareParticipantDTOs(List<EmployeeDTO> participantDTOList) {
        if (participantDTOList.isEmpty())
            return true;
        for (EmployeeDTO participantDTO : participantDTOs) {
            if (participantDTOList.stream().noneMatch(t -> t.checkIfDataEquals(participantDTO)))
                return false;
        }
        return true;
    }
}

MessageController.java:

@RestController
@CrossOrigin(origins = "*")
public class MessageController {

    private final MessageRepository messageRepository;
    private final ChannelRepository channelRepository;
    private final EmployeeRepository employeeRepository;

    @Autowired
    public MessageController(MessageRepository messageRepository, ChannelRepository channelRepository,
                             EmployeeRepository employeeRepository) {
        this.messageRepository = messageRepository;
        this.channelRepository = channelRepository;
        this.employeeRepository = employeeRepository;
    }

    @PostMapping("/channels/{id}/messages")
    @ResponseStatus(HttpStatus.CREATED)
    public MessageDTO addOneMessage(@PathVariable Long id, @RequestBody MessageRequest messageRequest) {
        checkIfChannelExists(1L);

        String content = messageRequest.getContent();
        Channel channel = channelRepository.findById(id).get();

        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String username = ((UserDetails) principal).getUsername();
        Employee author = employeeRepository.findByEmail(username).get();

        Message message = new Message(content, author, channel);
        messageRepository.save(message);
        return new MessageDTO(message);
    }

    private void checkIfChannelExists(Long id) {
        if (!channelRepository.findById(id).isPresent())
            throw new NotFoundException("Such channel doesn't exist!");
    }
}

ChannelController.java

@RestController
@CrossOrigin(origins = "*")
public class ChannelController {

    private final ChannelRepository channelRepository;
    private final EmployeeRepository employeeRepository;

    @Autowired
    public ChannelController(ChannelRepository channelRepository, EmployeeRepository employeeRepository) {
        this.channelRepository = channelRepository;
        this.employeeRepository = employeeRepository;
    }

    @GetMapping("/channels")
    @ResponseStatus(HttpStatus.OK)
    public List<ChannelDTO> getAllChannels() {
        List<Channel> channels = channelRepository.findAll();
        List<ChannelDTO> channelDTOs = new ArrayList<>();
        channels.forEach(channel -> channelDTOs.add(new ChannelDTO(channel)));
        return channelDTOs;
    }

    @GetMapping("/channels/{id}")
    @ResponseStatus(HttpStatus.OK)
    public ChannelDTO getOneChannel(@PathVariable("id") Long id) {
        checkIfChannelExists(id);
        return new ChannelDTO(channelRepository.findById(id).get());
    }

    @GetMapping("/employees/{id}/channels")
    @ResponseStatus(HttpStatus.OK)
    public List<ChannelDTO> getChannelsByParticipant(@PathVariable("id") Long id) {
        checkIfParticipantExists(id);

        if (!channelRepository.findByParticipantsId(id).isPresent())
            return new ArrayList<>();

        List<Channel> channels = channelRepository.findByParticipantsId(id).get();
        List<ChannelDTO> channelDTOs = new ArrayList<>();
        channels.forEach(channel -> channelDTOs.add(new ChannelDTO(channel)));

        return channelDTOs;
    }

    @PostMapping("/channels")
    @ResponseStatus(HttpStatus.CREATED)
    public ChannelDTO addOneChannel(@RequestBody ChannelRequest channelRequest) {
        String name = channelRequest.getName();

        checkIfParticipantListIsEmpty(channelRequest.getParticipantIds());
        List<Employee> participants = new ArrayList<>();
        channelRequest.getParticipantIds().forEach(this::checkIfParticipantExists);
        channelRequest.getParticipantIds().forEach(id -> participants.add(employeeRepository.findById(id).get()));

        Channel channel = new Channel(name, participants);
        channelRepository.save(channel);
        return new ChannelDTO(channel);
    }

    @PutMapping("/channels/{id}")
    public HttpStatus updateChannel(@PathVariable("id") Long id, @RequestBody ChannelRequest channelRequest) {
        checkIfChannelExists(id);
        Channel channel = channelRepository.findById(id).get();

        channel.setName(channelRequest.getName());

        checkIfParticipantListIsEmpty(channelRequest.getParticipantIds());
        List<Employee> participants = new ArrayList<>();
        if (channelRequest.getParticipantIds() != null) {
            channelRequest.getParticipantIds().forEach(this::checkIfParticipantExists);
            channelRequest.getParticipantIds().forEach(index -> participants.add(employeeRepository.findById(index).get()));
        }
        channel.setParticipants(participants);

        channelRepository.save(channel);
        return HttpStatus.NO_CONTENT;
    }

    @DeleteMapping("/channels/{id}")
    public HttpStatus removeChannel(@PathVariable("id") Long id) {
        checkIfChannelExists(id);
        channelRepository.delete(channelRepository.findById(id).get());
        return HttpStatus.OK;
    }

    private void checkIfChannelExists(Long id) {
        if (!channelRepository.findById(id).isPresent())
            throw new NotFoundException("Such channel doesn't exist!");
    }

    private void checkIfParticipantExists(Long id) {
        if (!employeeRepository.findById(id).isPresent())
            throw new NotFoundException("At least one of the participant doesn't exist!");
    }

    private void checkIfParticipantListIsEmpty(List<Long> participantIds) {
        if (participantIds.isEmpty())
            throw new InvalidRequestException("List of participants can't be empty!");
    }
}

Может ли кто-нибудь помочь, пожалуйста?

1 Ответ

0 голосов
/ 17 сентября 2018

Проблема в том, что у вас двунаправленная связь между каналом и сообщением,

  • Сообщение на канал - это ManyToOne, которое по умолчанию стремится
  • , канал к сообщению - OneToMany, который по умолчанию равенленивый, но вы навязываете нетерпеливое извлечение.

Таким образом, у вас двунаправленное отношение с извлечением извлечения с двух сторон, что приведет к переходу гибернации в бесконечный цикл извлечения каждой стороны с другой стороны.

КомуРешите эту проблему, удалите нетерпеливую выборку для сообщений в канале

...