Jackson Circular Dependencies - java

I have a circular dependency that I am struggling to solve right now
Take these two classes - boiler plate code removed for demo purposes
Class 1
#Entity
#Table(name = "T_CREDENTIAL")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
public class Credential implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//Removed #JsonIgnore as want to disable certain fields if no credentials are available
//Also fetch this in an eager fashion instead of lazily loading it
#OneToMany(mappedBy = "credential",fetch=FetchType.EAGER)
private Set<UserTask> userTasks = new HashSet<>();
....
.....
Class 2
#Entity
#Table(name = "T_USERTASK")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
public class UserTask implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8179545669754377924L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToOne(fetch=FetchType.EAGER)
#NotNull(message = "Credential must not be null")
private Credential credential;
Unfortunately I have use cases where UserTask needs to load the credentials and cases where Credential needs to load the Usertasks
The annotation #JsonIdentityInfo seems to be doing its job
If i load all UserTasks, it loads the first userTask and its credential, but then that credential object will also load any UserTasks that are assigned to that credential. This then encounters the new #Id or a userTask which now loads it with the credential instead of as 2 users tasks
Any ideas what I can do to circumvent this problem?
Cheers
Damien
--Question Update
I have updated my code now to use the new annotations as mentioned by other users
Class 1
#Entity
#Table(name = "T_CREDENTIAL")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Credential implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8696943454186195541L;
//Removed #JsonIgnore as want to disable certain fields if no credentials are available
//Also fetch this in an eager fashion instead of lazily loading it
#OneToMany(mappedBy = "credential",fetch=FetchType.EAGER)
#JsonManagedReference("credential")
private Set<UserTask> userTasks = new HashSet<>();
....
....
Class 2
#Entity
#Table(name = "T_USERTASK")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class UserTask implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToOne(fetch=FetchType.EAGER)
#NotNull(message = "Credential must not be null")
#JsonBackReference("credential")
private Credential credential;
....
....
These classes now now work and have no circular dependencies
However, when i do a query now to get my usertasks or a single task, the credential object is not returned even though I do not have the #jsonIgnore annotation specified. Any ideas what is causing this?

if you are using Jackson HttpMessageConverter, you should check their documentation - http://wiki.fasterxml.com/JacksonFeatureBiDirReferences
You should add annotations shown here:
public class NodeList
{
#JsonManagedReference
public List<NodeForList> nodes;
}
public class NodeForList
{
public String name;
#JsonBackReference public NodeList parent;
public NodeForList() { this(null); }
public NodeForList(String n) { name = n; }
}

I went with the annotation below in the end
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
Thanks for your help guys

Related

How to remove foreign key without deleting the whole entity

I am new to Spring boot and I want to be able to delete the value of a forgein key if its entity is removed without deleting the whole entity linked to it; I explain in my case a single person who has an Account can be at the same time an Author and a Player, so if I delete an author I want to delete its refrence in Account table without deleting the whole account because this account can still point on player. I searched on the internet I found cascadetype but it will delete the whole account!
Thank you in advance!
here is my entities
#Table(name = "account")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "ID")
private Long id;
#ManyToOne
#JoinColumn(name = "Author")
private Author author;
#ManyToOne
#JoinColumn(name = "Player")
private Player player;
//attributs, getters & setters
}
#Table(name = "player")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Player implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "ID")
private Long id;
//attributs, getters & setters
}
//ma.myapp.usersgestion.domain.Author
#Table(name = "author")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Author implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "ID")
private Long id;
#OneToMany(mappedBy = "author")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIgnoreProperties(value = { "player", "author"}, allowSetters = true)
private Set<Account> accounts = new HashSet<>();
//attributs, getters & setters
}
UPDATE
Im using jhipster (spring boot with React) and h2 database (with disk-based persistence)
//AuthorResource.java
#RestController
#RequestMapping("/api")
#Transactional
public class AuthorResource {
private final Logger log = LoggerFactory.getLogger(AuthorResource.class);
private static final String ENTITY_NAME = "author";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final AuthorRepository authorRepository;
public AuthorResource(AuthorRepository authorRepository) {
this.authorRepository = authorRepository;
}
/**
* {#code DELETE /authors/:id} : delete the "id" author.
*
* #param id the id of the author to delete.
* #return the {#link ResponseEntity} with status {#code 204 (NO_CONTENT)}.
*/
#DeleteMapping("/authors/{id}")
public ResponseEntity<Void> deleteAuthor(#PathVariable Long id) {
log.debug("REST request to delete Author : {}", id);
authorRepository.deleteById(id);
return ResponseEntity
.noContent()
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString()))
.build();
}
//...
}
//AuthorRepository
#SuppressWarnings("unused")
#Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {}
In your entity class author add the following:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "author", cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST })
private Set<Account> accounts;
I've omitted the cascadetype CascadeType.REMOVE from the list. This will prevent Account from also being deleted when the related Author entity is deleted.
EDIT:
If the above solution somehow doesn't work then you can also try adding #OnDelete(action = OnDeleteAction.NO_ACTION) above the accounts field.
#OnDelete is a hibernate specific annotation.
EDIT 2:
If none of the solutions provided above work then you can also consider making a javax.persistence.#PreRemove annotated method that manually sets the author field for each related Account to null. You place this method inside the Author class. A method that is annotated with #PreRemove will always run before the entity is deleted. So for Author you could use the following method to set all author_id fields to null.
#PreRemove
public void deleteAuthor(){
this.getAccounts().forEach(account -> account.setAuthor(null));
}

