Jersey ClientResponse Get List of Composite Entities - java

I am trying to get a Result of a List, basically a list of entities using Jersey RESTful API (Server and Client)
UserRESTClient client = new UserRESTClient();
ClientResponse response = client.getUsersByType(ClientResponse.class, String.valueOf(userType));
List<User> participants = response.getEntity(new GenericType<List<User>>() {
});
However, the above code does not work if Entity User has a Composite Object, if for instance,
public class User {
private UserId userId;
}
public class UserId {
private int id;
private int categoryId;
}
In this case, the JSON is deserialized by Jersey and returned null for the field type UserId inside Class User. I inspected the JSON returned and everything seems good at the RESTful Server end, but the nested JSON response is not clearly processed at the Client.
Any help would be greatly appreciated. I am not sure if it because of the Jackson preprocessor.
Following is the actual Code Snippet. It involves two classes Participant and ParticipantPK (primary for each Participant).
#Entity
#Table(name = "conference_participant")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Participant.findAll", query = "SELECT p FROM Participant p"),
public class Participant implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected ParticipantPK participantPK;
}
#Embeddable
public class ParticipantPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "conference_id")
private int conferenceId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 150)
#Column(name = "participant_sip_uri")
private String participantSipUri;
public ParticipantPK() {
}
public ParticipantPK(int conferenceId, String participantSipUri) {
this.conferenceId = conferenceId;
this.participantSipUri = participantSipUri;
}
And the Code for retrieving ClientResponse,
List<Participant> participants = response.getEntity(new GenericType<List<Participant>>() {
});
However, the ParticipantPK (Composite PK) is null.

You only pasted a code snippet so I don't know if this part is excluded, but in my code I didn't have setters for the fields. I had getters, but no setters.
Without the setters, my composite objects themselves were non-null, but the members of those objects were themselves null.

I tried to reproduce it, but using the same data structures worked for me. What version of Jersey are you using? Is User class annotated with #XmlRootElement or are you using the POJO mapping feature?

Related

Problem with saving foreign key with #OneToOne annotation. Saving as null

I have two entities (Project, OtherData) with one abstract entity. I'm using MySQL and Quarkus framework.
Problem: When I try to save Project entity field project_id remains null.
Table schemas:
On next picture there is shown, fk constraint in "project_other_data" table:
Abstract Entity:
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
// getters and setters
}
Project Entity
#Entity
#Table(name = "projects")
public class Project extends AbstractEntity {
#NotNull
#Column(name = "name")
private String name;
#NotNull
#Column(name = "surname")
private String surname;
#Column(name = "date_create")
#JsonbDateFormat(value = "yyyy-MM-dd")
private LocalDate dateCreate;
#Column(name = "date_update")
#JsonbDateFormat(value = "yyyy-MM-dd")
private LocalDate dateUpdate;
#OneToOne(mappedBy = "project", cascade = CascadeType.ALL)
private OtherData otherData;
// getters and setters
}
OtherData Entity
#Entity
#Table(name = "project_other_data")
public class OtherData extends AbstractEntity {
#OneToOne
#JoinColumn(name = "project_id")
private Project project;
#Column(name = "days_in_year")
private Integer daysInYear;
#Column(name = "holidays_in_year")
private Integer holidaysInYear;
#Column(name = "weeks_in_year")
private Integer weeksInYear;
#Column(name = "free_saturdays")
private Integer freeSaturdays;
#Column(name = "downtime_coefficient")
private BigDecimal downtimeCoefficient;
#Column(name = "changes")
private Integer changes;
// getters and setters
}
Saving entities with code:
#Path("projects")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ProjectRest {
#Inject
ProjectService projectService;
#POST
public Response saveProject(Project project) {
return Response.ok(projectService.saveProject(project)).build();
}
}
#RequestScoped
#Transactional
public class ProjectService {
#Inject
EntityManager entityManager;
public Project saveProject(Project project) {
if (project.getId() == null) {
entityManager.persist(project);
} else {
entityManager.merge(project);
}
return project;
}
}
I was able to reproduce the problem by POSTing a new Project with an embedded OtherData. The body I used for the POST:
{
"name": "John",
"surname": "Doe",
"otherData": {}
}
Point is: the database entity is also used as DTO. Thus, the field project in otherData for the request body is set to null (since no Project is passed along this would be a recursive infinite definition).
During processing the entity from the rest controller to the service to the repository, the project of otherData is never set. A quick fix is to modify ProjectService::saveProject as follows:
public Project saveProject(Project project) {
project.getOtherData().setProject(project); // This line was added
if (project.getId() == null) {
entityManager.persist(project);
} else {
entityManager.merge(project);
}
return project;
}
This will fix the database issue (the project_id will be set), but leads to the next issue. The response body cannot be serialized due to an
org.jboss.resteasy.spi.UnhandledException: javax.ws.rs.ProcessingException: RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Unable to serialize property 'otherData' from com.nikitap.org_prod.entities.Project
...
Caused by: javax.json.bind.JsonbException: Recursive reference has been found in class class com.nikitap.org_prod.entities.Project.
The object structure is cyclic (project references otherData, which return references project, ...) and Jackson is unable to resolve this cycle.
To fix this issue, I would suggest to separate DTOs and database entity and explicitly map between them. In essence:
Structure the Dto-object to represent the JSON-Request and -Response you expect to receive, in a non-cyclic order
Transfer JSON-related annotations from the database entity classes to the DTO classes
In the service- or repository-layer (your choice), map the DTO to the database entites, setting all fields (including the references from project to otherData and vice-versa)
In the same layer, map database-entites back to non-cyclic DTOs
Return the DTOs from the REST endpoint

