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<>();
Related
I have a class Profile that has a SkillLevel list and SkillLevel has a Skill which has a String: name property.
I want to get profiles that have SkillLevels with Skills whose names contain a given string. For example, profiles that have "C#" or "Java" (or even "Ja" should work as well) as Skills.
For illustration, a JSON Get of a profile gives me the following code:
{
"id": 13,
"tenure": "Tenure1",
"seniority": "Junior Developer",
"projects": [
{
"id": 1,
"description": "JTP",
"start": "2020-12-31",
"end": "2020-11-02"
}
],
"developer": {
"id": 14,
"fullName": "Dev Name",
"age": 25
},
"skills": [
{
"id": 1,
"skill": {
"id": 1,
"name": "C#"
},
"level": {
"level": "low"
}
}
]
}
Profile class:
#Entity public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String tenure;
private String seniority;
#ManyToMany
private Collection<Project> projects;
#ManyToMany
private Collection<SkillLevel> skillLevels;
#OneToOne(cascade = CascadeType.ALL)
private Developer developer;
}
SkillLevel class:
#Entity public class SkillLevel {
#Id
private long id;
#ManyToOne
private Skill skill;
#ManyToOne
private Level level;
Skill class:
#Entity public class Skill {
#Id
private long id;
private String name;
I'm having a lot of trouble trying to figure out how to write this CrudRepository method. If possible I'd like to avoid writing the query myself.
The following is one of the methods I tried:
public List<Profile> findAllBySkillLevelsContainingSkill_NameContains(String str);
Solved, in this case:
List<Profile> findProfilesBySkillLevels_SkillNameIgnoreCaseContains(String string);
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
I'm having an issue when trying to fetch all of the "Discount" objects stored in my application. A "Discount" is an object represented by the following class
#Entity
#NoArgsConstructor
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "store")
public class Discount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// some fields like a description or a percentage providing info about a discount
#ManyToOne
#JoinColumn(name = "id_store")
private Store store;
#ManyToOne
#JoinColumn(name = "id_brand")
#JsonManagedReference
private Brand brand;
// getters, setters, equals and hashcode overriden below
As you can see, a Discount object can belong to a Store or to a Brand. I'm ensuring that only one field is set both in my code and in my database with a trigger. In my application, a Discount can be provided both by a Brand (therefore I don't have to create a Discount for each Store of this Brand) or a particular Store can provide its own Discount. My class Store looks like this
#Entity
#NoArgsConstructor
public class Store extends Entite {
#OneToMany(mappedBy = "store", cascade = CascadeType.ALL)
private Set<Discount> discounts;
#ManyToOne
#JoinColumn(name = "id_brand", nullable = false)
private Brand brand;
// getters, setters, equals and hashcode overriden below
As you can see, a Store has a Set of Discounts and belong to a Brand. It extends the class Entite because in my app I also have Sport Clubs and they share some info, but this is not relevant to my problem.
Finally, here is the Brand class
#Entity
#NoArgsConstructor
public class Brand {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "brand", cascade = CascadeType.ALL)
private Set<Store> stores;
#OneToMany(mappedBy = "brand", cascade = CascadeType.ALL)
#JsonBackReference
private Set<Discount> discounts;
// getters, setters, equals and hashcode overriden below
My Brand class then have a Set of Store and a Set of Discount.
My problem is that I'm unable to fetch all the Discount with a simple FindAll from my interface DiscountRepository extending JpaRepository<Discount, Long> because some of the Discount are retrieved within an association. The answer that I get from the FindAll method is the following
[
{
// discount fields such as percentage, ruling, etc
"store": {
"id": 75,
"name": "Nike Store",
"discounts": [
75
],
"brand": {
"name": "Nike",
"store": [
75
]
}
},
"brand": null
},
{
// discount fields such as percentage, ruling, etc
"store": null,
"brand": {
"name": "Uniqlo",
"stores": [
90
]
}
},
{
// discount fields such as percentage, ruling, etc
"store": null,
"brand": {
"name": "FNAC",
"stores": [
{
"id": 76,
// some store fields
// problem here because Discount are retrieved here and therefore not displayed in the list, only here
"discounts": [
{
// discount fields such as percentage, ruling, etc
// for an offer that isn't present in the original list returned
// by the find all method but only here, associated to this Store object
"store": 76,
"brand": null
}
],
"brand": {
"name": "FNAC",
"stores": [
76
]
}
}
]
}
},
76
]
The problem here is that there's a Discount fetch with a Store and therefore this Discount isn't in the original list that the method returns, but in a Store object associated to a Discount. I tried to add FetchType.LAZY to my Store and Discount association but this doesn't resolve the problem. Therefore is there a way to fetch "only one level association" ? What I mean is, I'd like to retrieve my Discount object but only with the Brand and the Store info, not with the Store or Brand and all its assocations (like its Set of Discount). I hope that I've been clear, do not hesitate to ask me any question.
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 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);
}