Spring Data one to many mapping
Asked Answered
A

3

11

I try to configure OneToMany and ManyToOne mapping in Spring Data project but have some issues.

So I have two entities: Employer and Project. One Employer could have many projects.

Entity classes:

Employer.java

@Entity
@Table (name="Employer")
public class Employer {

    @Id
    @SequenceGenerator(name="my_seq", sequenceName="GLOBAL_SEQUENCE")
    @GeneratedValue(strategy = GenerationType.SEQUENCE ,generator="my_seq")
    @Column (name="employer_id")
    private int id;

    @Column (name="name")
    private String name;

    @JoinColumn (name="employer_id")
    @OneToMany(mappedBy = "employer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Project> project = new HashSet<Project>();

    ......................
}   

Project.java

@Entity
@Table (name="Project")
public class Project {

    @Id
    @SequenceGenerator(name="my_seq", sequenceName="GLOBAL_SEQUENCE")
    @GeneratedValue(strategy = GenerationType.SEQUENCE ,generator="my_seq")
    @Column (name="project_id")
    private int id;

    @Column (name="name")
    private String name;

    @ManyToOne
    @JoinColumn(name="employer_id", nullable = false)
    private Employer employer;
    ......................
}

Repository classes:

public interface EmployerRepository extends JpaRepository<Employer, Integer> {
}

public interface ProjectRepository extends JpaRepository<Project, Integer> {
}

Services:

@Service
public class EmployerServiceImpl implements EmployerService {

    @Autowired
    private EmployerRepository employerRepository;

    @Override
    public List<Employer> getAllEmployers() {
        return employerRepository.findAll();
    }   
}

@Service
public class ProjectServiceImpl implements ProjectService {

    @Autowired
    private ProjectRepository projectRepository;

    @Override
    public List<Project> getAllProjects() {
        return projectRepository.findAll();
    }
}

Controller:

@Controller
public class MainController {

    private EmployerService employerService;

    private ProjectService projectService;

    @Autowired(required = true)
    @Qualifier(value = "employerService")
    public void setEmployerService(EmployerService employerService) {
        this.employerService = employerService;
    }

    @Autowired(required = true)
    @Qualifier(value = "projectService")
    public void setProjectService(ProjectService projectService) {
        this.projectService = projectService;
    }


    @RequestMapping(value="/employers", method=RequestMethod.GET)
    public @ResponseBody List<Employer> getEmployers(@RequestParam(value = "name", required = false) String name) {
        return employerService.getAllEmployers();        
    }
    ..............................
}

Employer table:

EMPLOYER_ID  .  NAME
......................
1            .  Google
2            .  Oracle
3            .  Facebook 

Project table:

PROJECT_ID .    NAME            . EMPLOYER_ID
.......................................
1          . Create web site    .  1
2          . Create reporting   .  2
3          . Create web service .  3
4          . Fixing web site    .  1        

I'm expecting something like this:

[{"id":1,"name":"Google","project":[{"id":1,"name":"Create web site"}, {"id":4,"name":"Fixing web site"}},
 {"id":2,"name":"Oracle","project":{"id":2,"name":"Create reporting"}},
 {"id":3,"name":"Facebook","project":{"id":3,"name":"Create web service"}}]

But getEmployers method from controller class return this one:

[{"id":1,"name":"Oracle","project":[{"id":4,"name":"Fixing web site",
"employer":{"id":1,"name":"Oracle","project":[{"id":4,"name":"Fixing web site",
"employer":{"id":1,"name":"Oracle","project":[{"id":4,"name":"Fixing web site",
"employer":{"id":1,"name":"Oracle","project":[{"id":4,"name":"Fixing web site",
"employer":{"id":1,"name":"Oracle","project":[{"id":4,"name":"Fixing web site",
"employer":{"id":1,"name":"Oracle","project":[{"id":4,"name":"Fixing web site","employer":
...................

Please sorry if this question discussed many times but I didn't find any suitable answer.

Thanks in advance!

Aoudad answered 1/7, 2015 at 21:29 Comment(0)
S
6

You have a bi-directional association between Employers and Projects. Additionally, you have configured both sides of the association to be loaded EAGERly (@ManyToOne associations are fetched EAGERly by default and you have forced the @OneToMany side to be fetched EAGERly as well). Due to this configuration, when the serialization framework loads the Employers, it finds valid Projects that have back-links to the Employers and ends up getting stuck in a cyclic loop.

In order to get the result you want, you will have to mark the @ManyToOne side (on the Project entity) to be fetched LAZYly as @ManyTone(fetch = FetchType.LAZY).

Splatter answered 2/7, 2015 at 3:21 Comment(6)
Thanks for replay. I add @ManyToOne(fetch = FetchType.LAZY) to Project entity but it doesn't help, the result is the same like the first one.Aoudad
It seems that Jackson ends up serializing lazy-loaded entities as well. This post has some options you could try.Splatter
I changed my OneToMany mapping to this one: @JsonInclude(JsonInclude.Include.NON_EMPTY) @OneToMany(fetch = FetchType.EAGER, mappedBy = ("employer"),cascade = CascadeType.ALL) private Set<Project> project = new HashSet<Project>(); but it also doesn't helpAoudad
You did not need any configuration on the employer.projects property since that was being serialized correctly already. You need to put @JsonIgnore on project.employer so that the serialization framework does not try to serialize it.Splatter
As an example, check out this project from Github, run it as mvn clean tomcat7:run and then send an HTTP GET request to http://localhost:8080/companies with Accept and Content-Type HTTP headers set to application/json. You will get your desired output back as JSON.Splatter
Sorry for delay and thanks for your help! Just adding @JsonIgnore to public getter employer object method resolve my issue. I accept your answer, Manish.Aoudad
I
2

Add the @JsonManagedReference In the forward part of the relationship (i.e. User.java class):

@Entity
public class User implements Serializable {

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

    @Column(name="name")
    private String name;

    @ManyToMany
    @JoinTable(name="users_roles",joinColumns=@JoinColumn(name = "user_fk"), inverseJoinColumns=@JoinColumn(name = "role_fk"))
    @JsonManagedReference
    private Set<Role> roles = new HashSet<Role>();

    ...

Add the @JsonBackReference In the back part of the relationship (i.e. Role.java class):

@Entity
public class Role implements Serializable {

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

    @ManyToMany(mappedBy="roles")
    @JsonBackReference
    private Set<User> users = new HashSet<User>();

    ...

This works very well for me. :-)

Irrigate answered 7/4, 2017 at 13:25 Comment(0)
F
0

To avoid the infinite recursion problem with one-to-many mapping, you need to add the @JsonBackReference annotation to your @ManyToOne field. This allows the JSON serializer to break the infinite loop.

Faddist answered 11/3 at 13:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.