7

How do I correctly expose lazy-loaded many-many fields so that users can GET/PATCH/POST/DELETE many-many entity relationships in Spring Data REST?

For example, given a Student entity and Teacher entity bound by a many to many relationship, with the following POJOs:

@Entity
public class Teacher { // owner of bidirectional relationship
    @Id
    private int id;
    private String name;
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "teacher_student",
            joinColumns = @JoinColumn(name = "teacher_id"),
            inverseJoinColumns = @JoinColumn(name = "student_id"))
    private Set<Student> students;

    // Constructor, getters/setters...
}

@Entity
public class Student {
    @Id
    private int id;
    private String name;
    @ManyToMany(mappedBy = "students", fetch = FetchType.LAZY)
    private Set<Teacher> teachers;

    // Constructor, getters/setters...
}

The entities are given repositories:

@RepositoryRestResource(path = "teacher")
public interface TeacherRepository extends CrudRepository<Teacher, int> {}

// similar repository for student

When I send a GET to localhost:8080/teacher, I get:

"_embedded": {
    "teacher": [
        {
        "name": "Bill Billie",
        "_links": {
            "self": { "href": "http://localhost:8080/teacher/1" },
            "teacher": { ... },
            "students": { "href": "http://localhost:8080/teacher/1/students" }
        }},
        (more teachers here...)
    ]
}
...

BUT, when I try a GET to http://localhost:8080/teacher/1/students, I get a 404 Not Found, even though the teacher "Bill Billie" does have a student associated with him in the database.

Interestingly, if I change the FetchType to FetchType.EAGER, everything works fine and I can perform the expected GET, PATCH, etc. What gives? Is this a bug, perhaps, or am I screwing something up?

tl;dr Many-many relations are not correctly exposed with lazy fetching, but work fine with eager fetching. How can I get lazy fetching to work with it?

Edit: If it matters, I am using Spring 4.2.6 with Spring Boot 1.3.5, and OpenJPA 2.4.1 as my JPA provider.

JW Lim
  • 1,794
  • 3
  • 21
  • 41
  • This is likely to be quite a complicated issue; especially since you are using OpenJPA instead of say, Hibernate, which is probably better known. If you provide a working example project that demonstrates the issue, you're much more likely to get a resolution. – Will Faithfull Jun 24 '16 at 21:31
  • @WillFaithfull that's fair. I might give that a shot and try to have a working example, though given the amount of dependencies and custom configuration, that would be challenging. Oh, the joys of custom business needs. – JW Lim Jun 24 '16 at 22:12
  • Try to make the most minimal example possible. It doesn't need to resemble your domain model, just reproduce the issue. – Will Faithfull Jun 24 '16 at 22:18
  • Can you please share logging information for this request? – Kowser Jun 29 '16 at 16:03

3 Answers3

1

Hmm I am not sure why it isn't auto-fetching someone more experienced would have to ask that, but you can specify a manual fetch with HQL's join fetch for each query.

select x from X left join fetch x.y y

After getting this working you can override your get statement with a specifically created controller as described here: Spring Data REST: Override repository method on the controller

Only other option I think might be worth trying is adding an @Lazy annotation on your Spring-data repository.

Community
  • 1
  • 1
Matthew Fontana
  • 3,790
  • 2
  • 30
  • 50
  • 1
    Yeah, I'm pretty sure I could get it to work if I manually write all the queries and override the `find` methods, but that would mostly defeat the point of using Spring JPA and Data REST in the first place. I'd prefer to keep that as a last resort. Oh and the `@Lazy` annotation doesn't work either, unfortunately. – JW Lim Jun 28 '16 at 22:40
  • I've awarded the bounty to you for the helpful response, even though it didn't exactly solve the problem I was facing. Still looking for more answers. – JW Lim Jun 29 '16 at 16:40
  • Thanks, When I get a chance I will try and setup a test project and see if I can run some debugging. – Matthew Fontana Jun 29 '16 at 18:08
1

Try making your RestResources transactional.

Annotate with @Transactional

Mihir Gohel
  • 316
  • 2
  • 6
0

Add the following dependency

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate4</artifactId>
</dependency>

Add the configuration class.

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{


    public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();

        ObjectMapper mapper = new ObjectMapper();
        //Registering Hibernate4Module to support lazy objects
        mapper.registerModule(new Hibernate4Module());

        messageConverter.setObjectMapper(mapper);
        return messageConverter;

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //Here we add our custom-configured HttpMessageConverter
        converters.add(jacksonMessageConverter());
        super.configureMessageConverters(converters);
    }
}
maruf571
  • 1,836
  • 2
  • 21
  • 18