JPA fetch only one level association - java

I'm having an issue when trying to fetch all of the "Discount" objects stored in my application. A "Discount" is an object represented by the following class
#Entity
#NoArgsConstructor
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "store")
public class Discount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// some fields like a description or a percentage providing info about a discount
#ManyToOne
#JoinColumn(name = "id_store")
private Store store;
#ManyToOne
#JoinColumn(name = "id_brand")
#JsonManagedReference
private Brand brand;
// getters, setters, equals and hashcode overriden below
As you can see, a Discount object can belong to a Store or to a Brand. I'm ensuring that only one field is set both in my code and in my database with a trigger. In my application, a Discount can be provided both by a Brand (therefore I don't have to create a Discount for each Store of this Brand) or a particular Store can provide its own Discount. My class Store looks like this
#Entity
#NoArgsConstructor
public class Store extends Entite {
#OneToMany(mappedBy = "store", cascade = CascadeType.ALL)
private Set<Discount> discounts;
#ManyToOne
#JoinColumn(name = "id_brand", nullable = false)
private Brand brand;
// getters, setters, equals and hashcode overriden below
As you can see, a Store has a Set of Discounts and belong to a Brand. It extends the class Entite because in my app I also have Sport Clubs and they share some info, but this is not relevant to my problem.
Finally, here is the Brand class
#Entity
#NoArgsConstructor
public class Brand {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "brand", cascade = CascadeType.ALL)
private Set<Store> stores;
#OneToMany(mappedBy = "brand", cascade = CascadeType.ALL)
#JsonBackReference
private Set<Discount> discounts;
// getters, setters, equals and hashcode overriden below
My Brand class then have a Set of Store and a Set of Discount.
My problem is that I'm unable to fetch all the Discount with a simple FindAll from my interface DiscountRepository extending JpaRepository<Discount, Long> because some of the Discount are retrieved within an association. The answer that I get from the FindAll method is the following
[
{
// discount fields such as percentage, ruling, etc
"store": {
"id": 75,
"name": "Nike Store",
"discounts": [
75
],
"brand": {
"name": "Nike",
"store": [
75
]
}
},
"brand": null
},
{
// discount fields such as percentage, ruling, etc
"store": null,
"brand": {
"name": "Uniqlo",
"stores": [
90
]
}
},
{
// discount fields such as percentage, ruling, etc
"store": null,
"brand": {
"name": "FNAC",
"stores": [
{
"id": 76,
// some store fields
// problem here because Discount are retrieved here and therefore not displayed in the list, only here
"discounts": [
{
// discount fields such as percentage, ruling, etc
// for an offer that isn't present in the original list returned
// by the find all method but only here, associated to this Store object
"store": 76,
"brand": null
}
],
"brand": {
"name": "FNAC",
"stores": [
76
]
}
}
]
}
},
76
]
The problem here is that there's a Discount fetch with a Store and therefore this Discount isn't in the original list that the method returns, but in a Store object associated to a Discount. I tried to add FetchType.LAZY to my Store and Discount association but this doesn't resolve the problem. Therefore is there a way to fetch "only one level association" ? What I mean is, I'd like to retrieve my Discount object but only with the Brand and the Store info, not with the Store or Brand and all its assocations (like its Set of Discount). I hope that I've been clear, do not hesitate to ask me any question.

Related

JSON returns one field with ID and other field with full object, when both objects are same

I have a class like below.
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Entity
#Table( name = "hires", indexes = { #Index( name = "idx_hire_date", columnList = "date DESC" ) }, uniqueConstraints = {
#UniqueConstraint( columnNames = { "vehicle_id", "po_number" } ) } )
#DynamicUpdate
#JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" )
public class Hire implements Serializable {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY )
int id;
#OneToOne( targetEntity = Driver.class, fetch = FetchType.EAGER )
#JoinColumn( name = "pass_payer", referencedColumnName = "id", nullable = true )
Driver passPayer;
#OneToOne( targetEntity = Driver.class, fetch = FetchType.EAGER )
#JoinColumn( name = "driver_id", referencedColumnName = "id", nullable = true )
Driver driver;
...
}
I get this object via a Rest endpoint.
The problem is when the field passPayer and driver objects are equal, in the returning JSON, the driver field contains only the ID (which is just an integer value) and passPayer field has all the object fields.
"passCost": 300.0,
"passPayer": {
"id": 9,
"firstName": "XXXX",
"lastName": "XXXXXX",
"idNo": "000000000000"
},
"driver": 9,
"driverSalary": xxxx.xx,
When these fields have different objects, both fields show full details like below.
"passCost": 300.0,
"passPayer": {
"id": 9,
"firstName": "XXXX",
"lastName": "XXXXXX",
"idNo": "000000000000"
},
"driver": {
"id": 4,
"firstName": "YYYYYY",
"lastName": "YYYYYYY",
"idNo": "10101010101"
},
"driverSalary": 00000.00,
I need both objects to contain data (fields. [id, firstName, lastName, idNo]) whether they are equal or not.
Any clue is appreciated!
This is caused by #JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" ), check the docs. To cite :
Annotation used for indicating that values of annotated type or
property should be serializing so that instances either contain
additional object identifier (in addition actual object properties),
or as a reference that consists of an object id that refers to a full
serialization.
Since both fields are referencing the same object, the second one is serialized as a reference to the first object.
In my experience, this annotation is mostly used to deal with circular references, so you can:
remove it, if your use case allows it(no circular references in object)
or you can use DTOs(which is the prefered approach anyway)

