I need to know is there any possibility to manage several entities with one crud repository in spring data rest.
Example :
Library entity
#Entity
public class Library {
#Id
#GeneratedValue
private long id;
#Column
private String name;
#OneToMany(mappedBy = "library")
private List<Book> books;
}
Book entity
#Entity
public class Book {
#Id
#GeneratedValue
private long id;
#Column(nullable=false)
private String title;
#ManyToOne
#JoinColumn(name="library_id")
private Library library;
}
My requirement is
public interface LibraryRepository extends CrudRepository<Library, Long> { }
is to have only this repository to manage both library and the book entities.
I tried inserting and it is working well so far. but other operations are not supported by this approach. is there any other approach rather than having two crud repositories to do this.
Of course you can. Just correct a little your Library like this:
#OneToMany(mappedBy = "library", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books;
Then you can create/update your Library and its books with this payload:
{
"name": "library1",
"books": [
{
"title": "book1"
},
{
"title": "book2"
}
]
}
Code example of the Spring Data author.
My example.
You cannot do that simply, since a bean will be created for each repository and this bean should be instantiated with entity type defined
Related
Let's assume there are two entities:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#ManyToMany(mappedBy = "authors")
private Set<Book> books;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String fileName;
private String fileType;
#Lob
private byte[] data;
#ManyToMany
#JoinTable(
name = "book_authors",
joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "author_id"))
private Set<Author> authors;
}
The following DTO interface projection is used to query only the columns that are needed.
public interface AuthorView {
String getFirstName();
String getLastName();
Set<BookView> getBooks();
interface BookView {
String getTitle();
}
}
A simple findAllBy query method is declared in the repository:
public interface AuthorRepository extends JpaRepository<Author, Long> {
#EntityGraph(attributePaths = "books")
List<AuthorView> findAllBy();
}
The method executes the following query:
select
author0_.id as id1_0_0_,
book2_.id as id1_1_1_,
author0_.first_name as first_na2_0_0_,
author0_.last_name as last_nam3_0_0_,
book2_.data as data2_1_1_,
book2_.file_name as file_nam3_1_1_,
book2_.file_type as file_typ4_1_1_,
book2_.title as title4_1_1_,
books1_.author_id as author_i2_2_0__,
books1_.book_id as book_id1_2_0__
from
author author0_
left outer join
book_authors books1_
on author0_.id=books1_.author_id
left outer join
book book2_
on books1_.book_id=book2_.id
Even though the projection doesn't contain data, file_name, and file_type properties, they are fetched from the database which is causing performance issues, especially if the files are large.
The problem is that Spring Data JPA fetches the entire entities and uses them to perform a programmatic mapping, according to Thorben Janssen's blog.
Is there any solution to prevent fetching entire entities when using interface-based DTO projections except for writing massive queries?
Recently, I found out Blaze Persistence - Entity View Module, which looks promising.
According to #Christian Beikov's answer, EntityView projections can be used almost like Spring Data Projections with the Spring Data integration, and those projections fetch only the necessary properties. Additionally, there is no need to use #EntityGraph, since the integration handles it ad-hoc by adapting the query generation.
I am stuck on given scenario:
There are three entity
a) Bill (many to one relationship with vendor) [bi-directional]
b) Vendor (one to many relationship with both vendor and vendorbank)
c) VendorBank (many to one relationship with vendor)[uni-directional]
Bills : Showing limited fields
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#Table(name="bill_details")
public class Bills {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="bill_id")
private int bill_id;
#Column(name="bill_no")
private String billno;
#ManyToOne(cascade = {CascadeType.MERGE})
#JoinColumn(name="b_vendor_id")
private Vendors vendors;
/* Args contructor code here */
#JsonManagedReference
public Vendors getVendors() { return vendors; }
VendorBank:Showing limited fields
public class VendorBank {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="v_bank_id")
private int vendorBankId;
#Column(name="v_acc_no")
private String accountNumber;
#Column(name="v_vendor_id")
private int vendor_id;
/*Both constructor code here*/
}
Vendor Class:Showing imp fields only
/*Lombok code here*/
public class Vendors {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="vendor_id")
private int vendor_id;
#Column(name="vendor_name")
private String vendor_name;
#OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name="v_vendor_id")
private List<VendorBank> vendorBank;
#OneToMany(mappedBy = "vendors",cascade = {CascadeType.DETACH,CascadeType.MERGE,
CascadeType.PERSIST,CascadeType.REFRESH})
private List<Bills> bills;
#JsonBackReference
public List<Bills> getBills() {
return bills;
}
//#JsonManagedReference()
// #JsonIgnore
public List<VendorBank> getVendorBank() {
return vendorBank;
}
Output:
{
"bill_id": 102,
"billno": "B-858",
"vendors": {
"vendor_id": 3,
"vendor_name": "ABC Company",
"vendorBank": [
{
"vendorBankId": 14,
"accountNumber": "502998745002",
"vendor_id": 3
}
]
}
}
1.When I call vendor endpoint I get data from vendor + vendor bank as desired.
2.But When I call the Bill endpoints then I get data from Bill + vendor + vendorbank as above. I don't want vendor bank to come.[If I use JsonIgnore on vendorbank then i get the correct output but then above point 1 goes wrong]
OK, so what you want to achieve is to include some data from the entity in one context and not include it in another context.
I don't think you can do this by using purely annotations you put on entities, since they don't have the calling context.
So, what can we do here ?
We can use Jackson Mixins. For example:
class YourClass {
public int ignoreThis() { return 0; }
}
With this Mixin
abstract class MixIn {
#JsonIgnore abstract int ignoreThis(); // we don't need it!
}
With this:
objectMapper.getSerializationConfig().addMixInAnnotations(YourClass.class, MixIn.class)
And you can then use this to serialize the object into json in your controller (where you get it to the service) and then add it as the response body and send to the user.
It would be ideal if you could somehow configure the ObjectMapper, which is used by Spring behind the scenes to do this, however, this is not possible ( at least easily ), because you need to tie this objectMapper to your specific controller only and not all controllers.
Please note that I have looked at similar questions and I have explained why they haven't worked for me
I have a simple Spring boot JPA-Hibernate application with one to one mapping between User and Address. (Please note that I do not have this issue with one to many mapping)
User Entity
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Address address;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Note> notes;
}
Address Entity
#Entity
#Table(name = "addresses")
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String street;
#Column
private String city;
#JsonIgnore
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
Note Entity
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
My problem is that whenever I call the controller mapped to get all users I was getting the address and all the associated notes with it as well. But I would expect FetchType.LAZY to take care of that.
I read a lot of questions on StackOverflow mentioning that Jackson might be the culprit here:
Post 1
I also read that spring.jpa.open-in-view defualt value might be the culprit:
Post 2
Post 3
So i tried the following options:
I disabled default open in view property by adding spring.jpa.open-in-view=false to my application.properties which started giving me
Could not write JSON: failed to lazily initialize a collection of role error
I am assuming its because Jackson is calling the getters on my lazily loaded objects so I followed the instructions from another post and added the following for Jackson to leave the lazily loaded collections alone:
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.9</version>
</dependency>
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
This solution above fixed the issue with the One to Many mapping but still has the Address associated in the response.
I am not sure what can I do here. The User Entity on the default landing page does not need any address details so I do not want to load it on the landing page. When the record is clicked then it navigates to another page and that's where I would like all the lazy loaded objects to be returned in the response.
I have tried everything I could find online but still nothing has worked so far. I would really appreciate some help with this.
As mentioned by one of the users that it might a duplicate of another question on SO:
Suggested Possible duplicate
I would like to mention that I got the Lazy loading working by disabling spring.jpa.open-in-view property but adding
mapper.registerModule(new Hibernate5Module());
brings back the address associated to the User in the response.
It's working as in the JPA spec:-
Refer the below URL
https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FetchType.html
LAZY fetching strategy is only a hint (as the javadoc says the data can be lazily fetched).. not a mandatory action.
Eager is mandatory (as the javadoc says the data must be eagerly fetched).
You may take a look at Jackson Serialization Views.
I´ve taken a look into the Hibernate5 module you tried and it has some interesting features... but none should fix this issue out of the box for you.
By the way, I normally fix this issue by not returning the Entity as the response but DTOs instead.
The problem is jackson triggering initialization when he writes the JSON, so just don't write the current field (address). But you should not use #jsonIgnore so at other places you could return an Eager obj.
You can use the #jsonView annotation that can provide different JSON for the same obj at different requests. You can look this example :
Create view class:
public class ViewFetchType {
static class lazy{ }
static class Eager extends lazy{ }
}
Annotate your Entity
#Entity
public class User {
#Id
#JsonView(ViewFetchType.Lazy.class)
private String id;
#JsonView(ViewFetchType.Eager.class)
#OneToOne( fetch = FetchType.LAZY)
private Address address ;
}
Specify the FetchType class in your controller:
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping("get-user-details")
#JsonView(ViewFetchType.Eager.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
{
#RequestMapping("get-all-users")
#JsonView(ViewFetchType.Lazy.class)
public #ResponseBody List<User> getUsers() {
return userRepository.findAll();
}
}
Here is the answer that i took the idea from... https://stackoverflow.com/a/49207551/10162200
I'm writing a Spring Application, which has two entities that are related by a one to many relationship, lets call them mother and kid.
When I create a mother entity via POST request, I want a kid entity be created automatically. Using the #OneToMany and #ManyToOne annotations, that works fine. At least, as long as I provide the kid information within the MotherService.
Here is my code
Mother.java
#Entity
#Table(name="mother")
public class Mother{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#OneToMany(mappedBy = "mother", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Kid> kidList = new ArrayList<>();
//constructor, getter, setter
private void addKid(Kid kid) {
this.kidList.add(kid);
kid.setMother(this);
}
}
Kid.java
#Entity
#Table(name="kid")
public class Kid{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "mother_id", nullable=false)
private Mother mother;
//constructor, getter, setter
}
MotherController.java
#RestController
#RequestMapping("mothers")
public class MotherController {
#Autowired
private MotherService motherService;
MotherController(MotherService motherService) {
this.motherService = motherService;
}
#PostMapping
Mother createMother(#RequestBody Mother mother) {
return this.motherService.createMother(mother);
}
}
MotherService.java
#Service
public class MotherService {
private MotherRepository motherRepository;
#Autowired
public MotherService (MotherRepository motherRepository) {
super();
this.motherRepository= motherRepository;
}
public Mother createMother(Mother mother) {
Kid kid = new Kid("Peter");
mother.addKid(kid);
return this.motherRepository.save(mother);
}
}
The repositories for mother and kid extend the JpaRepository without any custom methods so far.
My POST request is something like (using Postman)
{
"name":"motherName"
}
Now a mother is created with a name "motherName" and a kid with the name of "Peter".
My idea: Using a DTO
I now try to implement a DTO, that contains the mothers name and the kids name, map this information in the MotherService to the entities and save them via the corresponding repository, so I can define both names in the POST request.
motherDto.java
public class mother {
private String motherName;
private String kidName;
//getter, setter
}
So when I POST
{
"motherName":"Susanne",
"kidName":"Peter"
}
or even better
{
"mother": {
"name":"Susanne"
},
"kid": {
"name":"Peter"
}
}
a mother with name Susanne and a kid with name Peter are created.
My question is
How do I map a DTO to two entities?
Or do I not get something right? Is there an easier way to achieve my goal?
I know this is old and probably long solved, but let me offer a different take on the subject.
Another option would be to design a DTO solely for the purpose of creating the two entities you mentioned. You could call this MotherChildCreationDTO or something like that so the name already conveys its use and maybe create a REST-target consuming the DTO.
Asymmetric DTOs (receiving and sending) are an established pattern, and the DTOs are closely coupled to the REST controller any way.
First solution:
You can don't use DTO and send your JSON with same structure of Mother and kids and Jackson in Spring MVC deserialize it correctly for you.
{
id:2,
name:'sarah'
kidList:[{id:546,name:'bob'},{id:478,name:'tom'}]
}
Second solution:
If you want to different structure in JSON and Models and you can use Jackson annotation like #JsonProperty or #JsonDeserialize. Read this like for more information.
Third solution:
You can use DozzerMapper for complex mapping between your DTO and your Model. you define XML's file for mapping each model to your DTO and DozzerMapper map your DTO to your models.Read this link for more information.
You have 2 ways:
Map DTO to entities by yourself. In this case, you should create custom mapper and define how exactly DTO should be converted to entity. Then just inject and use your custom mapper in service.
Use one of existing mapper libraries. For example, good candidates are MapStruct and ModelMapper. You can find usage examples in corresponding getting started guides.
I have a problem using JPA and RelationsShips One to Many with Jackson and Spring Rest ... I try to find multiples solutions but anything is working for me , and I don't kno where is the problem.
For example I have a table Team that has One to Many/Many To One relationship
I have two repository one for Team and another for Player
Team >>> has Many >> Player
Player >>> many to one >> Team
My entity Team has the following content
#Entity
#Table(name = "teams")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Team {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long teamId;
private String abbreviation;
private String team;
private String simpleName;
private String logo;
#OneToMany(cascade = {CascadeType.ALL,CascadeType.PERSIST,CascadeType.MERGE}, mappedBy = "team")
#Column(nullable = false)
private List<Player> players;
Theirs getters/setter , hashcodes and string similars.
On the other hand the entity Player
#Entity
#Table(name = "player")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
private String name;
So , I have the typical get call in a controller in a repository.
#RestController
#RequestMapping("/api/public/team")
public class TeamController {
#Autowired
private TeamRepository teamRepository;
#Autowired
private GenericMethods genericMethods;
#GetMapping(value = "/{id}")
public Team getPersona(#PathVariable("id") int id) {
return teamRepository.findOne(genericMethods.toLong(id));
}
And repository
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
Now , when I call this endpoint I receive the following answer and I think that is incorrect , I only need a List With Players
{
"id":2,
"teamId":0,
"abbreviation":null,
"team":null,
"simpleName":"Betis",
"logo":null,
"players":[
{
"id":1,
"team":2,
"category":{
"id":1,
"nombre":"juvenil a",
"language":null,
"description":null,
"league":[
],
"players":[
1,
{
"id":2,
"team":2,
"category":1,
"name":"hulio"
}
]
},
"name":"pepe"
},
2
]
}
I need to acces at information with Player and Team so I can't use #JsonIgnoreProperties
Could anyone help to solve this problem ?
Depending on what you really want to achieve you may try different options. I'm not sure if you're using (or intending to use) spring-data-rest or not.
1. Dedicated repository
Spring data rest will embed the related entities if they don't have their own repository. Try creating public interface PlayersRepository extends JpaRepository...
2. Lazy loading
Why are you using FetchType.EAGER ? Try without it.
3. Projections
Projections are only applicable to lists, not to individual entities (i.e. not explicitly what you're asking for). You can hide players from the Teams collection even if it was returned by default like so:
#Projection(name = "noPlayers", types = { Team.class })
public interface TeamWithoutPlayers {
Long getId();
long getTeamId();
String getAbbreviation();
String getTeam();
String getSimpleName();
String getLogo();
}
More info - Spring Data Rest Projections
4. Ignore during serialization in Team Entity using #JsonIgnore
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
Final thought
With spring-data-rest you can extend a CrudRepository instead of JpaRepository and access the item directly through the repository. That way you don't need to write a controller.