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
Related
I use Micronaut Data with JPA and have two entities. The first one is Recipe:
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToOne
private Category category;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Step> steps;
// + other fields, getters and setters
}
The second one is ParseError which refers to Recipe:
#Entity
#Table(name = "parse_error")
public class ParseError implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Recipe recipe;
#Id
#Enumerated(EnumType.ORDINAL)
#Column(name = "problem_area")
private ProblemArea problemArea;
private String message;
// + other fields, getters and setters
}
Now I would like to provide DTO in API with ParseError properties but not with whole Recipe entity because it contains ManyToOne and OneToMany relations which are not needed in this case. So I created projection DTO for that:
#Introspected
public class ParseErrorDto {
private Integer recipeId;
private String recipeName;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
And added listAll() method into ParseErrorRepository:
#Repository
public interface ParseErrorRepository extends CrudRepository<ParseError, Integer> {
List<ParseErrorDto> listAll();
}
But it seems that Micronaut Data is not able to project properties from nested entities or I missed something in the DTO or the repository method:
ParseErrorRepository.java:22: error: Unable to implement Repository
method: ParseErrorRepository.listAll(). Property recipeId is not
present in entity: ParseError
I also tried to create RecipeDto:
#Introspected
public class RecipeDto {
private Integer id;
private String name;
// + getters and setters
}
And updated ParseErrorDto accordingly:
#Introspected
public class ParseErrorDto {
private RecipeDto recipe;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
Again no success:
ParseErrorRepository.java:22: error: Unable to implement Repository
method: ParseErrorRepository.listAll(). Property [recipe] of type
[RecipeDto] is not compatible with equivalent property declared in
entity: ParseError
Is Micronaut Data able to handle this use case by DTO projection? If not then is there another way how can I solve it in Micronaut Data?
Now (in latest version 1.0.0.M1) it is not possible. So I created feature request issue for that: https://github.com/micronaut-projects/micronaut-data/issues/184
Current workaround is to map entity bean into DTO bean in Java stream or reactive stream for example and do the properties mapping manually or by Mapstruct.
Update: Here is an answer to question from comments with an example how to do the workaround using Mapstruct:
Add Mapstruct dependency into build.gradle:
implementation "org.mapstruct:mapstruct:$mapstructVersion"
annotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
Define mapper:
import org.mapstruct.Mapper;
#Mapper(
componentModel = "jsr330"
)
public interface ParseErrorMapper {
ParseErrorDto entityToDto(#NotNull ParseError parseError);
EntityReference recipeToDto(#NotNull Recipe recipe);
}
And here is a usage of that mapper in the controller:
#Controller("/parse-error")
public class ParseErrorController {
private final ParseErrorRepository repository;
private final ParseErrorMapper mapper;
public ParseErrorController(ParseErrorRepository repository, ParseErrorMapper mapper) {
this.repository = repository;
this.mapper = mapper;
}
#Get("all")
#Transactional
public Page<ParseErrorDto> getAll(final Pageable pageable) {
return repository.findAll(pageable).map(mapper::entityToDto);
}
}
Hibernate 4.3.11
I have an issue saving the following object graph in hibernate. The Employer is being saved using the merge() method.
Employer
|_ List<EmployerProducts> employerProductsList;
|_ List<EmployerProductsPlan> employerProductsPlan;
The Employer & EmployerProducts have a auto generated pk. The EmployerProductsPlan is a composite key consisting of the EmployerProducts id and a String with the plan code.
The error occurs when there is a transient object in the EmployerProducts list that cascades to List<EmployerProductsPlan>. The 1st error that I encountered which I have been trying to get past was an internal hibernate NPE. This post here perfectly describes the issue that I am having which causes the null pointer Hibernate NullPointer on INSERTED id when persisting three levels using #Embeddable and cascade
The OP left a comment specifying what they did to resolve, but I end up with a different error when changing to the suggested mapping. After changing the mapping, I am now getting
org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.webexchange.model.EmployerProductsPlan#com.webexchange.model.EmployerProductsPlanId#c733f9bd]
Due to other library dependencies, I cannot upgrade above 4.3.x at this time. This project is using spring-boot-starter-data-jpa 1.3.3. No other work is being performed on the session other than calling merge() and passing the employer object.
Below is the mappings for each class:
Employer
#Entity
#Table(name = "employer")
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"employerNo"})
public class Employer implements java.io.Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "EMPLOYER_NO", unique = true, nullable = false)
private Long employerNo;
.....
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employer", orphanRemoval = true)
private List<EmployerProducts> employerProductsList = new ArrayList<>(0);
}
EmployerProducts
#Entity
#Table(name = "employer_products")
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"employerProductsNo"})
public class EmployerProducts implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "employer_products_no", unique = true, nullable = false)
private Long employerProductsNo;
#ManyToOne
#JoinColumn(name = "employer_no", nullable = false)
private Employer employer;
......
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employerProducts", orphanRemoval = true)
private List<EmployerProductsPlan> employerProductsPlanList = new ArrayList<>(0);
}
EmployerProductsPlan
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"id"})
#Entity
#Table(name="employer_products_plan")
public class EmployerProductsPlan implements Serializable {
#EmbeddedId
#AttributeOverrides({ #AttributeOverride(name = "plan", column = #Column(name = "epp_plan", nullable = false)),
#AttributeOverride(name = "employerProductsNo", column = #Column(name = "employer_products_no", nullable = false)) })
private EmployerProductsPlanId id;
#ManyToOne
#JoinColumn(name = "employer_products_no")
#MapsId("employerProductsNo")
private EmployerProducts employerProducts;
}
I am populating the employerProducts above with the same instance of the EmployerProducts object that is being saved. It is transient and has no id populated as it does not existing in the db yet.
EmployerProductsPlanId
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"plan", "employerProductsNo"})
#Embeddable
public class EmployerProductsPlanId implements Serializable {
private String plan;
private Long employerProductsNo;
// This was my previous mapping that was causing the internal NPE in hibernate
/* #ManyToOne
#JoinColumn(name = "employer_products_no")
private EmployerProducts employerProducts;*/
}
UPDATE:
Showing struts controller and dao. The Employer object is never loaded from the db prior to the save. Struts is creating this entire object graph from the Http request parameters.
Struts 2.5 controller
#lombok.Getter
#lombok.Setter
public class EditEmployers extends ActionHelper implements Preparable {
#Autowired
#lombok.Getter(AccessLevel.NONE)
#lombok.Setter(AccessLevel.NONE)
private IEmployerDao employerDao;
private Employer entity;
....
public String save() {
beforeSave();
boolean newRecord = getEntity().getEmployerNo() == null || getEntity().getEmployerNo() == 0;
Employer savedEmployer = newRecord ?
employerDao.create(getEntity()) :
employerDao.update(getEntity());
setEntity(savedEmployer);
return "success";
}
private void beforeSave() {
Employer emp = getEntity();
// associate this employer record with any products attached
for (EmployerProducts employerProduct : emp.getEmployerProductsList()) {
employerProduct.setEmployer(emp);
employerProduct.getEmployerProductsPlanList().forEach(x ->
x.setEmployerProducts(employerProduct));
}
// check to see if branding needs to be NULL. It will create the object from the select parameter with no id
// if a branding record has not been selected
if (emp.getBranding() != null && emp.getBranding().getBrandingNo() == null) {
emp.setBranding(null);
}
}
}
Employer DAO
#Repository
#Transactional
#Service
#Log4j
public class EmployerDao extends WebexchangeBaseDao implements IEmployerDao {
private Criteria criteria() {
return getCurrentSession().createCriteria(Employer.class);
}
#Override
#Transactional(readOnly = true)
public Employer read(Serializable id) {
return (Employer)getCurrentSession().load(Employer.class, id);
}
#Override
public Employer create(Employer employer) {
getCurrentSession().persist(employer);
return employer;
}
#Override
public Employer update(Employer employer) {
getCurrentSession().merge(employer);
return employer;
}
}
As of right now, my solution is to loop through the EmployerProducts and check for new records. I called a persist on the new ones before calling the merge() on the parent Employer. I also moved the logic I had associating all the keys into the dao instead of having it in my Struts action. Below is what my update() method in the Employer DAO now looks like
public Employer update(Employer employer) {
// associate this employer record with any products attached
for (EmployerProducts employerProduct : employer.getEmployerProductsList()) {
employerProduct.setEmployer(employer);
if (employerProduct.getEmployerProductsNo() == null) {
// The cascade down to employerProductsPlanList has issues getting the employerProductsNo
// automatically if the employerProduct does not exists yet. Persist the new employer product
// before we try to insert the new composite key in the plan
// https://stackoverflow.com/questions/54517061/hibernate-4-3-cascade-merge-through-multiple-lists-with-embeded-id
List<EmployerProductsPlan> plansToBeSaved = employerProduct.getEmployerProductsPlanList();
employerProduct.setEmployerProductsPlanList(new ArrayList<>());
getCurrentSession().persist(employerProduct);
// add the plans back in
employerProduct.setEmployerProductsPlanList(plansToBeSaved);
}
// associate the plan with the employer product
employerProduct.getEmployerProductsPlanList().forEach(x ->
x.getId().setEmployerProductsNo(employerProduct.getEmployerProductsNo())
);
}
return (Employer)getCurrentSession().merge(employer);
}
I have a customer object and inside that customer object i have a login object which contains username and password. When i do a POST the request works fine however when i try to do a PUT request it fails. It fails because it says Duplicate entry on the username.
I would like to be able to update the customer details without having to change the username. How can i achieve this.
This is my code :
UserLogin Entity :
#Entity
#Table(name = "Customer",
uniqueConstraints =
{
#UniqueConstraint(columnNames = "email"),
#UniqueConstraint(columnNames = "id"),
#UniqueConstraint(columnNames = "phoneNumber")
}
)
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int customerNumber;
#OneToOne(cascade= CascadeType.ALL)
#JoinColumn(name = "loginCredentialsID")
private UserLogin userlogin;
private String phoneNumber;
private String email;
private String physicalAddress;
private String country;
... getters and setters
}
UserLogin Entity :
#Entity
#Table(name = "UserLogin",
uniqueConstraints =
{
#UniqueConstraint(columnNames = "userName")
})
public class UserLogin implements Serializable, UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int loginCredentialsID;
private String username;
private String password;
... getters and setters
}
CustomerService Class :
public Response updatCustomeretails(int id,Customer customer) {
customer.setCustomerNumber(id);
if( customer== null ){
throw new ResourceNotFoundException("Empty", "Missing Data");
}else {
customerRepository.save(customer);
return new Response(" Customer Updated Successfully","Thank you ");
}
When using Sping data JPA to update you should use save which you correctly did when saving on this line customerRepository.save(customer);. However when persisting data to a database in a PUT request JPA uses the keys within your entity mappings to be able to update the proper record.
So in your case you get that error when JPA tries to save a new record rather than an update to an existing record. Your intent is to update but I suspect your keys are missing or they are not properly defined so JPA tries to go and save a new record instead of updating.
So when you do the update(PUT) make sure the object you are passing has the same keys as the one you want to update.
I just started using Play Framework 2.1.1 as a Java developer.
Got some eBeans from an existing database and I'm building a RESTful architecture to serve and save those beans from a RESTful client web application.
I have the following beans definitions. Feed is the core object of the application. For simplicity I have not included getters and setters and many other beans, I'm focusing on the ones that give me troubles:
#Entity
public class Feed extends Model implements Serializable
{
#Id
Long feedId;
...
...
...
#JsonManagedReference("feed-item")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "feed")
List<Item> items;
#JsonManagedReference("feed-userFeed")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "feed")
List<UserFeed> userFeeds;
}
#Entity
public class Item extends Model implements Serializable
{
#Id
Long itemId;
...
...
...
Long urlId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "url_id")
Url url;
#Formats.DateTime(pattern = "yyyy-MM-ddThh:mm:ss")
Timestamp itemPublishedAt;
#Formats.DateTime(pattern = "yyyy-MM-ddThh:mm:ss")
Timestamp itemUpdatedAt;
#Formats.DateTime(pattern = "yyyy-MM-ddThh:mm:ss")
Timestamp createdAt;
#Version
Timestamp updatedAt;
Long feedId;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference("item-feed")
#JoinColumn(name = "feed_id")
Feed feed;
Long urlId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "url_id")
Url url;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "item")
#JsonManagedReference("item-unseen")
List<Unseen> unseen;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "item")
#JsonManagedReference("item-score")
List<Score> scores;
}
#Entity
public class User extends Model implements Serializable
{
#Id
Long userId;
...
...
...
#OneToMany
#JsonManagedReference("user-userFeed")
List<UserFeed> userFeeds;
#OneToMany
#JsonManagedReference("user-userTag")
List<UserTag> userTags;
#OneToMany
#JsonManagedReference("user-unseen")
List<Unseen> unseen;
#OneToMany
#JsonManagedReference("user-score")
List<Score> scores;
}
#Entity
public class Score extends Model implements Serializable
{
#Id
Long scoreId;
...
...
...
Long itemId;
#ManyToOne(fetch=FetchType.LAZY)
#JsonBackReference("score-item")
#JoinColumn(name = "item_id")
Item item;
Long userId;
#ManyToOne(fetch=FetchType.LAZY)
#JsonBackReference("score-user")
#JoinColumn(name = "user_id")
User user;
}
Here's my feed controller:
import me.zenfeed.model.Feed;
import org.codehaus.jackson.JsonNode;
import play.db.ebean.Model;
import play.libs.Json;
import play.mvc.BodyParser;
import play.mvc.Controller;
import play.mvc.Result;
public class FeedController extends Controller
{
public static Result delete(Long id)
{
new Model.Finder<>(Long.class, Feed.class).byId(id).delete();
return ok();
}
/* return a JSON with all of the objects found*/
public static Result get()
{
return ok(Json.toJson(new Model.Finder<>(Long.class, Feed.class).fetch("userFeeds", new FetchConfig().query()).where().eq("userFeeds.userId", Long.parseLong(session("connectedUserId"))).findList()));
}
/* accept a JSON with the object to save and return a JSON with the new object*/
#BodyParser.Of(BodyParser.Json.class)
public static Result post()
{
JsonNode json = request().body().asJson();
Feed f = Json.fromJson(json, Feed.class);
f.save();
return ok(Json.toJson(f));
}
/* accept a JSON with the object to save and return a JSON with the new object*/
#BodyParser.Of(BodyParser.Json.class)
public static Result put(Long id)
{
JsonNode json = request().body().asJson();
Feed f = Json.fromJson(json, Feed.class);
f.update(id);
return ok(Json.toJson(f));
}
}
Here's a custom function I use to make a query on the DB with some parameters:
List<Item> l = new Model.Finder<>(Long.class, Item.class)
.select("itemId, feedId, urlId, title, description, content, author, categories, itemPublishedAt, itemUpdatedAt, wordCount, fresh, vector, createdAt, updatedAt")
.fetch("unseen", "itemId", new FetchConfig().query())
.fetch("scores", "score", new FetchConfig().query())
.fetch("feed.userFeeds", "userId", new FetchConfig().query())
.where()
.eq("feed.userFeeds.userId", Long.parseLong(session("connectedUserId")))
.eq("feed.feedId", feedId)
.orderBy("scores.score")
.findList();
I have several problems I would like help with:
- When I try a POST request that calls the method post() of this controller I get the following exception:
[RuntimeException: org.codehaus.jackson.map.JsonMappingException: Can not handle managed/back reference 'user-score': no back reference property found from type [collection type; class java.util.List, contains [simple type, class me.zenfeed.model.Score]]]
I think I put the back reference in the Score model correctly, but I don't understand... Are my ebeans correct?
Is there something wrong in my controller? Can something be made better?
I now understand I saw a wrong tutorial. The argument for #JsonManagedReference and #JsonBackReference must be the same. So for feed-item relationship the correct annotations are #JsonManagedReference("feed-item") and #JsonBackReference("feed-item").
As you can see I tried to format the Timestamp objects with an HTML5 like format, but with no luck. I always get the Long number. Should I make a conversion to a Date object? If yes where?
- When I run the query with my custom function I get the URL object populated, even though I clearly excluded it from my select statement (I only would like the URL_ID, not the entire object).
I went manual way with the generation of an HashMap for the properties I care of and gave that to the Json.toJson() method.
Thank you for your help.
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?