Self join to JSON in Spring Boot rest

I have an Account object containing a OneToMany relation with Beneficiary object and this relationship is bi-directional so I have a ManyToOne relation in the Beneficiary Object with Account Object
public class Account {
#Id
#GeneratedValue
private Long id;
private String name;
private String number;
//Other fields
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Beneficiary> beneficiaries = new ArrayList<>();
}
public class Beneficiary {
#Id
#GeneratedValue
private Long id;
//Other fields
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private Account account;
}
In the JSON response, I need the Account information containing the list of Beneficiaries and for each Beneficiary I just need the Account name and Account number. Is it possible to serialize it somehow so that I get response in this fashion? Or do I need to modify my entity structures?
Sample Account Response -
{
"id": 123,
"name": "Name1",
"number": "111111",
"beneficiaries": [
{
"id": 1,
"account": {
"name": "Name2",
"number": "222222"
}
},
{
"id": 2,
"account": {
"name": "Name3",
"number": "333333"
}
}
]
}
You are not supposed to serialize your JPA objects. Instead, you need to define domain objects. These are objects are the ones to be serialize and exposed to the business. This abstraction decouples your REST or SOAP or whatever interface with your JPA layer.
I would create a domain class for your account class. Call it AccountDTO or something like that. Any object being returned from your JPA repositories need to be mapped to this DTO objects and bubbled up to the services layer. Then your DTO is the class which models your business needs. In there you can just put the accounts and the beneficiaries names.
DTO stands for Data Transfer Objects. These are the ones supposed to be serialized and sent between systems.
One idea would be to use a custom serializer.
You would have to write a custom serializer, similar to this:
public class NestedAccountSerializer extends StdSerializer<Account> {
public NestedAccountSerializer() {
this(null);
}
public NestedAccountSerializer(Class<Account> t) {
super(t);
}
#Override
public void serialize(Account account, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeObject(new AccountView(account.getName(), account.getNumber()));
}
private static class AccountView {
#JsonProperty
private final String name;
#JsonProperty
private final String number;
AccountView(String name, String number) {
this.name = name;
this.number = number;
}
}
}
And then use it like this in your Beneficiary class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id")
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonSerialize(using = NestedAccountSerializer.class)
private Account account;
Please, let me know if it helped.

Spring Entity OneToMany Ignore Self

