Self-Referencing record leading to "Direct self-reference leading to cycle" exception - spring-boot

I have self referencing class
#Entity
#Table(name = "contacts")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "contacts")
public class Contacts implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password_smartlpc")
private String password;
#Column(name = "full_name")
private String fullName;
#ManyToOne
private Contacts companyContact;
}
But for my one database record
id full_name username password company_contact_id
5 JAK movies abc xyz 5
This record has company_contact_id as its self id.Which while retrieving goes into self-referencing cycle.
Enter: com.fps.web.rest.errors.ExceptionTranslator.processRuntimeException()
with argument[s] =
[org.springframework.http.converter.HttpMessageNotWritableException: Could not
write content: Direct self-reference leading to cycle (through reference
chain: java.util.UnmodifiableRandomAccessList[2]-
>com.fps.domain.Contacts["companyContact"]-
>com.fps.domain.Contacts["companyContact"]); nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Direct self-reference
leading to cycle (through reference chain:
java.util.UnmodifiableRandomAccessList[2]-
>com.fps.domain.Contacts["companyContact"]-
>com.fps.domain.Contacts["companyContact"])]
Work Around i have tried
(fetch = FetchType.LAZY) = gives same error as above.
#JsonIgnore : removes error but does not retrieves Company_Contact_id
#JsonManagedReference #JsonBackReference same as above.
Unfortunately i cannot change database or alter it.Since its legacy.Any more things i can try ??
Thanks

Try using DTOs in JHipster, you'll get more control over JSON serialization rather than simply exposing your entity especially when you are constrained by legacy database schema.

Related

JPA repository findBy foreign key for ManyToMany relationship

I have the following relationship in the data base
Database Diagram
And these are the entities
Apartment.java
#Entity
#Table(name = "apartment")
#Data
public class Apartment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_apartment")
private long id;
#ManyToMany
#JoinTable(
name = "apartment_facility",
joinColumns = #JoinColumn(name = "id_apartment"),
inverseJoinColumns = #JoinColumn(name = "id_facility"))
#Column(name = "city")
private String city;
#Column(name = "country")
private String country;
}
Facility.java
public class Facility {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_facility")
private long id;
#Column(name = "name")
private String name;
}
My question is how can I create a findBy method in JpaRepository in order to find all Apartments having the required facilities
I'm thinking of the result of this request:
api/apartments/findByFacilities?facilities=gym,pool,parking
I have tried to do a query but couldn't quite get it. Also tried using Jpa as following
List<Apartment> findByFacilities(#RequestParam("facilities") List<String> facilities);
But I'm getting the following error. Is there a walkaround?
Failed to convert from type [java.lang.String] to type [java.lang.Long]
In your first code block List <Facility> facilities = new ArrayList<>(); I guess you forgot to add the code line.
When I look at your link below, I understand that you want to search by the name of the facility.
api/apartments/findByFacilities?facilities=gym,pool,parking
For this, your code in Repository should be as follows:
List<Apartment> findDistinctByFacilitiesNameIn(List<String> facilities);
If we don't add distinct supported keyword here, each record repeats as many as the facilities it contains.
If you want to search differently like, not like, etc., See the supported keywords inside method names table here: Query Creation document.

Jpa OneToOne shared primary key half works

I have SpringBoot 2.1.3 and Java 8 application. Building DB with JPA I have 3 table in one to one relationship. Suppose the tables is the follows:
#Entity
#Data //lombok
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Address address;
}
And then:
#Entity
#Data
#Table(name = "address")
public class Address {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private User user;
}
That's works.. and it is the best way to do (this exactly example is taken from documentation).
If I start the application the DB is created and if I tried to add entities all works well. The model created follows:
Now I want to add a Country object to my address Entities (for example) and I modified the Entities as follows:
#Entity
#Data
#Table(name = "address")
public class Address {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private User user;
#OneToOne
#MapsId
private Country country;
}
And Country Entities:
#Entity
#Data
#Table(name = "country")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "country", cascade = CascadeType.ALL)
private Address address;
}
The application still starts, the DB is created and the model follows:
But if I try to save a User as follows:
User user = new User();
Address address = new Address();
Country country = new Country();
user.setAddress(address);
address.setUser(user);
address.setCountry(country);
country.setAddress(address);
userRepository.save(user);
I obtain the error:
java.sql.SQLException: Field 'country_id' doesn't have a default value
Anyway I solve the issue removing #MapsId and added #JoinColumn but I would like to understand what's wrong.
P.S.: I'm using MySQL 5.7 with InnoDB dialect (setting on application.properties)
Thanks all
It works only with one #MapsId annotation. Using two is causing that country id is not inserted:
insert into Country (id) values (?)
insert into Users (id) values (?)
insert into Address (user_id) values (?)