Spring JPA exception while inserting Json value (using hibernate-types-52)

I'm using hibernate-types-52 by Vlad Mihalcea together with Spring JPA to insert a POJO as a Json value into my Postgresql database.
My entity is defined this way:
#Entity
#Table(name = "hoshin_kanri")
#TypeDef(
name = "jsonb",
typeClass = JsonBinaryType.class
)
public class HKEntity {
#Id
#Column(name = "id_ai", columnDefinition = "bigint")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id_ai;
#Column(name = "id_hk", columnDefinition = "bigint")
private Integer id_hk;
#Type(type = "jsonb")
#Column(name = "hk_data", columnDefinition = "jsonb")
private HKData hk_data;
public HKEntity(Integer id_hk, HKData hk_data) {
this.id_hk = id_hk;
this.hk_data = hk_data;
}
And this is the POJO:
public class HKData {
private String name;
private Year targetYear;
private String description;
public HKData(String name, Year targetYear, String description) {
this.name = name;
this.targetYear = targetYear;
this.description = description;
}
I've defined a Repository interface to query the objects into the database:
public interface HKRepository extends CrudRepository<HKEntity, Integer> {
#Query(value = "INSERT INTO 'hk_data' VALUES :Entity", nativeQuery = true)
void test_json(#Param("Entity") HKEntity e);
}
and a test Service just to see if it's working properly:
#Service
public class HKService {
#Autowired
HKRepository hk_repository;
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.test_json(e);
return "Value created";
}
}
However, i keep getting the following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.ehk.rest.entity.HKEntity
I've tried many fixes suggested for this error, but i cannot understand the nature of the error itself. What is wrong with this approach? Beside a tip for fixing this, i would like to understand why this error is originated.
The error means that there's an instance of the HKEntity entity which is referenced from somewhere in the current Hibernate session, and you've neither explicitly persisted this instance, nor instructed Hibernate to persist it cascadly. It's hard to say what exactly is going on, but there are some issues with your code that might have confused either Spring Data JPA framework, or the Hibernate itself.
First, the Spring's CrudRepository interface already has a save() method, so you could use it instead of your test_json() method.
I also see no reason in inserting a Hibernate entity with a native query, and I don't even think this is a valid query. Your test_json() method tries to natively insert an HKEntity entity into the hk_data table, but the HKEntity entity should be saved into the hoshin_kanri table, according to your mapping.
So I would change your service code as follows:
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.save(e);
return "Value created";
}

convertToDatabaseColumn when data is not being persisted?

I have implemented method AttributeConverter.convertToEntityAttribute to load json data from the db. I am not trying to persist data, but for some reason convertToDatabaseColumn is being called.
This is what happens:
1. I call a repository method
2. then a call to AttributeConverter.convertToEntityAttribute follows -> returns a list of entity Cx. Till this point everything is normal.
3. But for some reason AttributeConverter.convertToDatabaseColumn is called right after, with that same list of entity Cx as argument -> returns stringV
4.Now convertToEntityAttribute is called again with stringV as argument, which is also strange.
Could it be that a #OneToOne relation is causing this? Why is this executing convertToDatabaseColumn if I'm not persisting an entity, at least explicitly?
All of this happens just by calling a single method in one of my repository classes:
Here is the code
public interface RSTRepository extends CrudRepository<RST, Long> {
List<RST> findByDuctNameIgnoreCase(String ductName);
}
#Entity
#Table(name="r_s_t")
public class RST {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToOne
#JoinColumn(name = "r_s_id")
private Rs rs;
#Column(name = "channel")
private String channelName;
...
}
#Entity
#Table(name="r_s")
public class RS {
#Id
#Column(name = "rs_id", columnDefinition = "json")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "c_x", columnDefinition = "json")
#Convert(converter = JsonToCxConverter.class)
private List<Cx> cxs;
...
}
public class Cx {
private Long someId;
private List<Long> values;
...
}
#Converter
public class JsonToCxConverterimplements AttributeConverter<List<Cx>, String>{
//this gets executed
#Override
public String convertToDatabaseColumn(List<Cx> entityAttribute) {
ObjectMapper objectMapper = new ObjectMapper();
log.info("--------------------");
return "";
}
#Override
public List<Cs> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.isEmpty()) return Collections.emptyList();
//... uses the object mapper to parse the json and return a simple object.
...
}
Like I said, this happens when calling RSTRepository.findByDuctNameIgnoreCase
Yes its really behaving like you are saying. Also when persisting RST, Converter is also called 3x.
It also called 3x when reading just RS entity, i.e. it is not caused by #OneToOne relation.
I think it is how hibernate works. It should not be a problem, you get the right data without error.
From stacktrace I see that second and third call is from AbstractRowReader.performTwoPhaseLoad().
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:241)
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209)
at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133)
I think its something that cannot be disabled. From hibernate sources I see that entity is registered to "hydrating". I found more about it here https://stackoverflow.com/a/29538797/2044957
Another thing: This is happening only when using converter on a collection. Converter is called once if it used on single type, for example AttributeConverter<String, String>.

