Can't save a OneToMany/ManyToOne relationship in Spring Data REST request - spring

I am using Spring Data JPA and Spring Data Rest.
When making a REST request to persist an entity, I get the next error:
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value
My data model has the following entities:
Contract:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
public class Contract implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY,
mappedBy="contract"
)
private List<Participation> participants = new ArrayList<Participation>();
private String name;
}
Participation:
#Entity
public class Participation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false) //By default the column will be CONTRACT_ID
private Contract contract;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false)
private Contact contact;
private String clauses;
}
Contact:
#Entity
public class Contact implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String emailAddress;
}
I have 2 JPARepositories:
public interface ContractRepository extends JpaRepository<Contract, Long> {
List<Contract> findByNameContainsIgnoreCase(String name);
}
public interface ContactRepository extends JpaRepository<Contact, Long> {
}
To save a new Contract with a couple of participations, I am doing the next steps in Postman:
Create a Contract and get its href:
Request: POST http://localhost:8080/api/contracts
Body:
{
"name": "Contract1"
}
The response is successful:
201 Created
{
"name": "Contract1",
"participants": [],
"_links": {
"self": {
"href": "http://localhost:8080/api/contracts/4"
},
"contract": {
"href": "http://localhost:8080/api/contracts/4"
},
}
}
So far so good. Now that I have the contract persisted, I am adding participants:
Contact 1 already exists in the data base.
Request: PATCH http://localhost:8080/api/contracts/4
Body:
{
"participants": [
{
"clauses": "Bla bla bla",
"contact": {
"href": "http://localhost:8080/api/contacts/1"
},
"contract": {
"href": "http://localhost:8080/api/contracts/4"
}
}
]
}
When executing this request the system complains on the field/fk contract:
{
"cause": {
"cause": null,
"message": "not-null property references a null or transient value : com.xxx.xxx.model.Participation.contract"
},
"message": "not-null property references a null or transient value : com.xxx.xxx.model.Participation.contract; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.xxx.xxx.model.Participation.contract"
}
I tried several ways to reference the contract in the participation, like:
"contract": "http://localhost:8080/api/contracts/4"
No luck. For some reason the system is leaving the field empty instead of using the foreing key of the entity created in step 1.
What am I doing wrong?

The problem can be solved by:
Add a new repository ParticipationRepository (extends JpaRepository);
Create first a Contract without Participations:
POST http://localhost:8080/api/contracts { "name": "Contract1" }
Response:
201 Created
{
"name": "Contract1",
"_links": {
"self": {
"href": "http://localhost:8080/api/contracts/3"
},
"contract": {
"href": "http://localhost:8080/api/contracts/3"
},
"participants": {
"href": "http://localhost:8080/api/contracts/3/participants"
}
}
}
Create a Participation and use the URI from the just created Contract to set the FK. Assume Contact 1 already exists in the Data Base.
POST http://localhost:8080/api/participations {
"clauses": "bla, bla, bla",
"contract": "http://localhost:8080/api/contracts/3",
"contact": "http://localhost:8080/api/contacts/1" }
Response:
201 Created
{
"clauses": "bla, bla, bla",
"_links": {
"self": {
"href": "http://localhost:8080/api/participations/5"
},
"participation": {
"href": "http://localhost:8080/api/participations/5"
},
"contract": {
"href": "http://localhost:8080/api/participations/5/contract"
},
"contact": {
"href": "http://localhost:8080/api/contacts/5/contact"
}
}
}

Related

Is there a way to update and send the name to the response after calling the save method in spring?