How to prevent saving of referred entity when using #IdClass?

I have two entities, Type and TypeValue. Each Type can have several TypeValues. While trying to persist a new TypeValue, I get a database error that Type already exists (which is correct, but I don't want to add it again, I want to add just a new 'TypeValue'). I have similar classes without IdClass that are working, so I assume that either the #IdClass definition is wrong or I forgot to define something so that the referred object is not updated.
How to prevent saving of the referred entity Type when using #IdClass for TypeValue?
Class definitions:
#Entity
#Table(name = "TYPE", schema = "VOC")
public class Type implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "TYPEID")
private String typeID;
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "type")
private List<TypeValue> listTypeValue;
// constructor, getter, setter, equals, hashcode, ...
}
#Entity
#IdClass(TypeValueID.class)
#Table(name = "TYPE_VALUE", schema = "VOC")
public class TypeValue implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "TYPEID")
#ForeignKey(name = "TYPEVALUE_FK")
private Type type;
#Id
#Column(name = "VALUE")
private String value;
// constructor, getter, setter, equals, hashcode, ...
}
public class TypeValueID implements Serializable {
private static final long serialVersionUID = 1L;
String type;
String value;
// equals, hashcode
}
Example of usage:
Type type = ... // get existing type with typeID "DETAIL"
Session session = sessionFactory.getCurrentSession();
TypeValue newTypeValue = new TypeValue(type, "new value");
session.save(newTypeValue);
session.flush();
Thrown exception:
SEVERE: Servlet.service() for servlet [spring] in context with path [/project] threw exception [Request processing failed; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "type_pkey"
Detail: Key (typeid)=(DETAIL) already exists.
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2455)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2155)
...
please change your String typeID to int or long. Then use #GeneratedValue for auto-increment.
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int typeID ;
Check this example
#Entity
#Table(name = "USERS")
#Proxy(lazy = false)
public class User {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int uID ;
private String uName ;
private String uEmail ;
private String uPassword;
#OneToMany(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
private List<Reminder> uReminders = new ArrayList<>();
Next Entity
#Entity
#Proxy(lazy = false)
public class Reminder {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int reminderID ;
private Date reminderDate ;
private String reminderDescription ;
You have defined the foreign key column with #Id.
#Id
#ManyToOne
#JoinColumn(name = "TYPEID")
#ForeignKey(name = "TYPEVALUE_FK")
private Type type;
So it is expecting unique value in the column "type".Hope this may help.
The type attribute in the TypeValueID class is wrong, the class should look like this:
public class TypeValueID implements Serializable {
private static final long serialVersionUID = 1L;
Type type;
String value;
// equals, hashcode
}
The JPA Persistence API 2.1 documentation states:
The names of the fields or properties in the primary key class and the
primary key fields or properties of the entity must correspond and
their types must match according to the rules specified in Section
2.4, “Primary Keys and Entity Identity” and Section 2.4.1, “Primary Keys Corresponding to Derived Identities”.
And the rule that applies in this case is:
If the composite primary key class is represented as an id class, the
names of primary key fields or properties in the primary key class and
those of the entity class to which the id class is mapped must
correspond and their types must be the same.

Spring Data Rest - PUT is not working for associated reference types?

I have the following domain class implemented for a Spring Data Rest project.
#Entity
#Data
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long addressID;
private String houseName;
private String apartmentNumber;
#ManyToOne
private City city;
#ManyToOne
private Country country;
}
Now I am creating an Address resource by sending a POST with following JSON.
{
"houseName":"Some House",
"apartmentNumber":"13 B",
"city": "http://localhost:8080/city/1"
"country":"http://localhost:8080/countries/1"
}
When I send a PUT request to the endpoint http://localhost:8080/addresses/1 with the following JSON, the values for houseName is updated. However the city remains unchanged even though I am sending a different URI for the city.
{
"houseName":"Another House",
"apartmentNumber":"13 B",
"city": "http://localhost:8080/city/2"
"country":"http://localhost:8080/countries/1"
}
If I send a PATCH instead of PUT the city value is also updated. So how do I fix this?
UPDATE 1
Country class
#Data
#Entity
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long countryID;
private String countryName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "country", orphanRemoval = true)
private List<City> cities;
}
City class
#Data
#Entity
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long cityID;
private String cityName;
#ManyToOne
#JoinColumn(name = "country_id")
private Country country;
}
I had the same problem and manage to find some information on it.
It is a change in version 2.5.7 of Spring Data Rest and is "by purpose".
The answer of Oliver Drotbohm is:
I looked into this and I'd argue you're expecting things to work in a
way they don't work. PUT requests don't consider associations to
linkable resources, i.e. related resources that are pointed to by
links. The reason for that is two-fold:
If we consider URIs for association fields in the payload to update those associations, the question comes up about what's supposed to
happen if no URI is specified. With the current behavior, linked
associations are simply not a part of the payload as they only reside
in the _links block. We have two options in this scenario: wiping the
associations that are not handed, which breaks the "PUT what you GET"
approach. Only wiping the ones that are supplied using null would sort
of blur the "you PUT the entire state of the resource".
For all the reasons mentioned in 1. there are dedicated assoctiation resources exposed that can be manipulated directly.
So it looks like that if you want to change both state of the resource
plus associations at the same time, I guess exposing a dedicated
resource to do that is the way to go.
Full answer you can find on Jira Spring site: Unable to update associated resource using PUT request on the item resource
(the question I wrote on stack overflow is here: Spring Data Rest - PUT on repository silently fails on child references)
If you're using Hibernate as your JPA provider, then you must let know how the entities are mapped in both the sides and indicate the how it is mapped in the child entity which will take care how the relationships are managed during a transaction.
EDITED and UPDATED:
// City Class
#Entity
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "city_id")
private Long cityID;
#Column(name = "city_name")
private String cityName;
#ManyToOne
private Country country;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "city", orphanRemoval = true)
private List<Address> addresses;
}
// Country Class
#Entity
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id")
private Long countryID;
#Column(name = "country_name")
private String countryName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "country", orphanRemoval = true)
private List<City> cities = new ArrayList<>();;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "country", orphanRemoval = true)
private List<Address> addresses;
}
USE PATCH: If you're updating part of the resource, subset of the resource and relationships
USE PUT: If you're replacing the resource with an entirely new representation