JPA one to one mapping creates multiple query when child entity is not found

I have a parent entity 'contracts' that has a one-to-one relation with another entity 'child-contract'. the interesting thing is that the mapping field ('contract_number')id not a primary key-foreign key but is rather a unique field in both the tables. Also it is possible for a contracts to not have any child contract altogether. With this configuration I have observed hibernate to generate 1 additional query every time a contracts does not have a child-contract. I filed this behavior very strange. Is there a way to stop these unnecessary query generation or have I got something wrong.
below is a piece of my code configuration.
#Data
#Entity
#Table(name = "contracts")
public class Contracts implements Serializable {
#Id
#JsonIgnore
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
#OneToOne(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JsonProperty("crm_contracts")
#JoinColumn(name = "contract_number", referencedColumnName = "contract_number")
private ChildContract childContract ;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "child_contract")
#BatchSize(size=1000)
public class ChildContract implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty("id")
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
}
Please help.
Thank-you
You can use NamedEntityGraph to solve multiple query problem.
#NamedEntityGraph(name = "graph.Contracts.CRMContracts", attributeNodes = {
#NamedAttributeNode(value = "crmContract") })
Use this on your repository method as
#EntityGraph(value = "graph.Contracts.CRMContracts", type = EntityGraphType.FETCH)
// Your repo method in repository

#JsonIdentityInfo Stop serialization after first occurrence

