Spring Data JPA - bidirectional relation with infinite recursion - java

First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?

Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}

In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion

You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.

Related

How can I use polymorphism in Spring Boot Services?

I am currently working on a Spring Boot project and I would like to speed up process of writing the service/data layer boilerplate code (one service and one repository (CrudRepository) for every entity, every one having mostly the same methods).
As of now I am using TABLE_PER_CLASS inheritance in several entities (e.g.: Warehouse and Office are subclasses of Location (an abstract class defining common attributes for all locations).
I would like to define 1 repository and 1 service to manage both Location and its subtypes so I can do something like in my control layer:
#Autowired
LocationsService locationsService;
Warehouse cityWarehouse = new Warehouse();
Office centralOffice = new Office();
locationsService.addNewLocation(cityWarehouse);
locationsService.addNewLocation(centralOffice);
I know I can just use method overloading but I would really like to avoid repeating the same code in situations like this one.
I've also tried using parametric polymorphism:
#Service
public class LocationsService {
#Autowired
LocationsRepository locationsRepository;
public void addNewLocation(Location location) {
locationsRepository.save(location);
}
}
Unfortunately this won't work as Spring can't tell if I want to save a Location or a Warehouse object:
nested exception is org.springframework.orm.jpa.JpaObjectRetrievalFailureException:
Unable to find com.test.springboot.entities.locations.Location with id 55db6993-8a58-4e3a-a6ab-d60d93ab6182; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.test.springboot.entities.locations.Location with id 55db6993-8a58-4e3a-a6ab-d60d93ab6182
I need to use concrete Location objects so using #MappedSuperclass is not an option.
Is there something I am missing? Is it even posible to achieve what I want?
Please note that I am fairly new to Spring Boot so maybe there's something obvious I don't know about yet.
I got it working thanks to some of the comments and after briefly reading the JPA specification.
Because I wanted to use my superclasses as entities I ended up using SINGLE_TABLE inheritance.
For example, this is the Location entity:
#Entity
#Data
#Accessors(chain = true)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "location_type")
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Skipped
}
The key here is to use #DiscriminatorColumn with SINGLE_TABLE inheritance in the parent class and add #DiscriminatorValue in the correspondent subclasses:
#Entity
#Data #Accessors(chain = true)
#DiscriminatorValue("warehouse_location")
public class Warehouse extends Location {
#JoinColumn(name = "INTERNAL_ROUTE_ID")
#OneToOne(orphanRemoval = true)
private Route internalRoute;
#JoinColumn(name = "EXTERNAL_WAREHOUSE_ID")
#OneToMany(orphanRemoval = true, fetch = FetchType.EAGER)
private List<Warehouse> externalWarehouses;
}
This way, I can define LocationsRepository as:
public interface LocationsRepository extends CrudRepository<Location, Long> {
Warehouse findByCityIgnoreCase(String city);
}
Also note that subclass-specific methods can be defined here as long as its return type is explicitly specified (otherwise the method would return ALL Locations, not just the Warehouses).
Finally, in the service layer I can make the relevant methods return any entity just by downcasting the result of the repository call into the appropriate subclass

Lazy proxies through interfaces (entities with final methods)

I have a requirement in my project that all my methods should be either abstract or final (please don't argue this requirement -- I know it's dumb, just assume it's there).
This is a problem with Hibernate mapped entities since Hibernate needs to create proxies in run-time in order to be able to initialize relations when those are lazily loaded. Not being able to override setter methods results in those not being loaded at all (query is indeed executed, but the object is never populated).
As stated in Hibernate's documentation:
If the final class does implement a proper interface, you could alternatively tell Hibernate to use the interface instead when generating the proxies. See Example 4.4, “Proxying an interface in hbm.xml” and Example 4.5, “Proxying an interface in annotations”.
Example:
#Entity #Proxy(proxyClass=ICat.class) public class Cat implements ICat { ... }
So theoretically it's possible to just tell hibernate to implement an interface instead of extending the original class.
I've tried this solution, but my problem comes with the relations themselves. Here's an over-simplified example:
#Entity
#Proxy(proxyClass = ICat.class)
#Table(name = "cat")
public class Cat implements ICat {
#Id
private Long catId;
#OneToMany(mappedBy = "cat", fetch = FetchType.LAZY)
private List<Kitten> kittens;
...
}
#Entity
#Proxy(proxyClass = IKitten.class)
#Table(name="kitten")
public class Cat implements IKitten {
#Id
private Long kittenId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="catId")
private Cat cat;
...
}
Now if I try to obtain a Cat object, I get a ClassCastException since it is trying to cast an IKitten collection into a Kitten collection. Which leads me to think I should declare relations using interfaces instead of implementations -- which also produces a compilation-time error since my Interfaces are never declared as entities, but the implementations are (which is clearly stated in the example from the documentation).
How can I solve this?
You con use the interface in both one-to-many and many-to-one associations, but you need to supply the actual Class in the targetEntity attribute. The relations should be something like this:
#Entity
#Proxy(proxyClass = ICat.class)
#Table(name = "cat")
public class Cat implements ICat {
#Id
private Long catId;
#OneToMany(mappedBy = "cat", fetch = FetchType.LAZY, targetEntity=Cat.class)
private List<IKitten> kittens;
...
}
#Entity
#Proxy(proxyClass = IKitten.class)
#Table(name="kitten")
public class Cat implements IKitten {
#Id
private Long kittenId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="catId", targetEntity=Cat.class)
private ICat cat;
...
}
I had the same requirement before, and bypassed it by declaring final all classes. My hibernate structure was not proxying, and thus I don't know if that will fix the problem, but the requirement. There is a side problem if you are using mockito. Have this in mind.
By the way, there is a typo in the second class shown in your code, it shall be named Kitten

