Hibernate @OneToMany annotation doesn't seem to be working

98 views Asked by At

I'm trying to fetch my "Ticket" entity with my "FileData" included, but I can't get it to work and I'm completely out of ideas as of why. Others' code seems to work just fine with just what I did with my own. But whenever I fetch it, my "Ticket" object's "files" column comes back empty, no matter what.

(and yes, there's data for it in the database, so the associated column to the given ID is not null or anything like that)

My BaseEntity class includes the @Id annotation, and date created, modified, created by user etc.

Ticket entity:

package com.issue.tracker.ticket;

import com.issue.tracker.common.BaseEntity;
import com.issue.tracker.filesystem.FileData;
import com.issue.tracker.project.Project;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.HashSet;
import java.util.Set;

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "tickets")
public class Ticket extends BaseEntity {

    @NotEmpty
    private String issueName;

    @NotEmpty
    private String description;

    @ManyToOne
    @JoinColumn(name="project_id", nullable=false)
    private Project project;

    //This is supposed to work but for some reason it doesn't seem to
    @OneToMany(mappedBy = "ticket", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<FileData> files = new HashSet<>();
}

FileData entity:

package com.issue.tracker.filesystem;

import com.issue.tracker.common.BaseEntity;
import com.issue.tracker.ticket.Ticket;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "file_datas")
@AllArgsConstructor
@NoArgsConstructor
public class FileData extends BaseEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="ticket_id", nullable=false)
    private Ticket ticket;

    @NotEmpty
    private String filePath;

    @NotEmpty
    private String fileName;

    @NotEmpty
    private String fileType;

    @NotNull
    private Long fileSize;
}

TicketRepository:


package com.issue.tracker.ticket;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface TicketRepository extends JpaRepository<Ticket, Long> {

    List<Ticket> findAllByCreatedBy(String username);
}

TicketServiceImpl (including the commented out part where I get an empty "files" column back):

package com.issue.tracker.ticket;

import com.issue.tracker.common.RollbackOnException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
@Service
public class TicketServiceImpl implements TicketService {

    private final TicketRepository ticketRepository;

    @RollbackOnException
    @Override
    public Ticket createTicket(Ticket ticket) {
        return ticketRepository.save(ticket);
    }

    @Override
    public List<Ticket> getAllTicketsByName(String username) {
        return ticketRepository.findAllByCreatedBy(username);
    }

    @Override
    public List<Ticket> getAllTickets() {
        return ticketRepository.findAll();
    }

    @Override
    public Ticket getTicketById(Long id) {
        Ticket ticket = ticketRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Ticket not found"));
        log.info(ticket.toString());
    //this is what I get here, note that "files" is empty:
    //Ticket(issueName=AAAAAAAAAA...
    //description=ahdfjduafadjfhadjfhadjfhadjfhadjlfhadjfadjfhad,
    //project=Project(projectName=Webshop), files=[])
        return ticket;
    }
}

I was curious about the SQL log and this is the hibernate magic that happens in the background:

select
    t1_0.id,
    t1_0.created_at,
    t1_0.created_by,
    t1_0.description,
    t1_0.issue_name,
    t1_0.modified_by,
    p1_0.id,
    p1_0.created_at,
    p1_0.created_by,
    p1_0.modified_by,
    p1_0.project_name,
    p1_0.updated_at,
    t1_0.updated_at
from tickets t1_0 join projects p1_0 on p1_0.id=t1_0.project_id
where t1_0.id=?

select f1_0.ticket_id,
f1_0.id,
f1_0.created_at,
f1_0.created_by,
f1_0.file_name,
f1_0.file_path,
f1_0.file_size,
f1_0.file_type,
f1_0.modified_by,
f1_0.updated_at
from file_datas f1_0
where f1_0.ticket_id=?

select f1_0.ticket_id,
f1_0.id,
f1_0.created_at,
f1_0.created_by,
f1_0.file_name,
f1_0.file_path,
f1_0.file_size,
f1_0.file_type,
f1_0.modified_by,
f1_0.updated_at
from file_datas f1_0
where f1_0.ticket_id=?

My file_datas part is getting called separately, twice. I'm guessing twice because there's two rows that correspond to the given ticket_id. I've included some pictures of the data and connection between the tables.

tickets table file_datas table connection between

UPDATE

It still doesn't work.

public TicketDataDto getTicketById(@PathVariable Long id) {

    Ticket ticket = ticketService.getTicketById(id);
    ticket.getFiles();
    log.info(ticket.toString());

    return modelMapper.map(ticket, TicketDataDto.class);
}

Simply calling ticket.getFiles() doesn't do anything, it's still empty.

Modifying it to

public TicketDataDto getTicketById(@PathVariable Long id) {

    Ticket ticket = ticketService.getTicketById(id);
    var files = ticket.getFiles();
    log.info(files.toString());

    return modelMapper.map(ticket, TicketDataDto.class);
}

doesn't work either. Changing the Ticket entity's fetchtype to fetch=FetchType.EAGER doesn't solve it.

UPDATE 2 Removing @Data from the Ticket entity and creating its own toString doesn't help either, same response every time.

@Override
public String toString() {
    return "Ticket{" +
            "issueName='" + issueName + '\'' +
            ", description='" + description + '\'' +
            ", project=" + project +
            ", files=" + files +
            '}';
}

This toString produces:

Ticket{issueName='AAAAAAAAAAAAAAAAAAAAA',  description='ahdfjduafadjfhadjfhadjfhadjfhadjlfhadjfadjfhad', project=Project(projectName=Webshop), files=[]}

UPDATE 3 files variable in debugging "PersistentSet" type

1

There are 1 answers

6
Tristan On

Your "files" attribut is supposed to be empty because u define it like that : fetch = FetchType.LAZY

Then if u call getFiles on your retrieved ticket, it will fetch files.