Currently, I have the following relationship between two entities:
#Entity
public class Pokemon {
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "trainer_id")
#JsonIgnoreProperties("pokemons")
private Trainer trainer;
}
and also:
#Entity
public class Trainer {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,
mappedBy = "trainer")
private Set<Pokemon> pokemons = new HashSet<>();
}
Everything is fine when updating the entities. However, when I attempt to retrieve all trainers, it includes the trainer within the pokemon, which I don't want. Observe:
GET ALL POKEMON (all is fine):
{
"id": 1,
"name": "squirtle",
"type": "water",
"trainer": {
"id": 1,
"name": "Ash Ketchum",
"level": 1
}
}
GET ALL TRAINERS:
{
"id": 1,
"name": "Ash Ketchum",
"level": 1,
"pokemons": [
{
"id": 1,
"name": "squirtle",
"type": "water",
"trainer": {
"id": 1,
"name": "Ash Ketchum",
"level": 1
}
}
]
}
Notice how the trainer class is returned within each pokemon inside of the the pokemons set? I'd prefer not to return that, since I already have access to that information. Is there anyway I can tell the entity not to return its own info from the Pokemon class? If it helps, my retrieval query looks like this:
public List<Trainer> getAllTrainers() {
em.getTransaction().begin();
List<Trainer> trainer = em.createNativeQuery("SELECT * FROM
Trainer", Trainer.class).getResultList();
em.getTransaction().commit();
return trainer;
}
Thank you so much.
For anyone interested, I was able to achieve my goal by telling my Trainer class to ignore the trainer property in the Pokemon object via #JsonIgnoreProperties
#Entity
public class Trainer implements Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "trainer")
#JsonIgnoreProperties(value = "trainer")
private Set<Pokemon> pokemons = new HashSet<>();

JPA all referenced relations are retrieved (ManyToMany - ManyToOne - OneToMany)

Working on this 'twitter' application where a user can have posts #OneToMany and can have followers #ManyToMany.
While retrieving a user all it's posts and followers get retrieved as well.
This is all correct but it's also retrieving every 'poster' for each post (which is the user itself) and for each follower, all it's posts and followers.
I can't figure out how to limit this to the user itself.
User
#Entity
#Table(name = "User")
#NamedQueries({
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
#NamedQuery(
name = "User.auth",
query = "SELECT u FROM User u WHERE u.username = :username AND u.password = :password"
)
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String username;
#Column(nullable = false)
private String password;
#ManyToMany
#OneToMany(mappedBy = "poster", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
#JoinTable(name = "Followers",
joinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
},
inverseJoinColumns = {
#JoinColumn(name = "FOLLOWER_ID", referencedColumnName = "ID")
}
)
private List<User> followers = new ArrayList<>();
.... constructor, getters and setters
Post
#Entity
#Table(name = "Post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
#ManyToOne
private User poster;
.... constructor, getters and setters
Outcome I get vs what I want
{
"id": 1,
"username": "jim",
"posts": [
{
"id": 1,
"content": "Post 1 by jim",
"poster": {
// ^ this is the user itself (I don't need this one)
"id": 1,
"username": "jim",
"posts": [
// keeps recurse
]
}
}
],
"followers": [
{
"id": 2,
"username": "follower1",
"posts": [
{
"id": 4,
"content": "Post 2 by follower 1",
"poster": {
// ^ this is the follower itself (I don't need this one)
"id": 2,
"username": "follower1",
"posts": [
// Same issue
]
}
}
],
"followers": [], // <-- I don't need this one either
}
]
}
Well it's pretty clear that fetching one user fill keeps fetching all it's relations which are recursive.
Is this a designer's fault or can this be ignored/limited?
Note: I am using Gson to serialise objects to JSON format
Update
Tried to use:
#ManyToOne(fetch = FetchType.LAZY)
private User poster;
Which works but still gets the following extra prop in JSONso not sure if this is a neath solution:
"_persistence_poster_vh": {
"sourceAttributeName": "poster",
"isInstantiated": false,
"row": {
"Post.ID": 3,
"Post.CONTENT": "Post 3 by jim",
"Post.DATETIME": "2018-01-22",
"Post.POSTER_ID": 1
},
"isCoordinatedWithProperty": false
}
And
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
...
)
private List<User> followers = new ArrayList<>();
Which still returns all followers (which I want!) I just don't want the followers.followers and followers.posts..
Best guess: it’s not actually fetching these objects until you try to dereference them.
Be default, JPA will eager fetch #OneToOne and #OneToMany relations, but not #ManyToOne or #ManyToMany. What happens is that when you reference these fields, it will then go and fetch the actual contents of the list.
How can you tell this is happening? Check the list’s class using getFollowers().getClass()
What you see won’t be a LinkedList or an ArrayList but a class from your JPA provider, probably with “Lazy” somewhere in the name. When you call Size or Get on the list, it will perform the fetch.
You can set OneToOne and OneToMany relations to be lazy as well, and use EntityGraphs to determine what entities you want to eagerly fetch as well. JPA has a number of gotchas like this.
I’ve seen GSON mentioned, and just a warning: it won’t be aware of the lazy loading lists, so you MUST tell It to avoid the properties you don’t want it to follow.
Typically with JSON marshaling, you’ll want it to ignore the parent object, so in Post, User should be ignored for example. Additionally links to same types should typically be ignored (followers) or else mapped specially, such that it doesn’t Marshall the entire object, but only produces an array of usernames. You can tell it to ignore the actual followers field, and have it marshal a getter which returns an array of usernames to implement this.
You can specify fetch=FetchType.LAZY in the annotation you don't want to fetch immediately. The downside is, that if you need the data later you have to access it in the scope of the still open session.
There are two ways to handle this -
You can either use #JsonIgnoreProperties(ignoreUnknown=true) anotation on attributes you want to skip while serializing the object.
Or you change your FetchType to FetchType.LAZY so that you can get the required data on need basis while preparing your JSON , rather than getting all records at once.

