Am I using OneToOne relationship right? - java

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.

Related

How to mapping an entity to multiple mongodb collection in Spring Data MongoDB with JPA without use MongoTempalte directly?

I'm using org.mongodb:bson:4.1.2 with org.springframework.boot:spring-boot-starter-data-mongodb:2.4.7.
My entity looks like:
#Entity
#Table(name = "fire_alert")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Document(collection = "alert_<dynamic>")
#Data
public class AlertPO implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "owner_id")
private Long ownerId;
#Column(name = "alert_type")
private Long alertType;
}
Cause there will be millions of alerts, so I need to save records into different mongodb collections based on AlertPO.alertType.
After digging into org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity, I found the field collection of annotation #Document support SpEL expression. This kind of expressions will be evaluated in org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity#getCollection and it is obviously that current entity won't be added into the EvaluationContext.
According to this question:
How to Map a Java Entity to Multiple MongoDB Collections in Spring Data?
We can overwrite repositories to use MongoTemplate to persistent data into proper collection programmatically. But we're going to using JPA and we do not want to using MongoTemplate directly. How to do this?
Although the SpEL expression's EvaluationContext contains no instances of AlertPO, but we can make it possible through ThreadLocal instances registered in BeanFactory.
Such as:
#Component(value = "collection")
public class CollectionHolder {
private static final ThreadLocal<Stack<String>> COLLECTION = ThreadLocal.withInitial(Stack::new);
public void push(Alert alert) {
COLLECTION.get().push(typeToCollection(alert.getAlertType()));
}
public void push(Long type) {
COLLECTION.get().push(typeToCollection(type));
}
public String top() {
return COLLECTION.get().peek();
}
public void pop() {
COLLECTION.get().pop();
}
}
Now we can push collection name:
#Service
public class MongoAlertStorage implements IAlertStorage {
#Autowired
private CollectionHolder holder;
#Autowired
private AlertRepository alertRepository;
#Autowired
private Converters converters;
#Override
public Alert save(Alert alert) throws Exception {
try {
holder.push(alert);
return converters.of(alertRepository.save(converters.of(alert)));
} finally {
holder.pop();
}
}
}
It is time to specify dynamic collection in expression:
#Document(collection = "alert_#{#collection.top()}")
public class AlertPO implements Serializable {
// ...
}
Now, you can debug at org.springframework.expression.common.CompositeStringExpression#getValue(org.springframework.expression.EvaluationContext) to check the value evaluated, it should be what you want.

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 Boot Jackson ResponseEntity No serializer found for class

