I have an entity called Sale, which has list of objects SaleData, I have set up unidirectional #OneToMany relationship between these two. Now I ran into a problems when doing CRUD operations on my Sale object.
CascadeType set to ALL.
#Entity
#Table(name = "sale")
public class Sale extends BaseEntity {
#OneToOne(cascade = CascadeType.DETACH)
private Client client;
#OneToMany(cascade = {CascadeType.ALL}) // <--
#JoinColumn(name = "sale_id", nullable = false)
private List<SaleData> saleData = new ArrayList<>();
}
Save (POST) and Delete works. What's causing problem is when POST-ing for update, my JSON sent from Angular controller looks like that:
{"id": 1, "client": {
"id": 1,
"firstName": "Test",
"lastName": "Client"
}, "saleData": [
{
"id": 1,
"employee": {
"id": 1,
"firstName": "Herp",
"lastName": "Derp"
}
},
{
"id": 2,
"employee": {
"id": 1,
"firstName": "John",
"lastName": "Smith"
}
}
]}
Update call from Spring service throws:
org.hibernate.PersistentObjectException: detached entity passed to persist: app.sales.SaleData
Service implementation
#Override
#Transactional(rollbackFor = SaleNotFoundException.class)
public Sale update(#NotNull SaleDTO updated) {
Sale sale = repository.findOne(updated.getId());
if (sale == null)
throw new SaleNotFoundException("Sale not found");
return transformSaleObject(sale, updated);
}
As I understand, cascade ALL doesn't make the sale object Deattached, because in 2nd scenario where I replace the type with any combination of...
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
.. then Update and Delete works, but Saving a new object does not. I haven't used Hiberate that much, but maybe anyone knows what's causing this behaviour? Is it the matter of defining relationship wrong way?
Update:
The transform method is simply this, but anyway seems like it's all solved for me now :)
private Sale transformSaleObject(Sale sale, SaleDTO dto) {
sale.setClient(dto.getClient());
sale.setSaleData(dto.getSaleData());
sale.setService(dto.getService());
sale.setHours(dto.getHours());
sale.setPrice(dto.getPrice());
sale.setSaleStatus(dto.getSaleStatus());
sale.setSaleDate(dto.getSaleDate());
return sale;
}
I changed my update method as follows:
public Sale update(#NotNull SaleDTO updated) {
Sale sale = repository.findOne(updated.getId());
if (sale == null)
throw new SaleNotFoundException("Sale not found");
transformSaleObject(sale, updated);
return repository.saveAndFlush(sale);
}
Related
I have been struggling to solve this issue on my project: Is possible to use the annotation #JsonIgnore only when endpoint has an specific value?
For example, i want to use the annotation when endpoint.equals("xxxxxxxxx"), but not use when endpoint.equals("yyyyyy").
There are 3 classes with these relationship annotations:
Client
#OneToMany(mappedBy = "ownerOfTheProduct")
#JsonIgnore
private List<Product> ownProducts = new ArrayList<>();
Category
#JsonIgnore
#OneToMany(mappedBy = "category")
private List<Product> products;
Product
#ManyToOne
#JoinTable(name = "PRODUCT_CATEGORY", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "category_id"))
private Category category;
#ManyToOne
#JoinTable(name = "CLIENT_PRODUCT", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "client_id"))
private Client ownerOfTheProduct;
The point is:
If i dont put the #JsonIgnore, i get a StackOverflow error, the json gets into looping and wont stop.
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning",
"products": [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
...
When i mapped in a different way, and put the #JsonIgnore into the both classes: Client and Product, it works, the loopings were not more hapenning. However, when i have to use other endpoint, which the fields products and ownerOfTheProduct need to show up through api, it doesnt work cuz the #JsonIgnore is annotated.
LOOPING SOLVED
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
"ownOfTheProduct": {
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
}
OTHER ENDPOINTS ARE NOT WORKING
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
I'd like the field that i have mapped with #JsonIgnore (ownProducts) shows up in this request exactly this way:
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
"ownProducts" [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
]
}
Is there a way to change this? Summing up, i just want to use #JsonIgnore with especific especific endpoints, not every single endpoint on my API.
I hope yall got my question, anyway here is the link of the repository on github: https://github.com/reness0/spring-restapi-ecommerce
You cant use only #JsonIgnore but you can use #JsonView and #JsonIdentityInfo annotations from com.fasterxml.jackson.core
How it works:
You need define class with interfaces. For example:
public class SomeView {
public interface id {}
public interface CoreData extends id {}
public interface FullData extends CoreData {}
}
Mark entity fields with #JsonView(<some interface.class>)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(SomeView.id.class)
private Long id;
#Column(nullable = false)
#JsonView(SomeView.CoreData.class)
private String username;
#Column(nullable = false)
#JsonView(SomeView.FullData.class)
private String email;
}
Annotate endpoint with #JsonView(<some interface.class>)
#GetMapping()
#JsonView(SomeView.FullData.class)
public User getUser() {
return <get user entity somwhere>
}
In case #JsonView(SomeView.id.class) you will get this JSON:
{
id: <some id>
}
In case #JsonView(SomeView.CoreData.class):
{
id: <some id>,
username: <some username>
}
In case #JsonView(SomeView.FullData.class):
{
id: <some id>,
username: <some username>,
email: <some email>
}
#JsonView also works with embeded objects and you can annotate one field with multiply views classes - #JsonView({SomeView.FullData.class, SomeOtherView.OtherData.class})
About Cycleing JSON. Annotate your entity class with
#JsonIdentityInfo(
property = "id",
generator = ObjectIdGenerators.PropertyGenerator.class
)
Every time when JSON serialization go in circles object data will be replaced with object id or orher field of entity for your choose.
Or as alternative you can just use DTO classes
While this is not possible to achieve using the annotation based approach (annotations make it static), you can achieve the same using any data mapper library. Create a filter based on the attribute from API. Orika library can be used: https://www.baeldung.com/orika-mapping
Currently, I have the following relationship between two entities:
#Entity
public class Pokemon {
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "trainer_id")
#JsonIgnoreProperties("pokemons")
private Trainer trainer;
}
and also:
#Entity
public class Trainer {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,
mappedBy = "trainer")
private Set<Pokemon> pokemons = new HashSet<>();
}
Everything is fine when updating the entities. However, when I attempt to retrieve all trainers, it includes the trainer within the pokemon, which I don't want. Observe:
GET ALL POKEMON (all is fine):
{
"id": 1,
"name": "squirtle",
"type": "water",
"trainer": {
"id": 1,
"name": "Ash Ketchum",
"level": 1
}
}
GET ALL TRAINERS:
{
"id": 1,
"name": "Ash Ketchum",
"level": 1,
"pokemons": [
{
"id": 1,
"name": "squirtle",
"type": "water",
"trainer": {
"id": 1,
"name": "Ash Ketchum",
"level": 1
}
}
]
}
Notice how the trainer class is returned within each pokemon inside of the the pokemons set? I'd prefer not to return that, since I already have access to that information. Is there anyway I can tell the entity not to return its own info from the Pokemon class? If it helps, my retrieval query looks like this:
public List<Trainer> getAllTrainers() {
em.getTransaction().begin();
List<Trainer> trainer = em.createNativeQuery("SELECT * FROM
Trainer", Trainer.class).getResultList();
em.getTransaction().commit();
return trainer;
}
Thank you so much.
For anyone interested, I was able to achieve my goal by telling my Trainer class to ignore the trainer property in the Pokemon object via #JsonIgnoreProperties
#Entity
public class Trainer implements Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "trainer")
#JsonIgnoreProperties(value = "trainer")
private Set<Pokemon> pokemons = new HashSet<>();
I building an api rest with spring boot. I have a parent-child relationship in witch the child its an array of objects.
The problem is that deserialization only picks the first item of the array. Everything else seems to work fine. The parent and the child are pesisted in the database too.
I send something like this:
"user": {
"name": "foo",
"childs": [
{
"name": "bar",
....
},
{
"name": "foobar",
....
}
],
....
}
But got persisted this:
"user": {
"id": 1,
"name": "foo",
"childs": [
{
"id": 1,
"name": "bar",
....
}
],
....
}
Any clue on this?
Update
Parent Entity:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = User.class)
#Entity( name = "users" )
#Table( name = "users" )
public class User extends ModelEntity {
Model's fields...
...
#JsonView( value = {DTOViews.PrivateProfile.class, DTOViews.Owner.class} )
#JsonManagedReference( value = "User-ProfessionalExperience" )
#OneToMany( mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY )
private Set<ProfessionalExperience> professionalExperiences;
}
Child entity:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = ProfessionalExperience.class)
#Entity
#Table( name = "professional_experiences")
public class ProfessionalExperience extends ModelEntity {
Model's fields...
...
#JsonBackReference( value = "User-ProfessionalExperience" )
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
Controller:
#RequestMapping(method = RequestMethod.POST)
public MappingJacksonValue create(#RequestBody #Valid User userToCreate, BindingResult result) {
...
}
Thank you all in advance.
So, I finally solved it. The issue comes from the relationship collection type and hasCode() / equals() methods.
All the entities in my model extend from "ModelEntity" class. This class provides id and record active fields for all extending models and a hasCode/equals method based on these fields. As the relationship between "User" and "ProfessionalExperience" is defined as a set, it can't store duplicated elements.
So, to tell jackson that the children are different elements, we need to override hasCode/equals in each model class with the fields defined in each one.
I am working with REST API, and I am stacked cause of some weird issue.
I want to return as JSON, my request object, with all details and relations.
There is my repository:
#Repository("requestRepository")
public interface RequestRepository extends JpaRepository<Request, Integer> {
Request findByTitle(String title);
List<Request> findAll();
}
My Controller method:
enter image description here
{
#Autowired
private RequestServiceImpl requestService;
#RequestMapping("/getall")
public List<Request> findAll() {
for (Request req:requestService.findAllRequests()) {
System.out.println("Profession:" + req.getProfession().getName());
}
return requestService.findAllRequests();
}
}
Mapping:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "profession_id")
private Profession profession;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "profession")
private List<Request> requests;
I also use #JsonManagedReference and #JsonBackReference with getters.
When I call http GET, I receive JSON that looks like:
enter image description here
[
{
"id": 1,
"title": "Hydraulik na juz!!!",
"minPayment": 200,
"maxPayment": 300,
"description": "short description",
"active": 1,
"creationDate": "2017-11-10"
},
{
"id": 2,
"title": "Potrzebny kierowca do Warszawy",
"minPayment": 100,
"maxPayment": 700,
"description": "another desc...",
"active": 1,
"creationDate": "2017-11-10"
}
]
There is no relation between profession and request in that JSON. Even in my database, I see relation between them:
enter image description here
I tried changing fetch type from LAZY to EAGER, and it didn't help. I don't know what is the reason that this JSON has no relation between profession and request
I have two entities, Company and Job, with an OneToMany bidirectional relationship. My problem is that i can't lazy load the Company's List<Job> jobs.
For example when i do:
GET /api/companies/1 this is the JSON response:
{
"id": 1,
"name": "foo",
...
"_embedded": {
"jobs": [
{...},
...
{...}
],
"employees": [
{...},
{...}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/companies/1"
},
"jobs": {
"href": "http://localhost:8080/api/companies/1/jobs"
},
"employees": {
"href": "http://localhost:8080/api/companies/1/employees"
}
}
}
I don't want to have the _embedded since i didn't set the FetchType=EAGER.
Here are my models:
Company.java
#Entity
public class Company {
#Column(nullable = false, unique = true)
private String name;
#OneToMany(mappedBy = "company", fetch = FetchType.LAZY)
private List<Job> jobs;
...
public Company() {
}
...
}
Job.java
#Entity
public class Job {
#Column(nullable = false)
public String title;
#Column(length = 10000)
public String description;
#ManyToOne(fetch=FetchType.LAZY)
private Company company;
...
public Job() {
}
...
}
As you can see the same thing happens for other OneToMany relationships (employees). Can i avoid returning the whole list of job openings or employees every time?
EDIT: from the Job side the lazy load works fine! I don't get in the response the company that is related with a Job. I have to explicitly do /api/jobs/123/company in order to get the company.
EDIT2: Projections only work for collections. In this case it's not what i need. Excerpts could work, but i want to avoid them. I don't want to explicilty do /api/companies/1?projection=MyProjection since i won't use more than one. I want to change the default behavior, just like the projections do in collections.
EDIT3: i tried this
#RestResource(exported = false)
#OneToMany(mappedBy = "company")
private List<Job> jobs;
and i get the error Detected multiple association links with same relation type! Disambiguate association.
it's really annoying. I just need to get rid of _embedded. Anything?
You can use Entity Graph.Entity graphs are used to override at runtime the fetch settings of attribute mappings.For example
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
From Spring Data Jpa Documentation "4.3.10. Configuring Fetch- and LoadGraphs"
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
In addition;