JPA OneToMany - Collection is null

I'm trying to set up a bidirectional relationship using JPA. I understand that it's the responsability of the application to maintain both sides of the relationship.
For example, a Library has multiple Books. In the Library-entity I have:
#Entity
public class Library {
..
#OneToMany(mappedBy = "library", cascade = CascadeType.ALL)
private Collection<Book> books;
public void addBook(Book b) {
this.books.add(b);
if(b.getLibrary() != this)
b.setLibrary(this);
}
..
}
The Book-entity is:
#Entity
public class Book {
..
#ManyToOne
#JoinColumn(name = "LibraryId")
private Library library;
public void setLibrary(Library l) {
this.library = l;
if(!this.library.getBooks().contains(this))
this.library.getBooks().add(this);
}
..
}
Unfortunately, the collection at the OneToMany-side is null. So for example a call to setLibrary() fails because this.library.getBooks().contains(this) results in a NullPointerException.
Is this normal behavior? Should I instantiate the collection myself (which seems a bit strange), or are there other solutions?
Entities are Java objects. The basic rules of Java aren't changed just because there is an #Entity annotation on the class.
So, if you instantiate an object and its constructor doesn't initialize one of the fields, this field is initialized to null.
Yes, it's your responsibility to make sure that the constructor initializes the collection, or that all the methods deal with the nullability of the field.
If you get an instance of this entity from the database (using em.find(), a query, or by navigating through associations of attached entities), the collection will never be null, because JPA will always initialize the collection.
It seems that books type of Collection in Library class is not initilized. It is null;
So when class addBook method to add a book object to collection. It cause NullPointerException.
#OneToMany(mappedBy = "library", cascade = CascadeType.ALL)
private Collection<Book> books;
public void addBook(Book b) {
this.books.add(b);
if(b.getLibrary() != this)
b.setLibrary(this);
}
Initilize it and have a try.
Change
private Collection<Book> books;
To
private Collection<Book> books = new ArrayList<Book>();
Try to set the fetch type association property to eager on the OneToMany side. Indeed, you may leave this part (this.library.getBooks().add(this)) to be written within a session:
Library l = new Library();
Book b = new Book();
b.setLibrary(l);
l.getBooks().add(b);

How annotation mapping is done in java persistence?

We use annotations for mapping the entity class with the database table by simply specifying #Entity and more like #Id, table joins and many things. I do not know how these entity variables are getting mapped with database table. Can anyone give a short description for understanding.
Thanks :)
Well the idea is to translate your objects and their connections with other objects into a relational database. These two ways of representing data (objects defined by classes and in tables in a database) are not directly compatible and that is where a so called Object Relational Mapper framework comes into play.
So a class like
class MyObject
{
private String name;
private int age;
private String password;
// Getters and setters
}
Will translate into a database table containing a column name which is of type varchar, age of type int and password of type varchar.
Annotations in Java simply add additional information (so called meta data) to your class definitions, which can be read by any other class (e.g. JavaDoc) and in the case of the Java Persistence API will be used by an ORM framework like Hibernate to read additional information you need to translate your object into the database (your database table needs a primary id and some information - like what type of a relation an object has to another - can't be automatically determined by just looking at your class definition).
Annotations are very well explained here:
http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/
annotations are just metadata on a class, nothing magical. You can write your own annotations. Those annotations are given retention policies of runtime (which means you have access to that metadata at runtime). When you call persist etc the persistence provider iterates through the fields (java.lang.reflect.Field) in your class and checks what annotations are present to build up your SQL statement. Try writing your own annotation and doing something with it. It won't seem very magical after that.
in your case annotation working means mapping with tablename with entity class is look like as ....
#Entity
#Table(name = "CompanyUser")
public class CompanyUserCAB implements java.io.Serializable
{
private long companyUserID;
private int companyID;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "companyUserID")
public long getCompanyUserID()
{
return this.companyUserID;
}
public void setCompanyUserID(long companyUserID)
{
this.companyUserID = companyUserID;
}
#Column(name = "companyID")
public int getCompanyID()
{
return this.companyID;
}
public void setCompanyID(int companyID)
{
this.companyID = companyID;
}
}

JPA: persisting object, parent is ok but child not updated

I have my domain object, Client, I've got a form on my JSP that is pre-populated with its data, I can take in amended values, and persist the object.
Client has an abstract entity called MarketResearch, which is then extended by one of three more concrete sub-classes.
I have a form to pre-populate some MarketResearch data, but when I make changes and try to persist the Client, it doesn't get saved, can someone give me some pointers on where I've gone wrong?
My 3 domain classes are as follows (removed accessors etc)
public class Client extends NamedEntity
{
#OneToOne
#JoinColumn(name = "MARKET_RESEARCH_ID")
private MarketResearch marketResearch;
...
}
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class MarketResearch extends AbstractEntity
{
...
}
#Entity(name="MARKETRESEARCHLG")
public class MarketResearchLocalGovernment extends MarketResearch
{
#Column(name = "CURRENT_HR_SYSTEM")
private String currentHRSystem;
...
}
This is how I'm persisting
public void persistClient(Client client)
{
if (client.getId() != null)
{
getJpaTemplate().merge(client);
getJpaTemplate().flush();
} else
{
getJpaTemplate().persist(client);
}
}
To summarize, if I change something on the parent object, it persists, but if I change something on the child object it doesn't. Have I missed something blatantly obvious?
I've put a breakpoint right before the persist/merge calls, I can see the updated value on the object, but it doesn't seem to save. I've checked at database level as well, no luck
Thanks
You need to set a proper cascade option on #OneToOne in order to get your operations cascaded:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "MARKET_RESEARCH_ID")
private MarketResearch marketResearch;

Categories

Resources