Spring Jackson deserialization only picks the first item of an array

I building an api rest with spring boot. I have a parent-child relationship in witch the child its an array of objects.
The problem is that deserialization only picks the first item of the array. Everything else seems to work fine. The parent and the child are pesisted in the database too.
I send something like this:
"user": {
"name": "foo",
"childs": [
{
"name": "bar",
....
},
{
"name": "foobar",
....
}
],
....
}
But got persisted this:
"user": {
"id": 1,
"name": "foo",
"childs": [
{
"id": 1,
"name": "bar",
....
}
],
....
}
Any clue on this?
Update
Parent Entity:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = User.class)
#Entity( name = "users" )
#Table( name = "users" )
public class User extends ModelEntity {
Model's fields...
...
#JsonView( value = {DTOViews.PrivateProfile.class, DTOViews.Owner.class} )
#JsonManagedReference( value = "User-ProfessionalExperience" )
#OneToMany( mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY )
private Set<ProfessionalExperience> professionalExperiences;
}
Child entity:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = ProfessionalExperience.class)
#Entity
#Table( name = "professional_experiences")
public class ProfessionalExperience extends ModelEntity {
Model's fields...
...
#JsonBackReference( value = "User-ProfessionalExperience" )
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
Controller:
#RequestMapping(method = RequestMethod.POST)
public MappingJacksonValue create(#RequestBody #Valid User userToCreate, BindingResult result) {
...
}
Thank you all in advance.
So, I finally solved it. The issue comes from the relationship collection type and hasCode() / equals() methods.
All the entities in my model extend from "ModelEntity" class. This class provides id and record active fields for all extending models and a hasCode/equals method based on these fields. As the relationship between "User" and "ProfessionalExperience" is defined as a set, it can't store duplicated elements.
So, to tell jackson that the children are different elements, we need to override hasCode/equals in each model class with the fields defined in each one.

Categories