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.
Related
I have a class like below.
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Entity
#Table( name = "hires", indexes = { #Index( name = "idx_hire_date", columnList = "date DESC" ) }, uniqueConstraints = {
#UniqueConstraint( columnNames = { "vehicle_id", "po_number" } ) } )
#DynamicUpdate
#JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" )
public class Hire implements Serializable {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY )
int id;
#OneToOne( targetEntity = Driver.class, fetch = FetchType.EAGER )
#JoinColumn( name = "pass_payer", referencedColumnName = "id", nullable = true )
Driver passPayer;
#OneToOne( targetEntity = Driver.class, fetch = FetchType.EAGER )
#JoinColumn( name = "driver_id", referencedColumnName = "id", nullable = true )
Driver driver;
...
}
I get this object via a Rest endpoint.
The problem is when the field passPayer and driver objects are equal, in the returning JSON, the driver field contains only the ID (which is just an integer value) and passPayer field has all the object fields.
"passCost": 300.0,
"passPayer": {
"id": 9,
"firstName": "XXXX",
"lastName": "XXXXXX",
"idNo": "000000000000"
},
"driver": 9,
"driverSalary": xxxx.xx,
When these fields have different objects, both fields show full details like below.
"passCost": 300.0,
"passPayer": {
"id": 9,
"firstName": "XXXX",
"lastName": "XXXXXX",
"idNo": "000000000000"
},
"driver": {
"id": 4,
"firstName": "YYYYYY",
"lastName": "YYYYYYY",
"idNo": "10101010101"
},
"driverSalary": 00000.00,
I need both objects to contain data (fields. [id, firstName, lastName, idNo]) whether they are equal or not.
Any clue is appreciated!
This is caused by #JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" ), check the docs. To cite :
Annotation used for indicating that values of annotated type or
property should be serializing so that instances either contain
additional object identifier (in addition actual object properties),
or as a reference that consists of an object id that refers to a full
serialization.
Since both fields are referencing the same object, the second one is serialized as a reference to the first object.
In my experience, this annotation is mostly used to deal with circular references, so you can:
remove it, if your use case allows it(no circular references in object)
or you can use DTOs(which is the prefered approach anyway)
maybe another many-to-many relationship issue with spring data-jpa and how to update an existing entity with another existing entities.
I'll put a short version of my Entities just for clarify only when the error occurs.
I have a Peticion entity:
#Entity
#Table(name = "peticiones")
#JsonIdentityInfo(generator =
ObjectIdGenerators.PropertyGenerator.class,property = "id", scope =
Peticion.class)
#Validated
public class Peticion
{
private int id;
private Usuario usuario;
private Categoria categoria;
private Set<Tag> tags;
#ManyToMany( fetch = FetchType.LAZY, cascade = {
CascadeType.MERGE
} )
#JoinTable( name="peticion_tag", joinColumns= #JoinColumn(name = "peticion_id", referencedColumnName="id"), inverseJoinColumns = #JoinColumn(name="tag_id", referencedColumnName="id") )
public Set<Tag> getTags() {
return tags;
}
public void setTags(Set<Tag> tags) {
this.tags = tags;
}
And Tag entity:
#Entity
#Table(name = "tags")
#JsonIdentityInfo(generator =
ObjectIdGenerators.PropertyGenerator.class,property = "id", scope =
Tag.class)
public class Tag
{
private int id;
#Size( min = 4 )
private String nombre;
private Set<Peticion> peticiones;
private Set<Categoria> categorias;
private Set<Usuario> usuarios;
#ManyToMany( mappedBy="tags" )
public Set<Peticion> getPeticiones() {
return peticiones;
}
public void setPeticiones(Set<Peticion> peticiones) {
this.peticiones = peticiones;
}
Ok, so when I try to put or patch one Peticion in the format of:
{
"id": 123,
"usuario":{
"id": 5
},
"categoria":{
"id": 7
},
"tags":[
{
"id":3
},
{
"id":10
}
]
}
When I send this information, I got an error that says that I have a constraint violation saying that name I suppose the one property for Tag is null... So I figure it out that this is trying to create another entity, but that's not the case I wanna do, I wanna update the relationships between Peticion and Tag, and for example if I do this:
{
"id": 123,
"usuario":{
"id": 5
},
"categoria":{
"id": 7
},
"tags":[]
}
It works perfectly, I mean it deletes the relationship tags that were before. So I don't know if I'm sending the json correctly or do I have to put another configuration annotation or something in my Entities.
Note: I'm using JpaRepository for saving/updating and my controller only calls the method save.
Thank you
You might be missing fetches on the relation entity on your database layer, I think you should also share the repositories and queries that you use to fetch the linked data.
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<>();
Working on this 'twitter' application where a user can have posts #OneToMany and can have followers #ManyToMany.
While retrieving a user all it's posts and followers get retrieved as well.
This is all correct but it's also retrieving every 'poster' for each post (which is the user itself) and for each follower, all it's posts and followers.
I can't figure out how to limit this to the user itself.
User
#Entity
#Table(name = "User")
#NamedQueries({
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
#NamedQuery(
name = "User.auth",
query = "SELECT u FROM User u WHERE u.username = :username AND u.password = :password"
)
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String username;
#Column(nullable = false)
private String password;
#ManyToMany
#OneToMany(mappedBy = "poster", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
#JoinTable(name = "Followers",
joinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
},
inverseJoinColumns = {
#JoinColumn(name = "FOLLOWER_ID", referencedColumnName = "ID")
}
)
private List<User> followers = new ArrayList<>();
.... constructor, getters and setters
Post
#Entity
#Table(name = "Post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
#ManyToOne
private User poster;
.... constructor, getters and setters
Outcome I get vs what I want
{
"id": 1,
"username": "jim",
"posts": [
{
"id": 1,
"content": "Post 1 by jim",
"poster": {
// ^ this is the user itself (I don't need this one)
"id": 1,
"username": "jim",
"posts": [
// keeps recurse
]
}
}
],
"followers": [
{
"id": 2,
"username": "follower1",
"posts": [
{
"id": 4,
"content": "Post 2 by follower 1",
"poster": {
// ^ this is the follower itself (I don't need this one)
"id": 2,
"username": "follower1",
"posts": [
// Same issue
]
}
}
],
"followers": [], // <-- I don't need this one either
}
]
}
Well it's pretty clear that fetching one user fill keeps fetching all it's relations which are recursive.
Is this a designer's fault or can this be ignored/limited?
Note: I am using Gson to serialise objects to JSON format
Update
Tried to use:
#ManyToOne(fetch = FetchType.LAZY)
private User poster;
Which works but still gets the following extra prop in JSONso not sure if this is a neath solution:
"_persistence_poster_vh": {
"sourceAttributeName": "poster",
"isInstantiated": false,
"row": {
"Post.ID": 3,
"Post.CONTENT": "Post 3 by jim",
"Post.DATETIME": "2018-01-22",
"Post.POSTER_ID": 1
},
"isCoordinatedWithProperty": false
}
And
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
...
)
private List<User> followers = new ArrayList<>();
Which still returns all followers (which I want!) I just don't want the followers.followers and followers.posts..
Best guess: it’s not actually fetching these objects until you try to dereference them.
Be default, JPA will eager fetch #OneToOne and #OneToMany relations, but not #ManyToOne or #ManyToMany. What happens is that when you reference these fields, it will then go and fetch the actual contents of the list.
How can you tell this is happening? Check the list’s class using getFollowers().getClass()
What you see won’t be a LinkedList or an ArrayList but a class from your JPA provider, probably with “Lazy” somewhere in the name. When you call Size or Get on the list, it will perform the fetch.
You can set OneToOne and OneToMany relations to be lazy as well, and use EntityGraphs to determine what entities you want to eagerly fetch as well. JPA has a number of gotchas like this.
I’ve seen GSON mentioned, and just a warning: it won’t be aware of the lazy loading lists, so you MUST tell It to avoid the properties you don’t want it to follow.
Typically with JSON marshaling, you’ll want it to ignore the parent object, so in Post, User should be ignored for example. Additionally links to same types should typically be ignored (followers) or else mapped specially, such that it doesn’t Marshall the entire object, but only produces an array of usernames. You can tell it to ignore the actual followers field, and have it marshal a getter which returns an array of usernames to implement this.
You can specify fetch=FetchType.LAZY in the annotation you don't want to fetch immediately. The downside is, that if you need the data later you have to access it in the scope of the still open session.
There are two ways to handle this -
You can either use #JsonIgnoreProperties(ignoreUnknown=true) anotation on attributes you want to skip while serializing the object.
Or you change your FetchType to FetchType.LAZY so that you can get the required data on need basis while preparing your JSON , rather than getting all records at once.
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);
}