There are employees and department class as below.
Employee
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne
private Department department;
}
Department
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
I want to save the employee and return the name of the department together.
Service
public ResponseEntity<Employee> create(Employee employee) throws URISyntaxException {
Employee savedEmployee = repository.save(employee);
URI location = new URI(String.format("/employee/%s", savedEmployee.getId()));
return ResponseEntity.created(location).body(savedEmployee);
}
Controller
#PostMapping
public ResponseEntity<Employee> postEmployee(#RequestBody Employee employee) throws URISyntaxException {
return service.create(employee);
}
I want to receive the response as below.
Request Body :
{
"name":"David",
"department":{
"id":1
}
}
Actual Response Body:
{
"id": 1,
"name": "David",
"department": {
"id": 1,
"name": null
}
}
Wanted Response Body :
{
"id": 1,
"name": "David",
"department": {
"id": 1,
"name": "HR"
}
}
Is there an easy way?

spring-data-jpa - Limit how deep objects populate

I have the following entities:
#Entity
#Table(name = "business", schema = "public")
public class Business {
// some properties
}
#Entity
#Table(name = "appuser", schema = "public")
public class AppUser implements UserDetails {
// some properties
#JsonManagedReference
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
private List<UserBusinessRole> userBusinessRoles = new ArrayList<>();
}
#Entity
#Table(name = "appuser_business_role", schema = "public")
public class UserBusinessRole {
// some properties
#ManyToOne
#JoinColumn(name = "business_id")
private Business business;
}
These work without issue when calling individually, however, I also have an entity that has business AND app user:
#Entity
#Table(name = "import_session", schema = "public")
public class ImportSession {
// some properties
#JsonIgnore
#ManyToOne()
#JoinColumn(
name = "requester_user_id",
referencedColumnName = "id"
)
private AppUser requester;
#ManyToOne
#JoinColumn(name = "business_id")
private Business business;
}
But it returns duplicate values for business like below (listed under roles and in the root object):
{
"id": 14,
...
"requesterDto": {
"id": 123,
"emailAddress": "bar#bar.com",
"userBusinessRolesDto": [
{
"id": 6,
"type": "ADMIN",
"businessDto": {
"name": "Foo Inc"
...
}
}
]
},
"businessDto": {
"name": "Foo Inc"
}
}
Is there a way to make it ONLY return certain fields, or control how 'deep' it populates, without a lot of manual fiddeling / creating separate DTOs all over? So it would look something like this for example:
{
"id": 14,
...
"requesterDto": {
"id": 123,
"emailAddress": "bar#bar.com"
},
"businessDto": {
"name": "Foo Inc"
...
}
}

Spring boot JSON return infinite nested objects