I have two entities as such:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "numeroOfferta")
#Entity
#Table(name=DatabaseConstants.TABELLA_OFFERTE)
public class Offerta {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private BigInteger numeroOfferta;
#ManyToOne(fetch=FetchType.EAGER)
private Cliente cliente;
private Double importoOfferta;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "offerta")
private Set<Ordine> ordini;
And:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table(name=DatabaseConstants.TABELLA_ORDINI)
public class Ordine {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private BigInteger id;
private String numeroOrdine;
#ManyToOne(fetch=FetchType.EAGER)
private Offerta offerta;
private String stato;
#OneToOne(fetch= FetchType.EAGER)
private Binder binder;
Where exist a many-to-one relationship between the ORDINE and OFFERTA objects (more ORDINE to one OFFERTA).
When the entities are serialized in JSON it appears that if there is more than one ORDINE, only the first one is retreived completely, while the others appear only as their ID.
What I need is to serialize the ORDINE and its OFFERTA, without going back to ORDINE.
Is the use of #JsonIdentityInfo correct?
Can anyone provide an explanation of the behavior described?

Hibernate infinite recursion and bidirectional relationship

I have two entities. I want a bidirectional relationship between projects and tasks.
A project has one or more tasks
A task is associated to only one project
There are my entities:
ProjectEntity.java
#Entity
#Table(name = "projects")
public class ProjectEntity {
#Id
#GeneratedValue
#Column(name = "pr_id")
private long id;
#Column(name = "pr_name", nullable = false)
private String name;
#OneToMany(cascade=CascadeType.ALL, mappedBy="project",orphanRemoval=true)
#JsonBackReference
private Set<TaskEntity> tasks = new HashSet<TaskEntity>();
// Getters, constructors, setters
TaskEntity.java
#Entity
#Table(name = "tasks")
public class TaskEntity {
#Id
#GeneratedValue
#Column(name = "ta_id")
private long id;
#Column(name = "ta_name")
private String name;
#ManyToOne
#JoinColumn(name="pr_id")
#JsonManagedReference
private ProjectEntity project;
// Getters, constructors, setters
I would like to have the list of tasks in each ProjectEntity, and in each TaskEntity the project associated.
Here, I'm using #JsonManagedReference and #JsonBackReference to stop the infinite recursion it generated, but in my ProjectEntity, I don't have the tasks list (because of the #JsonBackReference) ...
Could you help me to get back tasks list in ProjectEntity ? I heard about #JsonIdentifyInfo, but I have not managed to do with.
Hope I'm understandable :)
The solution that I know is really using '#JsonIdentityInfo' in instead of '#JsonBackReference' and '#JsonManagedReference'.
You have to remove the '#JsonBackReference' and '#JsonManagedReference' to use '#JsonIdentityInfo'.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
#Entity
#Table(name = "projects")
public class ProjectEntity {
#Id
#GeneratedValue
#Column(name = "pr_id")
private long id;
#Column(name = "pr_name", nullable = false)
private String name;
#OneToMany(cascade=CascadeType.ALL, mappedBy="project",orphanRemoval=true)
private Set<TaskEntity> tasks = new HashSet<TaskEntity>();
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
#Entity
#Table(name = "tasks")
public class TaskEntity {
#Id
#GeneratedValue
#Column(name = "ta_id")
private long id;
#Column(name = "ta_name")
private String name;
#ManyToOne
#JoinColumn(name="pr_id")
private ProjectEntity project;
}
You could opt for implement a custom serializer for the list ProjectEntity.tasks. But you must controll/stop the serialization cycle at some point; this will depend on your requirements.
Here is an example of what I mean.
#Entity
#Table(name = "projects")
public class ProjectEntity {
...
#OneToMany(cascade=CascadeType.ALL, mappedBy="project", orphanRemoval=true)
#JsonSerialize(using = CustomListSerializer.class)
private Set<TaskEntity> tasks = new HashSet<TaskEntity>();
}
And the CustomListSerializer could be something like this,
public class CustomListSerializer extends JsonSerializer<List<TaskEntity>>{
#Override
public void serialize(List<TaskEntity> tasks, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartArray();
for (TaskEntity task : tasks) {
generator.writeStartObject("taskEntity")
.write("id", task.id)
.write("name", task.name)
.write("project", task.project.id) //<- here you stop the cycle
.writeEnd();
}
generator.writeEnd();
}
}
Note that it is basically the example of the mentioned mini guide but it serialize information of the elements of project's tasks list instead of the tasks' id only.

jpa one-to-many self reference is fetching all levels

I'm trying to create a social app service. I have user with confirmed or nonconfirmed relationships.
When I load UserA, the result look like belove.
"result":{
"idUser":"UserA",
"unconFriendships":[
{
"idUser":"UserB",
"unconFriendships":[
{
"idUser":"UserC",
"unconFriendships":[
...
While it has to be look like
"result":{
"idUser":"UserA",
"unconFriendships":[
{
"idUser":"UserB",
"unconFriendships":null //only one level have to fetched
....
I thought that this was because jackson json library, I debbuged the code. Before serialization, I inspected userA object and I saw that userA.unconFriendships.userB.unconFriendships was not null and with size bigger than 0.
Nearly it has been 12 hours, still couldn't solve the problem. Please help me to solve this. Thanks in advence.
Here is UserEntity.java
#Entity
#Table(name="aduser",uniqueConstraints=#UniqueConstraint(columnNames = {"idUser","nmEmail"}))
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="cdUser")
public class UserEntity extends BaseEntity {
protected static final long serialVersionUID = 8864033727886664353L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "admin_seq")
#SequenceGenerator(name = "admin_seq", sequenceName = "CDUSER_SEQUENCE", allocationSize = 1)
#Column(name="cdUser")
private long cdUser;
#OneToMany(mappedBy = "owner", targetEntity=Friendship.class)
#JsonProperty
protected Set<UnconfirmedFriendship> unconFriendships;
#OneToMany(mappedBy = "owner", targetEntity=Friendship.class)
#JsonProperty
protected Set<UnconfirmedFriendship> conFriendships;
...
Friendship.java
#Entity
#Table(name="aduserfriend")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "verified")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="friend_cduser",scope=UserEntity.class)
public abstract class Friendship extends BaseEntity{
protected static final long serialVersionUID = -670863816551430192L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "cdFriendship")
private long cdFriendship;
#ManyToOne
#JsonIgnore
#JoinColumn(name = "owner_cduser")
protected UserEntity owner;
#ManyToOne
#JoinColumn(name = "friend_cduser")
protected UserEntity friend;
#Column(name = "verified",insertable=false,updatable=false)
private boolean verified;
...
UnconfirmedFriendship.java and ConfirmedFriendship.java
#Entity
#DiscriminatorValue(value = "0")//this value is 1 for Confirmed relationship
public class UnconfirmedFriendship extends Friendship {
private static final long serialVersionUID = 57796452166904132L;
}

Categories