12

I have an entity that owns another entity:

//psuedocode    
public class ClassA{
   private String name;

   @OneToOne
   private ClassB classb;
}

public class ClassB{
   private String thing1;
   private String thing2;
   private String thing3;
}

When I retrieve ClassA objects, I don't want to see ClassB.thing3, but I do want to see thing1 and thing 2:

{
"name":"classa",
"classb":{
         "thing1":"hi",
         "thing2":"there"
        }
}

But if I query for ClassB I want to see everything:

{"thing1":"hi",
 "thing2":"there",
 "thing3":"joseph"}

So I can't just put an ignore annotation over thing3, because then I'll ignore it on the second fetch. I tried a Converter<ClassB>, but that forces me to implement toString() and fromString() for the JSON, which dies on converting the JSON object to Java-side (the converter expects a String, but gets the object instead).

I want to avoid building/parsing the JSON object myself if possible to let my json provider do the work, if possible. I'm on Johnzon.

GuitarStrum
  • 713
  • 8
  • 24
  • 1
    What is your json provider? Jackson supports Json views which can do what you want, but its non-standard. Ex: http://www.baeldung.com/jackson-json-view-annotation – Gimby Feb 09 '17 at 14:51

3 Answers3

1

This is possible, you need to use @NamedEntityGraph,

This should help, http://www.thoughts-on-java.org/jpa-21-entity-graph-part-1-named-entity/

zillani
  • 1,070
  • 3
  • 18
  • 26
  • I think that in order for that to be accepted as answer you should at least describe the implementation for OPs case, not just point at a generic tutorial. – Deltharis Feb 09 '17 at 15:01
  • Not sure if @NamedEntityGraph's are the way to go here because [JPA 2.1](http://download.oracle.com/otndocs/jcp/persistence-2_1-fr-eval-spec/index.html) Spec allows the provider to fetch as much data as it sees fit, so there's no guarantee ClassB.thing3 won't be loaded, just because it's not in the Entity Graph: _The persistence provider is permitted to fetch additional entity state beyond that specified by a fetch graph or load graph. It is required, however, that the persistence provider fetch all state specified by the fetch or load graph._ – tom Feb 11 '17 at 18:54
  • I tried this out and it doesn't do anything for me on OpenJPA. On a LAZY fetch I get nothing, and an EAGER fetch I get everything. I'm tinkering with the annotation to see if I can get it to work. – GuitarStrum Feb 15 '17 at 22:15
  • After a few hours of researching this and trying it out, I'm not sure it'll work in my case. I couldn't find any confirmation that this would work with OpenJPA, and my few hours of trying to get it to work with it didn't help its case. It does more or less look like what I wanted, judging from the different tutorials and whatnot, and I really wanted it to just work; it just doesn't work for my setup it seems. – GuitarStrum Feb 16 '17 at 13:03
1

Something like this should be possible by querying using SELECT NEW, but you're going to need some new Classes for that ... and won't be passing your entities directly to JSON.

new Classes: (pseudocode)

class ResultB {
   String thing1;
   String thing2;

   public ResultB(ClassB classB) {
       this.thing1 = classB.thing1;
       this.thing2 = classB.thing2; 
   } 
}

class ResultA {
   String name;
   ResultB resultB;

   public ResultA(ClassA classA) {
      this.name=classA.name;
      this.resultB=new ResultB(classA);
   }
}

Query:

select new ResultA(a) from ClassA a fetch join a.classB;

Then you can pass ResultA instead of ClassA to JSON.

PS: As mentioned in the comment above, I don't think NamedEntityGraphs are the way to go here

tom
  • 1,455
  • 1
  • 8
  • 13
  • This could be one solution, but instead of passing ResultA to my Json provider I'd be tempted to convert it back into ClassA and ClassB so that the interface to my team's frontend stays the same. This does come with the benefit of not needing to spend resources of fetching unneeded fields, but comes with the whole "need special data access classes" thing that I'll have to run through my team since it goes against our current standard of just using the entities as our data transfer and access classes. – GuitarStrum Feb 16 '17 at 13:06
  • Hmm, yes, you could convert it back to the original classes, however you should be careful that those classes aren't persistet "as is" when they're returned because you might be erasing (nullable) fields (or producing Exceptions if the fields aren't nullable). – tom Feb 21 '17 at 16:23
1

I would always fetch all the data from the database and let the filtering to be done by the JSON provider if you want to serialize it anyway. If you use Jackson you can simply add views to your fields:

public class ClassA {

   @JsonView(Views.AlwaysInclude.class)
   private String name;

   @JsonView(Views.AlwaysInclude.class)
   @OneToOne
   private ClassB classb;
}

public class ClassB {
   @JsonView(Views.AlwaysInclude.class)
   private String thing1;

   @JsonView(Views.AlwaysInclude.class)
   private String thing2;

   private String thing3;
}

public class Views {
    public static class AlwaysInclude {
    }
}

Later when you serialize your object you just need to use your view:

String result = mapper
  .writerWithView(Views.AlwaysInclude.class)
  .writeValueAsString(new ClassA());

When you want to serialize only ClassB then you shouldn't use views.

String result = mapper.writeValueAsString(new ClassB());
akos.bordas
  • 179
  • 1
  • 7
  • We're not using Jackson but thank you. For the "I would always fetch all the data from the database and let the filtering to be done by the JSON provider" comment, I can go in a similar route and just null out fields I don't need from ClassB when fetching ClassA. The downside to that approach is that I'm still spending resources fetching data I don't need, but on the flipside I can then let my provider construct the correct json for me. – GuitarStrum Feb 16 '17 at 12:43