I have the following in my code:
CompanyEntity
#Entity
#Table(name = "company")
public class Company{
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
#JsonUnwrapped
private Set<User> users;
}
UserEntity
#Entity
#Table(name="user")
public class User{
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name="company_id")
private Company company;
}
CompanyController
#GetMapping("/company")
public ResponseEntity<Object> getAllCompanies(){
List<Company> allCompanies = companyService.findAll();
return ResponseEntity.ok(allCompanies);
}
problem is when i call /company in the browser i am getting the users object including the company object. something like this
[
{
"id": 1,
"name": "company",
"users": [
{
"id": 14,
"firstName": "Yamen",
"lastName": "Nassif",
"company": {
"id": 1,
"name": "company",
"users": [
{
"id": 14,
"firstName": "Yamen",
"lastName": "Nassif",
"company": {
"id": 1,
"name": "company",
"users": [
...
same goes when i getAllUsers companies and users are also exanding.
my database looks just fine.
and its endless and of course Stackoverflow error is in the console. How can i fix this ?
You have this error because of the infinite recursion.
Company has a link on User and User has a link on Company.
You have at least two options:
use #JsonManagedReference and #JsonBackReference annotation on the relation fields.
create a pair of DTOs and fill them manually with data from you entities.
e.g.
#GetMapping("/company")
public ResponseEntity<Object> getAllCompanies() {
List<Company> allCompanies = companyService.findAll();
List<CompanyDto> allCompanyDtoList = convertToCompanyDtoList(allCompanies);
return ResponseEntity.ok(allCompanyDtoList );
}
Personally, I'd prefer the second option, since returning Entities is NOT a good practice.
You can use #JsonIgnore annotation to prevent this type of behavior. This usually happens with bidirectional mapping within your entities. It is caused by infinite recursion.
#Entity
#Table(name="user")
public class User{
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name="company_id")
#JsonIgnore
private Company company;
}

Override related links in Spring Data REST

I'm using Spring Boot 2, Spring Data REST, Spring HATEOAS.
Let's say I've a model:
#EntityListeners({ContactListener.class})
#Data
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Contact extends AbstractEntity {
#NotNull
#Enumerated(EnumType.STRING)
#Column(nullable = false, columnDefinition = "VARCHAR(30) DEFAULT 'CUSTOMER'")
private ContactType type = ContactType.CUSTOMER;
#NotNull
#Enumerated(EnumType.STRING)
#Column(nullable = false, columnDefinition = "VARCHAR(30) DEFAULT 'NATURAL_PERSON'")
private PersonType personType = PersonType.NATURAL_PERSON;
private String firstName;
private String lastName;
private String companyName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "store_id", updatable = false)
private Store store;
and Store:
#Data
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Store extends AbstractEntity {
#NotBlank
#Column(nullable = false)
private String name;
#Username
#NotBlank
#Length(max = 16)
#Column(nullable = false/*, unique = true*/)
#ColumnTransformer(write = "UPPER(?)")
private String code;
private String address;
private String zipCode;
private String city;
private String district;
When I get a contact the response looks like this:
{
"sid": "962732c2-68a8-413b-9762-f676d42046b4",
"createdBy": "1ccf2329-4aa3-4d55-8878-25517edf1522",
"createdDate": "2019-05-28T14:06:07.011Z",
"lastModifiedDate": "2019-06-04T08:46:02.591Z",
"lastModifiedBy": "system",
"createdByName": "Rossi Mario",
"lastModifiedByName": null,
"type": "CUSTOMER",
"personType": "NATURAL_PERSON",
"firstName": "Mario",
"lastName": "Rossi",
"companyName": null,
"fullName": "Rossi Mario",
"gender": "MALE",
"birthDate": "2019-05-21T00:00:00Z",
"birthCity": null,
"job": null,
"billingAddress": "Via 123",
"billingZipCode": "14018",
"billingCity": "Roatto",
"billingDistrict": "AT",
"billingCountry": "IT",
"shippingAddress": "Via 123",
"shippingZipCode": "14018",
"shippingCity": "Roatto",
"shippingDistrict": "AT",
"shippingCountry": "IT",
"taxCode": "XXXX",
"vatNumber": null,
"landlinePhone": null,
"mobilePhone": null,
"fax": null,
"email": "aaa#sdfg.it",
"certifiedEmail": null,
"survey": null,
"iban": null,
"swift": null,
"publicAdministration": false,
"sdiAccountId": "0000000",
"preset": false,
"_links": {
"self": {
"href": "http://localhost:8082/api/v1/contacts/1"
},
"contact": {
"href": "http://localhost:8082/api/v1/contacts/1{?projection}",
"templated": true
},
"store": {
"href": "http://localhost:8082/api/v1/contacts/1/store{?projection}",
"templated": true
}
}
}
as you can see the link of store it's not the self link of the resource Store.
I'd like to override that link setting the self resource. So I created this processor:
#Component
public class DocumentRowProcessor implements ResourceProcessor<Resource<Contact>> {
#Autowired
private BasePathAwareLinks service;
#Autowired
private EntityLinks entityLinks;
#Override
public Resource<Contact> process(Resource<Contact> resource) {
Store store = resource.getContent().getStore();
if(store != null){
resource.add(entityLinks.linkToSingleResource(store.getClass(), store.getId()).withRel("store"));
}
return resource;
}
}
Unfortunately, the link is now overriden but I find 2 links inside "store". Debugging I saw that inside the resource is present just the self link. My guess is that related links are added in following steps.
How can I accomplish my goal in a clean way?
The hateoas links are added the the result during serialization (using a specific JSON serializer), so you cannot remove it using a ResourceProcessor.
The hateoas link in the result is the proper link for that resource. http://localhost:8082/api/v1/contacts/1/store is the endpoint where you can check which store is linked to this contant, or you can delete/modify the association between this two object.
However in certain use-cases you need the self-link for further actions and you don't want to send an extra request from the client.
Do the following:
1. Create a projection for the contant.
2. Include all the properties you need and also the store.
3. If you don't need any properties of the store here - only the self link - then create an 'empty projection' for the store entoty and include that projection as store property into the contact property.
When you get this projection of the contact then the result will contain the self-link of the store inside the store property. So the main _links collection will be still a regular hateos link-collection but there will be a store._links.self.href property which will contain the self link of the associated store.

How populate Spring Data Repositories with ManyToMany relationship

I want to populate the repository with user roles and two initial users related to those roles.
This is the JSON I want to upload:
[
{
"_class": "models.Role",
"#id": 1,
"name": "ROLE_BLOG_ADMIN",
"description": "Rol de los administradores del blog"
},
{
"_class": "models.Role",
"#id": 2,
"name": "ROLE_BLOG_CONTRIBUTOR",
"description": "Rol de los editores de artículos"
},
{
"_class": "models.User",
"username": "sergio11",
"password": "$2a$10$0eCQpFRdw8i6jJzjj/IuNuKpJYnLaO5Yp9xSJ3itcfPmQNXVhmNyu",
"email": "gfhdsgfjhdsgfjhdsgf#gmail.com",
"fullName": "Sergio Sánchez Sánchez",
"roles": [1, 2]
},
{
"_class": "models.User",
"username": "dani33",
"password": "$2a$10$0eCQpFRdw8i6jJzjj/IuNuKpJYnLaO5Yp9xSJ3itcfPmQNXVhmNyu",
"email": "danihiglesias#usal.es",
"fullName": "Daniel de la Iglesia",
"roles": [2]
}
]
I am using JsonIdentityInfo in the Roles entity:
#Entity
#Table(name = "roles")
#JsonIdentityInfo(generator=IntSequenceGenerator.class, property="#id")
public class Role implements Serializable
I have included Jackson2RepositoryPopulatorFactoryBean in the context:
#Bean(name="repositoryPopulator")
public Jackson2RepositoryPopulatorFactoryBean provideJackson2RepositoryPopulatorFactoryBean(){
Resource sourceData = new ClassPathResource("data.json");
Jackson2RepositoryPopulatorFactoryBean factory = new Jackson2RepositoryPopulatorFactoryBean();
factory.setResources(new Resource[] { sourceData });
return factory;
}
But, no role is associated with any user.
This is the association:
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name="USER_ROLES",
joinColumns=#JoinColumn(name="USER_ID", referencedColumnName="ID"),
inverseJoinColumns=#JoinColumn(name="ROLE_ID", referencedColumnName="ID"))
private Set<Role> roles;
Does anyone know how to fix them?
It should only complain about a missing constructor for the Role entity, for me everything went smoothly with the following :
#Entity
#Table(name = "roles")
public class Role implements Serializable {
public Role() {
}
public Role(Integer id) {
this.id = id;
}
#Id
#JsonProperty("#id")
private Integer id;
//additional properties, getters & setters
...
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name="USER_ROLES",
joinColumns=#JoinColumn(name="USER_ID", referencedColumnName="ID"),
inverseJoinColumns=#JoinColumn(name="ROLE_ID", referencedColumnName="ID"))
private Set<Role> roles;
//additional properties, getters & setters
...
}
And I properly get :
[
{
"id":1,
"roles":[
{
"name":"ROLE_BLOG_ADMIN",
"#id":1
},
{
"name":"ROLE_BLOG_CONTRIBUTOR",
"#id":2
}
],
"email":"sss4esob#gmail.com"
},
{
"id":2,
"roles":[
{
"name":"ROLE_BLOG_CONTRIBUTOR",
"#id":2
}
],
"email":"danihiglesias#usal.es"
}
]
Can you provide more of your entities code if this isn't working for you ? Do you encounter any exception ?

Resources