I have two entities:
#Entity
public class Father{
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "father",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Child> children;
// other
}
and
#Entity
public class Child{
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Father father;
// other
}
By using Spring Data REST i can save entity:
Create Father by POST
{
"name":"Father1"
}
And then create child by POST http://localhost:8080/children/
{
"name":"Child1",
"father":"http://localhost:8080/fathers/1"
}
Or i can save two independent entities and binds the resource pointed to by the given URI(s) to the resource.
Wich way is the best?
And i can't understand this: can i add child to father by:
curl -X PUT -H "ContentType: text/uri-list" http://localhost:8080/children/1 http://localhost:8080/fathers/1/children
At first you created father entity and saved it.
Then you want to bind new child to your child, you will the following in your child create request
{
"father.id": 1,
"name":"SomeName"
}
Spring will automatically convert to father entity when request will be received.
Related
I have two objects, one parent and one child as follows :
#Entity
#Table(name="category")
public class CategoryModel {
private #Id #GeneratedValue Long id;
private String name;
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
private #Id #GeneratedValue Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}
I also have dtos which maps to these model objects but I ommited them.
When I try to save a category object with this payload Attribute values are also created in the attribute table but with null category ids.
{
"name":"Chemicals",
"attributes":[
{"name": "volume"}, {"name":"humidity"}
]
}
What can I do to have my attribute values persisted into the database with the category id which is created before them?
First of all, this problem is not a "Spring Data JPA" problem, it is a JPA (probably Hibernate) problem.
Analysis
Since you left out the code for the controller and the JSON mapping, I have to guess a bit:
fact 1: The relationship between category and attributes is controlled by the attribute AttributeModel.category but not by CategoryModel.attributes. (That is how JPA works).
observation 2: Your JSON object define CategoryModel.attributes (i.e. opposite to how JPA works).
Without knowing your JSON mapping configuration and controller code, I would guess that the problem is: that your JSON mapper does not set the AttributeModel.category field when it deserialises the JSON object.
Solution
So you need to instruct the JSON mapper to set the AttributeModel.category field during deserialisation. If you use Jackson, you could use:
#JsonManagedReference and
#JsonBackReference
#Entity
#Table(name="category")
public class CategoryModel {
...
#JsonManagedReference
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
...
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}
I solved this by manually setting child object's reference to the parent object as follows :
public Long createCategory(CategoryDto categoryDto) {
CategoryModel categoryModel = categoryDto.toModel(true,true);
categoryModel.getAttributes().forEach(a -> a.setCategory(categoryModel));
return categoryRepository.save(categoryModel).getId();
}
I'm using two JPA entities annotated with #OneToMany (parent) <-> #ManyToOne (child) and I also wrote a RessourceAssembler to turn the entities into resources in the controller of a Springboot application (see below for code samples).
Without the relationship #OneToMany in the parent entity, Ressource assembling and serialisation works just fine.
As soon as I add the OneToMany relation on the parent the serialisation breaks with this:
"Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.hateoas.Resource[\"content\"]->com.marcelser.app.entities.Storage[\"storageStock\"])"
As you can see the infinite loop comes from the hateoas Resource, not the entities themselves.
I already tried to add #JsonManagedReference and #JsonBackReference on the entities or #JsonIgnore on the child but nothing really helps. The Hateoas RessourceAssembler always ends up in a infinite loop as soon as the child entity is embedded. It seems that shose #Json.... annotations help with the JSON serialisation of the entity itself but they don't solve problems with the RessourceAssembler
I have these 2 entities (Storage & Stock)
#Entity
#Table(name = "storage")
#Data
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;
}
#Entity
#Table(name = "stock")
#Data
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
and I'm using a RessourceAssemlber like follows for the parent entity 'Storage':
#Component
public class StorageResourceAssembler implements ResourceAssembler<Storage, Resource<Storage>> {
#Override
public Resource<Storage> toResource(Storage storage) {
return new Resource<>(storage,
linkTo(methodOn(StorageController.class).one(storage.getId())).withSelfRel(),
linkTo(methodOn(StorageController.class).all()).withRel("storages"));
}
}
and in the controller I have 2 get classes to list all or just a single Storage with its childs
public class StorageController {
private final StorageRepository repository;
private final StorageResourceAssembler assembler;
#GetMapping
ResponseEntity<?> all() {
List<Resource<Storage>> storages = repository.findAll().stream()
.map(assembler::toResource)
.collect(Collectors.toList());
Resources<Resource<Storage>> resources = new Resources<>(storages,
linkTo(methodOn(StorageController.class).all()).withSelfRel());
return ResponseEntity.ok(resources);
}
private static final Logger log = LoggerFactory.getLogger(StorageController.class);
StorageController(StorageRepository repository, StorageResourceAssembler assembler) {
this.repository = repository;
this.assembler = assembler;
}
#GetMapping("/{id}")
ResponseEntity<?> one(#PathVariable Long id) {
try {
Storage storage = repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(id));
Resource<Storage> resource = assembler.toResource(storage);
return ResponseEntity.ok(resource);
}
catch (EntityNotFoundException e) {
log.info(e.getLocalizedMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body (new VndErrors.VndError("Storage not found", "could not find storage with id " + id ));
}
}
... omitted Put/Post/Delete
}
Can anyone enlighten me how I can solve this infinite loop in HateOAS. What I want is that the embedded child entries just either don't link back to the parent (so no links to parent are created) or they contain the link for the one level but no further processing is done.
To handle the problem related to the serialization of the model using Jackson API when the model attributes have a lazy loading defined, we have to tell the serializer to ignore the chain or helpful garbage that Hibernate adds to classes, so it can manage lazy loading of data by declaring #JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) annotation.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {...
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {...
or you can just declare unilaterally mapping commenting the Storage entity declaration and changing the private Storage storage; to fetch EAGER #ManyToOne(fetch = FetchType.EAGER) in Stock class.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
/*#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;*/
}
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
Maybe a little late, but I've had this problem or very similar and I've only found one solution. The same error 500 gave me the clue on how to solve it:
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: org.springframework.hateoas.PagedModel["_embedded"]->java.util.Collections$UnmodifiableMap["usuarios"]->java.util.ArrayList[0]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Usuario["rol"]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Rol$HibernateProxy$QFcQnzTB["hibernateLazyInitializer"])
So I only had to add in the application.properties:
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false
I am using spring boot (version - 2.1.1). I have a one to many database model exposed for CRUD operations through rest api's. The model looks as below. How do I configure the POST /departments api (that creates a department object) to accept just the organization id in the input json body?
#PostMapping
public Long createDepartment(#RequestBody Department Department) {
Department d = departmentService.save(Department);
return d.getId();
}
Note - I do not want to allow creating organization object when creating a department.
Model object mapping
#Entity
#Table(name="ORGANIZATIONS")
public class Organization{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#OneToMany(mappedBy = "organization", fetch = FetchType.EAGER)
private List<Department> departments;
}
#Entity
#Table(name="DEPARTMENTS")
Public class Department{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Organization organization;
}
Thanks!
The easiest and most sane way in my opinion is to utilize the DTO (Data Transfer Object) pattern.
Create a class that represent the model you want to get as your input:
public class CreateDepartmentRequest {
private long id;
// getters and setters
}
Then use it in your controller:
#PostMapping
public Long createDepartment(#RequestBody CreateDepartmentRequest request) {
Department d = new Department();
d.setId(request.getId());
Department d = departmentService.save(d);
return d.getId();
}
Side note, its better to ALWAYS return JSON through REST API (unless you use some other format across your APIs) so you can also utilize the same pattern as I mentioned above to return a proper model as a result of the POST operation or a simple Map if you don't want to create to many models.
I'm using spring boot and jpa, I'm trying to get data from parent entity and child entity using jparepository.
Parent Entity:
#Entity
#Table(name = "parent")
public class Parent {
#Id
private int id;
private String name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<Child> children;
}
Child Entity:
#Entity
#Table(name = "child")
public class Child {
#Id
private int id;
private String name;
private int parent_id;
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private Parent parent;
jpaRepository:
public interface ParentRepository extends JpaRepository<Parent, Integer>, JpaSpecificationExecutor<Parent> {
}
the reason I set the fecth to FetchType.LAZY is that sometimes I just want to get parent without child.
So, here is my question:
when I use
parentRepository.findAll(pagable);
the result only contains parents, not child, but I want the result to contain children, and in some situation I don't want it. how to write it ?
To fetch children collection you can declare an entity graph. Something like this:
#NamedEntityGraph(
name = "parent.withChildren",
attributeNodes = {
#NamedAttributeNode("children")
}
)
And then use it with repository methods:
#EntityGraph("parent.withChildren")
Page<Parent> findWithChidren(Pageable page);
how to add one-to-one mapping for the self entity. Like in this example. I want to have parent-child relationship for the Person itself.
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="personId")
private int id;
#OneToOne
#JoinColumn()
private Person parentPerson;
}
Here is example of bidirectional self mapping #OneToOne (I change column names to SQL notation):
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="person_id")
private int id;
#OneToOne
#JoinColumn(name = "parent_person_id")
private Person parentPerson;
#OneToOne(mappedBy = "parentPerson")
private Person childPerson;
}
But, I don't understand why you want to use #OneToOne in this case.
I am using it like this:
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "PARENT_ID", nullable = true)
private Person parent;
In order to add parent from your service layer, you need to already have at least one Person in the database.
Lets presume you do. Then create a new person. For e.g.:
#Transactional
public void createPerson() {
Person parent = //get your parent
Person child = new Person();
if (parent != null) {
child.setParent(parent);
}
}
If this is what you mean..