I have an odd error with a spring boot controller, not returning a recently created object.
I have a controller, with 2 methods (see below). One simply retrieves an Object of the class "OrderPay" and returns it as the payload of a response entity. This works fine, the object is therefore okay.
The other one creates and persists a new instance of "OrderPay" and is then supposed to return that newly created object. The creation of the new object and its persistence work fine. However, when I try to return it, I get the error message below.
Now I would understand that error message if it occured consistently. However, when returning this newly created object using the first function ("getPaymentByIdTest"), it returns it without problems, even though I retrieve it in the exact same way from the database and return it in the same way, with the same return type of the method.
Now I know that executing the code in a HTTP-GET method is not best practise, however it is quicker and more convenient for testing.
Can anyone see where I need to adjust the code?
2020-04-13 21:37:57.507 ERROR 26796 --- [nio-8081-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.brownbag_api.model.OrderPay["posSend"]->com.brownbag_api.model.Pos$HibernateProxy$7l7MDMEi["hibernateLazyInitializer"])] with root cause
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.brownbag_api.model.OrderPay["posSend"]->com.brownbag_api.model.Pos$HibernateProxy$7l7MDMEi["hibernateLazyInitializer"])
The Controller
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/pay")
public class PaymentController {
#Autowired
private OrderPayRepo orderPayRepo;
#Autowired
private OrderPaySvc orderPaySvc;
#GetMapping("/{id}")
public ResponseEntity<?> getPaymentByIdTest(#PathVariable Long id) {
Optional<OrderPay> orderPay = orderPayRepo.findById(id);
return ResponseEntity.ok(orderPay);
}
#GetMapping("/exec/from/{from}/to/{to}/amount/{amount}")
public ResponseEntity<?> execPayment(#PathVariable Long from, #PathVariable Long to, #PathVariable double amount) {
Pos posFrom = posRepo.getOne(from);
Pos posTo = posRepo.getOne(to);
OrderPay pay = orderPaySvc.createPay(amount, posFrom, posTo);
pay = orderPaySvc.execPay(pay);
if (pay == null) {
return ResponseEntity.ok("Payment could not be executed. Please see log for more details!");
} else {
System.err.println("Payment executed: " + pay.getPosRcv().getParty().getName());
Long payId = pay.getId();
System.err.println("Payment executed: " + payId);
// payId returns the expected value here, the object is therefore saved in the database (verified).
Optional<OrderPay> orderPay = orderPayRepo.findById(payId);
return ResponseEntity.ok(pay);
}
}
}
Order.java
#Entity
#Table(name = "order_base")
#Inheritance(strategy = InheritanceType.JOINED)
public class Order implements Serializable {
private static final long serialVersionUID = -3458221490393509305L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#NotNull
#Column(name = "QTY")
private double qty;
public Order() {
}
public Order(#NotNull double qty) {
super();
this.qty = qty;
}
}
OrderPay
#Entity
#Table(name = "order_pay")
public class OrderPay extends Order implements Serializable {
private static final long serialVersionUID = 4643589803146964779L;
#NotNull
#OneToOne(targetEntity = Pos.class)
#JoinColumn(name = "POS_SEND_ID")
private Pos posSend;
#NotNull
#OneToOne(targetEntity = Pos.class)
#JoinColumn(name = "POS_RCV_ID")
private Pos posRcv;
public OrderPay() {
super();
}
public OrderPay(#NotNull double qty, #NotNull Pos posSend, #NotNull Pos posRcv) {
super(qty);
this.posSend = posSend;
this.posRcv = posRcv;
}
}
Pos.java
#Entity
#Table(name = "POS")
public class Pos implements Serializable {
private static final long serialVersionUID = 1530699992135610397L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#NotNull
#Column(name = "QTY")
private double qty;
#NotNull
#ManyToOne(targetEntity = Party.class)
#JoinColumn(name = "PARTY_ID")
#JsonBackReference
private Party party;
public Pos() {
}
public Pos(#NotNull double qty, #NotNull Party owner) {
super();
this.qty = qty;
this.party = owner;
}
}
JSON
{
"id":7,
"qty":33000.0,
"posSend":
{
"id":1,
"qty":-266010.0,
"hibernateLazyInitializer":{}
},
"posRcv":
{
"id":2,
"qty":66000.0,
"hibernateLazyInitializer":{}
}
}
If you are using Spring Boot, you can set the following property in application.properties file. That should solve the issue according to you stacktrace (see: "to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS")
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false

Why user defined #javax.persistence.Converter isn't recognized by hibernate when mapping the entity field to database column

I have the following Entity containing a field of Enum type:
#Entity
#Table(name = "INPUT_DATA")
public class InputDataEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "INPUT_DATA_SEQ", allocationSize = 1, sequenceName = "INPUT_DATA_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INPUT_DATA_SEQ")
private Long id;
#Column(name = "FIELD1", nullable = false)
private String field1;
#Column(name = "FIELD2", nullable = false)
#Convert(converter = Type.Converter.class)
private Type field2;
// getters and setters
}
The Enum type looks like:
public enum Type {
ENUM_ITEM_1("item1"),
// more items
ENUM_ITEM_N("itemN");
private String code;
private Type(String code) {
this.code = code;
}
public static Type fromString(String name) {
switch (name) {
case "item1":
return ENUM_ITEM_1;
// more cases
case "itemN":
return ENUM_ITEM_N;
default:
throw new IllegalArgumentException("Wrong value for Type");
}
}
#Override
public String toString() {
return code;
}
#javax.persistence.Converter
public static class Converter implements AttributeConverter<Type, String> {
#Override
public String convertToDatabaseColumn(Type attribute) {
return attribute.toString();
}
#Override
public Type convertToEntityAttribute(String s) {
return Type.fromString(s);
}
}
}
The problem is that hibernate doesn't recognize my Converter when I want to fetch data from the database.
I've also tried:
#Embedded and #Embeddable but with no luck.
#Enumerated(EnumType.STRING) but again with no luck.
My question is:
how to make hibernate to recognize my converter when converting the appropriate field?
Many thanks in advance.
I eventually ended up by implementing a StringValuedEnum interface and its relevant reflector and type class by implementing EnhancedUserType, ParameterizedType as it was described here.
This helped me to properly store into and retrieve from DB data corresponding to user defined enum types, although the questions with converters remains still open. If someday a proper answer will be given, that will be very appreciated.

Jersey ClientResponse Get List of Composite Entities

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?

Categories