Am I using OneToOne relationship right?

Hello I am actually working on a REST server using Spring-boot, hibernate, psql and I am experiencing some difficulties after adding an OneToOne relationship between entities.
Here are the 2 entities:
Pays:
#Entity
#Table(name = "pays")
public class Pays implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty("codePays")
private String codePays;
#Column(name = "libelle_pays")
#JsonProperty("libellePays")
private String libellePays;
#OneToOne(mappedBy = "pays",cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional=false)
private Traduction traduction;
protected Pays() {
}
public Pays(String codePays,String libellePays) {
this.codePays = codePays;
this.libellePays = libellePays;
}
and Traduction:
#Entity
#Table(name = "traduction")
public class Traduction implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonProperty("codeTrad")
private long codeTrad;
#Column(name = "defaultLanguage")
#JsonProperty("defaultLanguage")
private boolean defaultLanguage;
#OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
#JoinColumn(name="fk_code_pays")
#JsonProperty("codePays")
private Pays pays;
public Traduction(){
}
public Traduction(String codePays,boolean defaultLanguage) {
this.defaultLanguage = defaultLanguage;
pays.setCodePays(codePays);
}
My problem happen when I try to populate my table traduction using a Post method:
#PostMapping("/traduction")
public Traduction createTraduction(#RequestBody Traduction trad) {
System.err.println(trad);
return repository.save(trad);
}
when I send JSON data to my server via PostMan like this:
{
"codeTrad":0,
"defaultLanguage":true,
"fk_code_pays":"FR"
}
or this way:
{
"codeTrad":0,
"defaultLanguage":true,
"pays":
{
"codePays":"FR",
"libellePays":"France"
}
}
My server doesn't seem to understand the mapping with the object Pays.
Here what the object Traduction look like after my request:
[codeTrad=0, null, defaultLanguage=true]
and the pretty error:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.auchan.corp.ipon.iponportail.model.Traduction["codePays"])]
So I am wondering if the problem comes from my server conception or just my JSON. Do you have an idea?
Your issue comes from :
public Traduction(String codePays, boolean defaultLanguage) {
this.defaultLanguage = defaultLanguage;
pays.setCodePays(codePays)
The pays is null and that's why you get an exception : java.lang.NullPointerException, well try to add Pays pays to that constructor.
This json won't work:
{ "codeTrad":0, "defaultLanguage":true, "fk_code_pays":"FR" }
as there is no field name fk_code_pays in your Traduction class.
Below won't work either:
{ "codeTrad":0, "defaultLanguage":true, "pays": { "codePays":"FR", "libellePays":"France" } }
Because pays is annotated with #JsonProperty("codePays")
As per your DTO classes, your json should be:
{ "codeTrad":0, "defaultLanguage":true, "codePays": { "codePays":"FR", "libellePays":"France" } }
Also I would recommend you to use wrapper classes in place of primitives. Boolean in place of boolean and Long in place of long.

In spring boot JPA, how to properly POST an object whose entity representation has a foreign key association to a different entity?

If I have a entity that contains an object of an another class, for example a Book entity that has within it a Publisher entity that is associated as follows:
#ManyToOne
#JoinColumn(name="PUB_CODE", referencedColumnName = "PUB_CODE")
private Publisher pub;
Is this a secure/correct (I saw the correct data in the DB in this example, but not 100% sure if it would work in all cases) approach to post an object that has foreign key association in the database? I don't know if this is safe to do in terms of transaction atomicity or in terms of threading, or if it is efficient. Relevant code below:
Book.java
package app.domain;
/*imports*/
#Entity
public class Book implements Serializable{
/**
*
*/
private static final long serialVersionUID = -6902184723423514234L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(nullable = false, unique=true)
private String bookName;
#Column(nullable = false)
private int pageCount;
#ManyToOne
#JoinColumn(name="PUB_CODE", referencedColumnName="PUB_CODE")
private Publisher pub;
/*public getters and setters*/
}
Publisher.java
package app.domain;
/*imports*/
#Entity
public class Publisher implements Serializable {
private static final long serialVersionUID = 4750079787174869458L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="PUB_CODE",nullable = false, unique = true)
private String publisherCode;
#Column(nullable = false)
private String publisherName;
/*public getters and setters*/
}
BookRepo.java
package app.service;
/*imports*/
public interface BookRepo extends JpaRepository<Book, Long>{
#Query("SELECT pb FROM Publisher pb WHERE pb.publisherCode = TRIM(UPPER(:pubCode))")
public Publisher findPublisherByPubCode(#Param("pubCode")String pubCode);
}
BookController.java
package app.controller;
/*imports*/
#RestController
#RequestMapping(value = "/books")
public class BookController {
private BookRepo bookRepo;
#Autowired
public BookController(BookRepo bookRepo) {
this.bookRepo = bookRepo;
}
//The ApiPathParam is for JSONDOC purposes
#RequestMapping(value = "/create", method = RequestMethod.POST)
public List<Book> create(#ApiPathParam(name = "book") #RequestBody Book book, #ApiPathParam(name = "pubCode") #RequestParam("pubCode") String pubCode) {
// Assume exception handling
Publisher pbToAttachToThisBook = bookRepo.findPublisherByPubCode(pubCode);
book.setPub(pbToAttachToThisBook);
bookRepo.save(book);
return bookRepo.findAll();
}
}
Post object body (input into a POST tool):
{
"bookName": "goosebumps",
"id": 0,
"pageCount": 332,
"pub": {
"id": 0,
"publisherCode": "",
"publisherName": "",
"serialVersionUID": 0
},
"serialVersionUID": 0
}
pubCode parameter input provided, also into the POST tool, in the same call as above: 'SC'
After the above code was executed, in the Book table, there was an entry for the book above, with its PUB_CODE foreign key column filled in with 'SC', and the returned List<Book> of the POST controller method that was called showed that the newly added book included the Publisher entity information (such as the full name "Scholastic") for publisher with PUB_CODE='SC' that was already existing in the database.
Thank you.
The technique you posted originally (passing the FK ID, retrieving it manually in your controller, and setting it on the entity explicitly) is valid and secure.
I don't know of a cleaner approach unless you move to HATEOAS principals, which allows for resource link handling: http://projects.spring.io/spring-hateoas/
Sounds like you need to separate/decouple your data layer's domain model from your Rest Resources/ API specs, as they could evolve at a different pace. Also your choice of JPA should not influence the API specs.
Should this feel like something you want to pursue there lots of resources out there including Best Practices for Better RESTful API

Categories