EntityNotFoundException in Hibernate Many To One mapping however data exist

I'm getting an error
Caused by: javax.persistence.EntityNotFoundException: Unable to find tn.entities.AgenceBnq with id 01
when I get AgenceBnq through Employee
Employee class:
#Table(name = "EMPLOYEE")
#NamedQuery(name = "Employee.findById", query = "SELECT e FROM Employee e WHERE e.employeMat = ?1"),
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "EMPLOYEE_MAT", unique = true, nullable = false, length = 15)
private String employeeMat;
...
#ManyToOne
#JoinColumn(name = "AGENCE_COD")
private AgenceBnq agenceBnq;
}
#Entity
#Table(name="AGENCEBNQ")
public class AgenceBnq implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="AGENCE_COD", unique=true, nullable=false, length=10)
private String agenceCod;
...
//bi-directional many-to-one association to Employee
#OneToMany(mappedBy="agenceBnq")
private Set<Employee> employees;
}
I'm calling namedQuery Employee.findById in DAO to retrieve data and I have to get AgenceBnq from Employee but get this error while calling query.getResultList()
#NotFound( action = NotFoundAction.IGNORE) isn't useful for me because data exist in AGENCEBNQ table and I have to retrieve date through Employee.
Is this a bug in hibernate ? I'm using hibernate version 3.6.7.Final
Firstly, You dont need query for it, the EnityManger.find(Employee.class, YOUR_ID) will do the job.
Secondly dont use ? in your queries but names (e.employeMat = :id) as it is easier to debug and less error prones for complicated queries.
Finally, check your DB table if the AGENCE_COD column in Employee table really contains the valid ID for your entitity that crashes (and that it length matches the ID length of AgenceBnq). It should work, the typical reason why it doesnt will be that your Employe.AGENCE_COD has defualt value and when creatubg the new EMploye you add it only to the Agence but you did not set Agence in the Employ.

Resources